Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter shows by watch provider #969

Merged
merged 32 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
69863a8
draft Database: add watch provider and show mapping.
UweTrottmann Jan 19, 2024
af4cde1
draft Database: update watch provider mapping.
UweTrottmann Jan 25, 2024
67ff41a
draft Filter shows by watch provider.
UweTrottmann Jan 25, 2024
9f83a13
draft Add watch provider filter view.
UweTrottmann Jan 26, 2024
0eac6c6
Provider filter: use compose to create provider filter view.
UweTrottmann Feb 1, 2024
efb0047
Provider filter: only list providers used by shows.
UweTrottmann Feb 2, 2024
cc27e01
Provider filter: add filter_local column to watch providers.
UweTrottmann Feb 2, 2024
0ed2dfc
Provider filter: change provider filter on switch change.
UweTrottmann Feb 2, 2024
aa202bd
Provider filter: make whole row toggleable.
UweTrottmann Feb 2, 2024
72d0d90
Provider filter: use database state for shows query.
UweTrottmann Feb 15, 2024
eaf212f
Provider filter: observe changes
UweTrottmann Feb 15, 2024
f85715b
Provider filter: move filter state into view model.
UweTrottmann Feb 15, 2024
cb3b543
Provider filter: migrate filter and order to StateFlow.
UweTrottmann Feb 15, 2024
d945fbc
Provider filter: update provider mappings when adding/updating shows.
UweTrottmann Feb 16, 2024
2e2e22c
Provider filter: remove provider mappings when deleting shows.
UweTrottmann Feb 16, 2024
b68adfc
ShowTools2: make show delete methods suspend.
UweTrottmann Feb 16, 2024
fa7a5aa
Provider filter: order providers ignoring case.
UweTrottmann Feb 16, 2024
9c43d2a
Provider filter: fix vertical sizing, losing swipe-ability though.
UweTrottmann Feb 15, 2024
dae272e
Provider filter: add option to remove all filters
UweTrottmann Feb 21, 2024
e37139e
Provider filter: move watch provider updating to StreamingSearch
UweTrottmann Feb 22, 2024
038075e
Rename .java to .kt
UweTrottmann Feb 22, 2024
cbc83b5
Kotlin: convert TmdbSettings
UweTrottmann Feb 22, 2024
346ac30
Provider filter: update show watch providers weekly when syncing
UweTrottmann Feb 22, 2024
5bc961d
TmdbSettings: wrap setting image URL with checks
UweTrottmann Feb 22, 2024
9c7f42d
Provider filter: add chip to configure region
UweTrottmann Feb 22, 2024
3b5cc6a
Provider filter: use icon to configure region due to space constraints
UweTrottmann Feb 22, 2024
2cee4fd
SgPreferencesFragment: use show of StreamingSearchConfigureDialog
UweTrottmann Feb 22, 2024
799d217
Provider filter: update watch providers when changing region
UweTrottmann Feb 22, 2024
3095cd4
Provider filter: populate mapping table quickly for existing users
UweTrottmann Feb 22, 2024
841033a
Provider filter: update mappings for all shows after changing region
UweTrottmann Feb 22, 2024
ed27d07
Provider filter: test database migration
UweTrottmann Feb 23, 2024
765f115
Provider filter: very simple debounce of shows query update
UweTrottmann Feb 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,617 changes: 1,617 additions & 0 deletions app/schemas/com.battlelancer.seriesguide.provider.SgRoomDatabase/52.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,26 @@ class MigrationTest {
}
}

@Test
fun migrationFrom51To52_newWatchProviderColumn() {
val dbOld = migrationTestHelper
.createDatabase(TEST_DB_NAME, SgRoomDatabase.VERSION_51_CUSTOM_RELEASE_TIME)
dbOld.execSQL(
"INSERT INTO sg_watch_provider " +
"(provider_id, provider_name, display_priority, logo_path, type, enabled) " +
"VALUES " +
"(1, 'Test provider', 1, 'logo-path', 1, 1)"
)
dbOld.close()

val db = getMigratedDatabase(SgRoomDatabase.VERSION_52_WATCH_PROVIDER_FILTERS)
queryAndAssert(db, "SELECT filter_local FROM sg_watch_provider") { provider ->
// New filter_local column value should default to false (0)
assertThat(provider.isNull(0)).isFalse()
assertThat(provider.getInt(0)).isEqualTo(0)
}
}

/**
* Validate test data for version [SgRoomDatabase.VERSION_49_AUTO_ID_MIGRATION] or higher.
*
Expand All @@ -406,7 +426,10 @@ class MigrationTest {
assertThat(it.getInt(2)).isEqualTo(SHOW49.runtime)
assertThat(it.getString(3)).isEqualTo(SHOW49.poster)
}
queryAndAssert(db, "SELECT season_tmdb_id, series_id, season_number, season_order FROM sg_season") {
queryAndAssert(
db,
"SELECT season_tmdb_id, series_id, season_number, season_order FROM sg_season"
) {
assertThat(it.getString(0)).isEqualTo(SEASON49.tmdbId)
assertThat(it.getLong(1)).isEqualTo(showId)
assertThat(it.getInt(2)).isEqualTo(SEASON49.number)
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/battlelancer/seriesguide/SgApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ class SgApp : Application() {

const val RELEASE_VERSION_59_BETA1 = 2105900

/**
* Added show watch provider mapping table.
*/
const val RELEASE_VERSION_72_0_1 = 2107201

/**
* The content authority used to identify the SeriesGuide [android.content.ContentProvider].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ class MovieDetailsFragment : Fragment(), MovieActionsContract {
binding.frameLayoutMoviePoster.isClickable = false
binding.frameLayoutMoviePoster.isFocusable = false
} else {
val smallImageUrl = (TmdbSettings.getImageBaseUrl(activity)
val smallImageUrl = (TmdbSettings.getImageBaseUrl(requireContext())
+ TmdbSettings.POSTER_SIZE_SPEC_W342 + tmdbMovie.poster_path)
ImageTools.loadWithPicasso(requireContext(), smallImageUrl)
.into(binding.imageViewMoviePoster, object : Callback.EmptyCallback() {
Expand Down Expand Up @@ -558,8 +558,9 @@ class MovieDetailsFragment : Fragment(), MovieActionsContract {
binding.frameLayoutMoviePoster.also {
it.isFocusable = true
it.setOnClickListener { view ->
val posterPath = tmdbMovie.poster_path ?: return@setOnClickListener
val largeImageUrl =
TmdbSettings.getImageOriginalUrl(activity, tmdbMovie.poster_path)
TmdbSettings.getImageOriginalUrl(requireContext(), posterPath)
val intent = FullscreenImageActivity.intent(
requireActivity(),
smallImageUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,7 @@ class SgPreferencesFragment : BasePreferencesFragment(),
return true
}
if (StreamingSearch.KEY_SETTING_REGION == key) {
StreamingSearchConfigureDialog().safeShow(
supportFragmentManager, "streaming-service"
)
StreamingSearchConfigureDialog.show(supportFragmentManager)
return true
}
if (DisplaySettings.KEY_SHOWS_TIME_OFFSET == key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.battlelancer.seriesguide.shows.history.SgActivity
import com.battlelancer.seriesguide.shows.history.SgActivityHelper
import com.battlelancer.seriesguide.streaming.SgWatchProvider
import com.battlelancer.seriesguide.streaming.SgWatchProviderHelper
import com.battlelancer.seriesguide.streaming.SgWatchProviderShowMapping
import timber.log.Timber

@Database(
Expand All @@ -50,13 +51,18 @@ import timber.log.Timber
SgMovie::class,
SgActivity::class,
SgJob::class,
SgWatchProvider::class
SgWatchProvider::class,
SgWatchProviderShowMapping::class,
],
version = SgRoomDatabase.VERSION,
autoMigrations = [
AutoMigration(
from = SgRoomDatabase.VERSION_50_WATCH_PROVIDERS,
to = SgRoomDatabase.VERSION_51_CUSTOM_RELEASE_TIME
),
AutoMigration(
from = SgRoomDatabase.VERSION_51_CUSTOM_RELEASE_TIME,
to = SgRoomDatabase.VERSION_52_WATCH_PROVIDER_FILTERS
)
]
)
Expand Down Expand Up @@ -117,7 +123,13 @@ abstract class SgRoomDatabase : RoomDatabase() {
* Add custom release time, day offset and time zone to shows.
*/
const val VERSION_51_CUSTOM_RELEASE_TIME = 51
const val VERSION = VERSION_51_CUSTOM_RELEASE_TIME

/**
* - Add [SgWatchProviderShowMapping]
* - Add [SgWatchProvider.filter_local]
*/
const val VERSION_52_WATCH_PROVIDER_FILTERS = 52
const val VERSION = VERSION_52_WATCH_PROVIDER_FILTERS

@Volatile
private var instance: SgRoomDatabase? = null
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2014, 2016-2018, 2020-2021, 2023-2024 Uwe Trottmann

package com.battlelancer.seriesguide.settings

import android.content.Context
import android.text.format.DateUtils
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.battlelancer.seriesguide.settings.DisplaySettings.isVeryHighDensityScreen

object TmdbSettings {

private const val KEY_LAST_UPDATED = "com.uwetrottmann.seriesguide.tmdb.lastupdated"

/* If the image URL changes the old one should be working for a while. If watch providers
change, typically only affects new shows or new seasons. As users likely check a show once a
week, updating weekly should be fine. */
private const val UPDATE_INTERVAL_MS = 7 * DateUtils.DAY_IN_MILLIS

private const val KEY_TMDB_BASE_URL = "com.battlelancer.seriesguide.tmdb.baseurl"
const val POSTER_SIZE_SPEC_W154 = "w154"
const val POSTER_SIZE_SPEC_W342 = "w342"
private const val STILL_SIZE_SPEC_W300 = "w300"
private const val IMAGE_SIZE_SPEC_ORIGINAL = "original"
const val DEFAULT_BASE_URL = "https://image.tmdb.org/t/p/"

fun isConfigurationUpToDate(context: Context): Boolean {
val lastUpdatedMs =
PreferenceManager.getDefaultSharedPreferences(context).getLong(KEY_LAST_UPDATED, 0)
return lastUpdatedMs + UPDATE_INTERVAL_MS >= System.currentTimeMillis()
}

fun setConfigurationLastUpdatedNow(context: Context) {
PreferenceManager.getDefaultSharedPreferences(context).edit {
putLong(KEY_LAST_UPDATED, System.currentTimeMillis())
}
}

/**
* Saves the base URL, unless it's empty or blank.
*/
fun setImageBaseUrl(context: Context, url: String) {
if (url.isEmpty() || url.isBlank()) return
PreferenceManager.getDefaultSharedPreferences(context).edit {
putString(KEY_TMDB_BASE_URL, url)
}
}

@JvmStatic
fun getImageBaseUrl(context: Context): String {
return PreferenceManager.getDefaultSharedPreferences(context)
.getString(KEY_TMDB_BASE_URL, null) ?: DEFAULT_BASE_URL
}

fun getImageOriginalUrl(context: Context, path: String): String {
return getImageBaseUrl(context) + IMAGE_SIZE_SPEC_ORIGINAL + path
}

/**
* Returns base image URL based on screen density.
*/
@JvmStatic
fun getPosterBaseUrl(context: Context): String {
return if (isVeryHighDensityScreen(context)) {
getImageBaseUrl(context) + POSTER_SIZE_SPEC_W342
} else {
getImageBaseUrl(context) + POSTER_SIZE_SPEC_W154
}
}

fun getStillUrl(context: Context, path: String): String {
return getImageBaseUrl(context) + STILL_SIZE_SPEC_W300 + path
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package com.battlelancer.seriesguide.shows
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.FrameLayout
import com.battlelancer.seriesguide.R
import com.battlelancer.seriesguide.databinding.ViewFilterShowsBinding
import com.battlelancer.seriesguide.shows.ShowsDistillationSettings.ShowFilter
Expand All @@ -16,12 +16,11 @@ class FilterShowsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
) : FrameLayout(context, attrs, defStyleAttr) {

val binding: ViewFilterShowsBinding

init {
orientation = VERTICAL
// can't do in onFinishInflate as that is only called when inflating from XML
binding = ViewFilterShowsBinding.inflate(LayoutInflater.from(context), this)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
// Copyright 2019-2024 Uwe Trottmann

package com.battlelancer.seriesguide.shows

Expand All @@ -9,23 +9,30 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.content.edit
import androidx.core.view.isGone
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import com.battlelancer.seriesguide.R
import com.battlelancer.seriesguide.appwidget.ListWidgetProvider
import com.battlelancer.seriesguide.databinding.DialogShowsDistillationBinding
import com.battlelancer.seriesguide.settings.AdvancedSettings
import com.battlelancer.seriesguide.settings.DisplaySettings
import com.battlelancer.seriesguide.shows.ShowsDistillationSettings.ShowFilter
import com.battlelancer.seriesguide.streaming.SgWatchProvider
import com.battlelancer.seriesguide.streaming.StreamingSearchInfoDialog
import com.battlelancer.seriesguide.ui.dialogs.SingleChoiceDialogFragment
import com.battlelancer.seriesguide.util.TaskManager
import com.battlelancer.seriesguide.util.ThemeUtils.setDefaultStyle
import com.battlelancer.seriesguide.util.safeShow
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener

class ShowsDistillationFragment : AppCompatDialogFragment() {

private val model: ShowsDistillationViewModel by viewModels()
private var binding: DialogShowsDistillationBinding? = null

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -43,23 +50,58 @@ class ShowsDistillationFragment : AppCompatDialogFragment() {

val binding = DialogShowsDistillationBinding.inflate(inflater, container, false)

val tabsAdapter = ShowsDistillationPageAdapter(
requireContext(),
ShowFilter.fromSettings(requireContext()),
filterListener,
SortShowsView.ShowSortOrder.fromSettings(requireContext()),
sortOrderListener
)
val viewPager = binding.viewPagerShowsDistillation
viewPager.adapter = tabsAdapter
binding.tabLayoutShowsDistillation.setDefaultStyle()
binding.tabLayoutShowsDistillation.setViewPager(viewPager)

// ensure size matches children in any case
// (on some devices did not resize correctly, Android layouting change?)
viewPager.post {
@Suppress("UNNECESSARY_SAFE_CALL") // view might already been unbound
viewPager?.requestLayout()
val initialShowFilter = ShowFilter.fromSettings(requireContext())
val initialShowSortOrder = SortShowsView.ShowSortOrder.fromSettings(requireContext())

binding.apply {
filterShowsView.isGone = false
watchProvidersFilterView.isGone = true
sortShowsView.isGone = true

tabLayoutShowsDistillation.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val position = tab?.position ?: return
filterShowsView.isGone = position != 0
watchProvidersFilterView.isGone = position != 1
sortShowsView.isGone = position != 2
}

override fun onTabUnselected(tab: TabLayout.Tab?) {
}

override fun onTabReselected(tab: TabLayout.Tab?) {
}

})

filterShowsView.apply {
setInitialFilter(
initialShowFilter,
DisplaySettings.isNoReleasedEpisodes(context)
)
setFilterListener(filterListener)
}
watchProvidersFilterView.apply {
// ComposeView in a fragment: dispose already when fragment is destroyed
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
WatchProviderFilter(
watchProvidersFlow = model.watchProvidersFlow,
onProviderFilterChange = { provider: SgWatchProvider, checked: Boolean ->
model.changeWatchProviderFilter(
provider,
checked
)
},
onProviderIncludeAny = { model.removeWatchProviderFilter() },
onSelectRegion = { StreamingSearchInfoDialog.show(parentFragmentManager) }
)
}
}
sortShowsView.apply {
setInitialSort(initialShowSortOrder)
setSortOrderListener(sortOrderListener)
}
}

return binding.root
Expand Down Expand Up @@ -125,7 +167,7 @@ class ShowsDistillationFragment : AppCompatDialogFragment() {
}

// broadcast new sort order
ShowsDistillationSettings.sortOrderLiveData.postValue(showSortOrder)
ShowsDistillationSettings.sortOrder.value = showSortOrder

if (showSortOrder.changedIgnoreArticles) {
// refresh all list widgets
Expand Down
Loading
Loading