From 3a868025d896e055bbaa2f46f1eca3a44a1154f8 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Sun, 25 Feb 2024 14:17:36 +0200 Subject: [PATCH] New TV UI --- .../lagradost/cloudstream3/MainActivity.kt | 1 + .../cloudstream3/ui/home/HomeFragment.kt | 1 + .../ui/result/ResultFragmentTv.kt | 172 +++--- .../res/drawable/ic_baseline_film_roll_24.xml | 10 + .../res/drawable/ic_baseline_resume_arrow.xml | 11 + .../drawable/ic_baseline_resume_arrow2.xml | 12 + .../res/drawable/player_button_tv_attr.xml | 4 +- .../drawable/player_button_tv_attr_no_bg.xml | 2 +- app/src/main/res/layout/cast_item.xml | 1 - .../main/res/layout/fragment_home_head_tv.xml | 2 + app/src/main/res/layout/fragment_result.xml | 10 +- .../main/res/layout/fragment_result_tv.xml | 550 ++++++++++++------ .../main/res/layout/result_button_small.xml | 196 +++++++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 19 + 15 files changed, 737 insertions(+), 257 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_film_roll_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_resume_arrow.xml create mode 100644 app/src/main/res/drawable/ic_baseline_resume_arrow2.xml create mode 100644 app/src/main/res/layout/result_button_small.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index afb2f76f..fd8892d1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -1185,6 +1185,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { if(isTrueTvSettings()) { newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus -> + if (newFocus?.tag == "tv_no_focus_tag") return@addOnGlobalFocusChangeListener centerView(newFocus) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index d54ea488..cd843517 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -529,6 +529,7 @@ class HomeFragment : Fragment() { super.onScrolled(recyclerView, dx, dy) } }) + } 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 427e9cb3..5793edae 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 @@ -129,9 +129,9 @@ class ResultFragmentTv : Fragment() { * Note that this will steal any focus if the episode loading is too slow (unlikely). */ private fun focusPlayButton() { - binding?.resultPlayMovie?.requestFocus() - binding?.resultPlaySeries?.requestFocus() - binding?.resultResumeSeries?.requestFocus() + binding?.resultPlayMovieButton?.requestFocus() + binding?.resultPlaySeriesButton?.requestFocus() + binding?.resultResumeSeriesButton?.requestFocus() } private fun setRecommendations(rec: List?, validApiName: String?) { @@ -251,8 +251,11 @@ class ResultFragmentTv : Fragment() { //episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f val leftListener: View.OnFocusChangeListener = - View.OnFocusChangeListener { _, hasFocus -> + View.OnFocusChangeListener { view, hasFocus -> if (!hasFocus) return@OnFocusChangeListener + if (view?.tag == "tv_no_focus_tag"){ + resultFinishLoading.scrollTo(0,0) + } toggleEpisodes(false) } @@ -262,15 +265,15 @@ class ResultFragmentTv : Fragment() { toggleEpisodes(true) } - resultPlayMovie.onFocusChangeListener = leftListener - resultPlaySeries.onFocusChangeListener = leftListener - resultResumeSeries.onFocusChangeListener = leftListener - resultPlayTrailer.onFocusChangeListener = leftListener - resultEpisodesShow.onFocusChangeListener = rightListener + resultPlayMovieButton.onFocusChangeListener = leftListener + resultPlaySeriesButton.onFocusChangeListener = leftListener + resultResumeSeriesButton.onFocusChangeListener = leftListener + resultPlayTrailerButton.onFocusChangeListener = leftListener + resultEpisodesShowButton.onFocusChangeListener = rightListener resultDescription.onFocusChangeListener = leftListener resultBookmarkButton.onFocusChangeListener = leftListener resultFavoriteButton.onFocusChangeListener = leftListener - resultEpisodesShow.setOnClickListener { + resultEpisodesShowButton.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 toggleEpisodes(!episodeHolderTv.isVisible) @@ -284,10 +287,10 @@ class ResultFragmentTv : Fragment() { binding?.apply { val views = listOf( - resultPlayMovie, - resultPlaySeries, - resultResumeSeries, - resultPlayTrailer, + resultPlayMovieButton, + resultPlaySeriesButton, + resultResumeSeriesButton, + resultPlayTrailerButton, resultBookmarkButton, resultFavoriteButton, resultSubscribeButton @@ -300,7 +303,7 @@ class ResultFragmentTv : Fragment() { } // parallax on background - resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY -> backgroundPosterHolder.translationY = -scrollY.toFloat() * 0.8f }) @@ -313,7 +316,7 @@ class ResultFragmentTv : Fragment() { resultSeasonSelection, resultRangeSelection, resultEpisodes, - resultPlayTrailer, + resultPlayTrailerButton, ) for (requestView in views) { if (!requestView.isShown) continue @@ -430,9 +433,9 @@ class ResultFragmentTv : Fragment() { val aboveCast = listOf( binding?.resultEpisodesShow, - binding?.resultBookmarkButton, - binding?.resultFavoriteButton, - binding?.resultSubscribeButton, + binding?.resultBookmark, + binding?.resultFavorite, + binding?.resultSubscribe, ).firstOrNull { it?.isVisible == true } @@ -443,8 +446,15 @@ class ResultFragmentTv : Fragment() { observeNullable(viewModel.resumeWatching) { resume -> binding?.apply { + + // > resultResumeSeries is visible when not null + if (resume == null) { + resultResumeSeries.isVisible = false + return@observeNullable + } + // show progress no matter if series or movie - resume?.progress?.let { progress -> + resume.progress?.let { progress -> resultResumeSeriesProgressText.setText(progress.progressLeft) resultResumeSeriesProgress.apply { isVisible = true @@ -456,37 +466,16 @@ class ResultFragmentTv : Fragment() { resultResumeProgressHolder.isVisible = false } - // if movie then hide both as movie button is - // always visible on movies, this is done in movie observe - - if (resume?.isMovie == true) { - resultPlaySeries.isVisible = false - resultResumeSeries.isVisible = false - return@observeNullable - } - - // if series then - // > resultPlaySeries is visible when null - // > resultResumeSeries is visible when not null - if (resume == null) { - resultPlaySeries.isVisible = true - resultResumeSeries.isVisible = false - return@observeNullable - } - + resultPlayMovie.isVisible = false resultPlaySeries.isVisible = false resultResumeSeries.isVisible = true focusPlayButton() - resultResumeSeries.text = - if (resume.isMovie) context?.getString(R.string.play_movie_button) else context?.getNameFull( - null, // resume.result.name, we don't want episode title - resume.result.episode, - resume.result.season - ) + resultResumeSeriesText.text = + if (resume.isMovie) context?.getString(R.string.resume) else "${getString(R.string.season_short)}${resume.result.season}:${getString(R.string.episode_short)}${resume.result.episode}" - resultResumeSeries.setOnClickListener { + resultResumeSeriesButton.setOnClickListener { viewModel.handleAction( EpisodeClickEvent( storedData.playerAction, //?: ACTION_PLAY_EPISODE_IN_PLAYER, @@ -495,7 +484,7 @@ class ResultFragmentTv : Fragment() { ) } - resultResumeSeries.setOnLongClickListener { + resultResumeSeriesButton.setOnLongClickListener { viewModel.handleAction( EpisodeClickEvent(ACTION_SHOW_OPTIONS, resume.result) ) @@ -509,9 +498,9 @@ class ResultFragmentTv : Fragment() { context?.updateHasTrailers() if (!LoadResponse.isTrailersEnabled) return@observe val trailers = trailersLinks.flatMap { it.mirros } - binding?.resultPlayTrailer?.apply { - isGone = trailers.isEmpty() - setOnClickListener { + binding?.apply { + resultPlayTrailer.isGone = trailers.isEmpty() + resultPlayTrailerButton.setOnClickListener { if (trailers.isEmpty()) return@setOnClickListener activity.navigate( R.id.global_to_navigation_player, GeneratorPlayer.newInstance( @@ -526,24 +515,27 @@ class ResultFragmentTv : Fragment() { } observe(viewModel.watchStatus) { watchType -> - binding?.resultBookmarkButton?.apply { - setText(watchType.stringRes) - setOnClickListener { view -> + binding?.apply { + resultBookmarkText.setText(watchType.stringRes) + resultBookmarkButton.setOnClickListener { view -> activity?.showBottomDialog( - WatchType.values().map { view.context.getString(it.stringRes) }.toList(), + WatchType.entries.map { view.context.getString(it.stringRes) }.toList(), watchType.ordinal, view.context.getString(R.string.action_add_to_bookmarks), showApply = false, {}) { - viewModel.updateWatchStatus(WatchType.values()[it], context) + viewModel.updateWatchStatus(WatchType.entries[it], context) } } } } observeNullable(viewModel.favoriteStatus) { isFavorite -> + + binding?.resultFavorite?.isVisible = isFavorite != null + binding?.resultFavoriteButton?.apply { - isVisible = isFavorite != null + if (isFavorite == null) return@observeNullable val drawable = if (isFavorite) { @@ -552,14 +544,8 @@ class ResultFragmentTv : Fragment() { 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 { viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? -> if (newStatus == null) return@toggleFavoriteStatus @@ -576,11 +562,21 @@ class ResultFragmentTv : Fragment() { } } } + + binding?.resultFavoriteText?.apply { + val text = if (isFavorite == true) { + R.string.unfavorite + } else { + R.string.favorite + } + setText(text) + } } observeNullable(viewModel.subscribeStatus) { isSubscribed -> + binding?.resultSubscribe?.isVisible = isSubscribed != null && requireContext().isEmulatorSettings() binding?.resultSubscribeButton?.apply { - isVisible = isSubscribed != null && context.isEmulatorSettings() + if (isSubscribed == null) return@observeNullable val drawable = if (isSubscribed) { @@ -589,14 +585,8 @@ class ResultFragmentTv : Fragment() { R.drawable.baseline_notifications_none_24 } - val text = if (isSubscribed) { - R.string.action_unsubscribe - } else { - R.string.action_subscribe - } - setIconResource(drawable) - setText(text) + setOnClickListener { viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? -> if (newStatus == null) return@toggleSubscriptionStatus @@ -614,32 +604,42 @@ class ResultFragmentTv : Fragment() { CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) } } + + binding?.resultSubscribeText?.apply { + val text = if (isSubscribed) { + R.string.action_unsubscribe + } else { + R.string.action_subscribe + } + setText(text) + } } } observeNullable(viewModel.movie) { data -> binding?.apply { resultPlayMovie.isVisible = data is Resource.Success - resultPlaySeries.isVisible = data == null - seriesHolder.isVisible = data == null - resultEpisodesShow.isVisible = data == null + resultPlaySeries.isVisible = false + resultEpisodesShow.isVisible = false (data as? Resource.Success)?.value?.let { (text, ep) -> - resultPlayMovie.setText(text) - resultPlayMovie.setOnClickListener { + //resultPlayMovieText.setText(text) + resultPlayMovieButton.setOnClickListener { viewModel.handleAction( EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep) ) } - resultPlayMovie.setOnLongClickListener { + resultPlayMovieButton.setOnLongClickListener { viewModel.handleAction( EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep) ) return@setOnLongClickListener true } - focusPlayButton() + //focusPlayButton() + resultPlayMovieButton.requestFocus() } } + //focusPlayButton() } observeNullable(viewModel.selectPopup) { popup -> @@ -737,18 +737,19 @@ class ResultFragmentTv : Fragment() { var hasLoadedEpisodesOnce = false observeNullable(viewModel.episodes) { episodes -> binding?.apply { - resultEpisodes.isVisible = episodes is Resource.Success + // resultEpisodeLoading.isVisible = episodes is Resource.Loading if (episodes is Resource.Success) { + resultPlayMovie.isVisible = false + resultPlaySeries.isVisible = true + resultEpisodes.isVisible = true + resultEpisodesShow.isVisible = true + val first = episodes.value.firstOrNull() if (first != null) { - resultPlaySeries.text = context?.getNameFull( - null, // resume.result.name, we don't want episode title - first.episode, - first.season - ) + resultPlaySeriesText.text = "${getString(R.string.season_short)}${first.season}:${getString(R.string.episode_short)}${first.episode}" - resultPlaySeries.setOnClickListener { + resultPlaySeriesButton.setOnClickListener { viewModel.handleAction( EpisodeClickEvent( ACTION_CLICK_DEFAULT, @@ -756,7 +757,7 @@ class ResultFragmentTv : Fragment() { ) ) } - resultPlaySeries.setOnLongClickListener { + resultPlaySeriesButton.setOnLongClickListener { viewModel.handleAction( EpisodeClickEvent(ACTION_SHOW_OPTIONS, first) ) @@ -765,6 +766,7 @@ class ResultFragmentTv : Fragment() { if (!hasLoadedEpisodesOnce) { hasLoadedEpisodesOnce = true focusPlayButton() + resultPlaySeries.requestFocus() } } diff --git a/app/src/main/res/drawable/ic_baseline_film_roll_24.xml b/app/src/main/res/drawable/ic_baseline_film_roll_24.xml new file mode 100644 index 00000000..941d936f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_film_roll_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_resume_arrow.xml b/app/src/main/res/drawable/ic_baseline_resume_arrow.xml new file mode 100644 index 00000000..0326fbd4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_resume_arrow.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_resume_arrow2.xml b/app/src/main/res/drawable/ic_baseline_resume_arrow2.xml new file mode 100644 index 00000000..fc533a0e --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_resume_arrow2.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/player_button_tv_attr.xml b/app/src/main/res/drawable/player_button_tv_attr.xml index 4c90a64e..ed83887d 100644 --- a/app/src/main/res/drawable/player_button_tv_attr.xml +++ b/app/src/main/res/drawable/player_button_tv_attr.xml @@ -3,13 +3,13 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml b/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml index b9b927da..0dd8c256 100644 --- a/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml +++ b/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/cast_item.xml b/app/src/main/res/layout/cast_item.xml index f164384b..99a9750b 100644 --- a/app/src/main/res/layout/cast_item.xml +++ b/app/src/main/res/layout/cast_item.xml @@ -17,7 +17,6 @@ android:layout_width="100dp" android:layout_height="wrap_content" android:orientation="vertical" - android:focusable="true" android:padding="5dp"> diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 70461518..045b9cc2 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -412,7 +412,7 @@ android:foreground="@drawable/outline_drawable" android:maxLength="1000" android:nextFocusUp="@id/result_back" - android:nextFocusDown="@id/result_bookmark_button" + android:nextFocusDown="@id/result_bookmark_Button" android:paddingTop="5dp" android:textColor="?attr/textColor" android:textSize="15sp" @@ -474,7 +474,7 @@ android:fadingEdge="horizontal" android:focusable="false" android:focusableInTouchMode="false" - android:nextFocusUp="@id/result_bookmark_button" + android:nextFocusUp="@id/result_bookmark_Button" android:nextFocusDown="@id/result_play_movie" android:orientation="horizontal" android:paddingTop="5dp" @@ -580,7 +580,7 @@ android:layout_marginStart="0dp" android:layout_marginEnd="0dp" android:layout_marginBottom="10dp" - android:nextFocusUp="@id/result_bookmark_button" + android:nextFocusUp="@id/result_bookmark_Button" android:nextFocusDown="@id/result_download_movie" android:text="@string/play_movie_button" android:visibility="visible" @@ -658,7 +658,7 @@ android:layout_marginStart="0dp" android:layout_marginEnd="0dp" android:layout_marginBottom="10dp" - android:nextFocusUp="@id/result_bookmark_button" + android:nextFocusUp="@id/result_bookmark_Button" android:nextFocusDown="@id/result_download_movie" android:text="@string/resume" android:visibility="visible" @@ -674,7 +674,7 @@ android:layout_marginStart="0dp" android:layout_marginEnd="0dp" android:layout_marginBottom="10dp" - android:nextFocusUp="@id/result_bookmark_button" + android:nextFocusUp="@id/result_bookmark_Button" android:nextFocusDown="@id/result_download_movie" android:text="@string/next_episode" android:visibility="gone" diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index a7ba4334..4d273da0 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -127,7 +127,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit + android:layout_marginTop="225dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_marginTop="10dp" + android:baselineAligned="false"> + app:layout_constraintTop_toTopOf="parent" + tools:ignore="UselessParent"> + android:layout_height="wrap_content"> @@ -495,14 +585,11 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:maxLines="1" android:paddingEnd="5dp" android:textColor="?attr/grayTextColor" - android:visibility="gone" tools:ignore="RtlSymmetry" tools:text="69m remaining" /> - - @@ -513,10 +600,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:descendantFocusability="afterDescendants" android:fadingEdge="horizontal" - android:focusable="false" - android:focusableInTouchMode="false" - android:nextFocusUp="@id/result_episodes_show" - android:nextFocusDown="@id/result_recommendations_filter_selection" + android:nextFocusUp="@id/result_description" + android:nextFocusDown="@id/result_recommendations_list" android:orientation="horizontal" android:paddingTop="5dp" android:requiresFadingEdge="horizontal" @@ -527,6 +612,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit @@ -1131,6 +1217,144 @@ https://developer.android.com/design/ui/tv/samples/jet-fit --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/result_button_small.xml b/app/src/main/res/layout/result_button_small.xml new file mode 100644 index 00000000..da48abc8 --- /dev/null +++ b/app/src/main/res/layout/result_button_small.xml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14bb9552..6a76bc2c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -745,4 +745,7 @@ Display a toggle button for screen orientation Enable automatic switching of screen orientation based on video orientation Auto rotate + Favorite + Movie + Unfavorite diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2fa4eb41..059aaa70 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -804,6 +804,25 @@ 0dp + +