From 1ccd3d732d7bf966c92ea4dab14b97c82b1ecade Mon Sep 17 00:00:00 2001 From: self-similarity <137652432+self-similarity@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:06:22 +0000 Subject: [PATCH 01/15] Reduce image cache to 100mb (#683) * Reduce image cache to 100mb --- .../main/java/com/lagradost/cloudstream3/utils/GlideApp.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt index 4b0ee890..8d73db3d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt @@ -8,6 +8,7 @@ import com.bumptech.glide.Registry import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.request.RequestOptions @@ -27,6 +28,10 @@ class GlideModule : AppGlideModule() { RequestOptions() .diskCacheStrategy(DiskCacheStrategy.ALL) .signature(ObjectKey(System.currentTimeMillis().toShort())) + }.setDiskCache { + // Possible to make this a setting in the future. + val memoryCacheSizeBytes: Long = 1024 * 1024 * 100; // 100mb + InternalCacheDiskCacheFactory(context, memoryCacheSizeBytes).build() } } From fc8c0e809d9c1413293b85c694daf135e2a1fe40 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Thu, 12 Oct 2023 20:56:54 +0300 Subject: [PATCH 02/15] Focus fixes for old devices (#686) - Fix Library on TV main buttons focus. - Quick search back button focus. --- app/src/main/res/layout/fragment_library_tv.xml | 3 +++ app/src/main/res/layout/quick_search.xml | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/layout/fragment_library_tv.xml b/app/src/main/res/layout/fragment_library_tv.xml index 8d2da9f2..ec6565ee 100644 --- a/app/src/main/res/layout/fragment_library_tv.xml +++ b/app/src/main/res/layout/fragment_library_tv.xml @@ -38,6 +38,7 @@ android:padding="2dp" android:background="?selectableItemBackgroundBorderless" android:contentDescription="@string/change_providers_img_des" + android:focusable="true" android:nextFocusLeft="@id/nav_rail_view" android:nextFocusRight="@id/library_sort" android:tag="@string/tv_no_focus_tag" @@ -52,6 +53,7 @@ android:layout_marginStart="10dp" android:background="?selectableItemBackgroundBorderless" android:contentDescription="@string/change_providers_img_des" + android:focusable="true" android:nextFocusLeft="@id/provider_selector" android:nextFocusRight="@id/list_selector" android:tag="@string/tv_no_focus_tag" @@ -67,6 +69,7 @@ android:layout_marginStart="10dp" android:background="?selectableItemBackgroundBorderless" android:contentDescription="@string/change_providers_img_des" + android:focusable="true" android:nextFocusLeft="@id/library_sort" android:nextFocusRight="@id/main_search" android:tag="@string/tv_no_focus_tag" diff --git a/app/src/main/res/layout/quick_search.xml b/app/src/main/res/layout/quick_search.xml index eeec4a5c..f34591b0 100644 --- a/app/src/main/res/layout/quick_search.xml +++ b/app/src/main/res/layout/quick_search.xml @@ -17,14 +17,15 @@ android:layout_height="wrap_content"> + android:id="@+id/quick_search_back" + android:layout_gravity="center" + android:foregroundGravity="center" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:src="@drawable/ic_baseline_arrow_back_24" + app:tint="@android:color/white" + android:focusable="true" + android:layout_width="25dp" + android:layout_height="wrap_content"> From fd1620f3d7f9d0fca58123a4eefae79a06f985db Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Fri, 13 Oct 2023 22:34:26 +0000 Subject: [PATCH 03/15] Fix unresponsive settings (#688) * Lower targetSdk to get all installed packages * Update sdk version * Let's not be too radical * Many fixes * Revert targetSdk * Make account homepage persistent * Update mobile_navigation.xml * Update SettingsFragment.kt --- .../ui/settings/SettingsFragment.kt | 16 +-- .../main/res/navigation/mobile_navigation.xml | 103 +++++++++--------- 2 files changed, 59 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 4895b0d2..a4d19eba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -187,13 +187,13 @@ class SettingsFragment : Fragment() { } binding?.apply { listOf( - settingsGeneral to R.id.action_navigation_settings_to_navigation_settings_general, - settingsPlayer to R.id.action_navigation_settings_to_navigation_settings_player, - settingsCredits to R.id.action_navigation_settings_to_navigation_settings_account, - settingsUi to R.id.action_navigation_settings_to_navigation_settings_ui, - settingsProviders to R.id.action_navigation_settings_to_navigation_settings_providers, - settingsUpdates to R.id.action_navigation_settings_to_navigation_settings_updates, - settingsExtensions to R.id.action_navigation_settings_to_navigation_settings_extensions, + settingsGeneral to R.id.action_navigation_global_to_navigation_settings_general, + settingsPlayer to R.id.action_navigation_global_to_navigation_settings_player, + settingsCredits to R.id.action_navigation_global_to_navigation_settings_account, + settingsUi to R.id.action_navigation_global_to_navigation_settings_ui, + settingsProviders to R.id.action_navigation_global_to_navigation_settings_providers, + settingsUpdates to R.id.action_navigation_global_to_navigation_settings_updates, + settingsExtensions to R.id.action_navigation_global_to_navigation_settings_extensions, ).forEach { (view, navigationId) -> view.apply { setOnClickListener { @@ -212,4 +212,4 @@ class SettingsFragment : Fragment() { } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 6d0a94fb..d0df339b 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -331,57 +331,56 @@ app:exitAnim="@anim/exit_anim" app:popEnterAnim="@anim/enter_anim" app:popExitAnim="@anim/exit_anim" - tools:layout="@layout/main_settings"> - - - - - - - - + tools:layout="@layout/main_settings" /> + + + + + + + - \ No newline at end of file + From b7322ffb192d3f500e3d3070854fdcdd2b40f0e0 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:44:51 -0600 Subject: [PATCH 04/15] Add clear search query icons (#687) --- .../cloudstream3/ui/library/LibraryFragment.kt | 9 +++++++++ .../ui/quicksearch/QuickSearchFragment.kt | 13 ++++++++++--- .../cloudstream3/ui/search/SearchFragment.kt | 13 ++++++++++--- app/src/main/res/layout/fragment_library.xml | 2 ++ app/src/main/res/layout/fragment_library_tv.xml | 1 + app/src/main/res/layout/fragment_search.xml | 1 + app/src/main/res/layout/fragment_search_tv.xml | 1 + app/src/main/res/layout/quick_search.xml | 2 ++ 8 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index 85f0aedd..c3986dca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -8,12 +8,14 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS import android.view.ViewGroup.FOCUS_BLOCK_DESCENDANTS import android.view.animation.AlphaAnimation +import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast @@ -137,6 +139,13 @@ class LibraryFragment : Fragment() { tag = "tv_no_focus_tag" } + // Set the color for the search exit icon to the correct theme text color + val searchExitIcon = binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) + val searchExitIconColor = TypedValue() + + activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true) + searchExitIcon?.setColorFilter(searchExitIconColor.data) + binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { libraryViewModel.sort(ListSorting.Query, query) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index 53c7c2fa..5b300c06 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.res.Configuration import android.os.Bundle +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -215,10 +216,16 @@ class QuickSearchFragment : Fragment() { binding?.quickSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) //val searchMagIcon = - // binding.quickSearch.findViewById(androidx.appcompat.R.id.search_mag_icon) + // binding?.quickSearch?.findViewById(androidx.appcompat.R.id.search_mag_icon) - //searchMagIcon?.scaleX = 0.65f - //searchMagIcon?.scaleY = 0.65f + // searchMagIcon?.scaleX = 0.65f + // searchMagIcon?.scaleY = 0.65f + + // Set the color for the search exit icon to the correct theme text color + val searchExitIconColor = TypedValue() + + activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true) + searchExitIcon?.setColorFilter(searchExitIconColor.data) binding?.quickSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index ce92d723..65dfd679 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.search import android.content.DialogInterface import android.content.res.Configuration import android.os.Bundle +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -231,9 +232,15 @@ class SearchFragment : Fragment() { val searchExitIcon = binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_close_btn) // val searchMagIcon = - // main_search.findViewById(androidx.appcompat.R.id.search_mag_icon) - //searchMagIcon.scaleX = 0.65f - //searchMagIcon.scaleY = 0.65f + // binding?.mainSearch?.findViewById(androidx.appcompat.R.id.search_mag_icon) + // searchMagIcon.scaleX = 0.65f + // searchMagIcon.scaleY = 0.65f + + // Set the color for the search exit icon to the correct theme text color + val searchExitIconColor = TypedValue() + + activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true) + searchExitIcon?.setColorFilter(searchExitIconColor.data) selectedApis = DataStoreHelper.searchPreferenceProviders.toMutableSet() diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 879ddbd9..446c2a81 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -68,6 +68,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:layout_marginEnd="25dp" android:iconifiedByDefault="false" android:imeOptions="actionSearch" @@ -81,6 +82,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/fragment_library_tv.xml b/app/src/main/res/layout/fragment_library_tv.xml index ec6565ee..22b9feb1 100644 --- a/app/src/main/res/layout/fragment_library_tv.xml +++ b/app/src/main/res/layout/fragment_library_tv.xml @@ -102,6 +102,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index c5a43b6d..7dc2b9d2 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -49,6 +49,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index e34b0ac3..8b352c1f 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -50,6 +50,7 @@ app:queryBackground="@color/transparent" app:queryHint="@string/search_hint" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" tools:ignore="RtlSymmetry"> diff --git a/app/src/main/res/layout/quick_search.xml b/app/src/main/res/layout/quick_search.xml index f34591b0..12d94aaa 100644 --- a/app/src/main/res/layout/quick_search.xml +++ b/app/src/main/res/layout/quick_search.xml @@ -49,6 +49,8 @@ app:queryBackground="@color/transparent" app:searchIcon="@drawable/search_icon" + app:closeIcon="@drawable/ic_baseline_close_24" + android:paddingStart="-10dp" android:iconifiedByDefault="false" app:queryHint="@string/search_hint" From 7e9d1ded7fb74f97038948e5e81ee10d58356c84 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Sat, 14 Oct 2023 01:54:34 +0300 Subject: [PATCH 05/15] Larger top poster in TV loading layout (#685) --- .../main/res/layout/fragment_result_tv.xml | 131 +++++++++--------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index feaf6fbc..b53639ba 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -124,17 +124,16 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:textColor="?attr/textColor" /> - - @@ -155,7 +153,6 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_width="match_parent" android:layout_height="wrap_content"> - + android:orientation="vertical" + android:layout_marginTop="175dp"> + + + + + + + + + + + + + + - - - - - - - - - - - From 3cb2196e62a2cd435d1d76172d8a9113dff5d573 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:02:12 -0600 Subject: [PATCH 06/15] Add favorites (#682) * Add favorites --- .../syncproviders/providers/LocalList.kt | 45 ++++++++++++---- .../ui/result/ResultFragmentPhone.kt | 27 ++++++++++ .../ui/result/ResultFragmentTv.kt | 42 ++++++++++++++- .../ui/result/ResultViewModel2.kt | 47 +++++++++++++++++ .../cloudstream3/utils/DataStoreHelper.kt | 51 +++++++++++++++++++ .../main/res/layout/fragment_result_swipe.xml | 21 +++++++- .../main/res/layout/fragment_result_tv.xml | 15 +++++- app/src/main/res/values/strings.xml | 5 ++ 8 files changed, 238 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index e6ca9711..71bb2633 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -8,7 +8,9 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.Coroutines.ioWork +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData @@ -69,24 +71,47 @@ class LocalList : SyncAPI { }?.distinctBy { it.first } ?: return null val list = ioWork { - watchStatusIds.groupBy { - it.second.stringRes - }.mapValues { group -> + val isTv = isTvSettings() + + val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { + // None is not something to display + it.stringRes to emptyList() + } + mapOf( + R.string.favorites_list_name to emptyList() + ) + if (!isTv) { + mapOf( + R.string.subscription_list_name to emptyList(), + ) + } else { + emptyMap() + } + + val watchStatusMap = watchStatusIds.groupBy { it.second.stringRes }.mapValues { group -> group.value.mapNotNull { getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) } - } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { + } + + val favoritesMap = mapOf(R.string.favorites_list_name to getAllFavorites().mapNotNull { it.toLibraryItem() }) + + // Don't show subscriptions or favorites on TV + val result = if (isTv) { + baseMap + watchStatusMap + favoritesMap + } else { + val subscriptionsMap = mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { + it.toLibraryItem() + }) + + baseMap + watchStatusMap + subscriptionsMap + favoritesMap + } + + result } - val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { - // None is not something to display - it.stringRes to emptyList() - } + mapOf(R.string.subscription_list_name to emptyList()) - return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, + list.map { SyncAPI.LibraryList(txt(it.key), it.value) }, setOf( ListSorting.AlphabeticalA, ListSorting.AlphabeticalZ, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index e5f16dd5..a0d82062 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -445,6 +445,20 @@ open class ResultFragmentPhone : FullScreenPlayer() { ?: txt(R.string.no_data).asStringNull(context) ?: "" CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) } + resultFavorite.setOnClickListener { + val isFavorite = + viewModel.toggleFavoriteStatus() ?: return@setOnClickListener + + val message = if (isFavorite) { + R.string.favorite_added + } else { + R.string.favorite_removed + } + + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } mediaRouteButton.apply { val chromecastSupport = api?.hasChromecastSupport == true alpha = if (chromecastSupport) 1f else 0.3f @@ -564,6 +578,19 @@ open class ResultFragmentPhone : FullScreenPlayer() { binding?.resultSubscribe?.setImageResource(drawable) } + observeNullable(viewModel.favoriteStatus) { isFavorite -> + binding?.resultFavorite?.isVisible = isFavorite != null + if (isFavorite == null) return@observeNullable + + val drawable = if (isFavorite) { + R.drawable.ic_baseline_favorite_24 + } else { + R.drawable.ic_baseline_favorite_border_24 + } + + binding?.resultFavorite?.setImageResource(drawable) + } + observe(viewModel.trailers) { trailers -> setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet! } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 5e4869cc..13734b67 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.DecelerateInterpolator +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible @@ -17,6 +18,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.updateHasTrailers +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent @@ -265,6 +267,7 @@ class ResultFragmentTv : Fragment() { resultEpisodesShow.onFocusChangeListener = rightListener resultDescription.onFocusChangeListener = leftListener resultBookmarkButton.onFocusChangeListener = leftListener + resultFavoriteButton.onFocusChangeListener = leftListener resultEpisodesShow.setOnClickListener { // toggle, to make it more touch accessable just in case someone thinks that a // tv layout is better but is using a touch device @@ -283,7 +286,8 @@ class ResultFragmentTv : Fragment() { resultPlaySeries, resultResumeSeries, resultPlayTrailer, - resultBookmarkButton + resultBookmarkButton, + resultFavoriteButton ) for (requestView in views) { if (!requestView.isVisible) continue @@ -424,6 +428,7 @@ class ResultFragmentTv : Fragment() { val aboveCast = listOf( binding?.resultEpisodesShow, binding?.resultBookmarkButton, + binding?.resultFavoriteButton, ).firstOrNull { it?.isVisible == true } @@ -532,6 +537,41 @@ class ResultFragmentTv : Fragment() { } } + observeNullable(viewModel.favoriteStatus) { isFavorite -> + binding?.resultFavoriteButton?.apply { + isVisible = isFavorite != null + if (isFavorite == null) return@observeNullable + + val drawable = if (isFavorite) { + R.drawable.ic_baseline_favorite_24 + } else { + R.drawable.ic_baseline_favorite_border_24 + } + + val text = if (isFavorite) { + R.string.action_remove_from_favorites + } else { + R.string.action_add_to_favorites + } + + setIconResource(drawable) + setText(text) + setOnClickListener { + val isFavorite = viewModel.toggleFavoriteStatus() ?: return@setOnClickListener + + val message = if (isFavorite) { + R.string.favorite_added + } else { + R.string.favorite_removed + } + + val name = (viewModel.page.value as? Resource.Success)?.value?.title + ?: txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + } + } + } + observeNullable(viewModel.movie) { data -> binding?.apply { resultPlayMovie.isVisible = data is Resource.Success diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 6acf476a..e5ed7b92 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -51,11 +51,14 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub +import com.lagradost.cloudstream3.utils.DataStoreHelper.getFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.DataStoreHelper.removeFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub +import com.lagradost.cloudstream3.utils.DataStoreHelper.setFavoritesData import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.UIHelper.navigate @@ -425,6 +428,9 @@ class ResultViewModel2 : ViewModel() { private val _subscribeStatus: MutableLiveData = MutableLiveData(null) val subscribeStatus: LiveData = _subscribeStatus + private val _favoriteStatus: MutableLiveData = MutableLiveData(null) + val favoriteStatus: LiveData = _favoriteStatus + companion object { const val TAG = "RVM2" //private const val EPISODE_RANGE_SIZE = 20 @@ -868,6 +874,40 @@ class ResultViewModel2 : ViewModel() { return !isSubscribed } + /** + * @return true if added to favorites, false if not. Null if not possible to favorite. + **/ + fun toggleFavoriteStatus(): Boolean? { + val isFavorite = _favoriteStatus.value ?: return null + val response = currentResponse ?: return null + + val currentId = response.getId() + + if (isFavorite) { + removeFavoritesData(currentId) + } else { + val current = getFavoritesData(currentId) + + setFavoritesData( + currentId, + DataStoreHelper.FavoritesData( + currentId, + current?.favoritesTime ?: unixTimeMS, + unixTimeMS, + response.name, + response.url, + response.apiName, + response.type, + response.posterUrl, + response.year + ) + ) + } + + _favoriteStatus.postValue(!isFavorite) + return !isFavorite + } + private fun startChromecast( activity: Activity?, result: ResultEpisode, @@ -1750,6 +1790,12 @@ class ResultViewModel2 : ViewModel() { } } + private fun postFavorites(loadResponse: LoadResponse) { + val id = loadResponse.getId() + val isFavorite = getFavoritesData(id) != null + _favoriteStatus.postValue(isFavorite) + } + private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { if (range == null || indexer == null) { return @@ -1887,6 +1933,7 @@ class ResultViewModel2 : ViewModel() { currentResponse = loadResponse postPage(loadResponse, apiRepository) postSubscription(loadResponse) + postFavorites(loadResponse) if (updateEpisodes) postEpisodes(loadResponse, updateFillers) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 952422a4..4b4157d6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -42,6 +42,7 @@ const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" +const val RESULT_FAVORITES_STATE_DATA = "result_favorites_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" @@ -406,6 +407,33 @@ object DataStoreHelper { } } + data class FavoritesData( + @JsonProperty("id") override var id: Int?, + @JsonProperty("favoritesTime") val favoritesTime: Long, + @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, + @JsonProperty("name") override val name: String, + @JsonProperty("url") override val url: String, + @JsonProperty("apiName") override val apiName: String, + @JsonProperty("type") override var type: TvType? = null, + @JsonProperty("posterUrl") override var posterUrl: String?, + @JsonProperty("year") val year: Int?, + @JsonProperty("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, + ) : SearchResponse { + fun toLibraryItem(): SyncAPI.LibraryItem? { + return SyncAPI.LibraryItem( + name, + url, + id?.toString() ?: return null, + null, + null, + null, + latestUpdatedTime, + apiName, type, posterUrl, posterHeaders, quality, this.id + ) + } + } + data class ResumeWatchingResult( @JsonProperty("name") override val name: String, @JsonProperty("url") override val url: String, @@ -579,6 +607,29 @@ object DataStoreHelper { return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) } + fun getAllFavorites(): List { + return getKeys("$currentAccount/$RESULT_FAVORITES_STATE_DATA")?.mapNotNull { + getKey(it) + } ?: emptyList() + } + + fun removeFavoritesData(id: Int?) { + if (id == null) return + AccountManager.localListApi.requireLibraryRefresh = true + removeKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + } + + fun setFavoritesData(id: Int?, data: FavoritesData) { + if (id == null) return + setKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString(), data) + AccountManager.localListApi.requireLibraryRefresh = true + } + + fun getFavoritesData(id: Int?): FavoritesData? { + if (id == null) return null + return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + } + fun setViewPos(id: Int?, pos: Long, dur: Long) { if (id == null) return if (dur < 30_000) return // too short diff --git a/app/src/main/res/layout/fragment_result_swipe.xml b/app/src/main/res/layout/fragment_result_swipe.xml index 4e8e3c14..eb2653d0 100644 --- a/app/src/main/res/layout/fragment_result_swipe.xml +++ b/app/src/main/res/layout/fragment_result_swipe.xml @@ -74,7 +74,7 @@ android:nextFocusUp="@id/result_back" android:nextFocusDown="@id/result_description" android:nextFocusLeft="@id/result_add_sync" - android:nextFocusRight="@id/result_share" + android:nextFocusRight="@id/result_favorite" tools:visibility="visible" @@ -89,10 +89,27 @@ android:layout_gravity="end|center_vertical" app:tint="?attr/textColor" /> + + + + tv_no_focus_tag You have already voted + Favorites + %s added to favorites + %s removed from favorites + Add to favorites + Remove from favorites From 8ed7418fe41d314342ae5cf9bb50c78d46b8b7b1 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 14 Oct 2023 10:30:45 -0600 Subject: [PATCH 07/15] Enable sorting by updated date in LocalList --- .../cloudstream3/syncproviders/providers/LocalList.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 71bb2633..0c01c0d5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -80,7 +80,7 @@ class LocalList : SyncAPI { R.string.favorites_list_name to emptyList() ) + if (!isTv) { mapOf( - R.string.subscription_list_name to emptyList(), + R.string.subscription_list_name to emptyList() ) } else { emptyMap() @@ -115,8 +115,8 @@ class LocalList : SyncAPI { setOf( ListSorting.AlphabeticalA, ListSorting.AlphabeticalZ, -// ListSorting.UpdatedNew, -// ListSorting.UpdatedOld, + ListSorting.UpdatedNew, + ListSorting.UpdatedOld, // ListSorting.RatingHigh, // ListSorting.RatingLow, ) From a7a6f2282a539082b9e864ca36848c0a74e47601 Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Wed, 18 Oct 2023 00:43:29 +0530 Subject: [PATCH 08/15] Update build.gradle.kts (#694) --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b0798e44..cea5684e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,7 +232,7 @@ dependencies { // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") // implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1") - implementation("com.github.Blatzar:NiceHttp:0.4.3") + implementation("com.github.Blatzar:NiceHttp:0.4.4") // http library // To fix SSL fuckery on android 9 implementation("org.conscrypt:conscrypt-android:2.5.2") // Util to skip the URI file fuckery 🙏 From eb58cb1184389ddc0eff0de339fcea62939877dd Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Mon, 23 Oct 2023 21:41:05 +0530 Subject: [PATCH 09/15] fix crash when navigation graph is null (#706) --- app/build.gradle.kts | 12 ++++++------ .../com/lagradost/cloudstream3/utils/UIHelper.kt | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cea5684e..73c53292 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,7 +51,7 @@ android { } // https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading - compileSdk = 33 // android 14 is fucked + compileSdk = 34 // android 14 is fucked buildToolsVersion = "34.0.0" defaultConfig { @@ -157,7 +157,7 @@ dependencies { implementation("androidx.test.ext:junit-ktx:1.1.5") testImplementation("org.json:json:20230618") - implementation("androidx.core:core-ktx:1.10.1") // need 34 for higher + implementation("androidx.core:core-ktx:1.12.0") // need 34 for higher implementation("androidx.appcompat:appcompat:1.6.1") // need target 32 for 1.5.0 // dont change this to 1.6.0 it looks ugly af @@ -165,10 +165,10 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.1.4") // need 34 for higher - implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") - implementation("androidx.navigation:navigation-ui-ktx:2.6.0") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") + implementation("androidx.navigation:navigation-ui-ktx:2.7.4") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index 9b40e70e..d5357e0c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -178,9 +178,10 @@ object UIHelper { fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) { try { if (this is FragmentActivity) { - (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.navController?.navigate( - navigation, arguments - ) + val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment? + navHostFragment?.navController?.let { + it.navigate(navigation, arguments) + } } } catch (t: Throwable) { logError(t) From 138dea88c4404aafb8026574c89e69c8e7387ca9 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Mon, 23 Oct 2023 19:16:48 +0300 Subject: [PATCH 10/15] Poster cropped at 20% from Top (#693) --- .../ui/result/ResultFragmentTv.kt | 6 +- .../utils/PercentageCropImageView.kt | 94 +++++++++++++++++++ .../main/res/layout/fragment_result_tv.xml | 7 +- 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 13734b67..9ccc7c01 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -247,7 +247,7 @@ class ResultFragmentTv : Fragment() { binding?.apply { //episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f - + val leftListener: View.OnFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (!hasFocus) return@OnFocusChangeListener @@ -804,12 +804,14 @@ class ResultFragmentTv : Fragment() { R.drawable.profile_bg_red, R.drawable.profile_bg_teal ).random() + //Change poster crop area to 20% from Top + backgroundPoster.cropYCenterOffsetPct = 0.20F + backgroundPoster.setImage( d.posterBackgroundImage ?: UiImage.Drawable(error), radius = 0, errorImageDrawable = error ) - resultComingSoon.isVisible = d.comingSoon resultDataHolder.isGone = d.comingSoon UIHelper.populateChips(resultTag, d.tags) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt new file mode 100644 index 00000000..1e572fb7 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt @@ -0,0 +1,94 @@ +package com.lagradost.cloudstream3.utils +//Reference: https://stackoverflow.com/a/29055283 +import android.content.Context +import android.graphics.Matrix +import android.graphics.drawable.Drawable +import android.util.AttributeSet + +class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { + private var mCropYCenterOffsetPct: Float? = null + private var mCropXCenterOffsetPct: Float? = null + constructor(context: Context?) : super(context!!) + constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) + constructor( + context: Context?, attrs: AttributeSet?, + defStyle: Int + ) : super(context!!, attrs, defStyle) + + var cropYCenterOffsetPct: Float + get() = mCropYCenterOffsetPct!! + set(cropYCenterOffsetPct) { + require(cropYCenterOffsetPct <= 1.0) { "Value too large: Must be <= 1.0" } + mCropYCenterOffsetPct = cropYCenterOffsetPct + } + var cropXCenterOffsetPct: Float + get() = mCropXCenterOffsetPct!! + set(cropXCenterOffsetPct) { + require(cropXCenterOffsetPct <= 1.0) { "Value too large: Must be <= 1.0" } + mCropXCenterOffsetPct = cropXCenterOffsetPct + } + + private fun myConfigureBounds() { + if (this.scaleType == ScaleType.MATRIX) { + + val d = this.drawable + if (d != null) { + val dWidth = d.intrinsicWidth + val dHeight = d.intrinsicHeight + val m = Matrix() + val vWidth = width - this.paddingLeft - this.paddingRight + val vHeight = height - this.paddingTop - this.paddingBottom + val scale: Float + var dx = 0f + var dy = 0f + if (dWidth * vHeight > vWidth * dHeight) { + val cropXCenterOffsetPct = + if (mCropXCenterOffsetPct != null) mCropXCenterOffsetPct!!.toFloat() else 0.5f + scale = vHeight.toFloat() / dHeight.toFloat() + dx = (vWidth - dWidth * scale) * cropXCenterOffsetPct + } else { + val cropYCenterOffsetPct = + if (mCropYCenterOffsetPct != null) mCropYCenterOffsetPct!!.toFloat() else 0f + scale = vWidth.toFloat() / dWidth.toFloat() + dy = (vHeight - dHeight * scale) * cropYCenterOffsetPct + } + m.setScale(scale, scale) + m.postTranslate((dx + 0.5f).toInt().toFloat(), (dy + 0.5f).toInt().toFloat()) + this.imageMatrix = m + } + } + } + + // These 3 methods call configureBounds in ImageView.java class, which + // adjusts the matrix in a call to center_crop (android's built-in + // scaling and centering crop method). We also want to trigger + // in the same place, but using our own matrix, which is then set + // directly at line 588 of ImageView.java and then copied over + // as the draw matrix at line 942 of ImageView.java + override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean { + val changed = super.setFrame(l, t, r, b) + myConfigureBounds() + return changed + } + + override fun setImageDrawable(d: Drawable?) { + super.setImageDrawable(d) + myConfigureBounds() + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + myConfigureBounds() + } + // In case you can change the ScaleType in code you have to call redraw() + //fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); + //fullsizeImageView.redraw(); + fun redraw() { + val d = this.drawable + if (d != null) { + // Force toggle to recalculate our bounds + setImageDrawable(null) + setImageDrawable(d) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 64550457..75215183 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -130,14 +130,15 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="250dp" android:visibility="visible"> - + android:scaleType="matrix" + tools:src="@drawable/profile_bg_dark_blue" > + Date: Mon, 23 Oct 2023 10:21:32 -0600 Subject: [PATCH 11/15] Library/LocalList: enable subscriptions on emulator layout (#702) --- .../cloudstream3/syncproviders/providers/LocalList.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 0c01c0d5..99723e90 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions @@ -71,14 +71,14 @@ class LocalList : SyncAPI { }?.distinctBy { it.first } ?: return null val list = ioWork { - val isTv = isTvSettings() + val isTrueTv = isTrueTvSettings() val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { // None is not something to display it.stringRes to emptyList() } + mapOf( R.string.favorites_list_name to emptyList() - ) + if (!isTv) { + ) + if (!isTrueTv) { mapOf( R.string.subscription_list_name to emptyList() ) @@ -96,8 +96,8 @@ class LocalList : SyncAPI { it.toLibraryItem() }) - // Don't show subscriptions or favorites on TV - val result = if (isTv) { + // Don't show subscriptions on TV + val result = if (isTrueTv) { baseMap + watchStatusMap + favoritesMap } else { val subscriptionsMap = mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { From 2a4468eb44fe16c90f3312dbefc06d77010d49e8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:33:44 -0600 Subject: [PATCH 12/15] Add more info on homepage to emulator layout (#698) * Add more info on homepage to emulator layout * Support for continue watching and bookmarks It does some manual changes to avoid having to duplicate the entire layout for minor changes --- .../cloudstream3/ExampleInstrumentedTest.kt | 6 ++- .../ui/home/HomeParentItemAdapter.kt | 17 ++++--- .../ui/home/HomeParentItemAdapterPreview.kt | 47 +++++++++++++++++-- .../main/res/layout/fragment_home_head_tv.xml | 24 +++++++++- .../res/layout/homepage_parent_emulator.xml | 35 ++++++++++++++ 5 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/layout/homepage_parent_emulator.xml diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index a84b2457..faacdf50 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.databinding.FragmentSearchBinding import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomepageParentBinding +import com.lagradost.cloudstream3.databinding.HomepageParentEmulatorBinding import com.lagradost.cloudstream3.databinding.HomepageParentTvBinding import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutTvBinding @@ -119,8 +120,9 @@ class ExampleInstrumentedTest { // testAllLayouts(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv) // testAllLayouts(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv) - testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) - testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent) testAllLayouts(activity, R.layout.fragment_library_tv, R.layout.fragment_library) testAllLayouts(activity, R.layout.fragment_library_tv, R.layout.fragment_library) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 163a60a1..443278a9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -15,7 +15,8 @@ import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable class LoadClickCallback( @@ -34,11 +35,13 @@ open class ParentItemAdapter( ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val root = LayoutInflater.from(parent.context).inflate( - if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent, - parent, - false - ) + val layoutResId = when { + isTrueTvSettings() -> R.layout.homepage_parent_tv + parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator + else -> R.layout.homepage_parent + } + + val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false) val binding = HomepageParentBinding.bind(root) @@ -234,7 +237,7 @@ open class ParentItemAdapter( }) //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() - if (!isTvSettings()) { + if (!isTrueTvSettings()) { title.setOnClickListener { moreInfoClickCallback.invoke(expand) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index d7956f39..b7e52b88 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -35,6 +35,7 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA import com.lagradost.cloudstream3.ui.search.SearchClickCallback +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog @@ -81,6 +82,28 @@ class HomeParentItemAdapterPreview( parent, false ) else FragmentHomeHeadBinding.inflate(inflater, parent, false) + + if (binding is FragmentHomeHeadTvBinding && parent.context.isEmulatorSettings()) { + binding.homeBookmarkParentItemMoreInfo.isVisible = true + + val marginInDp = 50 + val density = binding.horizontalScrollChips.context.resources.displayMetrics.density + val marginInPixels = (marginInDp * density).toInt() + + val params = binding.horizontalScrollChips.layoutParams as ViewGroup.MarginLayoutParams + params.marginEnd = marginInPixels + binding.horizontalScrollChips.layoutParams = params + binding.homeWatchParentItemTitle.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + ContextCompat.getDrawable( + parent.context, + R.drawable.ic_baseline_arrow_forward_24 + ), + null + ) + } + HeaderViewHolder( binding, viewModel, @@ -553,12 +576,19 @@ class HomeParentItemAdapterPreview( resumeHolder.isVisible = resumeWatching.isNotEmpty() resumeAdapter.updateList(resumeWatching) - if (binding is FragmentHomeHeadBinding) { - binding.homeWatchParentItemTitle.setOnClickListener { + if ( + binding is FragmentHomeHeadBinding || + binding is FragmentHomeHeadTvBinding && + binding.root.context.isEmulatorSettings() + ) { + val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle + ?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle + + title?.setOnClickListener { viewModel.popup( HomeViewModel.ExpandableHomepageList( HomePageList( - binding.homeWatchParentItemTitle.text.toString(), + title.text.toString(), resumeWatching, false ), 1, false @@ -576,8 +606,15 @@ class HomeParentItemAdapterPreview( bookmarkHolder.isVisible = visible bookmarkAdapter.updateList(list) - if (binding is FragmentHomeHeadBinding) { - binding.homeBookmarkParentItemTitle.setOnClickListener { + if ( + binding is FragmentHomeHeadBinding || + binding is FragmentHomeHeadTvBinding && + binding.root.context.isEmulatorSettings() + ) { + val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle + ?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle + + title?.setOnClickListener { val items = toggleList.map { it.first }.filter { it.isChecked } if (items.isEmpty()) return@setOnClickListener // we don't want to show an empty dialog val textSum = items diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index 05cb3a41..6db7536f 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -232,7 +232,9 @@ android:layout_marginStart="@dimen/navbar_width" android:layout_marginEnd="0dp" android:padding="12dp" - android:text="@string/continue_watching" /> + android:text="@string/continue_watching" + android:background="?android:attr/selectableItemBackground" + app:drawableTint="?attr/white" /> + + + + + + + + + + + \ No newline at end of file From 48053164dc9737fefeb66a4d6dadc5fcc6410f3a Mon Sep 17 00:00:00 2001 From: KingLucius Date: Mon, 23 Oct 2023 19:38:53 +0300 Subject: [PATCH 13/15] Old APIs focus fixes (#692) * Movie button focus fix in TV layout * Cast item focusable in old API * Media description focus fix in old API * Switch account button focus fix in old APi --- .../com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt | 1 + app/src/main/res/layout/cast_item.xml | 2 +- app/src/main/res/layout/fragment_home_tv.xml | 1 + app/src/main/res/layout/fragment_result_tv.xml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 9ccc7c01..feb8ca04 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -575,6 +575,7 @@ class ResultFragmentTv : Fragment() { observeNullable(viewModel.movie) { data -> binding?.apply { resultPlayMovie.isVisible = data is Resource.Success + resultPlaySeries.isVisible = data == null seriesHolder.isVisible = data == null resultEpisodesShow.isVisible = data == null diff --git a/app/src/main/res/layout/cast_item.xml b/app/src/main/res/layout/cast_item.xml index 368fa770..f164384b 100644 --- a/app/src/main/res/layout/cast_item.xml +++ b/app/src/main/res/layout/cast_item.xml @@ -9,7 +9,7 @@ android:layout_margin="5dp" android:foreground="@drawable/outline_drawable" app:cardBackgroundColor="@color/transparent" - + android:focusable="true" app:cardCornerRadius="@dimen/rounded_image_radius" app:cardElevation="0dp"> diff --git a/app/src/main/res/layout/fragment_home_tv.xml b/app/src/main/res/layout/fragment_home_tv.xml index e44457d9..86ef884e 100644 --- a/app/src/main/res/layout/fragment_home_tv.xml +++ b/app/src/main/res/layout/fragment_home_tv.xml @@ -140,6 +140,7 @@ android:layout_gravity="end" android:background="@drawable/player_button_tv_attr_no_bg" android:contentDescription="@string/account" + android:focusable="true" android:nextFocusLeft="@id/home_preview_search_button" android:nextFocusRight="@id/home_switch_account" android:nextFocusDown="@id/home_change_api" diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 75215183..51add4cb 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -419,6 +419,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:fadingEdgeLength="30dp" android:foreground="@drawable/outline_drawable" android:maxLines="7" + android:focusable="true" android:nextFocusUp="@id/result_back" android:nextFocusDown="@id/result_bookmark_button" android:padding="5dp" From 504258bf152d773e3f09e0152778f7168f5648a8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:42:17 -0600 Subject: [PATCH 14/15] Show confirm exit dialog on emulator layout (#704) --- app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 4e0d93c9..a4d1c7b4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -662,7 +662,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val isAtHome = navController?.currentDestination?.matchDestination(R.id.navigation_home) == true - if (isAtHome && isTrueTvSettings()) { + if (isAtHome && isTvSettings()) { showConfirmExitDialog() } else { super.onBackPressed() From e4ba8520076b84d7b9d219852b2aabc8e92cf034 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Wed, 25 Oct 2023 08:43:29 -0600 Subject: [PATCH 15/15] Use bottom dialogs for synopsis (#709) * Use bottom dialogs for synopsis * Add bottomTextDialog --- .../ui/result/ResultFragmentPhone.kt | 32 ++++++++---------- .../utils/SingleSelectionHelper.kt | 22 +++++++++++++ .../main/res/layout/bottom_text_dialog.xml | 33 +++++++++++++++++++ 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/layout/bottom_text_dialog.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index a0d82062..ed9fd270 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -17,7 +17,6 @@ import android.view.animation.DecelerateInterpolator import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.Toast -import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView @@ -66,6 +65,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute @@ -681,14 +681,13 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultPoster.setImage(d.posterImage) resultPosterBackground.setImage(d.posterBackgroundImage) resultDescription.setTextHtml(d.plotText) - resultDescription.setOnClickListener { view -> - // todo bottom view? - view.context?.let { ctx -> - val builder: AlertDialog.Builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - builder.setMessage(d.plotText.asString(ctx).html()) - .setTitle(d.plotHeaderText.asString(ctx)) - .show() + resultDescription.setOnClickListener { + activity?.let { activity -> + activity.showBottomDialogText( + d.titleText.asString(activity), + d.plotText.asString(activity).html(), + {} + ) } } @@ -879,16 +878,11 @@ open class ResultFragmentPhone : FullScreenPlayer() { setRecommendations(recommendations, null) } observe(viewModel.episodeSynopsis) { description -> - // TODO bottom dialog - view.context?.let { ctx -> - val builder: AlertDialog.Builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - builder.setMessage(description.html()) - .setTitle(R.string.synopsis) - .setOnDismissListener { - viewModel.releaseEpisodeSynopsis() - } - .show() + activity?.let { activity -> + activity.showBottomDialogText( + activity.getString(R.string.synopsis), + description.html() + ) { viewModel.releaseEpisodeSynopsis() } } } context?.let { ctx -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 8285b8ab..5d54ffe5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils import android.app.Activity import android.app.Dialog +import android.text.Spanned import android.view.LayoutInflater import android.view.View import android.widget.AbsListView @@ -19,6 +20,7 @@ import androidx.core.view.marginRight import androidx.core.view.marginTop import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -363,4 +365,24 @@ object SingleSelectionHelper { dismissCallback ) } + + fun Activity.showBottomDialogText( + title: String, + text: Spanned, + dismissCallback: () -> Unit + ) { + val binding = BottomTextDialogBinding.inflate(layoutInflater) + val dialog = BottomSheetDialog(this) + + dialog.setContentView(binding.root) + + binding.dialogTitle.text = title + binding.dialogText.text = text + + dialog.setOnDismissListener { + dismissCallback.invoke() + } + + dialog.show() + } } diff --git a/app/src/main/res/layout/bottom_text_dialog.xml b/app/src/main/res/layout/bottom_text_dialog.xml new file mode 100644 index 00000000..01b4834d --- /dev/null +++ b/app/src/main/res/layout/bottom_text_dialog.xml @@ -0,0 +1,33 @@ + + + + + + + +