From 757208a89454b4fb18c97096f221ac73df9475fb Mon Sep 17 00:00:00 2001 From: Michal Date: Thu, 11 Jun 2026 16:25:48 +0200 Subject: [PATCH 1/2] feat: WIP edit screen experiments --- .../java/tv/trakt/trakt/TraktApplication.kt | 2 + .../trakt/core/discover/DiscoverScreen.kt | 110 +++++---- .../trakt/core/discover/DiscoverState.kt | 3 + .../trakt/core/discover/DiscoverViewModel.kt | 22 +- .../trakt/core/discover/di/DiscoverModule.kt | 5 +- .../anticipated/DiscoverAnticipatedState.kt | 1 - .../anticipated/DiscoverAnticipatedView.kt | 87 +++---- .../DiscoverAnticipatedViewModel.kt | 42 +--- .../sections/popular/DiscoverPopularState.kt | 1 - .../sections/popular/DiscoverPopularView.kt | 87 +++---- .../popular/DiscoverPopularViewModel.kt | 42 +--- .../recommended/DiscoverRecommendedState.kt | 1 - .../recommended/DiscoverRecommendedView.kt | 87 +++---- .../DiscoverRecommendedViewModel.kt | 42 +--- .../trending/DiscoverTrendingState.kt | 1 - .../sections/trending/DiscoverTrendingView.kt | 87 +++---- .../trending/DiscoverTrendingViewModel.kt | 42 +--- .../tv/trakt/trakt/core/home/HomeScreen.kt | 121 ++++++---- .../tv/trakt/trakt/core/home/HomeState.kt | 3 + .../tv/trakt/trakt/core/home/HomeViewModel.kt | 19 +- .../tv/trakt/trakt/core/home/di/HomeModule.kt | 6 +- .../features/history/HomeHistoryState.kt | 1 - .../features/history/HomeHistoryView.kt | 113 ++++----- .../features/history/HomeHistoryViewModel.kt | 46 +--- .../features/social/HomeSocialState.kt | 1 - .../features/social/HomeSocialView.kt | 109 ++++----- .../features/social/HomeSocialViewModel.kt | 46 +--- .../sections/upcoming/HomeUpcomingState.kt | 1 - .../sections/upcoming/HomeUpcomingView.kt | 131 +++++----- .../upcoming/HomeUpcomingViewModel.kt | 44 +--- .../home/sections/upnext/HomeUpNextState.kt | 1 - .../home/sections/upnext/HomeUpNextView.kt | 150 ++++++------ .../sections/upnext/HomeUpNextViewModel.kt | 44 +--- .../sections/watchlist/HomeWatchlistState.kt | 1 - .../sections/watchlist/HomeWatchlistView.kt | 141 +++++------ .../watchlist/HomeWatchlistViewModel.kt | 43 +--- .../tv/trakt/trakt/core/lists/ListsScreen.kt | 80 ++++--- .../tv/trakt/trakt/core/lists/ListsState.kt | 3 + .../trakt/trakt/core/lists/ListsViewModel.kt | 35 ++- .../trakt/trakt/core/lists/di/ListsModule.kt | 5 +- .../ListsCollaborationsState.kt | 1 - .../collaborations/ListsCollaborationsView.kt | 111 ++++----- .../ListsCollaborationsViewModel.kt | 48 +--- .../lists/sections/liked/ListsLikedState.kt | 1 - .../lists/sections/liked/ListsLikedView.kt | 111 ++++----- .../sections/liked/ListsLikedViewModel.kt | 48 +--- .../sections/personal/ListsPersonalState.kt | 1 - .../sections/personal/ListsPersonalView.kt | 115 ++++----- .../personal/ListsPersonalViewModel.kt | 48 +--- .../sections/watchlist/ListsWatchlistState.kt | 1 - .../sections/watchlist/ListsWatchlistView.kt | 133 +++++------ .../watchlist/ListsWatchlistViewModel.kt | 43 +--- .../tv/trakt/trakt/core/main/di/MainModule.kt | 20 +- .../trakt/trakt/core/profile/ProfileScreen.kt | 153 ++++++++---- .../trakt/trakt/core/profile/ProfileState.kt | 3 + .../trakt/core/profile/ProfileViewModel.kt | 20 ++ .../trakt/core/profile/di/ProfileModule.kt | 7 +- .../favorites/ProfileFavoritesState.kt | 1 - .../favorites/ProfileFavoritesView.kt | 107 ++++----- .../favorites/ProfileFavoritesViewModel.kt | 25 +- .../sections/history/ProfileHistoryState.kt | 1 - .../sections/history/ProfileHistoryView.kt | 109 ++++----- .../history/ProfileHistoryViewModel.kt | 23 +- .../sections/library/ProfileLibraryState.kt | 1 - .../sections/library/ProfileLibraryView.kt | 123 ++++------ .../library/ProfileLibraryViewModel.kt | 25 +- .../sections/progress/ProfileProgressState.kt | 1 - .../sections/progress/ProfileProgressView.kt | 117 ++++----- .../progress/ProfileProgressViewModel.kt | 25 +- .../sections/social/ProfileSocialState.kt | 1 - .../sections/social/ProfileSocialView.kt | 109 ++++----- .../sections/social/ProfileSocialViewModel.kt | 25 +- .../ratings/rateprompt/ui/RatePromptView.kt | 12 +- .../summary/episodes/EpisodeDetailsScreen.kt | 158 ++++++------ .../summary/episodes/EpisodeDetailsState.kt | 3 + .../episodes/EpisodeDetailsViewModel.kt | 18 ++ .../episodes/di/EpisodeDetailsModule.kt | 6 +- .../features/actors/EpisodeActorsState.kt | 1 - .../features/actors/EpisodeActorsView.kt | 63 ++--- .../features/actors/EpisodeActorsViewModel.kt | 20 -- .../features/comments/EpisodeCommentsState.kt | 1 - .../features/comments/EpisodeCommentsView.kt | 101 ++++---- .../comments/EpisodeCommentsViewModel.kt | 17 -- .../more/EpisodeDetailsContextSheet.kt | 10 + .../context/more/EpisodeDetailsContextView.kt | 17 ++ .../features/related/EpisodeRelatedState.kt | 1 - .../features/related/EpisodeRelatedView.kt | 65 ++--- .../related/EpisodeRelatedViewModel.kt | 20 -- .../features/season/EpisodeSeasonState.kt | 1 - .../features/season/EpisodeSeasonView.kt | 75 +++--- .../features/season/EpisodeSeasonViewModel.kt | 20 -- .../streaming/EpisodeStreamingsState.kt | 1 - .../streaming/EpisodeStreamingsView.kt | 63 ++--- .../streaming/EpisodeStreamingsViewModel.kt | 20 -- .../core/summary/movies/MovieDetailsScreen.kt | 191 ++++++++------- .../core/summary/movies/MovieDetailsState.kt | 3 + .../summary/movies/MovieDetailsViewModel.kt | 18 ++ .../summary/movies/di/MovieDetailsModule.kt | 9 +- .../features/actors/MovieActorsState.kt | 1 - .../movies/features/actors/MovieActorsView.kt | 65 ++--- .../features/actors/MovieActorsViewModel.kt | 25 +- .../features/comments/MovieCommentsState.kt | 1 - .../features/comments/MovieCommentsView.kt | 101 ++++---- .../comments/MovieCommentsViewModel.kt | 17 -- .../context/more/MovieDetailsContextSheet.kt | 10 + .../context/more/MovieDetailsContextView.kt | 27 ++- .../features/extras/MovieExtrasState.kt | 1 - .../movies/features/extras/MovieExtrasView.kt | 85 +++---- .../features/extras/MovieExtrasViewModel.kt | 22 +- .../movies/features/lists/MovieListsState.kt | 1 - .../movies/features/lists/MovieListsView.kt | 75 +++--- .../features/lists/MovieListsViewModel.kt | 23 +- .../features/related/MovieRelatedState.kt | 1 - .../features/related/MovieRelatedView.kt | 69 ++---- .../features/related/MovieRelatedViewModel.kt | 19 +- .../features/sentiment/MovieSentimentState.kt | 1 - .../features/sentiment/MovieSentimentView.kt | 66 ++---- .../sentiment/MovieSentimentViewModel.kt | 23 +- .../streaming/MovieStreamingsState.kt | 1 - .../features/streaming/MovieStreamingsView.kt | 65 ++--- .../streaming/MovieStreamingsViewModel.kt | 19 +- .../features/trivia/MovieTriviaState.kt | 1 - .../movies/features/trivia/MovieTriviaView.kt | 71 ++---- .../features/trivia/MovieTriviaViewModel.kt | 24 +- .../core/summary/shows/ShowDetailsScreen.kt | 224 ++++++++++-------- .../core/summary/shows/ShowDetailsState.kt | 3 + .../summary/shows/ShowDetailsViewModel.kt | 19 ++ .../summary/shows/di/ShowDetailsModule.kt | 12 +- .../shows/features/actors/ShowActorsState.kt | 1 - .../shows/features/actors/ShowActorsView.kt | 65 ++--- .../features/actors/ShowActorsViewModel.kt | 20 -- .../features/comments/ShowCommentsState.kt | 1 - .../features/comments/ShowCommentsView.kt | 101 ++++---- .../comments/ShowCommentsViewModel.kt | 17 -- .../context/more/ShowDetailsContextSheet.kt | 10 + .../context/more/ShowDetailsContextView.kt | 27 ++- .../shows/features/extras/ShowExtrasState.kt | 1 - .../shows/features/extras/ShowExtrasView.kt | 85 +++---- .../features/extras/ShowExtrasViewModel.kt | 20 -- .../features/history/ShowHistoryState.kt | 1 - .../shows/features/history/ShowHistoryView.kt | 67 ++---- .../features/history/ShowHistoryViewModel.kt | 16 -- .../shows/features/lists/ShowListsState.kt | 1 - .../shows/features/lists/ShowListsView.kt | 75 +++--- .../features/lists/ShowListsViewModel.kt | 20 -- .../features/related/ShowRelatedState.kt | 1 - .../shows/features/related/ShowRelatedView.kt | 69 ++---- .../features/related/ShowRelatedViewModel.kt | 20 -- .../features/seasons/ShowSeasonsState.kt | 1 - .../shows/features/seasons/ShowSeasonsView.kt | 110 ++++----- .../features/seasons/ShowSeasonsViewModel.kt | 20 -- .../features/sentiment/ShowSentimentState.kt | 1 - .../features/sentiment/ShowSentimentView.kt | 64 ++--- .../sentiment/ShowSentimentViewModel.kt | 20 -- .../features/streaming/ShowStreamingsState.kt | 1 - .../features/streaming/ShowStreamingsView.kt | 65 ++--- .../streaming/ShowStreamingsViewModel.kt | 20 -- .../shows/features/trivia/ShowTriviaState.kt | 1 - .../shows/features/trivia/ShowTriviaView.kt | 71 ++---- .../features/trivia/ShowTriviaViewModel.kt | 20 +- .../core/user/usecases/LogoutUserUseCase.kt | 3 - .../core/userprofile/UserProfileScreen.kt | 147 ++++++++---- .../core/userprofile/UserProfileState.kt | 3 + .../core/userprofile/UserProfileViewModel.kt | 20 ++ .../core/userprofile/di/UserProfileModule.kt | 4 - .../favorites/UserProfileFavoritesState.kt | 1 - .../favorites/UserProfileFavoritesView.kt | 107 ++++----- .../UserProfileFavoritesViewModel.kt | 22 +- .../history/UserProfileHistoryState.kt | 1 - .../history/UserProfileHistoryView.kt | 103 ++++---- .../history/UserProfileHistoryViewModel.kt | 20 -- .../sections/lists/UserProfileListsState.kt | 1 - .../sections/lists/UserProfileListsView.kt | 117 ++++----- .../lists/UserProfileListsViewModel.kt | 18 -- .../sections/social/UserProfileSocialState.kt | 1 - .../sections/social/UserProfileSocialView.kt | 113 ++++----- .../social/UserProfileSocialViewModel.kt | 18 -- .../helpers/collapsing/CollapsingManager.kt | 43 ---- .../collapsing/DefaultCollapsingManager.kt | 85 ------- .../helpers/collapsing/model/CollapsingKey.kt | 100 -------- .../helpers/editscreen/EditScreenSheet.kt | 51 ++++ .../helpers/editscreen/EditScreenState.kt | 10 + .../helpers/editscreen/EditScreenView.kt | 174 ++++++++++++++ .../helpers/editscreen/EditScreenViewModel.kt | 66 ++++++ .../data/DefaultEditScreenManager.kt | 73 ++++++ .../editscreen/data/EditScreenManager.kt | 16 ++ .../editscreen/data/model/EditScreenKey.kt | 136 +++++++++++ .../helpers/editscreen/di/EditScreenModule.kt | 15 ++ .../trakt/ui/components/TraktSectionHeader.kt | 39 +-- .../ui/components/headerbar/HeaderBar.kt | 71 +++++- .../src/main/res/drawable/ic_edit_screen.xml | 24 ++ .../src/main/res/drawable/ic_eye_off.xml | 24 ++ .../src/main/res/values/strings-todo.xml | 5 +- 193 files changed, 3409 insertions(+), 4583 deletions(-) delete mode 100644 app/src/main/java/tv/trakt/trakt/helpers/collapsing/CollapsingManager.kt delete mode 100644 app/src/main/java/tv/trakt/trakt/helpers/collapsing/DefaultCollapsingManager.kt delete mode 100644 app/src/main/java/tv/trakt/trakt/helpers/collapsing/model/CollapsingKey.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenSheet.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenState.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenView.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenViewModel.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/DefaultEditScreenManager.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/EditScreenManager.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/model/EditScreenKey.kt create mode 100644 app/src/main/java/tv/trakt/trakt/helpers/editscreen/di/EditScreenModule.kt create mode 100644 resources/src/main/res/drawable/ic_edit_screen.xml create mode 100644 resources/src/main/res/drawable/ic_eye_off.xml diff --git a/app/src/main/java/tv/trakt/trakt/TraktApplication.kt b/app/src/main/java/tv/trakt/trakt/TraktApplication.kt index 15ce09b58..b53d820bb 100644 --- a/app/src/main/java/tv/trakt/trakt/TraktApplication.kt +++ b/app/src/main/java/tv/trakt/trakt/TraktApplication.kt @@ -79,6 +79,7 @@ import tv.trakt.trakt.core.summary.shows.di.showDetailsModule import tv.trakt.trakt.core.sync.di.syncModule import tv.trakt.trakt.core.trivia.di.triviaModule import tv.trakt.trakt.core.userprofile.di.userProfileModule +import tv.trakt.trakt.helpers.editscreen.di.editScreenModule import tv.trakt.trakt.helpers.player.di.youTubePlayerModule import java.util.concurrent.TimeUnit.MINUTES import tv.trakt.trakt.common.R as RCommon @@ -189,6 +190,7 @@ internal class TraktApplication : Application() { userProfileModule, youTubePlayerModule, shareModule, + editScreenModule, ) } } diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverScreen.kt b/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverScreen.kt index 6db11d252..707b69658 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverScreen.kt @@ -41,6 +41,12 @@ import tv.trakt.trakt.core.discover.sections.recommended.DiscoverRecommendedView import tv.trakt.trakt.core.discover.sections.trending.DiscoverTrendingView import tv.trakt.trakt.core.filters.GlobalFiltersSheet import tv.trakt.trakt.helpers.ScreenHeaderState +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.DiscoverAnticipated +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.DiscoverPopular +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.DiscoverRecommended +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.DiscoverTrending import tv.trakt.trakt.helpers.rememberHeaderState import tv.trakt.trakt.ui.components.ScrollableBackdropImage import tv.trakt.trakt.ui.components.headerbar.HeaderBar @@ -61,6 +67,7 @@ internal fun DiscoverScreen( val state by viewModel.state.collectAsStateWithLifecycle() var filtersSheet by remember { mutableStateOf(false) } + var sectionsSheet by remember { mutableStateOf(false) } DiscoverScreen( state = state, @@ -71,16 +78,19 @@ internal fun DiscoverScreen( onMoreAnticipatedClick = onNavigateToAllAnticipated, onMoreRecommendedClick = onNavigateToAllRecommended, onVipClick = onNavigateToVip, - onFiltersClick = { - filtersSheet = true - }, + onFiltersClick = { filtersSheet = true }, + onEditScreenClick = { sectionsSheet = true }, ) GlobalFiltersSheet( active = filtersSheet, - onDismiss = { - filtersSheet = false - }, + onDismiss = { filtersSheet = false }, + ) + + EditScreenSheet( + active = sectionsSheet, + enabledValues = EditScreenKey.DiscoverKeys, + onDismiss = { sectionsSheet = false }, ) } @@ -96,6 +106,7 @@ private fun DiscoverScreen( onMoreRecommendedClick: () -> Unit = {}, onVipClick: () -> Unit = {}, onFiltersClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { val activity = LocalActivity.current val customThemeEnabled = (activity as? MainActivity)?.customThemeConfig?.enabled == true @@ -147,32 +158,36 @@ private fun DiscoverScreen( verticalArrangement = spacedBy(TraktTheme.spacing.mainSectionVerticalSpace), contentPadding = listPadding, ) { - item { - DiscoverTrendingView( - viewModel = koinViewModel { - parametersOf(customThemeEnabled) - }, - collection = state.collection, - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - onMoreClick = onMoreTrendingClick, - ) + if (state.visibility?.get(DiscoverTrending) == true) { + item { + DiscoverTrendingView( + viewModel = koinViewModel { + parametersOf(customThemeEnabled) + }, + collection = state.collection, + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + onMoreClick = onMoreTrendingClick, + ) + } } - item { - DiscoverAnticipatedView( - viewModel = koinViewModel { - parametersOf(customThemeEnabled) - }, - collection = state.collection, - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - onMoreClick = onMoreAnticipatedClick, - ) + if (state.visibility?.get(DiscoverAnticipated) == true) { + item { + DiscoverAnticipatedView( + viewModel = koinViewModel { + parametersOf(customThemeEnabled) + }, + collection = state.collection, + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + onMoreClick = onMoreAnticipatedClick, + ) + } } if (state.user.user != null && !state.user.user.isVip) { @@ -184,21 +199,26 @@ private fun DiscoverScreen( } } - item { - DiscoverPopularView( - viewModel = koinViewModel { - parametersOf(customThemeEnabled) - }, - collection = state.collection, - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - onMoreClick = onMorePopularClick, - ) + if (state.visibility?.get(DiscoverPopular) == true) { + item { + DiscoverPopularView( + viewModel = koinViewModel { + parametersOf(customThemeEnabled) + }, + collection = state.collection, + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + onMoreClick = onMorePopularClick, + ) + } } - if (state.user.isAuthenticated) { + if ( + state.user.isAuthenticated && + state.visibility?.get(DiscoverRecommended) == true + ) { item { DiscoverRecommendedView( viewModel = koinViewModel { @@ -221,6 +241,7 @@ private fun DiscoverScreen( isScrolledToTop = isScrolledToTop, onVipClick = onVipClick, onFiltersClick = onFiltersClick, + onEditScreenClick = onEditScreenClick, ) } } @@ -232,6 +253,7 @@ private fun ScreenHeader( isScrolledToTop: Boolean, onVipClick: () -> Unit, onFiltersClick: () -> Unit, + onEditScreenClick: () -> Unit, ) { val userState = remember(state.user) { val loadingDone = state.user.loading == Done @@ -244,8 +266,10 @@ private fun ScreenHeader( showLogin = userState.first && !userState.second, showVip = userState.second && state.user.user?.isVip == false, showFilters = true, + showEditScreen = true, onVipClick = onVipClick, onFilterClick = onFiltersClick, + onEditScreenClick = onEditScreenClick, modifier = Modifier.offset { IntOffset(0, headerState.connection.barOffset.fastRoundToInt()) }, diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverState.kt b/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverState.kt index da3ee2658..046837d0f 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverState.kt @@ -1,12 +1,15 @@ package tv.trakt.trakt.core.discover import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.user.UserCollectionState +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class DiscoverState( + val visibility: ImmutableMap? = null, val collection: UserCollectionState = UserCollectionState.Default, val user: UserState = UserState(), ) { diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverViewModel.kt index 5feb413f3..fbc70d999 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/DiscoverViewModel.kt @@ -2,6 +2,7 @@ package tv.trakt.trakt.core.discover import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -16,20 +17,25 @@ import tv.trakt.trakt.common.firebase.analytics.Analytics import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.core.discover.DiscoverState.UserState import tv.trakt.trakt.core.user.CollectionStateProvider +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.DiscoverKeys @OptIn(FlowPreview::class) internal class DiscoverViewModel( private val sessionManager: SessionManager, + private val editScreenManager: EditScreenManager, private val collectionStateProvider: CollectionStateProvider, analytics: Analytics, ) : ViewModel() { private val initialState = DiscoverState() private val userState = MutableStateFlow(initialState.user) + private val visibilityState = MutableStateFlow(initialState.visibility) init { observeUser() observeData() + observeVisibility() analytics.logScreenView( screenName = "discover", @@ -55,13 +61,25 @@ internal class DiscoverViewModel( .launchIn(viewModelScope) } + private fun observeVisibility() { + editScreenManager.observe(DiscoverKeys) + .onEach { visibility -> + visibilityState.update { + visibility.toImmutableMap() + } + } + .launchIn(viewModelScope) + } + val state = combine( userState, + visibilityState, collectionStateProvider.stateFlow, - ) { s1, s2 -> + ) { s1, s2, s3 -> DiscoverState( user = s1, - collection = s2, + visibility = s2, + collection = s3, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/di/DiscoverModule.kt b/app/src/main/java/tv/trakt/trakt/core/discover/di/DiscoverModule.kt index ce8482bb1..d5467d382 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/di/DiscoverModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/di/DiscoverModule.kt @@ -63,6 +63,7 @@ internal val discoverModule = module { sessionManager = get(), analytics = get(), collectionStateProvider = get(), + editScreenManager = get(), ) } @@ -87,7 +88,6 @@ internal val discoverModule = module { viewModel { (customTheme: Boolean) -> DiscoverTrendingViewModel( filterManager = get(), - collapsingManager = get(), getTrendingShowsUseCase = when { customTheme -> get(named("customTrendingShowsUseCase")) else -> get(named("defaultTrendingShowsUseCase")) @@ -102,7 +102,6 @@ internal val discoverModule = module { viewModel { (customTheme: Boolean) -> DiscoverAnticipatedViewModel( filterManager = get(), - collapsingManager = get(), getAnticipatedShowsUseCase = when { customTheme -> get(named("customAnticipatedShowsUseCase")) else -> get(named("defaultAnticipatedShowsUseCase")) @@ -117,7 +116,6 @@ internal val discoverModule = module { viewModel { (customTheme: Boolean) -> DiscoverPopularViewModel( filterManager = get(), - collapsingManager = get(), getPopularShowsUseCase = when { customTheme -> get(named("customPopularShowsUseCase")) else -> get(named("defaultPopularShowsUseCase")) @@ -132,7 +130,6 @@ internal val discoverModule = module { viewModel { (customTheme: Boolean) -> DiscoverRecommendedViewModel( filterManager = get(), - collapsingManager = get(), getRecommendedShowsUseCase = when { customTheme -> get(named("customRecommendedShowsUseCase")) else -> get(named("defaultRecommendedShowsUseCase")) diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedState.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedState.kt index b9f8c4e9a..e973f1995 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedState.kt @@ -10,7 +10,6 @@ import tv.trakt.trakt.core.discover.model.DiscoverItem internal data class DiscoverAnticipatedState( val items: ImmutableList? = null, val filter: GlobalFilter? = null, - val collapsed: Boolean? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedView.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedView.kt index 855983a4f..067baa5dc 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.discover.sections.anticipated import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -28,7 +25,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -86,7 +82,6 @@ internal fun DiscoverAnticipatedView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onMoreClick = { if (!state.loading.isLoading) { onMoreClick() @@ -130,25 +125,13 @@ internal fun DiscoverAnticipatedContent( onClick: (DiscoverItem) -> Unit = {}, onLongClick: (DiscoverItem) -> Unit = {}, onMoreClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_most_anticipated), - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -156,44 +139,42 @@ internal fun DiscoverAnticipatedContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } + + Done -> { + if (state.error != null) { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } else { + ContentList( + mode = state.filter?.mode, + collection = collectionState, + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.error != null) { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } else { - ContentList( - mode = state.filter?.mode, - collection = collectionState, - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedViewModel.kt index 84cd38c2a..be1df96d5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/anticipated/DiscoverAnticipatedViewModel.kt @@ -24,33 +24,25 @@ import tv.trakt.trakt.common.helpers.LoadingState.Done import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.interleave import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation -import tv.trakt.trakt.common.model.MediaMode.MEDIA -import tv.trakt.trakt.common.model.MediaMode.MOVIES -import tv.trakt.trakt.common.model.MediaMode.SHOWS import tv.trakt.trakt.common.model.globalfilter.GlobalFilter import tv.trakt.trakt.core.discover.model.DiscoverItem import tv.trakt.trakt.core.discover.sections.anticipated.usecases.GetAnticipatedMoviesUseCase import tv.trakt.trakt.core.discover.sections.anticipated.usecases.GetAnticipatedShowsUseCase import tv.trakt.trakt.core.filters.data.GlobalFilterManager -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class DiscoverAnticipatedViewModel( private val filterManager: GlobalFilterManager, private val getAnticipatedShowsUseCase: GetAnticipatedShowsUseCase, private val getAnticipatedMoviesUseCase: GetAnticipatedMoviesUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = DiscoverAnticipatedState() private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) private var dataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -61,7 +53,6 @@ internal class DiscoverAnticipatedViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -120,46 +111,17 @@ internal class DiscoverAnticipatedViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_ANTICIPATED - SHOWS -> CollapsingKey.DISCOVER_SHOWS_ANTICIPATED - MOVIES -> CollapsingKey.DISCOVER_MOVIES_ANTICIPATED - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_ANTICIPATED - SHOWS -> CollapsingKey.DISCOVER_SHOWS_ANTICIPATED - MOVIES -> CollapsingKey.DISCOVER_MOVIES_ANTICIPATED - }, - ) - } - val state = combine( itemsState, filterState, - collapseState, loadingState, errorState, ) { state -> DiscoverAnticipatedState( items = state[0] as ImmutableList?, filter = state[1] as GlobalFilter?, - collapsed = state[2] as Boolean, - loading = state[3] as LoadingState, - error = state[4] as Exception?, + loading = state[2] as LoadingState, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularState.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularState.kt index 4c885ad99..49ad81ff4 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularState.kt @@ -10,7 +10,6 @@ import tv.trakt.trakt.core.discover.model.DiscoverItem internal data class DiscoverPopularState( val items: ImmutableList? = null, val filter: GlobalFilter? = null, - val collapsed: Boolean? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularView.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularView.kt index 6545c2f07..e2d6f3253 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.discover.sections.popular import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -28,7 +25,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -88,7 +84,6 @@ internal fun DiscoverPopularView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onMoreClick = { if (!state.loading.isLoading) { onMoreClick() @@ -132,25 +127,13 @@ internal fun DiscoverPopularContent( onClick: (DiscoverItem) -> Unit = {}, onLongClick: (DiscoverItem) -> Unit = {}, onMoreClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_most_popular), - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -158,44 +141,42 @@ internal fun DiscoverPopularContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } + + Done -> { + if (state.error != null) { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } else { + ContentList( + mode = state.filter?.mode, + collection = collectionState, + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.error != null) { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } else { - ContentList( - mode = state.filter?.mode, - collection = collectionState, - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularViewModel.kt index 904ba3341..e88eea0f5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/popular/DiscoverPopularViewModel.kt @@ -24,33 +24,25 @@ import tv.trakt.trakt.common.helpers.LoadingState.Done import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.interleave import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation -import tv.trakt.trakt.common.model.MediaMode.MEDIA -import tv.trakt.trakt.common.model.MediaMode.MOVIES -import tv.trakt.trakt.common.model.MediaMode.SHOWS import tv.trakt.trakt.common.model.globalfilter.GlobalFilter import tv.trakt.trakt.core.discover.model.DiscoverItem import tv.trakt.trakt.core.discover.sections.popular.usecases.GetPopularMoviesUseCase import tv.trakt.trakt.core.discover.sections.popular.usecases.GetPopularShowsUseCase import tv.trakt.trakt.core.filters.data.GlobalFilterManager -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class DiscoverPopularViewModel( private val filterManager: GlobalFilterManager, private val getPopularShowsUseCase: GetPopularShowsUseCase, private val getPopularMoviesUseCase: GetPopularMoviesUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = DiscoverPopularState() private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) private var dataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -61,7 +53,6 @@ internal class DiscoverPopularViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -120,46 +111,17 @@ internal class DiscoverPopularViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_POPULAR - SHOWS -> CollapsingKey.DISCOVER_SHOWS_POPULAR - MOVIES -> CollapsingKey.DISCOVER_MOVIES_POPULAR - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_POPULAR - SHOWS -> CollapsingKey.DISCOVER_SHOWS_POPULAR - MOVIES -> CollapsingKey.DISCOVER_MOVIES_POPULAR - }, - ) - } - val state = combine( itemsState, filterState, - collapseState, loadingState, errorState, ) { state -> DiscoverPopularState( items = state[0] as ImmutableList?, filter = state[1] as GlobalFilter?, - collapsed = state[2] as Boolean, - loading = state[3] as LoadingState, - error = state[4] as Exception?, + loading = state[2] as LoadingState, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedState.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedState.kt index f4d20a5df..626195b45 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedState.kt @@ -10,7 +10,6 @@ import tv.trakt.trakt.core.discover.model.DiscoverItem internal data class DiscoverRecommendedState( val items: ImmutableList? = null, val filter: GlobalFilter? = null, - val collapsed: Boolean? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedView.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedView.kt index efffa73f8..cc487211f 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.discover.sections.recommended import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -28,7 +25,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -88,7 +84,6 @@ internal fun DiscoverRecommendedView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onMoreClick = { if (!state.loading.isLoading) { onMoreClick() @@ -132,25 +127,13 @@ internal fun DiscoverRecommendedContent( onClick: (DiscoverItem) -> Unit = {}, onLongClick: (DiscoverItem) -> Unit = {}, onMoreClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_recommended), - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -158,44 +141,42 @@ internal fun DiscoverRecommendedContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } + + Done -> { + if (state.error != null) { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } else { + ContentList( + mode = state.filter?.mode, + collection = collectionState, + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.error != null) { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } else { - ContentList( - mode = state.filter?.mode, - collection = collectionState, - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedViewModel.kt index eeb6f50ec..bbfa76913 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/recommended/DiscoverRecommendedViewModel.kt @@ -24,33 +24,25 @@ import tv.trakt.trakt.common.helpers.LoadingState.Done import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.interleave import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation -import tv.trakt.trakt.common.model.MediaMode.MEDIA -import tv.trakt.trakt.common.model.MediaMode.MOVIES -import tv.trakt.trakt.common.model.MediaMode.SHOWS import tv.trakt.trakt.common.model.globalfilter.GlobalFilter import tv.trakt.trakt.core.discover.model.DiscoverItem import tv.trakt.trakt.core.discover.sections.recommended.usecase.GetRecommendedMoviesUseCase import tv.trakt.trakt.core.discover.sections.recommended.usecase.GetRecommendedShowsUseCase import tv.trakt.trakt.core.filters.data.GlobalFilterManager -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class DiscoverRecommendedViewModel( private val filterManager: GlobalFilterManager, private val getRecommendedShowsUseCase: GetRecommendedShowsUseCase, private val getRecommendedMoviesUseCase: GetRecommendedMoviesUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = DiscoverRecommendedState() private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) private var dataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -61,7 +53,6 @@ internal class DiscoverRecommendedViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -118,46 +109,17 @@ internal class DiscoverRecommendedViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_RECOMMENDED - SHOWS -> CollapsingKey.DISCOVER_SHOWS_RECOMMENDED - MOVIES -> CollapsingKey.DISCOVER_MOVIES_RECOMMENDED - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_RECOMMENDED - SHOWS -> CollapsingKey.DISCOVER_SHOWS_RECOMMENDED - MOVIES -> CollapsingKey.DISCOVER_MOVIES_RECOMMENDED - }, - ) - } - val state = combine( itemsState, filterState, - collapseState, loadingState, errorState, ) { state -> DiscoverRecommendedState( items = state[0] as ImmutableList?, filter = state[1] as GlobalFilter?, - collapsed = state[2] as Boolean, - loading = state[3] as LoadingState, - error = state[4] as Exception?, + loading = state[2] as LoadingState, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingState.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingState.kt index ab8ef2edd..be8b20060 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingState.kt @@ -10,7 +10,6 @@ import tv.trakt.trakt.core.discover.model.DiscoverItem internal data class DiscoverTrendingState( val items: ImmutableList? = null, val filter: GlobalFilter? = null, - val collapsed: Boolean? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingView.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingView.kt index 736a7be09..a2e9e7602 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.discover.sections.trending import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -28,7 +25,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -86,7 +82,6 @@ internal fun DiscoverTrendingView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onMoreClick = { if (!state.loading.isLoading) { onMoreClick() @@ -130,25 +125,13 @@ internal fun DiscoverTrendingContent( onClick: (DiscoverItem) -> Unit = {}, onLongClick: (DiscoverItem) -> Unit = {}, onMoreClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_trending), - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -156,44 +139,42 @@ internal fun DiscoverTrendingContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } + + Done -> { + if (state.error != null) { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } else { + ContentList( + mode = state.filter?.mode, + collection = collectionState, + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.error != null) { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } else { - ContentList( - mode = state.filter?.mode, - collection = collectionState, - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingViewModel.kt index b4cf23c2f..2112ba5d0 100644 --- a/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/discover/sections/trending/DiscoverTrendingViewModel.kt @@ -24,33 +24,25 @@ import tv.trakt.trakt.common.helpers.LoadingState.Done import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.interleave import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation -import tv.trakt.trakt.common.model.MediaMode.MEDIA -import tv.trakt.trakt.common.model.MediaMode.MOVIES -import tv.trakt.trakt.common.model.MediaMode.SHOWS import tv.trakt.trakt.common.model.globalfilter.GlobalFilter import tv.trakt.trakt.core.discover.model.DiscoverItem import tv.trakt.trakt.core.discover.sections.trending.usecases.GetTrendingMoviesUseCase import tv.trakt.trakt.core.discover.sections.trending.usecases.GetTrendingShowsUseCase import tv.trakt.trakt.core.filters.data.GlobalFilterManager -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class DiscoverTrendingViewModel( private val filterManager: GlobalFilterManager, private val getTrendingShowsUseCase: GetTrendingShowsUseCase, private val getTrendingMoviesUseCase: GetTrendingMoviesUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = DiscoverTrendingState() private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) private var dataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -61,7 +53,6 @@ internal class DiscoverTrendingViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -122,46 +113,17 @@ internal class DiscoverTrendingViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_TRENDING - SHOWS -> CollapsingKey.DISCOVER_SHOWS_TRENDING - MOVIES -> CollapsingKey.DISCOVER_MOVIES_TRENDING - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.DISCOVER_MEDIA_TRENDING - SHOWS -> CollapsingKey.DISCOVER_SHOWS_TRENDING - MOVIES -> CollapsingKey.DISCOVER_MOVIES_TRENDING - }, - ) - } - val state = combine( itemsState, filterState, - collapseState, loadingState, errorState, ) { state -> DiscoverTrendingState( items = state[0] as ImmutableList?, filter = state[1] as GlobalFilter?, - collapsed = state[2] as Boolean, - loading = state[3] as LoadingState, - error = state[4] as Exception?, + loading = state[2] as LoadingState, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/HomeScreen.kt b/app/src/main/java/tv/trakt/trakt/core/home/HomeScreen.kt index ff2d26571..43bc1adb6 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/HomeScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/HomeScreen.kt @@ -47,6 +47,14 @@ import tv.trakt.trakt.core.home.sections.upcoming.HomeUpcomingView import tv.trakt.trakt.core.home.sections.upnext.HomeUpNextView import tv.trakt.trakt.core.home.sections.watchlist.HomeWatchlistView import tv.trakt.trakt.helpers.ScreenHeaderState +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.HomeKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.HomeHistory +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.HomeSocial +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.HomeStreaks +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.HomeUpNext +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.HomeUpcoming +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.HomeWatchlist import tv.trakt.trakt.helpers.rememberHeaderState import tv.trakt.trakt.ui.components.ScrollableBackdropImage import tv.trakt.trakt.ui.components.headerbar.HeaderBar @@ -75,6 +83,7 @@ internal fun HomeScreen( var filtersSheet by remember { mutableStateOf(false) } var streaksSheet by remember { mutableStateOf(false) } + var sectionsSheet by remember { mutableStateOf(false) } HomeScreenContent( state = state, @@ -109,6 +118,9 @@ internal fun HomeScreen( onFiltersClick = { filtersSheet = true }, + onEditScreenClick = { + sectionsSheet = true + }, ) GlobalFiltersSheet( @@ -124,6 +136,14 @@ internal fun HomeScreen( streaksSheet = false }, ) + + EditScreenSheet( + active = sectionsSheet, + enabledValues = HomeKeys, + onDismiss = { + sectionsSheet = false + }, + ) } @OptIn(ExperimentalFoundationApi::class) @@ -145,6 +165,7 @@ private fun HomeScreenContent( onCalendarClick: () -> Unit = {}, onStreakClick: () -> Unit = {}, onFiltersClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, onUserClick: (user: User) -> Unit = {}, ) { val headerState = rememberHeaderState() @@ -199,29 +220,33 @@ private fun HomeScreenContent( verticalArrangement = spacedBy(TraktTheme.spacing.mainSectionVerticalSpace), contentPadding = listPadding, ) { - item { - HomeUpNextView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - onShowsClick = onShowsClick, - onMoviesClick = onMoviesClick, - onEpisodeClick = onEpisodeClick, - onMoreClick = onMoreUpNextClick, - ) + if (state.visibility?.get(HomeUpNext) == true) { + item { + HomeUpNextView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + onShowsClick = onShowsClick, + onMoviesClick = onMoviesClick, + onEpisodeClick = onEpisodeClick, + onMoreClick = onMoreUpNextClick, + ) + } } - item { - HomeWatchlistView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onShowClick, - onShowsClick = onShowsClick, - onMovieClick = onMovieClick, - onMoviesClick = onMoviesClick, - onMoreClick = onMoreWatchlistClick, - ) + if (state.visibility?.get(HomeWatchlist) == true) { + item { + HomeWatchlistView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onShowClick, + onShowsClick = onShowsClick, + onMovieClick = onMovieClick, + onMoviesClick = onMoviesClick, + onMoreClick = onMoreWatchlistClick, + ) + } } if (state.user.user != null && !state.user.user.isVip) { @@ -235,7 +260,7 @@ private fun HomeScreenContent( } } - if (state.user.user != null) { + if (state.user.user != null && state.visibility?.get(HomeStreaks) == true) { item { HomeStreaksView( modifier = Modifier.padding(sectionPadding), @@ -244,20 +269,22 @@ private fun HomeScreenContent( } } - item { - HomeUpcomingView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onShowClick, - onShowsClick = onShowsClick, - onMoviesClick = onMoviesClick, - onEpisodeClick = onEpisodeClick, - onMovieClick = onMovieClick, - onCalendarClick = onCalendarClick, - ) + if (state.visibility?.get(HomeUpcoming) == true) { + item { + HomeUpcomingView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onShowClick, + onShowsClick = onShowsClick, + onMoviesClick = onMoviesClick, + onEpisodeClick = onEpisodeClick, + onMovieClick = onMovieClick, + onCalendarClick = onCalendarClick, + ) + } } - if (state.user.user != null) { + if (state.user.user != null && state.visibility?.get(HomeHistory) == true) { item { HomeHistoryView( headerPadding = sectionPadding, @@ -270,16 +297,18 @@ private fun HomeScreenContent( } } - item { - HomeSocialView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onMoreClick = onMoreSocialClick, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - onMovieClick = onMovieClick, - onUserClick = onUserClick, - ) + if (state.visibility?.get(HomeSocial) == true) { + item { + HomeSocialView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onMoreClick = onMoreSocialClick, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + onMovieClick = onMovieClick, + onUserClick = onUserClick, + ) + } } } @@ -290,6 +319,7 @@ private fun HomeScreenContent( isScrolledToTop = isScrolledToTop, onVipClick = onVipClick, onFiltersClick = onFiltersClick, + onEditScreenClick = onEditScreenClick, ) } } @@ -302,6 +332,7 @@ private fun HomeScreenHeader( isScrolledToTop: Boolean, onVipClick: () -> Unit, onFiltersClick: () -> Unit, + onEditScreenClick: () -> Unit, ) { val userState = remember(state.user) { val loadingDone = state.user.loading == Done @@ -314,9 +345,11 @@ private fun HomeScreenHeader( showLogin = userState.first && !userState.second, showVip = userState.second && state.user.user?.isVip == false, showFilters = true, + showEditScreen = true, userLoading = userLoading, onVipClick = onVipClick, onFilterClick = onFiltersClick, + onEditScreenClick = onEditScreenClick, modifier = Modifier.offset { IntOffset(0, headerState.connection.barOffset.fastRoundToInt()) }, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/HomeState.kt b/app/src/main/java/tv/trakt/trakt/core/home/HomeState.kt index 27e5de0ac..294ca7981 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/HomeState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/HomeState.kt @@ -1,14 +1,17 @@ package tv.trakt.trakt.core.home import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.User +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class HomeState( val mode: MediaMode? = null, val user: UserState = UserState(), + val visibility: ImmutableMap? = null, ) { data class UserState( val user: User? = null, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/HomeViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/home/HomeViewModel.kt index e7b003180..899442e2a 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/HomeViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/HomeViewModel.kt @@ -2,6 +2,7 @@ package tv.trakt.trakt.core.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -16,11 +17,14 @@ import tv.trakt.trakt.common.firebase.analytics.Analytics import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.core.filters.data.GlobalFilterManager import tv.trakt.trakt.core.home.HomeState.UserState +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.HomeKeys @OptIn(FlowPreview::class) internal class HomeViewModel( private val filterManager: GlobalFilterManager, private val sessionManager: SessionManager, + private val editScreenManager: EditScreenManager, analytics: Analytics, ) : ViewModel() { private val initialState = HomeState() @@ -28,10 +32,12 @@ internal class HomeViewModel( private val modeState = MutableStateFlow(initialMode) private val userState = MutableStateFlow(initialState.user) + private val visibilityState = MutableStateFlow(initialState.visibility) init { observeUser() observeMode() + observeVisibility() analytics.logScreenView(screenName = "home") analytics.logMediaMode(mode = initialMode.name) @@ -59,13 +65,24 @@ internal class HomeViewModel( .launchIn(viewModelScope) } + private fun observeVisibility() { + editScreenManager.observe(HomeKeys) + .distinctUntilChanged() + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) + } + val state = combine( modeState, userState, - ) { s1, s2 -> + visibilityState, + ) { s1, s2, s3 -> HomeState( mode = s1, user = s2, + visibility = s3, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/di/HomeModule.kt b/app/src/main/java/tv/trakt/trakt/core/home/di/HomeModule.kt index 171d6569a..e2cbcd140 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/di/HomeModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/di/HomeModule.kt @@ -168,6 +168,7 @@ internal val homeModule = module { HomeViewModel( sessionManager = get(), filterManager = get(), + editScreenManager = get(), analytics = get(), ) } @@ -188,7 +189,6 @@ internal val homeModule = module { watchlistUpdates = get(), filterManager = get(), sessionManager = get(), - collapsingManager = get(), checkInManager = get(), analytics = get(), ) @@ -225,7 +225,6 @@ internal val homeModule = module { userWatchlistSource = get(), filterManager = get(), sessionManager = get(), - collapsingManager = get(), checkInManager = get(), ratePromptManager = get(), checkInUpdates = get(), @@ -263,7 +262,6 @@ internal val homeModule = module { movieLocalDataSource = get(), sessionManager = get(), filterManager = get(), - collapsingManager = get(), ) } @@ -283,7 +281,6 @@ internal val homeModule = module { ratingsUpdates = get(), sessionManager = get(), filterManager = get(), - collapsingManager = get(), checkInUpdates = get(), ) } @@ -330,7 +327,6 @@ internal val homeModule = module { episodeUpdates = get(), sessionManager = get(), filterManager = get(), - collapsingManager = get(), watchlistUpdates = get(), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryState.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryState.kt index 0f36eecf6..4df898e54 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryState.kt @@ -14,7 +14,6 @@ import tv.trakt.trakt.core.home.sections.activity.model.HomeActivityItem internal data class HomeHistoryState( val items: ImmutableList? = null, val itemsRatings: ImmutableMap? = null, - val collapsed: Boolean? = null, val navigateShow: TraktId? = null, val navigateEpisode: Pair? = null, val navigateMovie: TraktId? = null, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryView.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryView.kt index 48d81337b..da44d9d14 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.home.sections.activity.features.history import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -24,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -92,7 +88,6 @@ internal fun HomeHistoryView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { viewModel.navigateToShow(it.show) }, @@ -140,26 +135,14 @@ internal fun HomeHistoryContent( onMovieClick: (Movie) -> Unit = { }, onMovieLongClick: (HomeActivityItem.MovieItem) -> Unit = {}, onMoreClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_history), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -167,57 +150,55 @@ internal fun HomeHistoryContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listRatings = (state.itemsRatings ?: emptyMap()).toImmutableMap(), - contentPadding = contentPadding, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - onEpisodeLongClick = onEpisodeLongClick, - onMovieClick = onMovieClick, - onMovieLongClick = onMovieLongClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listRatings = (state.itemsRatings ?: emptyMap()).toImmutableMap(), + contentPadding = contentPadding, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + onEpisodeLongClick = onEpisodeLongClick, + onMovieClick = onMovieClick, + onMovieLongClick = onMovieLongClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryViewModel.kt index 5b917b0aa..8403edb4d 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/history/HomeHistoryViewModel.kt @@ -35,7 +35,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Done import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.Episode -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId @@ -61,8 +60,6 @@ import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.AllWatchlist import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.Default import tv.trakt.trakt.core.user.usecases.ratings.LoadUserRatingsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class HomeHistoryViewModel( private val getPersonalActivityUseCase: GetPersonalActivityUseCase, @@ -80,7 +77,6 @@ internal class HomeHistoryViewModel( private val checkInUpdates: CheckInUpdates, private val sessionManager: SessionManager, private val filterManager: GlobalFilterManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = HomeHistoryState() @@ -88,7 +84,6 @@ internal class HomeHistoryViewModel( private val itemsState = MutableStateFlow(initialState.items) private val itemsRatingsState = MutableStateFlow(initialState.itemsRatings) private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val navigateShow = MutableStateFlow(initialState.navigateShow) private val navigateEpisode = MutableStateFlow(initialState.navigateEpisode) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) @@ -97,7 +92,6 @@ internal class HomeHistoryViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -114,7 +108,6 @@ internal class HomeHistoryViewModel( .distinctUntilChanged() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -307,39 +300,11 @@ internal class HomeHistoryViewModel( processingJob = null } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_HISTORY - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_HISTORY - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_HISTORY - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_HISTORY - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_HISTORY - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_HISTORY - }, - ) - } - @Suppress("UNCHECKED_CAST") val state = combine( loadingState, itemsState, itemsRatingsState, - collapseState, navigateShow, navigateEpisode, navigateMovie, @@ -350,12 +315,11 @@ internal class HomeHistoryViewModel( loading = states[0] as LoadingState, items = states[1] as? ImmutableList, itemsRatings = states[2] as? ImmutableMap, - collapsed = states[3] as Boolean, - navigateShow = states[4] as? TraktId, - navigateEpisode = states[5] as? Pair, - navigateMovie = states[6] as? TraktId, - error = states[7] as? Exception, - user = states[8] as? User, + navigateShow = states[3] as? TraktId, + navigateEpisode = states[4] as? Pair, + navigateMovie = states[5] as? TraktId, + error = states[6] as? Exception, + user = states[7] as? User, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialState.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialState.kt index a1850bc79..3e58d0a79 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialState.kt @@ -11,7 +11,6 @@ import tv.trakt.trakt.core.home.sections.activity.model.HomeActivityItem @Immutable internal data class HomeSocialState( val items: ImmutableList? = null, - val collapsed: Boolean? = null, val navigateShow: TraktId? = null, val navigateEpisode: Pair? = null, val navigateMovie: TraktId? = null, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialView.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialView.kt index e8601162f..045e8e513 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.home.sections.activity.features.social import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -22,10 +19,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource @@ -90,7 +84,6 @@ internal fun HomeSocialView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { viewModel.navigateToShow(it.show) }, @@ -121,26 +114,14 @@ internal fun HomeSocialContent( onMovieClick: (Movie) -> Unit = { }, onMoreClick: () -> Unit = {}, onUserClick: (user: User) -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_social_activity), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done && !state.items.isNullOrEmpty()) { @@ -148,54 +129,52 @@ internal fun HomeSocialContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - HomeEmptySocialView( - modifier = Modifier - .padding(contentPadding) - .padding(bottom = 6.dp), - ) - } + state.items?.isEmpty() == true -> { + HomeEmptySocialView( + modifier = Modifier + .padding(contentPadding) + .padding(bottom = 6.dp), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - onMovieClick = onMovieClick, - onUserClick = onUserClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + onMovieClick = onMovieClick, + onUserClick = onUserClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialViewModel.kt index 1b0f2730c..dc607dc51 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/activity/features/social/HomeSocialViewModel.kt @@ -31,7 +31,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.EmptyImmutableList import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.Episode -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId @@ -40,8 +39,6 @@ import tv.trakt.trakt.core.filters.data.GlobalFilterManager import tv.trakt.trakt.core.home.HomeConfig.HOME_SECTION_LIMIT import tv.trakt.trakt.core.home.sections.activity.model.HomeActivityItem import tv.trakt.trakt.core.home.sections.activity.usecases.GetSocialActivityUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class HomeSocialViewModel( private val getSocialActivityUseCase: GetSocialActivityUseCase, @@ -50,7 +47,6 @@ internal class HomeSocialViewModel( private val movieLocalDataSource: MovieLocalDataSource, private val sessionManager: SessionManager, private val filterManager: GlobalFilterManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = HomeSocialState() private val initialMode = filterManager.getFilter() @@ -58,7 +54,6 @@ internal class HomeSocialViewModel( private val userState = MutableStateFlow(initialState.user) private val itemsState = MutableStateFlow(initialState.items) private val filterState = MutableStateFlow(initialMode) - private val collapseState = MutableStateFlow(isCollapsed()) private val navigateShow = MutableStateFlow(initialState.navigateShow) private val navigateEpisode = MutableStateFlow(initialState.navigateEpisode) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) @@ -67,7 +62,6 @@ internal class HomeSocialViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -94,7 +88,6 @@ internal class HomeSocialViewModel( .distinctUntilChanged() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -194,38 +187,10 @@ internal class HomeSocialViewModel( navigateMovie.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_SOCIAL - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_SOCIAL - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_SOCIAL - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_SOCIAL - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_SOCIAL - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_SOCIAL - }, - ) - } - @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( loadingState, itemsState, - collapseState, navigateShow, navigateEpisode, navigateMovie, @@ -235,12 +200,11 @@ internal class HomeSocialViewModel( HomeSocialState( loading = states[0] as LoadingState, items = states[1] as? ImmutableList, - collapsed = states[2] as Boolean, - navigateShow = states[3] as? TraktId, - navigateEpisode = states[4] as? Pair, - navigateMovie = states[5] as? TraktId, - error = states[6] as? Exception, - user = states[7] as? User, + navigateShow = states[2] as? TraktId, + navigateEpisode = states[3] as? Pair, + navigateMovie = states[4] as? TraktId, + error = states[5] as? Exception, + user = states[6] as? User, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingState.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingState.kt index a70c0db46..8354ecd4e 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingState.kt @@ -14,7 +14,6 @@ internal data class HomeUpcomingState( val user: User? = null, val items: ImmutableList? = null, val filter: GlobalFilter? = null, - val collapsed: Boolean? = null, val navigateShow: TraktId? = null, val navigateEpisode: Pair? = null, val navigateMovie: TraktId? = null, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingView.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingView.kt index 0659c48f3..04e63ab10 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingView.kt @@ -1,9 +1,6 @@ package tv.trakt.trakt.core.home.sections.upcoming import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy @@ -20,10 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource @@ -91,7 +85,6 @@ internal fun HomeUpcomingView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onCalendarClick = onCalendarClick, onEmptyClick = { when (state.filter?.mode) { @@ -122,26 +115,14 @@ internal fun HomeUpcomingContent( onMovieClick: (Movie) -> Unit = {}, onEmptyClick: () -> Unit = {}, onCalendarClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = Arrangement.spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.page_title_calendar), chevron = state.user != null, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.user != null) { @@ -149,66 +130,64 @@ internal fun HomeUpcomingContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - val imageUrl = remember { - Firebase.remoteConfig.getString(MOBILE_EMPTY_IMAGE_3).ifBlank { null } - } - HomeEmptyView( - text = stringResource(R.string.text_cta_upcoming), - icon = R.drawable.ic_empty_upcoming, - buttonText = stringResource( - when (state.filter?.mode) { - MediaMode.MOVIES -> R.string.link_text_discover_movies - else -> R.string.link_text_discover_shows - }, - ), - backgroundImageUrl = imageUrl, - backgroundImage = if (imageUrl == null) R.drawable.ic_splash_background_2 else null, - onClick = onEmptyClick, - modifier = Modifier - .padding(contentPadding), - ) + state.items?.isEmpty() == true -> { + val imageUrl = remember { + Firebase.remoteConfig.getString(MOBILE_EMPTY_IMAGE_3).ifBlank { null } } + HomeEmptyView( + text = stringResource(R.string.text_cta_upcoming), + icon = R.drawable.ic_empty_upcoming, + buttonText = stringResource( + when (state.filter?.mode) { + MediaMode.MOVIES -> R.string.link_text_discover_movies + else -> R.string.link_text_discover_shows + }, + ), + backgroundImageUrl = imageUrl, + backgroundImage = if (imageUrl == null) R.drawable.ic_splash_background_2 else null, + onClick = onEmptyClick, + modifier = Modifier + .padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onClick = onClick, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingViewModel.kt index 7a627d551..df1bb641f 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/upcoming/HomeUpcomingViewModel.kt @@ -33,7 +33,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Idle import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.Episode -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId @@ -49,8 +48,6 @@ import tv.trakt.trakt.core.summary.episodes.data.EpisodeDetailsUpdates.Source.CA import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.AllWatchlist import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.Default -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @Suppress("UNCHECKED_CAST") @OptIn(FlowPreview::class) @@ -65,7 +62,6 @@ internal class HomeUpcomingViewModel( private val watchlistUpdates: WatchlistUpdates, private val episodeUpdates: EpisodeDetailsUpdates, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = HomeUpcomingState() private val initialMode = filterManager.getFilter() @@ -73,7 +69,6 @@ internal class HomeUpcomingViewModel( private val userState = MutableStateFlow(initialState.user) private val itemsState = MutableStateFlow(initialState.items) private val filterState = MutableStateFlow(initialMode) - private val collapseState = MutableStateFlow(isCollapsed()) private val navigateShow = MutableStateFlow(initialState.navigateShow) private val navigateEpisode = MutableStateFlow(initialState.navigateEpisode) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) @@ -82,7 +77,6 @@ internal class HomeUpcomingViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -97,7 +91,6 @@ internal class HomeUpcomingViewModel( .distinctUntilChanged() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -224,33 +217,6 @@ internal class HomeUpcomingViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_UPCOMING - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_UPCOMING - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_UPCOMING - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_UPCOMING - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_UPCOMING - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_UPCOMING - }, - ) - } - fun clearNavigation() { navigateShow.update { null } navigateEpisode.update { null } @@ -262,7 +228,6 @@ internal class HomeUpcomingViewModel( userState, itemsState, filterState, - collapseState, navigateShow, navigateEpisode, navigateMovie, @@ -273,11 +238,10 @@ internal class HomeUpcomingViewModel( user = state[1] as User?, items = state[2] as ImmutableList?, filter = state[3] as GlobalFilter?, - collapsed = state[4] as Boolean, - navigateShow = state[5] as TraktId?, - navigateEpisode = state[6] as Pair?, - navigateMovie = state[7] as TraktId?, - error = state[8] as Exception?, + navigateShow = state[4] as TraktId?, + navigateEpisode = state[5] as Pair?, + navigateMovie = state[6] as TraktId?, + error = state[7] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextState.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextState.kt index 0d45b4501..67d9fc84a 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextState.kt @@ -11,7 +11,6 @@ import tv.trakt.trakt.core.home.sections.upnext.model.UpNextItem internal data class HomeUpNextState( val items: ItemsState = ItemsState(), val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val filter: GlobalFilter? = null, val info: StringResource? = null, val error: Exception? = null, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextView.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextView.kt index 7b2d10fb9..267d5001b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.home.sections.upnext import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -90,7 +87,6 @@ internal fun HomeUpNextView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowsClick = onShowsClick, onMoviesClick = onMoviesClick, onShowClick = { onShowClick(it.show.ids.trakt) }, @@ -181,26 +177,14 @@ internal fun HomeUpNextContent( onLongClick: (UpNextItem) -> Unit = {}, onCheckClick: (UpNextItem) -> Unit = {}, onCheckLongClick: (UpNextItem) -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_up_next), chevron = !state.items.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -208,77 +192,75 @@ internal fun HomeUpNextContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items.items?.isEmpty() == true -> { - val imageUrl = remember { - Firebase.remoteConfig.getString(MOBILE_EMPTY_IMAGE_1).ifBlank { null } - } - HomeEmptyView( - text = stringResource( - when (state.filter?.mode) { - MediaMode.MOVIES -> R.string.text_cta_up_next_movies - else -> R.string.text_cta_up_next - }, - ), - icon = R.drawable.ic_empty_upnext, - buttonText = when (state.filter?.mode) { - MediaMode.MOVIES -> stringResource(R.string.link_text_discover_movies) - else -> stringResource(R.string.link_text_discover_shows) - }, - backgroundImageUrl = imageUrl, - backgroundImage = if (imageUrl == null) R.drawable.ic_splash_background_2 else null, - modifier = Modifier.padding(contentPadding), - onClick = { - when (state.filter?.mode) { - MediaMode.MOVIES -> onMoviesClick() - else -> onShowsClick() - } - }, - ) + state.items.items?.isEmpty() == true -> { + val imageUrl = remember { + Firebase.remoteConfig.getString(MOBILE_EMPTY_IMAGE_1).ifBlank { null } } + HomeEmptyView( + text = stringResource( + when (state.filter?.mode) { + MediaMode.MOVIES -> R.string.text_cta_up_next_movies + else -> R.string.text_cta_up_next + }, + ), + icon = R.drawable.ic_empty_upnext, + buttonText = when (state.filter?.mode) { + MediaMode.MOVIES -> stringResource(R.string.link_text_discover_movies) + else -> stringResource(R.string.link_text_discover_shows) + }, + backgroundImageUrl = imageUrl, + backgroundImage = if (imageUrl == null) R.drawable.ic_splash_background_2 else null, + modifier = Modifier.padding(contentPadding), + onClick = { + when (state.filter?.mode) { + MediaMode.MOVIES -> onMoviesClick() + else -> onShowsClick() + } + }, + ) + } - else -> { - ContentList( - listFilter = state.filter?.mode, - listItems = state.items, - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - onCheckClick = onCheckClick, - onCheckLongClick = onCheckLongClick, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - ) - } + else -> { + ContentList( + listFilter = state.filter?.mode, + listItems = state.items, + contentPadding = contentPadding, + onClick = onClick, + onLongClick = onLongClick, + onCheckClick = onCheckClick, + onCheckLongClick = onCheckLongClick, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextViewModel.kt index 120dccd7a..c2a2fc5b6 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/upnext/HomeUpNextViewModel.kt @@ -34,7 +34,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.StringResource import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.DateSelectionResult -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.MediaMode.MOVIES import tv.trakt.trakt.common.model.MediaMode.SHOWS import tv.trakt.trakt.common.model.MediaType.SHOW @@ -66,8 +65,6 @@ import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.AllWatchlist import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.Default import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import tv.trakt.trakt.resources.R @OptIn(FlowPreview::class) @@ -87,13 +84,11 @@ internal class HomeUpNextViewModel( private val checkInManager: CheckInManager, private val filterManager: GlobalFilterManager, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, private val analytics: Analytics, ) : ViewModel() { private val initialState = HomeUpNextState() private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val infoState = MutableStateFlow(initialState.info) @@ -103,7 +98,6 @@ internal class HomeUpNextViewModel( private var itemsOrder: List? = null private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -118,7 +112,6 @@ internal class HomeUpNextViewModel( .distinctUntilChanged() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -410,40 +403,12 @@ internal class HomeUpNextViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_UP_NEXT - SHOWS -> CollapsingKey.HOME_SHOWS_UP_NEXT - MOVIES -> CollapsingKey.HOME_MOVIES_UP_NEXT - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_UP_NEXT - SHOWS -> CollapsingKey.HOME_SHOWS_UP_NEXT - MOVIES -> CollapsingKey.HOME_MOVIES_UP_NEXT - }, - ) - } - fun clearInfo() { infoState.update { null } } val state = combine( loadingState, - collapseState, itemsState, infoState, errorState, @@ -451,11 +416,10 @@ internal class HomeUpNextViewModel( ) { state -> HomeUpNextState( loading = state[0] as LoadingState, - collapsed = state[1] as Boolean, - items = state[2] as ItemsState, - info = state[3] as StringResource?, - error = state[4] as Exception?, - filter = state[5] as GlobalFilter, + items = state[1] as ItemsState, + info = state[2] as StringResource?, + error = state[3] as Exception?, + filter = state[4] as GlobalFilter, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistState.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistState.kt index 013d4d433..3bc85d40b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistState.kt @@ -12,7 +12,6 @@ import tv.trakt.trakt.core.lists.sections.watchlist.model.WatchlistItem internal data class HomeWatchlistState( val filter: GlobalFilter? = null, val items: ImmutableList? = null, - val collapsed: Boolean? = null, val navigateShow: TraktId? = null, val navigateMovie: TraktId? = null, val loading: LoadingState = LoadingState.Idle, diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistView.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistView.kt index 6df9e32a8..a52da03e9 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.home.sections.watchlist import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.SpaceBetween import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -28,7 +25,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically @@ -115,7 +111,6 @@ internal fun HomeWatchlistView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onEmptyClick = { when (state.filter?.mode) { MediaMode.MOVIES -> onMoviesClick() @@ -229,26 +224,14 @@ internal fun HomeWatchlistContent( onCheckLongClick: (WatchlistItem) -> Unit = {}, onEmptyClick: () -> Unit = {}, onMoreClick: () -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_start_watching), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done && state.items?.isNotEmpty() == true) { @@ -256,72 +239,70 @@ internal fun HomeWatchlistContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } - - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - state.items?.isEmpty() == true -> { - val imageUrl = remember { - Firebase.remoteConfig.getString(MOBILE_EMPTY_IMAGE_2).ifBlank { null } - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - HomeEmptyView( - text = stringResource(R.string.text_cta_watchlist_released), - icon = R.drawable.ic_empty_watchlist, - buttonText = when (state.filter?.mode) { - MediaMode.MOVIES -> stringResource(R.string.link_text_discover_movies) - else -> stringResource(R.string.link_text_discover_shows) - }, - backgroundImageUrl = imageUrl, - backgroundImage = when (imageUrl) { - null -> R.drawable.ic_splash_background_2 - else -> null - }, - onClick = onEmptyClick, - height = (226.25).dp, - modifier = Modifier - .padding(contentPadding) - .padding(bottom = 6.dp), - ) + state.items?.isEmpty() == true -> { + val imageUrl = remember { + Firebase.remoteConfig.getString(MOBILE_EMPTY_IMAGE_2).ifBlank { null } } - else -> { - ContentList( - listFilter = state.filter?.mode, - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - onCheckClick = onCheckClick, - onCheckLongClick = onCheckLongClick, - ) - } + HomeEmptyView( + text = stringResource(R.string.text_cta_watchlist_released), + icon = R.drawable.ic_empty_watchlist, + buttonText = when (state.filter?.mode) { + MediaMode.MOVIES -> stringResource(R.string.link_text_discover_movies) + else -> stringResource(R.string.link_text_discover_shows) + }, + backgroundImageUrl = imageUrl, + backgroundImage = when (imageUrl) { + null -> R.drawable.ic_splash_background_2 + else -> null + }, + onClick = onEmptyClick, + height = (226.25).dp, + modifier = Modifier + .padding(contentPadding) + .padding(bottom = 6.dp), + ) + } + + else -> { + ContentList( + listFilter = state.filter?.mode, + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onClick = onClick, + onLongClick = onLongClick, + onCheckClick = onCheckClick, + onCheckLongClick = onCheckLongClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistViewModel.kt index 503f5ce37..44edb68eb 100644 --- a/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/home/sections/watchlist/HomeWatchlistViewModel.kt @@ -66,8 +66,6 @@ import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.All import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.Default import tv.trakt.trakt.core.user.data.local.watchlist.minimal.UserWatchlistMinimalLocalDataSource import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import tv.trakt.trakt.resources.R @OptIn(FlowPreview::class) @@ -86,7 +84,6 @@ internal class HomeWatchlistViewModel( private val checkInUpdates: CheckInUpdates, private val watchlistUpdates: WatchlistUpdates, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, private val ratePromptManager: RatePromptManager, private val analytics: Analytics, ) : ViewModel() { @@ -94,7 +91,6 @@ internal class HomeWatchlistViewModel( private val itemsState = MutableStateFlow(initialState.items) private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val navigateShow = MutableStateFlow(initialState.navigateShow) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) private val loadingState = MutableStateFlow(initialState.loading) @@ -104,7 +100,6 @@ internal class HomeWatchlistViewModel( private var user: User? = null private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -133,7 +128,6 @@ internal class HomeWatchlistViewModel( .distinctUntilChanged() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -495,32 +489,7 @@ internal class HomeWatchlistViewModel( infoState.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_START_WATCHING - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_START_WATCHING - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_START_WATCHING - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.HOME_MEDIA_START_WATCHING - MediaMode.SHOWS -> CollapsingKey.HOME_SHOWS_START_WATCHING - MediaMode.MOVIES -> CollapsingKey.HOME_MOVIES_START_WATCHING - }, - ) - } private suspend fun loadEmptyIfNeeded(): Boolean { if (!sessionManager.isAuthenticated()) { @@ -575,7 +544,6 @@ internal class HomeWatchlistViewModel( val state = combine( itemsState, filterState, - collapseState, loadingState, infoState, errorState, @@ -585,12 +553,11 @@ internal class HomeWatchlistViewModel( HomeWatchlistState( items = state[0] as ImmutableList?, filter = state[1] as GlobalFilter, - collapsed = state[2] as Boolean, - loading = state[3] as LoadingState, - info = state[4] as StringResource?, - error = state[5] as Exception?, - navigateMovie = state[6] as TraktId?, - navigateShow = state[7] as TraktId?, + loading = state[2] as LoadingState, + info = state[3] as StringResource?, + error = state[4] as Exception?, + navigateMovie = state[5] as TraktId?, + navigateShow = state[6] as TraktId?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/ListsScreen.kt b/app/src/main/java/tv/trakt/trakt/core/lists/ListsScreen.kt index ed1b496c5..cf2128b79 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/ListsScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/ListsScreen.kt @@ -74,6 +74,10 @@ import tv.trakt.trakt.core.lists.sections.watchlist.ListsWatchlistView import tv.trakt.trakt.core.lists.sheets.CreateListSheet import tv.trakt.trakt.core.lists.sheets.EditListSheet import tv.trakt.trakt.helpers.ScreenHeaderState +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.ListsKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ListsMyLists +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ListsWatchlist import tv.trakt.trakt.helpers.rememberHeaderState import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.components.ScrollableBackdropImage @@ -107,6 +111,7 @@ internal fun ListsScreen( var createListSheet by remember { mutableStateOf(false) } var editListSheet by remember { mutableStateOf(null) } var filtersSheet by remember { mutableStateOf(false) } + var sectionsSheet by remember { mutableStateOf(false) } ListsScreenContent( state = state, @@ -140,6 +145,17 @@ internal fun ListsScreen( onFiltersClick = { filtersSheet = true }, + onEditScreenClick = { + sectionsSheet = true + }, + ) + + EditScreenSheet( + active = sectionsSheet, + enabledValues = ListsKeys, + onDismiss = { + sectionsSheet = false + }, ) CreateListSheet( @@ -183,6 +199,7 @@ private fun ListsScreenContent( onAllListsClick: () -> Unit = { }, onVipClick: () -> Unit = {}, onFiltersClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { val headerState = rememberHeaderState() val lazyListState = rememberLazyListState( @@ -243,19 +260,21 @@ private fun ListsScreenContent( overscrollEffect = null, contentPadding = listPadding, ) { - item( - key = "watchlist", - ) { - ListsWatchlistView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowsClick = onShowsClick, - onShowClick = onShowClick, - onMoviesClick = onMoviesClick, - onMovieClick = onMovieClick, - onProfileClick = onProfileClick, - onWatchlistClick = onWatchlistClick, - ) + if (state.visibility?.get(ListsWatchlist) == true) { + item( + key = "watchlist", + ) { + ListsWatchlistView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowsClick = onShowsClick, + onShowClick = onShowClick, + onMoviesClick = onMoviesClick, + onMovieClick = onMovieClick, + onProfileClick = onProfileClick, + onWatchlistClick = onWatchlistClick, + ) + } } if (state.user.user != null && !state.user.user.isVip) { @@ -269,22 +288,25 @@ private fun ListsScreenContent( } } - item( - key = "personal_header", - ) { - MyListsHeader( - sectionPadding = sectionPadding, - state = state, - onHeaderClick = onAllListsClick, - onFilterClick = onFilterClick, - onCreateListClick = onCreateListClick, - modifier = Modifier.padding( - top = TraktTheme.spacing.mainSectionVerticalSpace, - ), - ) + if (state.visibility?.get(ListsMyLists) == true) { + item( + key = "personal_header", + ) { + MyListsHeader( + sectionPadding = sectionPadding, + state = state, + onHeaderClick = onAllListsClick, + onFilterClick = onFilterClick, + onCreateListClick = onCreateListClick, + modifier = Modifier.padding( + top = TraktTheme.spacing.mainSectionVerticalSpace, + ), + ) + } } val topVerticalPadding = 32.dp + if (state.visibility?.get(ListsMyLists) == true) { itemsIndexed( items = state.lists ?: emptyList(), key = { _, list -> list.ids.trakt.value }, @@ -436,6 +458,7 @@ private fun ListsScreenContent( } } } + } } ListsScreenHeader( @@ -444,6 +467,7 @@ private fun ListsScreenContent( isScrolledToTop = isScrolledToTop, onVipClick = onVipClick, onFiltersClick = onFiltersClick, + onEditScreenClick = onEditScreenClick, ) } } @@ -455,6 +479,7 @@ private fun ListsScreenHeader( isScrolledToTop: Boolean, onVipClick: () -> Unit, onFiltersClick: () -> Unit, + onEditScreenClick: () -> Unit, ) { val userState = remember(state.user) { val loadingDone = state.user.loading == Done @@ -467,8 +492,10 @@ private fun ListsScreenHeader( showLogin = userState.first && !userState.second, showVip = userState.second && state.user.user?.isVip == false, showFilters = true, + showEditScreen = true, onVipClick = onVipClick, onFilterClick = onFiltersClick, + onEditScreenClick = onEditScreenClick, modifier = Modifier.offset { IntOffset(0, headerState.connection.barOffset.fastRoundToInt()) }, @@ -495,7 +522,6 @@ private fun MyListsHeader( ) { TraktSectionHeader( title = stringResource(R.string.list_title_personal_lists), - collapsable = false, chevron = true, modifier = Modifier .onClick( diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/ListsState.kt b/app/src/main/java/tv/trakt/trakt/core/lists/ListsState.kt index 36da8beb4..8e3731fdf 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/ListsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/ListsState.kt @@ -2,11 +2,13 @@ package tv.trakt.trakt.core.lists import androidx.compose.runtime.Immutable import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.helpers.LoadingState.Idle import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.lists.sections.personal.model.PersonalListType +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class ListsState( @@ -15,6 +17,7 @@ internal data class ListsState( val lists: ImmutableList? = null, val listsLoading: LoadingState = Idle, val error: Exception? = null, + val visibility: ImmutableMap? = null, ) { data class UserState( val user: User? = null, diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/ListsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/lists/ListsViewModel.kt index ef829a247..1db588e75 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/ListsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/ListsViewModel.kt @@ -2,7 +2,10 @@ package tv.trakt.trakt.core.lists import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -20,11 +23,13 @@ import timber.log.Timber import tv.trakt.trakt.analytics.crashlytics.recordError import tv.trakt.trakt.common.auth.session.SessionManager import tv.trakt.trakt.common.firebase.analytics.Analytics +import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.helpers.LoadingState.Done import tv.trakt.trakt.common.helpers.LoadingState.Idle import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.EmptyImmutableList import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation +import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.pagination.Pagination import tv.trakt.trakt.core.lists.ListsState.UserState import tv.trakt.trakt.core.lists.sections.collaborations.data.local.lists.ListsCollaborationsLocalDataSource @@ -38,7 +43,11 @@ import tv.trakt.trakt.core.lists.sections.personal.model.PersonalListType.Collab import tv.trakt.trakt.core.lists.sections.personal.model.PersonalListType.Liked import tv.trakt.trakt.core.lists.sections.personal.model.PersonalListType.Personal import tv.trakt.trakt.core.lists.sections.personal.usecases.GetPersonalListsUseCase +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.ListsKeys +@Suppress("UNCHECKED_CAST") @OptIn(FlowPreview::class) internal class ListsViewModel( private val sessionManager: SessionManager, @@ -49,6 +58,7 @@ internal class ListsViewModel( private val localListsItemsSource: ListsPersonalItemsLocalDataSource, private val localLikedListsSource: ListsLikedLocalDataSource, private val localCollaborationsListsSource: ListsCollaborationsLocalDataSource, + private val editScreenManager: EditScreenManager, analytics: Analytics, ) : ViewModel() { private val initialState = ListsState() @@ -58,12 +68,14 @@ internal class ListsViewModel( private val listsState = MutableStateFlow(initialState.lists) private val listsLoadingState = MutableStateFlow(initialState.listsLoading) private val errorState = MutableStateFlow(initialState.error) + private val visibilityState = MutableStateFlow(initialState.visibility) private var dataJob: Job? = null init { observeUser() observeLists() + observeVisibility() analytics.logScreenView( screenName = "lists", @@ -86,6 +98,15 @@ internal class ListsViewModel( } } + private fun observeVisibility() { + editScreenManager.observe(ListsKeys) + .distinctUntilChanged() + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) + } + private fun observeLists() { merge( localListsSource.observeUpdates(), @@ -218,13 +239,15 @@ internal class ListsViewModel( listsState, listsLoadingState, errorState, - ) { s1, s2, s3, s4, s5 -> + visibilityState, + ) { state -> ListsState( - user = s1, - filter = s2, - lists = s3, - listsLoading = s4, - error = s5, + user = state[0] as UserState, + filter = state[1] as PersonalListType, + lists = state[2] as ImmutableList?, + listsLoading = state[3] as LoadingState, + error = state[4] as Exception?, + visibility = state[5] as ImmutableMap?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/di/ListsModule.kt b/app/src/main/java/tv/trakt/trakt/core/lists/di/ListsModule.kt index 4cb3ee216..751e794c7 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/di/ListsModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/di/ListsModule.kt @@ -242,6 +242,7 @@ internal val listsModule = module { localListsItemsSource = get(), localLikedListsSource = get(), localCollaborationsListsSource = get(), + editScreenManager = get(), analytics = get(), ) } @@ -256,7 +257,6 @@ internal val listsModule = module { collectionStateProvider = get(), filterManager = get(), sessionManager = get(), - collapsingManager = get(), watchlistUpdates = get(), ) } @@ -294,7 +294,6 @@ internal val listsModule = module { movieLocalDataSource = get(), episodeLocalDataSource = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } @@ -308,7 +307,6 @@ internal val listsModule = module { movieLocalDataSource = get(), episodeLocalDataSource = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } @@ -324,7 +322,6 @@ internal val listsModule = module { movieLocalDataSource = get(), episodeLocalDataSource = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsState.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsState.kt index 53d747dff..8552a9754 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsState.kt @@ -23,6 +23,5 @@ internal data class ListsCollaborationsState( val navigateMovie: TraktId? = null, val navigateEpisode: Pair? = null, val loading: LoadingState = Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsView.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsView.kt index 8653e979c..51aa828d5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsView.kt @@ -1,9 +1,6 @@ package tv.trakt.trakt.core.lists.sections.collaborations import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -26,7 +23,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -94,7 +90,6 @@ internal fun ListsCollaborationsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { if (!state.loading.isLoading) { viewModel.navigateToShow(it) @@ -139,20 +134,14 @@ private fun ListsCollaborationsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onShowClick: (Show) -> Unit = {}, onMovieClick: (Movie) -> Unit = {}, onEpisodeClick: (Show, Episode) -> Unit = { _, _ -> }, onAllClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = list?.name ?: "", @@ -161,12 +150,6 @@ private fun ListsCollaborationsContent( else -> null }, chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, maxSubtitleLength = 30, modifier = Modifier .padding(headerPadding) @@ -175,56 +158,54 @@ private fun ListsCollaborationsContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - ContentEmptyList( - filter = state.filter?.mode, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + state.items?.isEmpty() == true -> { + ContentEmptyList( + filter = state.filter?.mode, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listFilter = state.filter?.mode, - collectionState = state.collection, - contentPadding = contentPadding, - onMovieClick = onMovieClick, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listFilter = state.filter?.mode, + collectionState = state.collection, + contentPadding = contentPadding, + onMovieClick = onMovieClick, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsViewModel.kt index d4519aaa9..7ce2dbbc8 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/collaborations/ListsCollaborationsViewModel.kt @@ -25,7 +25,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.Episode -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId @@ -41,8 +40,6 @@ import tv.trakt.trakt.core.lists.sections.collaborations.usecases.GetCollaborati import tv.trakt.trakt.core.lists.sections.collaborations.usecases.GetCollaborationsListsUseCase import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @OptIn(FlowPreview::class) internal class ListsCollaborationsViewModel( @@ -56,14 +53,12 @@ internal class ListsCollaborationsViewModel( private val movieLocalDataSource: MovieLocalDataSource, private val collectionStateProvider: CollectionStateProvider, private val filterManager: GlobalFilterManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ListsCollaborationsState() private val initialMode = filterManager.getFilter() private val userState = MutableStateFlow(initialState.user) private val filterState = MutableStateFlow(initialMode) - private val collapseState = MutableStateFlow(isCollapsed()) private val listState = MutableStateFlow(initialState.list) private val itemsState = MutableStateFlow(initialState.items) private val navigateShow = MutableStateFlow(initialState.navigateShow) @@ -74,7 +69,6 @@ internal class ListsCollaborationsViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -88,7 +82,6 @@ internal class ListsCollaborationsViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -214,40 +207,12 @@ internal class ListsCollaborationsViewModel( navigateEpisode.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.LISTS_MEDIA_COLLABORATIONS - MediaMode.SHOWS -> CollapsingKey.LISTS_SHOWS_COLLABORATIONS - MediaMode.MOVIES -> CollapsingKey.LISTS_MOVIES_COLLABORATIONS - } - when { - collapsed -> collapsingManager.collapse("${key.preferenceKey}-${listId.value}") - else -> collapsingManager.expand("${key.preferenceKey}-${listId.value}") - } - } - } - - private fun isCollapsed(): Boolean { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.LISTS_MEDIA_COLLABORATIONS - MediaMode.SHOWS -> CollapsingKey.LISTS_SHOWS_COLLABORATIONS - MediaMode.MOVIES -> CollapsingKey.LISTS_MOVIES_COLLABORATIONS - } - return collapsingManager - .isCollapsed("${key.preferenceKey}-${listId.value}") - } - @Suppress("UNCHECKED_CAST") val state = combine( listState, userState, itemsState, filterState, - collapseState, collectionStateProvider.stateFlow, navigateShow, navigateMovie, @@ -260,13 +225,12 @@ internal class ListsCollaborationsViewModel( user = states[1] as User?, items = states[2] as ImmutableList?, filter = states[3] as GlobalFilter?, - collapsed = states[4] as Boolean, - collection = states[5] as UserCollectionState, - navigateShow = states[6] as TraktId?, - navigateMovie = states[7] as TraktId?, - navigateEpisode = states[8] as Pair?, - loading = states[9] as LoadingState, - error = states[10] as Exception?, + collection = states[4] as UserCollectionState, + navigateShow = states[5] as TraktId?, + navigateMovie = states[6] as TraktId?, + navigateEpisode = states[7] as Pair?, + loading = states[8] as LoadingState, + error = states[9] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedState.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedState.kt index 30a608537..a9f03437c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedState.kt @@ -23,6 +23,5 @@ internal data class ListsLikedState( val navigateMovie: TraktId? = null, val navigateEpisode: Pair? = null, val loading: LoadingState = Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedView.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedView.kt index 4d3b857f3..4a7c8a1be 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedView.kt @@ -1,9 +1,6 @@ package tv.trakt.trakt.core.lists.sections.liked import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -26,7 +23,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -96,7 +92,6 @@ internal fun ListsLikedView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onMovieClick = { if (!state.loading.isLoading) { viewModel.navigateToMovie(it) @@ -141,20 +136,14 @@ private fun ListsLikedContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onShowClick: (Show) -> Unit = {}, onMovieClick: (Movie) -> Unit = {}, onEpisodeClick: (Show, Episode) -> Unit = { _, _ -> }, onAllClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = list?.name ?: "", @@ -163,12 +152,6 @@ private fun ListsLikedContent( else -> null }, chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, maxSubtitleLength = 30, modifier = Modifier .padding(headerPadding) @@ -177,56 +160,54 @@ private fun ListsLikedContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - ContentEmptyList( - filter = state.filter?.mode, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + state.items?.isEmpty() == true -> { + ContentEmptyList( + filter = state.filter?.mode, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listFilter = state.filter?.mode, - collectionState = state.collection, - contentPadding = contentPadding, - onMovieClick = onMovieClick, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listFilter = state.filter?.mode, + collectionState = state.collection, + contentPadding = contentPadding, + onMovieClick = onMovieClick, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedViewModel.kt index 8c30ab0f4..5ef1f886c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/liked/ListsLikedViewModel.kt @@ -22,7 +22,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.Episode -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId @@ -36,8 +35,6 @@ import tv.trakt.trakt.core.lists.sections.liked.usecases.GetLikedListItemsUseCas import tv.trakt.trakt.core.lists.sections.liked.usecases.GetLikedListsUseCase import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @OptIn(FlowPreview::class) internal class ListsLikedViewModel( @@ -49,14 +46,12 @@ internal class ListsLikedViewModel( private val movieLocalDataSource: MovieLocalDataSource, private val collectionStateProvider: CollectionStateProvider, private val filterManager: GlobalFilterManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ListsLikedState() private val initialMode = filterManager.getFilter() private val userState = MutableStateFlow(initialState.user) private val filterState = MutableStateFlow(initialMode) - private val collapseState = MutableStateFlow(isCollapsed()) private val listState = MutableStateFlow(initialState.list) private val itemsState = MutableStateFlow(initialState.items) private val navigateShow = MutableStateFlow(initialState.navigateShow) @@ -67,7 +62,6 @@ internal class ListsLikedViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -80,7 +74,6 @@ internal class ListsLikedViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -175,40 +168,12 @@ internal class ListsLikedViewModel( navigateEpisode.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.LISTS_MEDIA_LIKED - MediaMode.SHOWS -> CollapsingKey.LISTS_SHOWS_LIKED - MediaMode.MOVIES -> CollapsingKey.LISTS_MOVIES_LIKED - } - when { - collapsed -> collapsingManager.collapse("${key.preferenceKey}-${listId.value}") - else -> collapsingManager.expand("${key.preferenceKey}-${listId.value}") - } - } - } - - private fun isCollapsed(): Boolean { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.LISTS_MEDIA_LIKED - MediaMode.SHOWS -> CollapsingKey.LISTS_SHOWS_LIKED - MediaMode.MOVIES -> CollapsingKey.LISTS_MOVIES_LIKED - } - return collapsingManager - .isCollapsed("${key.preferenceKey}-${listId.value}") - } - @Suppress("UNCHECKED_CAST") val state = combine( listState, userState, itemsState, filterState, - collapseState, collectionStateProvider.stateFlow, navigateShow, navigateMovie, @@ -221,13 +186,12 @@ internal class ListsLikedViewModel( user = states[1] as User?, items = states[2] as ImmutableList?, filter = states[3] as GlobalFilter?, - collapsed = states[4] as Boolean, - collection = states[5] as UserCollectionState, - navigateShow = states[6] as TraktId?, - navigateMovie = states[7] as TraktId?, - navigateEpisode = states[8] as Pair?, - loading = states[9] as LoadingState, - error = states[10] as Exception?, + collection = states[4] as UserCollectionState, + navigateShow = states[5] as TraktId?, + navigateMovie = states[6] as TraktId?, + navigateEpisode = states[7] as Pair?, + loading = states[8] as LoadingState, + error = states[9] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalState.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalState.kt index c9b3a9950..7d23ef767 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalState.kt @@ -23,6 +23,5 @@ internal data class ListsPersonalState( val navigateMovie: TraktId? = null, val navigateEpisode: Pair? = null, val loading: LoadingState = Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalView.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalView.kt index 8ab6a3097..dfd38ece1 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalView.kt @@ -1,9 +1,6 @@ package tv.trakt.trakt.core.lists.sections.personal import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -28,7 +25,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -104,7 +100,6 @@ internal fun ListsPersonalView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowLongClick = { if (!state.loading.isLoading) { showContextSheet = it @@ -160,7 +155,6 @@ internal fun ListsPersonalContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onShowLongClick: (Show) -> Unit = {}, onMovieLongClick: (Movie) -> Unit = {}, onShowClick: (Show) -> Unit = {}, @@ -169,14 +163,9 @@ internal fun ListsPersonalContent( onMoreClick: () -> Unit = {}, onAllClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = list?.name ?: "", @@ -185,12 +174,6 @@ internal fun ListsPersonalContent( else -> null }, chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, maxSubtitleLength = 30, extraIcon = { Icon( @@ -210,58 +193,56 @@ internal fun ListsPersonalContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - ContentEmptyList( - filter = state.filter?.mode, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + state.items?.isEmpty() == true -> { + ContentEmptyList( + filter = state.filter?.mode, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listFilter = state.filter?.mode, - collectionState = state.collection, - contentPadding = contentPadding, - onShowLongClick = onShowLongClick, - onMovieLongClick = onMovieLongClick, - onMovieClick = onMovieClick, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listFilter = state.filter?.mode, + collectionState = state.collection, + contentPadding = contentPadding, + onShowLongClick = onShowLongClick, + onMovieLongClick = onMovieLongClick, + onMovieClick = onMovieClick, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalViewModel.kt index c64d3527b..e224a2254 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/personal/ListsPersonalViewModel.kt @@ -25,7 +25,6 @@ import tv.trakt.trakt.common.helpers.LoadingState.Loading import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.Episode -import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId @@ -41,8 +40,6 @@ import tv.trakt.trakt.core.lists.sections.personal.usecases.GetPersonalListItems import tv.trakt.trakt.core.lists.sections.personal.usecases.GetPersonalListsUseCase import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @OptIn(FlowPreview::class) internal class ListsPersonalViewModel( @@ -56,13 +53,11 @@ internal class ListsPersonalViewModel( private val movieLocalDataSource: MovieLocalDataSource, private val collectionStateProvider: CollectionStateProvider, private val filterManager: GlobalFilterManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ListsPersonalState() private val userState = MutableStateFlow(initialState.user) private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val listState = MutableStateFlow(initialState.list) private val itemsState = MutableStateFlow(initialState.items) private val navigateShow = MutableStateFlow(initialState.navigateShow) @@ -73,7 +68,6 @@ internal class ListsPersonalViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -87,7 +81,6 @@ internal class ListsPersonalViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -213,40 +206,12 @@ internal class ListsPersonalViewModel( navigateEpisode.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.LISTS_MEDIA_PERSONAL - MediaMode.SHOWS -> CollapsingKey.LISTS_SHOWS_PERSONAL - MediaMode.MOVIES -> CollapsingKey.LISTS_MOVIES_PERSONAL - } - when { - collapsed -> collapsingManager.collapse("${key.preferenceKey}-${listId.value}") - else -> collapsingManager.expand("${key.preferenceKey}-${listId.value}") - } - } - } - - private fun isCollapsed(): Boolean { - val key = when (filterState.value.mode) { - MediaMode.MEDIA -> CollapsingKey.LISTS_MEDIA_PERSONAL - MediaMode.SHOWS -> CollapsingKey.LISTS_SHOWS_PERSONAL - MediaMode.MOVIES -> CollapsingKey.LISTS_MOVIES_PERSONAL - } - return collapsingManager - .isCollapsed("${key.preferenceKey}-${listId.value}") - } - @Suppress("UNCHECKED_CAST") val state = combine( listState, userState, itemsState, filterState, - collapseState, collectionStateProvider.stateFlow, navigateShow, navigateMovie, @@ -259,13 +224,12 @@ internal class ListsPersonalViewModel( user = states[1] as User?, items = states[2] as ImmutableList?, filter = states[3] as GlobalFilter?, - collapsed = states[4] as Boolean, - collection = states[5] as UserCollectionState, - navigateShow = states[6] as TraktId?, - navigateMovie = states[7] as TraktId?, - navigateEpisode = states[8] as Pair?, - loading = states[9] as LoadingState, - error = states[10] as Exception?, + collection = states[4] as UserCollectionState, + navigateShow = states[5] as TraktId?, + navigateMovie = states[6] as TraktId?, + navigateEpisode = states[7] as Pair?, + loading = states[8] as LoadingState, + error = states[9] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistState.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistState.kt index 900753747..a26afb941 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistState.kt @@ -18,6 +18,5 @@ internal data class ListsWatchlistState( val navigateShow: TraktId? = null, val navigateMovie: TraktId? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistView.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistView.kt index da7d09910..2796c7eb9 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.lists.sections.watchlist import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -24,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -97,7 +93,6 @@ internal fun ListsWatchlistView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowsClick = onShowsClick, onMoviesClick = onMoviesClick, onShowClick = { viewModel.navigateToShow(it) }, @@ -149,7 +144,6 @@ internal fun ListWatchlistContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onShowsClick: () -> Unit = {}, onShowClick: (Show) -> Unit = {}, onMoviesClick: () -> Unit = {}, @@ -159,25 +153,14 @@ internal fun ListWatchlistContent( onProfileClick: () -> Unit = {}, onWatchlistClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.page_title_watchlist), subtitle = stringResource(R.string.text_sort_recently_added), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -185,67 +168,65 @@ internal fun ListWatchlistContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - ContentEmptyView( - authenticated = (state.user != null), - filter = state.filter.mode, - onActionClick = { - if (state.user == null) { - onProfileClick() - return@ContentEmptyView - } - when (it) { - MEDIA, SHOWS -> onShowsClick() - MOVIES -> onMoviesClick() - } - }, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + ContentEmptyView( + authenticated = (state.user != null), + filter = state.filter.mode, + onActionClick = { + if (state.user == null) { + onProfileClick() + return@ContentEmptyView + } + when (it) { + MEDIA, SHOWS -> onShowsClick() + MOVIES -> onMoviesClick() + } + }, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - filter = state.filter.mode, - listItems = (state.items ?: emptyList()).toImmutableList(), - collection = state.collection, - contentPadding = contentPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - onShowLongClick = onShowLongClick, - onMovieLongClick = onMovieLongClick, - ) - } + else -> { + ContentList( + filter = state.filter.mode, + listItems = (state.items ?: emptyList()).toImmutableList(), + collection = state.collection, + contentPadding = contentPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + onShowLongClick = onShowLongClick, + onMovieLongClick = onMovieLongClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistViewModel.kt index f27d7201f..359361e4d 100644 --- a/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/lists/sections/watchlist/ListsWatchlistViewModel.kt @@ -49,8 +49,6 @@ import tv.trakt.trakt.core.user.UserCollectionState import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.AllWatchlist import tv.trakt.trakt.core.user.data.local.watchlist.WatchlistUpdates.Source.Default -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ListsWatchlistViewModel( private val filterManager: GlobalFilterManager, @@ -62,14 +60,12 @@ internal class ListsWatchlistViewModel( private val watchlistUpdates: WatchlistUpdates, private val collectionStateProvider: CollectionStateProvider, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ListsWatchlistState() private val userState = MutableStateFlow(initialState.user) private val itemsState = MutableStateFlow(initialState.items) private val filterState = MutableStateFlow(filterManager.getFilter()) - private val collapseState = MutableStateFlow(isCollapsed()) private val navigateShow = MutableStateFlow(initialState.navigateShow) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) private val loadingState = MutableStateFlow(initialState.loading) @@ -77,7 +73,6 @@ internal class ListsWatchlistViewModel( private var dataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -92,7 +87,6 @@ internal class ListsWatchlistViewModel( filterManager.observeFilter() .onEach { value -> filterState.update { value } - collapseState.update { isCollapsed() } loadData() } .launchIn(viewModelScope) @@ -233,39 +227,13 @@ internal class ListsWatchlistViewModel( navigateMovie.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - val key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.LISTS_MEDIA_WATCHLIST - SHOWS -> CollapsingKey.LISTS_SHOWS_WATCHLIST - MOVIES -> CollapsingKey.LISTS_MOVIES_WATCHLIST - } - when { - collapsed -> collapsingManager.collapse(key) - else -> collapsingManager.expand(key) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed( - key = when (filterState.value.mode) { - MEDIA -> CollapsingKey.LISTS_MEDIA_WATCHLIST - SHOWS -> CollapsingKey.LISTS_SHOWS_WATCHLIST - MOVIES -> CollapsingKey.LISTS_MOVIES_WATCHLIST - }, - ) - } @Suppress("UNCHECKED_CAST") val state = combine( loadingState, itemsState, filterState, - collapseState, collectionStateProvider.stateFlow, navigateShow, navigateMovie, @@ -276,12 +244,11 @@ internal class ListsWatchlistViewModel( loading = states[0] as LoadingState, items = states[1] as ImmutableList?, filter = states[2] as GlobalFilter, - collapsed = states[3] as Boolean, - collection = states[4] as UserCollectionState, - navigateShow = states[5] as TraktId?, - navigateMovie = states[6] as TraktId?, - error = states[7] as Exception?, - user = states[8] as User?, + collection = states[3] as UserCollectionState, + navigateShow = states[4] as TraktId?, + navigateMovie = states[5] as TraktId?, + error = states[6] as Exception?, + user = states[7] as User?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/main/di/MainModule.kt b/app/src/main/java/tv/trakt/trakt/core/main/di/MainModule.kt index f15d868f1..ccde91f03 100644 --- a/app/src/main/java/tv/trakt/trakt/core/main/di/MainModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/main/di/MainModule.kt @@ -24,12 +24,12 @@ import tv.trakt.trakt.core.main.MainViewModel import tv.trakt.trakt.core.main.usecases.CustomThemeUseCase import tv.trakt.trakt.core.main.usecases.DismissWelcomeUseCase import tv.trakt.trakt.core.main.usecases.LoadWhatsNewUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.DefaultCollapsingManager +import tv.trakt.trakt.helpers.editscreen.data.DefaultEditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager internal const val MAIN_PREFERENCES = "main_preferences" internal const val WHATS_NEW_PREFERENCES = "whats_new_preferences" -internal const val COLLAPSING_PREFERENCES = "collapsing_preferences" +internal const val EDIT_SCREEN_PREFERENCES = "edit_screen_preferences" internal val mainModule = module { single>(named(MAIN_PREFERENCES)) { @@ -39,23 +39,23 @@ internal val mainModule = module { ) } - single>(named(COLLAPSING_PREFERENCES)) { + single>(named(WHATS_NEW_PREFERENCES)) { createStore( context = androidApplication(), - file = COLLAPSING_PREFERENCES, + file = WHATS_NEW_PREFERENCES, ) } - single>(named(WHATS_NEW_PREFERENCES)) { + single>(named(EDIT_SCREEN_PREFERENCES)) { createStore( context = androidApplication(), - file = WHATS_NEW_PREFERENCES, + file = EDIT_SCREEN_PREFERENCES, ) } - single { - DefaultCollapsingManager( - dataStore = get(named(COLLAPSING_PREFERENCES)), + single { + DefaultEditScreenManager( + dataStore = get(named(EDIT_SCREEN_PREFERENCES)), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/ProfileScreen.kt b/app/src/main/java/tv/trakt/trakt/core/profile/ProfileScreen.kt index 109be41bc..0b01eccb4 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/ProfileScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/ProfileScreen.kt @@ -75,6 +75,13 @@ import tv.trakt.trakt.core.profile.sections.progress.ProfileProgressView import tv.trakt.trakt.core.profile.sections.social.ProfileSocialView import tv.trakt.trakt.core.profile.sections.thismonth.ThisMonthCard import tv.trakt.trakt.helpers.SimpleScrollConnection +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.ProfileKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ProfileFavorites +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ProfileHistory +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ProfileLibrary +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ProfileProgress +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ProfileSocial import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.components.ScrollableBackdropImage import tv.trakt.trakt.ui.components.TraktHeader @@ -104,6 +111,7 @@ internal fun ProfileScreen( val state by viewModel.state.collectAsStateWithLifecycle() var confirmLogout by remember { mutableStateOf(false) } + var sectionsSheet by remember { mutableStateOf(false) } LaunchedEffect(state.user, state.logoutLoading) { if (state.logoutLoading == Done && state.user == null) { @@ -133,6 +141,17 @@ internal fun ProfileScreen( onLogoutClick = { confirmLogout = true }, + onEditScreenClick = { + sectionsSheet = true + }, + ) + + EditScreenSheet( + active = sectionsSheet, + enabledValues = ProfileKeys, + onDismiss = { + sectionsSheet = false + }, ) ConfirmationSheet( @@ -167,6 +186,7 @@ private fun ProfileScreen( onShareClick: () -> Unit = {}, onSettingsClick: () -> Unit = {}, onLogoutClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { val inspection = LocalInspectionMode.current val windowClass = currentWindowAdaptiveInfo().windowSizeClass @@ -219,6 +239,7 @@ private fun ProfileScreen( onShareClick = onShareClick, onLogoutClick = onLogoutClick, onSettingsClick = onSettingsClick, + onEditScreenClick = onEditScreenClick, modifier = Modifier .padding(bottom = 8.dp), ) @@ -295,63 +316,73 @@ private fun ProfileScreen( } if (!inspection) { - item { - ProfileHistoryView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onMoreClick = onNavigateToHistory, - onEpisodeClick = onNavigateToEpisode, - onShowClick = onNavigateToShow, - onMovieClick = onNavigateToMovie, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(ProfileHistory) == true) { + item { + ProfileHistoryView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onMoreClick = onNavigateToHistory, + onEpisodeClick = onNavigateToEpisode, + onShowClick = onNavigateToShow, + onMovieClick = onNavigateToMovie, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - ProfileProgressView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onNavigateToShow, - onMoreClick = onNavigateToProgress, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(ProfileProgress) == true) { + item { + ProfileProgressView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onNavigateToShow, + onMoreClick = onNavigateToProgress, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - ProfileFavoritesView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onNavigateToShow, - onMovieClick = onNavigateToMovie, - onMoreClick = onNavigateToFavorites, - onShowsClick = onNavigateToShows, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(ProfileFavorites) == true) { + item { + ProfileFavoritesView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onNavigateToShow, + onMovieClick = onNavigateToMovie, + onMoreClick = onNavigateToFavorites, + onShowsClick = onNavigateToShows, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - ProfileLibraryView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onNavigateToShow, - onMovieClick = onNavigateToMovie, - onMoreClick = onNavigateToLibrary, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(ProfileLibrary) == true) { + item { + ProfileLibraryView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onNavigateToShow, + onMovieClick = onNavigateToMovie, + onMoreClick = onNavigateToLibrary, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - ProfileSocialView( - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onUserClick = onNavigateToUser, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(ProfileSocial) == true) { + item { + ProfileSocialView( + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onUserClick = onNavigateToUser, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } } } @@ -366,6 +397,7 @@ private fun TitleBar( onShareClick: () -> Unit = {}, onLogoutClick: () -> Unit = {}, onSettingsClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { Row( verticalAlignment = CenterVertically, @@ -494,6 +526,29 @@ private fun TitleBar( ) }, ) + + DropdownMenuItem( + text = { + Text( + text = stringResource(R.string.text_edit_screen), + style = TraktTheme.typography.buttonTertiary, + color = TraktTheme.colors.textPrimary, + ) + }, + onClick = { + onEditScreenClick() + showMenu = false + }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.ic_edit_screen), + contentDescription = null, + tint = TraktTheme.colors.textPrimary, + modifier = Modifier.size(22.dp), + ) + }, + ) + DropdownMenuItem( text = { Text( diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/ProfileState.kt b/app/src/main/java/tv/trakt/trakt/core/profile/ProfileState.kt index d409bf775..4e30723d5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/ProfileState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/ProfileState.kt @@ -1,9 +1,11 @@ package tv.trakt.trakt.core.profile import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.profile.sections.thismonth.model.ThisMonthStats +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class ProfileState( @@ -14,4 +16,5 @@ internal data class ProfileState( val loadingMonthStats: LoadingState = LoadingState.Idle, val logoutLoading: LoadingState = LoadingState.Idle, val checkIn: Boolean = false, + val visibility: ImmutableMap? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/ProfileViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/profile/ProfileViewModel.kt index 9eb6bc198..fa693f344 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/ProfileViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/ProfileViewModel.kt @@ -4,6 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.firebase.Firebase import com.google.firebase.remoteconfig.remoteConfig +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -29,6 +31,9 @@ import tv.trakt.trakt.core.checkin.data.CheckInManager import tv.trakt.trakt.core.profile.sections.thismonth.model.ThisMonthStats import tv.trakt.trakt.core.profile.sections.thismonth.usecases.GetThisMonthUseCase import tv.trakt.trakt.core.user.usecases.LogoutUserUseCase +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.ProfileKeys @OptIn(FlowPreview::class) internal class ProfileViewModel( @@ -36,6 +41,7 @@ internal class ProfileViewModel( private val getThisMonthUseCase: GetThisMonthUseCase, private val logoutUseCase: LogoutUserUseCase, private val checkInManager: CheckInManager, + private val editScreenManager: EditScreenManager, private val analytics: Analytics, ) : ViewModel() { private val initialState = ProfileState() @@ -47,6 +53,7 @@ internal class ProfileViewModel( private val loadingMonthStatsState = MutableStateFlow(initialState.loadingMonthStats) private val logoutLoadingState = MutableStateFlow(initialState.logoutLoading) private val checkInState = MutableStateFlow(initialState.checkIn) + private val visibilityState = MutableStateFlow(initialState.visibility) init { loadMonthBackground() @@ -54,6 +61,7 @@ internal class ProfileViewModel( observeUser() observeCheckIn() + observeVisibility() analytics.logScreenView( screenName = "profile", @@ -86,6 +94,15 @@ internal class ProfileViewModel( .launchIn(viewModelScope) } + private fun observeVisibility() { + editScreenManager.observe(ProfileKeys) + .distinctUntilChanged() + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) + } + private fun loadMonthBackground() { val configUrl = Firebase.remoteConfig.getString(MOBILE_THIS_MONTH_IMAGE_URL) monthBackgroundState.update { configUrl } @@ -126,6 +143,7 @@ internal class ProfileViewModel( } } + @Suppress("UNCHECKED_CAST") val state = combine( monthStatsState, monthBackgroundState, @@ -134,6 +152,7 @@ internal class ProfileViewModel( logoutLoadingState, userState, checkInState, + visibilityState, ) { state -> ProfileState( monthStats = state[0] as ThisMonthStats?, @@ -143,6 +162,7 @@ internal class ProfileViewModel( logoutLoading = state[4] as LoadingState, user = state[5] as User?, checkIn = state[6] as Boolean, + visibility = state[7] as ImmutableMap?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/di/ProfileModule.kt b/app/src/main/java/tv/trakt/trakt/core/profile/di/ProfileModule.kt index bae40c7a6..7c2d8b107 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/di/ProfileModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/di/ProfileModule.kt @@ -336,7 +336,6 @@ internal val profileModule = module { LogoutUserUseCase( appContext = androidApplication(), sessionManager = get(), - collapsingManager = get(), checkInManager = get(), apiClients = get(named("apiClients")), younifyApiClient = get(), @@ -377,6 +376,7 @@ internal val profileModule = module { checkInManager = get(), getThisMonthUseCase = get(), logoutUseCase = get(), + editScreenManager = get(), analytics = get(), ) } @@ -394,7 +394,6 @@ internal val profileModule = module { episodeUpdates = get(), ratingsUpdates = get(), checkInUpdate = get(), - collapsingManager = get(), sessionManager = get(), ) } @@ -406,7 +405,6 @@ internal val profileModule = module { movieLocalDataSource = get(), favoritesUpdates = get(), sessionManager = get(), - collapsingManager = get(), ) } @@ -416,7 +414,6 @@ internal val profileModule = module { showLocalDataSource = get(), movieLocalDataSource = get(), sessionManager = get(), - collapsingManager = get(), ) } @@ -425,7 +422,6 @@ internal val profileModule = module { loadSocialUseCase = get(), getFilterUseCase = get(), sessionManager = get(), - collapsingManager = get(), ) } @@ -436,7 +432,6 @@ internal val profileModule = module { getDroppedUseCase = get(), getWatchingUseCase = get(), localShowSource = get(), - collapsingManager = get(), episodeUpdates = get(), showUpdates = get(), ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesState.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesState.kt index fb5d14bbc..041e44be8 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesState.kt @@ -14,6 +14,5 @@ internal data class ProfileFavoritesState( val navigateShow: TraktId? = null, val navigateMovie: TraktId? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesView.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesView.kt index 9e7b949ea..f6a98d68c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.profile.sections.favorites import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -24,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -89,7 +85,6 @@ internal fun ProfileFavoritesView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { viewModel.navigateToShow(it) }, onMovieClick = { viewModel.navigateToMovie(it) }, onShowLongClick = { showContextSheet = it }, @@ -119,7 +114,6 @@ internal fun ProfileFavoritesContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onShowClick: (Show) -> Unit = {}, onMovieClick: (Movie) -> Unit = {}, onShowLongClick: (Show) -> Unit = {}, @@ -127,25 +121,14 @@ internal fun ProfileFavoritesContent( onFavoritesClick: () -> Unit = {}, onShowsClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_favorites), subtitle = stringResource(R.string.text_sort_recently_added), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -153,54 +136,52 @@ internal fun ProfileFavoritesContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - ContentEmptyView( - onShowsClick = onShowsClick, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + ContentEmptyView( + onShowsClick = onShowsClick, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - onShowLongClick = onShowLongClick, - onMovieLongClick = onMovieLongClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + onShowLongClick = onShowLongClick, + onMovieLongClick = onMovieLongClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesViewModel.kt index 5ece61022..d09165603 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/favorites/ProfileFavoritesViewModel.kt @@ -40,8 +40,6 @@ import tv.trakt.trakt.core.favorites.FavoritesUpdates.Source.RATE_PROMPT import tv.trakt.trakt.core.favorites.model.FavoriteItem import tv.trakt.trakt.core.lists.ListsConfig.FAVORITES_SECTION_LIMIT import tv.trakt.trakt.core.user.usecases.lists.LoadUserFavoritesUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @OptIn(FlowPreview::class) internal class ProfileFavoritesViewModel( @@ -50,7 +48,6 @@ internal class ProfileFavoritesViewModel( private val movieLocalDataSource: MovieLocalDataSource, private val favoritesUpdates: FavoritesUpdates, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ProfileFavoritesState() @@ -59,12 +56,10 @@ internal class ProfileFavoritesViewModel( private val navigateShow = MutableStateFlow(initialState.navigateShow) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(isCollapsed()) private val errorState = MutableStateFlow(initialState.error) private var loadDataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -180,21 +175,7 @@ internal class ProfileFavoritesViewModel( navigateMovie.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.PROFILE_FAVORITES) - else -> collapsingManager.expand(CollapsingKey.PROFILE_FAVORITES) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed(CollapsingKey.PROFILE_FAVORITES) - } @Suppress("UNCHECKED_CAST") val state = combine( @@ -202,7 +183,6 @@ internal class ProfileFavoritesViewModel( itemsState, navigateShow, navigateMovie, - collapseState, errorState, userState, ) { states -> @@ -211,9 +191,8 @@ internal class ProfileFavoritesViewModel( items = states[1] as ImmutableList?, navigateShow = states[2] as TraktId?, navigateMovie = states[3] as TraktId?, - collapsed = states[4] as Boolean, - error = states[5] as Exception?, - user = states[6] as User?, + error = states[4] as Exception?, + user = states[5] as User?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryState.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryState.kt index e6416d7bf..e10385a1f 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryState.kt @@ -17,6 +17,5 @@ internal data class ProfileHistoryState( val navigateEpisode: Pair? = null, val navigateMovie: TraktId? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryView.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryView.kt index f7fc08dc9..4f9a7bb1c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.profile.sections.history import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -24,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -93,7 +89,6 @@ internal fun ProfileHistoryView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { viewModel.navigateToShow(it.show) }, @@ -135,7 +130,6 @@ internal fun ProfileHistoryContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onShowClick: (HomeActivityItem.EpisodeItem) -> Unit = {}, onEpisodeClick: (HomeActivityItem.EpisodeItem) -> Unit = {}, onEpisodeLongClick: (HomeActivityItem.EpisodeItem) -> Unit = {}, @@ -143,79 +137,66 @@ internal fun ProfileHistoryContent( onMovieLongClick: (HomeActivityItem.MovieItem) -> Unit = {}, onMoreClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_history), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { onMoreClick() }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - HomeEmptySocialView( - modifier = Modifier - .padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + HomeEmptySocialView( + modifier = Modifier + .padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listRatings = (state.itemsRatings ?: emptyMap()).toImmutableMap(), - contentPadding = contentPadding, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - onEpisodeLongClick = onEpisodeLongClick, - onMovieClick = onMovieClick, - onMovieLongClick = onMovieLongClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listRatings = (state.itemsRatings ?: emptyMap()).toImmutableMap(), + contentPadding = contentPadding, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + onEpisodeLongClick = onEpisodeLongClick, + onMovieClick = onMovieClick, + onMovieLongClick = onMovieLongClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryViewModel.kt index 9c55a1e4f..5fb3e9b86 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/history/ProfileHistoryViewModel.kt @@ -54,8 +54,6 @@ import tv.trakt.trakt.core.summary.movies.data.MovieDetailsUpdates import tv.trakt.trakt.core.summary.shows.data.ShowDetailsUpdates import tv.trakt.trakt.core.summary.shows.data.ShowDetailsUpdates.Source import tv.trakt.trakt.core.user.usecases.ratings.LoadUserRatingsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ProfileHistoryViewModel( private val getPersonalActivityUseCase: GetPersonalActivityUseCase, @@ -70,7 +68,6 @@ internal class ProfileHistoryViewModel( private val ratingsUpdates: RatingsUpdates, private val checkInUpdate: CheckInUpdates, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ProfileHistoryState() @@ -80,12 +77,10 @@ internal class ProfileHistoryViewModel( private val navigateEpisode = MutableStateFlow(initialState.navigateEpisode) private val navigateMovie = MutableStateFlow(initialState.navigateMovie) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(isCollapsed()) private val errorState = MutableStateFlow(initialState.error) private var loadDataJob: Job? = null private var processingJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -261,21 +256,7 @@ internal class ProfileHistoryViewModel( navigateMovie.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.PROFILE_HISTORY) - else -> collapsingManager.expand(CollapsingKey.PROFILE_HISTORY) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed(CollapsingKey.PROFILE_HISTORY) - } @Suppress("UNCHECKED_CAST") val state = combine( @@ -285,7 +266,6 @@ internal class ProfileHistoryViewModel( navigateShow, navigateEpisode, navigateMovie, - collapseState, errorState, ) { states -> ProfileHistoryState( @@ -295,8 +275,7 @@ internal class ProfileHistoryViewModel( navigateShow = states[3] as? TraktId, navigateEpisode = states[4] as? Pair, navigateMovie = states[5] as? TraktId, - collapsed = states[6] as Boolean, - error = states[7] as? Exception, + error = states[6] as? Exception, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryState.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryState.kt index 06f5aacc8..acf955556 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryState.kt @@ -13,6 +13,5 @@ internal data class ProfileLibraryState( val items: ImmutableList? = null, val filter: LibraryFilter? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryView.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryView.kt index 305df282f..a1dd4f3b2 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.profile.sections.library import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -27,7 +24,6 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -81,7 +77,6 @@ internal fun ProfileLibraryView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onFilterClick = viewModel::setFilter, onEpisodeClick = { show, _ -> scope.launch { @@ -119,29 +114,17 @@ private fun ProfileLibraryView( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onFilterClick: (LibraryFilter) -> Unit = {}, onEpisodeClick: (Show, Episode) -> Unit = { _, _ -> }, onMovieClick: (Movie) -> Unit = {}, onLibraryClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_library), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done) { @@ -149,64 +132,62 @@ private fun ProfileLibraryView( }, ) - if (state.collapsed != true) { - ContentFilters( - state = state, - headerPadding = headerPadding, - onFilterClick = onFilterClick, - ) + ContentFilters( + state = state, + headerPadding = headerPadding, + onFilterClick = onFilterClick, + ) - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( + else -> { + ContentList( // filter = state.filter, - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onEpisodeClick = { - onEpisodeClick(it.show, it.episode) - }, - onMovieClick = { - onMovieClick(it.movie) - }, - ) - } + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onEpisodeClick = { + onEpisodeClick(it.show, it.episode) + }, + onMovieClick = { + onMovieClick(it.movie) + }, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryViewModel.kt index 7f7934ed6..475cac790 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/library/ProfileLibraryViewModel.kt @@ -32,8 +32,6 @@ import tv.trakt.trakt.core.library.model.LibraryItem import tv.trakt.trakt.core.lists.ListsConfig.LIBRARY_PAGE_LIMIT import tv.trakt.trakt.core.lists.ListsConfig.LIBRARY_SECTION_LIMIT import tv.trakt.trakt.core.user.usecases.lists.LoadUserLibraryUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @OptIn(FlowPreview::class) internal class ProfileLibraryViewModel( @@ -41,7 +39,6 @@ internal class ProfileLibraryViewModel( private val showLocalDataSource: ShowLocalDataSource, private val movieLocalDataSource: MovieLocalDataSource, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ProfileLibraryState() @@ -50,11 +47,9 @@ internal class ProfileLibraryViewModel( private val filterState = MutableStateFlow(initialState.filter) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(isCollapsed()) private val errorState = MutableStateFlow(initialState.error) private var loadDataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -154,27 +149,12 @@ internal class ProfileLibraryViewModel( movieLocalDataSource.upsertMovies(listOf(movie)) } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.PROFILE_LIBRARY) - else -> collapsingManager.expand(CollapsingKey.PROFILE_LIBRARY) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed(CollapsingKey.PROFILE_LIBRARY) - } val state = combine( loadingState, itemsState, filterState, - collapseState, errorState, userState, ) { states -> @@ -182,9 +162,8 @@ internal class ProfileLibraryViewModel( loading = states[0] as LoadingState, items = states[1] as ImmutableList?, filter = states[2] as LibraryFilter?, - collapsed = states[3] as Boolean, - error = states[4] as Exception?, - user = states[5] as User?, + error = states[3] as Exception?, + user = states[4] as User?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressState.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressState.kt index 0bf6c5cd6..b58364711 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressState.kt @@ -15,6 +15,5 @@ internal data class ProfileProgressState( val filter: ProgressFilter? = null, val navigateShow: TraktId? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressView.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressView.kt index 0f88351c7..4e2b4087e 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressView.kt @@ -1,9 +1,6 @@ package tv.trakt.trakt.core.profile.sections.progress import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -19,10 +16,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource @@ -70,7 +64,6 @@ internal fun ProfileProgressView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onFilterClick = viewModel::setFilter, onShowClick = viewModel::navigateToShow, onProgressClick = { onMoreClick() }, @@ -83,29 +76,17 @@ internal fun ProfileProgressContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onFilterClick: (ProgressFilter) -> Unit = {}, onShowClick: (Show) -> Unit = {}, onProgressClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_progress), subtitle = null, chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done && !state.items.isNullOrEmpty()) { @@ -113,58 +94,56 @@ internal fun ProfileProgressContent( }, ) - if (state.collapsed != true) { - ProgressFilters( - selected = state.filter, - onClick = onFilterClick, - paddingHorizontal = headerPadding, - paddingVertical = PaddingValues(top = 13.dp, bottom = 15.dp), - ) + ProgressFilters( + selected = state.filter, + onClick = onFilterClick, + paddingHorizontal = headerPadding, + paddingVertical = PaddingValues(top = 13.dp, bottom = 15.dp), + ) - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 3.75.dp), - ) - } - Done -> { - when { - state.error != null -> { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 3.75.dp), + ) + } + Done -> { + when { + state.error != null -> { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true && state.loading.isDone -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true && state.loading.isDone -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = state.items ?: EmptyImmutableList, - contentPadding = contentPadding, - onShowClick = onShowClick, - ) - } + else -> { + ContentList( + listItems = state.items ?: EmptyImmutableList, + contentPadding = contentPadding, + onShowClick = onShowClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressViewModel.kt index 02bf87559..19ec263f8 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/progress/ProfileProgressViewModel.kt @@ -42,8 +42,6 @@ import tv.trakt.trakt.core.summary.episodes.data.EpisodeDetailsUpdates.Source.PR import tv.trakt.trakt.core.summary.episodes.data.EpisodeDetailsUpdates.Source.SEASON import tv.trakt.trakt.core.summary.shows.data.ShowDetailsUpdates import tv.trakt.trakt.core.summary.shows.data.ShowDetailsUpdates.Source -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ProfileProgressViewModel( private val getFilterUseCase: GetProgressFilterUseCase, @@ -51,7 +49,6 @@ internal class ProfileProgressViewModel( private val getCompletedUseCase: GetProgressCompleteUseCase, private val getWatchingUseCase: GetProgressWatchingUseCase, private val localShowSource: ShowLocalDataSource, - private val collapsingManager: CollapsingManager, private val showUpdates: ShowDetailsUpdates, private val episodeUpdates: EpisodeDetailsUpdates, ) : ViewModel() { @@ -62,11 +59,9 @@ internal class ProfileProgressViewModel( private val filterState = MutableStateFlow(initialState.filter) private val navigateShow = MutableStateFlow(initialState.navigateShow) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(isCollapsed()) private val errorState = MutableStateFlow(initialState.error) private var loadDataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -160,21 +155,7 @@ internal class ProfileProgressViewModel( navigateShow.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.PROFILE_PROGRESS) - else -> collapsingManager.expand(CollapsingKey.PROFILE_PROGRESS) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed(CollapsingKey.PROFILE_PROGRESS) - } @Suppress("UNCHECKED_CAST") val state = combine( @@ -182,7 +163,6 @@ internal class ProfileProgressViewModel( itemsState, filterState, navigateShow, - collapseState, errorState, userState, ) { states -> @@ -191,9 +171,8 @@ internal class ProfileProgressViewModel( items = states[1] as ImmutableList?, filter = states[2] as ProgressFilter?, navigateShow = states[3] as TraktId?, - collapsed = states[4] as Boolean, - error = states[5] as Exception?, - user = states[6] as User?, + error = states[4] as Exception?, + user = states[5] as User?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialState.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialState.kt index ffe757bc7..29e87cce3 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialState.kt @@ -12,6 +12,5 @@ internal data class ProfileSocialState( val items: ImmutableList? = null, val filter: SocialFilter = SocialFilter.FOLLOWING, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialView.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialView.kt index bbf6a66e0..dcaaadb0e 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.profile.sections.social import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -24,10 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.painterResource @@ -68,7 +62,6 @@ internal fun ProfileSocialView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onFilterClick = viewModel::setFilter, onUserClick = { onUserClick(it) }, ) @@ -80,80 +73,66 @@ internal fun ProfileSocialContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onFilterClick: (SocialFilter) -> Unit = {}, onUserClick: (User) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_social), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding), ) - if (state.collapsed != true) { - ContentFilters( - state = state, - headerPadding = headerPadding, - onFilterClick = onFilterClick, - ) + ContentFilters( + state = state, + headerPadding = headerPadding, + onFilterClick = onFilterClick, + ) - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = - "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = + "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - HomeEmptySocialView( - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + HomeEmptySocialView( + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onUserClick = onUserClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onUserClick = onUserClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialViewModel.kt index 4ed9d3f4a..4585d8ac8 100644 --- a/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/profile/sections/social/ProfileSocialViewModel.kt @@ -23,14 +23,11 @@ import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.profile.sections.social.model.SocialFilter import tv.trakt.trakt.core.profile.sections.social.usecases.GetSocialFilterUseCase import tv.trakt.trakt.core.user.usecases.social.LoadUserSocialUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ProfileSocialViewModel( private val loadSocialUseCase: LoadUserSocialUseCase, private val getFilterUseCase: GetSocialFilterUseCase, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ProfileSocialState() @@ -38,10 +35,8 @@ internal class ProfileSocialViewModel( private val itemsState = MutableStateFlow(initialState.items) private val filterState = MutableStateFlow(initialState.filter) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(isCollapsed()) private val errorState = MutableStateFlow(initialState.error) private var loadDataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -106,28 +101,13 @@ internal class ProfileSocialViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.PROFILE_SOCIAL) - else -> collapsingManager.expand(CollapsingKey.PROFILE_SOCIAL) - } - } - } - - private fun isCollapsed(): Boolean { - return collapsingManager.isCollapsed(CollapsingKey.PROFILE_SOCIAL) - } @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( loadingState, itemsState, filterState, - collapseState, errorState, userState, ) { states -> @@ -135,9 +115,8 @@ internal class ProfileSocialViewModel( loading = states[0] as LoadingState, items = states[1] as ImmutableList?, filter = states[2] as SocialFilter, - collapsed = states[3] as Boolean, - error = states[4] as Exception?, - user = states[5] as User?, + error = states[3] as Exception?, + user = states[4] as User?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/ratings/rateprompt/ui/RatePromptView.kt b/app/src/main/java/tv/trakt/trakt/core/ratings/rateprompt/ui/RatePromptView.kt index fd75606e8..490fe59ab 100644 --- a/app/src/main/java/tv/trakt/trakt/core/ratings/rateprompt/ui/RatePromptView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/ratings/rateprompt/ui/RatePromptView.kt @@ -2,12 +2,9 @@ package tv.trakt.trakt.core.ratings.rateprompt.ui -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -52,7 +49,6 @@ import androidx.compose.ui.text.font.FontWeight.Companion.W500 import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -106,13 +102,7 @@ internal fun RatePromptView( color = TraktTheme.colors.navigationContainer, shape = viewShape, ) - .onEmptyClick() - .animateContentSize( - animationSpec = spring( - stiffness = 1200F, - visibilityThreshold = IntSize.VisibilityThreshold, - ), - ), + .onEmptyClick(), ) { ExpandedView( key = media.movie.ids.trakt.toString(), diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsScreen.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsScreen.kt index 603b5edda..c3034c1c0 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsScreen.kt @@ -5,7 +5,6 @@ package tv.trakt.trakt.core.summary.episodes import android.content.Context import android.content.Intent import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -89,6 +88,14 @@ import tv.trakt.trakt.core.summary.ui.DetailsActions import tv.trakt.trakt.core.summary.ui.DetailsBackground import tv.trakt.trakt.core.summary.ui.header.DetailsHeader import tv.trakt.trakt.helpers.SimpleScrollConnection +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.EpisodeKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.EpisodeActors +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.EpisodeHistory +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.EpisodeRelated +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.EpisodeReviews +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.EpisodeSeason +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.EpisodeWhereToWatch import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.components.confirmation.RemoveConfirmationSheet import tv.trakt.trakt.ui.components.dateselection.DateSelectionSheet @@ -124,6 +131,7 @@ internal fun EpisodeDetailsScreen( var dateSheet by remember { mutableStateOf(false) } var detailsSheet by remember { mutableStateOf?>(null) } var coverImageSheet by remember { mutableStateOf(false) } + var sectionsSheet by remember { mutableStateOf(false) } DisposableEffect(Unit) { localRateVisibility.value = false @@ -221,6 +229,9 @@ internal fun EpisodeDetailsScreen( onCoverClick = { coverImageSheet = true }, + onEditScreenClick = { + sectionsSheet = true + }, onDismiss = { contextSheet = false }, @@ -300,6 +311,12 @@ internal fun EpisodeDetailsScreen( }, ) + EditScreenSheet( + active = sectionsSheet, + enabledValues = EpisodeKeys, + onDismiss = { sectionsSheet = false }, + ) + LaunchedEffect(state.info) { if (state.info == null) { return@LaunchedEffect @@ -496,7 +513,7 @@ internal fun EpisodeDetailsContent( if (!previewMode) { val streamingsVisible = isReleased && (state.user != null) - if (streamingsVisible) { + if (streamingsVisible && state.visibility?.get(EpisodeWhereToWatch) == true) { item { EpisodeStreamingsView( viewModel = koinViewModel( @@ -513,80 +530,88 @@ internal fun EpisodeDetailsContent( } } - item { - EpisodeCommentsView( - viewModel = koinViewModel( - parameters = { - parametersOf(state.show, state.episode) - }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onMoreClick = onMoreCommentsClick, - onUserClick = onNavigateToUser, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding( - top = when { - streamingsVisible -> 32.dp - else -> 24.dp + if (state.visibility?.get(EpisodeReviews) == true) { + item { + EpisodeCommentsView( + viewModel = koinViewModel( + parameters = { + parametersOf(state.show, state.episode) }, ), - ) + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onMoreClick = onMoreCommentsClick, + onUserClick = onNavigateToUser, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding( + top = when { + streamingsVisible -> 32.dp + else -> 24.dp + }, + ), + ) + } } - item { - EpisodeActorsView( - viewModel = koinViewModel( - parameters = { - parametersOf(state.show, state.episode) - }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onPersonClick = onPersonClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(EpisodeActors) == true) { + item { + EpisodeActorsView( + viewModel = koinViewModel( + parameters = { + parametersOf(state.show, state.episode) + }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onPersonClick = onPersonClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - EpisodeSeasonView( - viewModel = koinViewModel( - parameters = { - parametersOf(state.show, state.episode) + if (state.visibility?.get(EpisodeSeason) == true) { + item { + EpisodeSeasonView( + viewModel = koinViewModel( + parameters = { + parametersOf(state.show, state.episode) + }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onEpisodeClick = onEpisodeClick ?: {}, + onAllSeasonsClick = { season -> + onAllSeasonsClick?.invoke(state.show, season) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onEpisodeClick = onEpisodeClick ?: {}, - onAllSeasonsClick = { season -> - onAllSeasonsClick?.invoke(state.show, season) - }, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - EpisodeRelatedView( - viewModel = koinViewModel( - parameters = { - parametersOf(state.show, state.episode) - }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onClick = onShowClick, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(EpisodeRelated) == true) { + item { + EpisodeRelatedView( + viewModel = koinViewModel( + parameters = { + parametersOf(state.show, state.episode) + }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onClick = onShowClick, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - if (isWatched) { + if (isWatched && state.visibility?.get(EpisodeHistory) == true) { item { EpisodeHistoryView( viewModel = koinViewModel( @@ -628,9 +653,6 @@ fun DetailsRating( .ifOrElse( condition = animated, isTrue = Modifier, - isFalse = Modifier.animateContentSize( - animationSpec = tween(200, delayMillis = 250), - ), ), ) { if (visible) { diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsState.kt index 2a9c3e8c9..0ed77b9f8 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsState.kt @@ -1,6 +1,7 @@ package tv.trakt.trakt.core.summary.episodes import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.core.translations.model.MediaTranslation import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.helpers.StringResource @@ -11,6 +12,7 @@ import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId import tv.trakt.trakt.common.model.User import tv.trakt.trakt.common.model.ratings.UserRating +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class EpisodeDetailsState( @@ -27,6 +29,7 @@ internal data class EpisodeDetailsState( val info: StringResource? = null, val error: Exception? = null, val user: User? = null, + val visibility: ImmutableMap? = null, ) { data class ProgressState( val plays: Int?, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsViewModel.kt index f6da1bd4e..3176bb642 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/EpisodeDetailsViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.async @@ -63,6 +65,9 @@ import tv.trakt.trakt.core.summary.shows.usecases.GetShowDetailsUseCase import tv.trakt.trakt.core.sync.usecases.UpdateEpisodeHistoryUseCase import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase import tv.trakt.trakt.core.user.usecases.ratings.LoadUserRatingsUseCase +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.EpisodeKeys import tv.trakt.trakt.resources.R import java.util.Locale @@ -85,6 +90,7 @@ internal class EpisodeDetailsViewModel( private val checkInManager: CheckInManager, private val checkInUpdates: CheckInUpdates, private val analytics: Analytics, + private val editScreenManager: EditScreenManager, ) : ViewModel() { private val initialState = EpisodeDetailsState() private val destination = savedStateHandle.toRoute() @@ -110,6 +116,7 @@ internal class EpisodeDetailsViewModel( private val infoState = MutableStateFlow(initialState.info) private val errorState = MutableStateFlow(initialState.error) private val userState = MutableStateFlow(initialState.user) + private val visibilityState = MutableStateFlow(initialState.visibility) private var ratingJob: Job? = null @@ -122,6 +129,7 @@ internal class EpisodeDetailsViewModel( observeData() observeCheckIn() + observeVisibility() analytics.logScreenView( screenName = "episode_details", @@ -158,6 +166,14 @@ internal class EpisodeDetailsViewModel( .launchIn(viewModelScope) } + private fun observeVisibility() { + editScreenManager.observe(EpisodeKeys) + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) + } + private fun loadUser() { viewModelScope.launch { try { @@ -649,6 +665,7 @@ internal class EpisodeDetailsViewModel( errorState, userState, navigateEpisode, + visibilityState, ) { state -> EpisodeDetailsState( show = state[0] as Show?, @@ -664,6 +681,7 @@ internal class EpisodeDetailsViewModel( error = state[10] as Exception?, user = state[11] as User?, navigateEpisode = state[12] as Pair?, + visibility = state[13] as ImmutableMap?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/di/EpisodeDetailsModule.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/di/EpisodeDetailsModule.kt index 439b1c7e8..f24048b3b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/di/EpisodeDetailsModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/di/EpisodeDetailsModule.kt @@ -143,6 +143,7 @@ internal val episodeDetailsModule = module { checkInManager = get(), checkInUpdates = get(), analytics = get(), + editScreenManager = get(), ) } @@ -160,7 +161,6 @@ internal val episodeDetailsModule = module { show = show, episode = episode, getActorsUseCase = get(), - collapsingManager = get(), ) } @@ -176,7 +176,6 @@ internal val episodeDetailsModule = module { reactionsUpdates = get(), sessionManager = get(), commentsUpdates = get(), - collapsingManager = get(), ) } @@ -200,7 +199,6 @@ internal val episodeDetailsModule = module { episodeDetailsUpdates = get(), sessionManager = get(), analytics = get(), - collapsingManager = get(), ) } @@ -209,7 +207,6 @@ internal val episodeDetailsModule = module { show = show, getRelatedShowsUseCase = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } @@ -219,7 +216,6 @@ internal val episodeDetailsModule = module { episode = episode, sessionManager = get(), getStreamingsUseCase = get(), - collapsingManager = get(), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsState.kt index f5c18640d..f02b50f23 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsState.kt @@ -10,5 +10,4 @@ internal data class EpisodeActorsState( val items: ImmutableList? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsView.kt index 8b01e4617..ee18491c0 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.actors import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -23,9 +20,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -69,7 +63,6 @@ internal fun EpisodeActorsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onPersonClick = onPersonClick, ) } @@ -80,56 +73,44 @@ private fun EpisodeActorsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onPersonClick: ((Person) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_actors), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onPersonClick = onPersonClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onPersonClick = onPersonClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsViewModel.kt index f4b195ec2..c7e9a6402 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/actors/EpisodeActorsViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.actors import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -21,23 +20,17 @@ import tv.trakt.trakt.common.model.CastPerson import tv.trakt.trakt.common.model.Episode import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.summary.episodes.features.actors.usecases.GetEpisodeActorsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class EpisodeActorsViewModel( private val show: Show, private val episode: Episode, private val getActorsUseCase: GetEpisodeActorsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = EpisodeActorsState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.EPISODE_ACTORS)) - - private var collapseJob: Job? = null init { loadData() @@ -66,29 +59,16 @@ internal class EpisodeActorsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.EPISODE_ACTORS) - else -> collapsingManager.expand(CollapsingKey.EPISODE_ACTORS) - } - } - } - @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( itemsState, loadingState, errorState, - collapseState, ) { state -> EpisodeActorsState( items = state[0] as ImmutableList?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsState.kt index a925b426a..9ccd0035d 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsState.kt @@ -22,5 +22,4 @@ internal data class EpisodeCommentsState( val loading: LoadingState = LoadingState.Idle, val user: User? = null, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsView.kt index c8f1bb4bf..6811b6e38 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.comments import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -34,7 +31,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier @@ -119,7 +115,6 @@ internal fun EpisodeCommentsView( onReactionClick = { reaction, comment -> viewModel.setReaction(reaction, comment.id) }, - onCollapse = viewModel::setCollapsed, onUserClick = onUserClick, ) @@ -164,15 +159,11 @@ private fun EpisodeCommentsContent( onAddCommentClick: (() -> Unit)? = null, onDeleteCommentClick: ((Comment) -> Unit)? = null, onMoreClick: (() -> Unit)? = null, - onCollapse: ((Boolean) -> Unit)? = null, onUserClick: ((User) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { Row( modifier = Modifier @@ -184,12 +175,6 @@ private fun EpisodeCommentsContent( TraktSectionHeader( title = stringResource(R.string.list_title_comments), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse?.invoke(!current) - }, extraIcon = when { state.user != null -> { { @@ -224,51 +209,49 @@ private fun EpisodeCommentsContent( ) } - if (state.collapsed != true) { - if (!state.items.isNullOrEmpty() || state.loading.isLoading || state.user != null) { - ContentFilters( - selectedFilter = state.filter, - headerPadding = headerPadding, - onFilterClick = onFilterClick, - ) - } else { - Spacer(modifier = Modifier.height(TraktTheme.spacing.mainRowHeaderSpace)) - } + if (!state.items.isNullOrEmpty() || state.loading.isLoading || state.user != null) { + ContentFilters( + selectedFilter = state.filter, + headerPadding = headerPadding, + onFilterClick = onFilterClick, + ) + } else { + Spacer(modifier = Modifier.height(TraktTheme.spacing.mainRowHeaderSpace)) + } - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listReactions = (state.reactions ?: emptyMap()).toImmutableMap(), - user = state.user, - userReactions = (state.userReactions ?: emptyMap()).toImmutableMap(), - contentPadding = contentPadding, - onCommentClick = onCommentClick, - onDeleteCommentClick = onDeleteCommentClick, - onCommentLoaded = onCommentLoaded, - onReactionClick = onReactionClick, - onUserClick = onUserClick, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listReactions = (state.reactions ?: emptyMap()).toImmutableMap(), + user = state.user, + userReactions = (state.userReactions ?: emptyMap()).toImmutableMap(), + contentPadding = contentPadding, + onCommentClick = onCommentClick, + onDeleteCommentClick = onDeleteCommentClick, + onCommentLoaded = onCommentLoaded, + onReactionClick = onReactionClick, + onUserClick = onUserClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsViewModel.kt index d8c1f32c7..847795b2a 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/comments/EpisodeCommentsViewModel.kt @@ -50,8 +50,6 @@ import tv.trakt.trakt.core.reactions.data.work.DeleteReactionWorker import tv.trakt.trakt.core.reactions.data.work.PostReactionWorker import tv.trakt.trakt.core.summary.episodes.features.comments.usecases.GetEpisodeCommentsUseCase import tv.trakt.trakt.core.user.usecases.reactions.LoadUserReactionsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import kotlin.time.Duration.Companion.seconds @OptIn(FlowPreview::class) @@ -66,7 +64,6 @@ internal class EpisodeCommentsViewModel( private val loadUserReactionsUseCase: LoadUserReactionsUseCase, private val reactionsUpdates: ReactionsUpdates, private val commentsUpdates: CommentsUpdates, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = EpisodeCommentsState() @@ -78,10 +75,8 @@ internal class EpisodeCommentsViewModel( private val loadingState = MutableStateFlow(initialState.loading) private val userState = MutableStateFlow(initialState.user) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.EPISODE_COMMENTS)) private var reactionJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -348,16 +343,6 @@ internal class EpisodeCommentsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.EPISODE_COMMENTS) - else -> collapsingManager.expand(CollapsingKey.EPISODE_COMMENTS) - } - } - } @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( @@ -369,7 +354,6 @@ internal class EpisodeCommentsViewModel( loadingState, userState, errorState, - collapseState, ) { state -> EpisodeCommentsState( media = state[0] as Pair?, @@ -380,7 +364,6 @@ internal class EpisodeCommentsViewModel( loading = state[5] as LoadingState, user = state[6] as User?, error = state[7] as Exception?, - collapsed = state[8] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextSheet.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextSheet.kt index f9b704c7f..b5af90c49 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextSheet.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextSheet.kt @@ -31,6 +31,7 @@ internal fun EpisodeDetailsContextSheet( onRemoveClick: (() -> Unit)? = null, onShareClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, onDismiss: () -> Unit, ) { val sheetScope = rememberCoroutineScope() @@ -83,6 +84,15 @@ internal fun EpisodeDetailsContextSheet( } } }, + onEditScreenClick = { + onEditScreenClick?.invoke() + sheetScope.launch { state.hide() } + .invokeOnCompletion { + if (!state.isVisible) { + onDismiss() + } + } + }, modifier = Modifier .padding(bottom = 24.dp) .padding(horizontal = 24.dp), diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextView.kt index d7c62559d..b68e39f00 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/context/more/EpisodeDetailsContextView.kt @@ -59,6 +59,7 @@ internal fun EpisodeDetailsContextView( onRemoveClick: (() -> Unit)? = null, onShareClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -70,6 +71,7 @@ internal fun EpisodeDetailsContextView( onRemoveClick = onRemoveClick, onShareClick = onShareClick, onCoverClick = onCoverClick, + onEditScreenClick = onEditScreenClick, modifier = modifier, ) } @@ -84,6 +86,7 @@ private fun EpisodeDetailsContextViewContent( onRemoveClick: (() -> Unit)? = null, onShareClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, ) { val context = LocalContext.current val isReleased = episode.rememberReleased() @@ -161,6 +164,7 @@ private fun EpisodeDetailsContextViewContent( onRemoveClick = onRemoveClick ?: {}, onShareClick = onShareClick ?: {}, onCoverClick = onCoverClick ?: {}, + onEditScreenClick = onEditScreenClick ?: {}, modifier = Modifier .padding(top = 14.dp), ) @@ -218,6 +222,7 @@ private fun ActionButtons( onShareClick: () -> Unit, onRemoveClick: () -> Unit, onCoverClick: () -> Unit, + onEditScreenClick: () -> Unit, ) { Column( verticalArrangement = spacedBy(TraktTheme.spacing.contextItemsSpace), @@ -292,6 +297,18 @@ private fun ActionButtons( }, onClick = onShareClick, ) + + GhostButton( + text = stringResource(R.string.text_edit_screen), + icon = painterResource(R.drawable.ic_edit_screen), + iconSize = 22.dp, + iconSpace = 15.dp, + modifier = Modifier + .graphicsLayer { + translationX = -5.dp.toPx() + }, + onClick = onEditScreenClick, + ) } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedState.kt index 82ac29045..baa35475a 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedState.kt @@ -12,5 +12,4 @@ internal data class EpisodeRelatedState( val collection: UserCollectionState = UserCollectionState.Default, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedView.kt index 771ca33d7..b8718f2ce 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.related import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -24,7 +21,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -74,7 +70,6 @@ internal fun EpisodeRelatedView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onClick = onClick, onLongClick = { contextSheet = it }, ) @@ -91,59 +86,47 @@ private fun EpisodeRelatedContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onClick: ((Show) -> Unit)? = null, onLongClick: ((Show) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_related_shows), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = state.items ?: EmptyImmutableList, + collection = state.collection, contentPadding = contentPadding, + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = state.items ?: EmptyImmutableList, - collection = state.collection, - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedViewModel.kt index 6905a27d6..63c5c5d8d 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/related/EpisodeRelatedViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.related import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -20,23 +19,17 @@ import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.summary.episodes.features.related.usecases.GetEpisodeRelatedUseCase import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class EpisodeRelatedViewModel( private val show: Show, private val getRelatedShowsUseCase: GetEpisodeRelatedUseCase, private val collectionStateProvider: CollectionStateProvider, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = EpisodeRelatedState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.EPISODE_RELATED)) - - private var collapseJob: Job? = null init { loadData() @@ -67,31 +60,18 @@ internal class EpisodeRelatedViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.EPISODE_RELATED) - else -> collapsingManager.expand(CollapsingKey.EPISODE_RELATED) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, collectionStateProvider.stateFlow, loadingState, errorState, - collapseState, ) { state -> EpisodeRelatedState( items = state[0] as ImmutableList?, collection = state[1] as UserCollectionState, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonState.kt index 3e031f265..f7bcf3020 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonState.kt @@ -18,5 +18,4 @@ internal data class EpisodeSeasonState( val loadingEpisode: LoadingState = LoadingState.Idle, val info: StringResource? = null, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonView.kt index 8a6ccd1f2..f1ed67971 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.season import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -21,7 +18,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -72,7 +68,6 @@ internal fun EpisodeSeasonView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onAllSeasonsClick = onAllSeasonsClick, onEpisodeClick = { onEpisodeClick(it.episode) }, onCheckClick = { @@ -145,19 +140,15 @@ private fun EpisodeSeasonContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onEpisodeClick: ((EpisodeItem) -> Unit)? = null, onCheckClick: ((EpisodeItem) -> Unit)? = null, onCheckLongClick: ((EpisodeItem) -> Unit)? = null, onRemoveClick: ((EpisodeItem) -> Unit)? = null, onAllSeasonsClick: ((Int?) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { val headerText = state.episode?.let { when { @@ -168,13 +159,7 @@ private fun EpisodeSeasonContent( TraktSectionHeader( title = headerText ?: stringResource(R.string.list_title_seasons), - chevron = state.collapsed != true && state.episodes.isNotEmpty() && !state.loading.isLoading, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, + chevron = state.episodes.isNotEmpty() && !state.loading.isLoading, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -183,37 +168,35 @@ private fun EpisodeSeasonContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(300), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(300), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.episodes.isEmpty()) { + ContentEmpty( contentPadding = contentPadding, ) - } - - Done -> { - if (state.episodes.isEmpty()) { - ContentEmpty( - contentPadding = contentPadding, - ) - } else { - EpisodeSeasonList( - show = state.show, - episodes = state.episodes, - currentEpisode = state.episode?.number, - contentPadding = contentPadding, - onEpisodeClick = onEpisodeClick ?: {}, - onCheckClick = onCheckClick ?: {}, - onCheckLongClick = onCheckLongClick ?: {}, - onRemoveClick = onRemoveClick ?: {}, - modifier = Modifier.fillMaxWidth(), - ) - } + } else { + EpisodeSeasonList( + show = state.show, + episodes = state.episodes, + currentEpisode = state.episode?.number, + contentPadding = contentPadding, + onEpisodeClick = onEpisodeClick ?: {}, + onCheckClick = onCheckClick ?: {}, + onCheckLongClick = onCheckLongClick ?: {}, + onRemoveClick = onRemoveClick ?: {}, + modifier = Modifier.fillMaxWidth(), + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonViewModel.kt index ef23cf072..0f395c0f3 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/season/EpisodeSeasonViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -44,8 +43,6 @@ import tv.trakt.trakt.core.summary.shows.features.seasons.model.EpisodeItem import tv.trakt.trakt.core.sync.model.ProgressItem import tv.trakt.trakt.core.sync.usecases.UpdateEpisodeHistoryUseCase import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import tv.trakt.trakt.resources.R @OptIn(FlowPreview::class) @@ -59,7 +56,6 @@ internal class EpisodeSeasonViewModel( private val episodeDetailsUpdates: EpisodeDetailsUpdates, private val sessionManager: SessionManager, private val analytics: Analytics, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = EpisodeSeasonState() @@ -70,9 +66,6 @@ internal class EpisodeSeasonViewModel( private val loadingEpisodeState = MutableStateFlow(initialState.loadingEpisode) private val infoState = MutableStateFlow(initialState.info) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.EPISODE_SEASON)) - - private var collapseJob: Job? = null init { loadData() @@ -297,17 +290,6 @@ internal class EpisodeSeasonViewModel( infoState.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.EPISODE_SEASON) - else -> collapsingManager.expand(CollapsingKey.EPISODE_SEASON) - } - } - } - @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( episodeState, @@ -317,7 +299,6 @@ internal class EpisodeSeasonViewModel( loadingEpisodeState, infoState, errorState, - collapseState, ) { state -> EpisodeSeasonState( episode = state[0] as Episode, @@ -327,7 +308,6 @@ internal class EpisodeSeasonViewModel( loadingEpisode = state[4] as LoadingState, info = state[5] as StringResource?, error = state[6] as Exception?, - collapsed = state[7] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsState.kt index f60009a22..887c1cf89 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsState.kt @@ -9,5 +9,4 @@ internal data class EpisodeStreamingsState( val items: StreamingsResult? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsView.kt index 9e976d706..13023e72c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.episodes.features.streaming import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -22,9 +19,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -71,7 +65,6 @@ internal fun EpisodeStreamingsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onClick = { service -> openExternalAppLink( packageId = StreamingServiceApp.findFromSource(service.source)?.packageId, @@ -89,15 +82,11 @@ private fun EpisodeStreamingsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: ((Boolean) -> Unit)? = null, onClick: ((StreamingService) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(12.dp), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { Column( verticalArrangement = spacedBy(3.dp), @@ -108,12 +97,6 @@ private fun EpisodeStreamingsContent( TraktSectionHeader( title = stringResource(R.string.page_title_where_to_watch), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse?.invoke(!current) - }, ) JustWatchRanksStrip( @@ -122,32 +105,30 @@ private fun EpisodeStreamingsContent( ) } - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.streamings?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items?.streamings ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onClick = onClick, ) } - - Done -> { - if (state.items?.streamings?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items?.streamings ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsViewModel.kt index 1a00c56f6..910008569 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/episodes/features/streaming/EpisodeStreamingsViewModel.kt @@ -2,7 +2,6 @@ package tv.trakt.trakt.core.summary.episodes.features.streaming import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -20,24 +19,18 @@ import tv.trakt.trakt.common.model.Episode import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.streamings.model.StreamingsResult import tv.trakt.trakt.core.summary.episodes.features.streaming.usecases.GetEpisodeStreamingsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class EpisodeStreamingsViewModel( private val show: Show, private val episode: Episode, private val sessionManager: SessionManager, private val getStreamingsUseCase: GetEpisodeStreamingsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = EpisodeStreamingsState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.EPISODE_WHERE_TO_WATCH)) - - private var collapseJob: Job? = null init { loadData() @@ -73,29 +66,16 @@ internal class EpisodeStreamingsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.EPISODE_WHERE_TO_WATCH) - else -> collapsingManager.expand(CollapsingKey.EPISODE_WHERE_TO_WATCH) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, loadingState, errorState, - collapseState, ) { state -> EpisodeStreamingsState( items = state[0] as StreamingsResult?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsScreen.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsScreen.kt index 457933fb7..55e4b5cfa 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsScreen.kt @@ -5,7 +5,6 @@ package tv.trakt.trakt.core.summary.movies import android.content.Context import android.content.Intent import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -94,6 +93,17 @@ import tv.trakt.trakt.core.summary.ui.DetailsActions import tv.trakt.trakt.core.summary.ui.DetailsBackground import tv.trakt.trakt.core.summary.ui.header.DetailsHeader import tv.trakt.trakt.helpers.SimpleScrollConnection +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.MovieKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieActors +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieExtras +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieHistory +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieLists +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieRelated +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieReviews +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieSentiment +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieTrivia +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.MovieWhereToWatch import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.components.confirmation.RemoveConfirmationSheet import tv.trakt.trakt.ui.components.dateselection.DateSelectionSheet @@ -135,6 +145,7 @@ internal fun MovieDetailsScreen( var dateSheet by remember { mutableStateOf(false) } var shareSheet by remember { mutableStateOf(false) } var coverImageSheet by remember { mutableStateOf(null) } + var sectionsSheet by remember { mutableStateOf(false) } DisposableEffect(Unit) { localRateVisibility.value = false @@ -304,6 +315,9 @@ internal fun MovieDetailsScreen( onCoverClick = { coverImageSheet = state.movie }, + onEditScreenClick = { + sectionsSheet = true + }, onDismiss = { contextSheet = null }, @@ -358,6 +372,12 @@ internal fun MovieDetailsScreen( onDismiss = { shareSheet = false }, ) + EditScreenSheet( + active = sectionsSheet, + enabledValues = MovieKeys, + onDismiss = { sectionsSheet = false }, + ) + LaunchedEffect(state.info) { if (state.info == null) { return@LaunchedEffect @@ -568,7 +588,7 @@ internal fun MovieDetailsContent( if (!previewMode) { val showStreamings = (state.user != null) && isReleased - if (showStreamings) { + if (showStreamings && state.visibility?.get(MovieWhereToWatch) == true) { item { MovieStreamingsView( viewModel = koinViewModel( @@ -583,7 +603,7 @@ internal fun MovieDetailsContent( } } - if (isReleased) { + if (isReleased && state.visibility?.get(MovieSentiment) == true) { item { MovieSentimentView( viewModel = koinViewModel( @@ -606,78 +626,88 @@ internal fun MovieDetailsContent( } } - item { - MovieCommentsView( - viewModel = koinViewModel( - parameters = { parametersOf(movie) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onMoreClick = onMoreCommentsClick, - onUserClick = onNavigateToUser, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(MovieReviews) == true) { + item { + MovieCommentsView( + viewModel = koinViewModel( + parameters = { parametersOf(movie) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onMoreClick = onMoreCommentsClick, + onUserClick = onNavigateToUser, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - MovieActorsView( - viewModel = koinViewModel( - parameters = { parametersOf(movie) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onPersonClick = onPersonClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(MovieActors) == true) { + item { + MovieActorsView( + viewModel = koinViewModel( + parameters = { parametersOf(movie) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onPersonClick = onPersonClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - MovieExtrasView( - viewModel = koinViewModel( - parameters = { parametersOf(movie) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onVideoClick = onExtraClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(MovieExtras) == true) { + item { + MovieExtrasView( + viewModel = koinViewModel( + parameters = { parametersOf(movie) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onVideoClick = onExtraClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - MovieRelatedView( - viewModel = koinViewModel( - parameters = { parametersOf(movie) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onClick = onMovieClick, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(MovieRelated) == true) { + item { + MovieRelatedView( + viewModel = koinViewModel( + parameters = { parametersOf(movie) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onClick = onMovieClick, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - MovieListsView( - viewModel = koinViewModel( - parameters = { parametersOf(movie) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onClick = onListClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(MovieLists) == true) { + item { + MovieListsView( + viewModel = koinViewModel( + parameters = { parametersOf(movie) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onClick = onListClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - if (isWatched) { + if (isWatched && state.visibility?.get(MovieHistory) == true) { item { MovieHistoryView( viewModel = koinViewModel( @@ -693,19 +723,21 @@ internal fun MovieDetailsContent( } } - item { - MovieTriviaView( - viewModel = koinViewModel( - parameters = { parametersOf(movie) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onVipClick = onVipClick ?: {}, - onTriviaClick = onTriviaClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(MovieTrivia) == true) { + item { + MovieTriviaView( + viewModel = koinViewModel( + parameters = { parametersOf(movie) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onVipClick = onVipClick ?: {}, + onTriviaClick = onTriviaClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } } } @@ -763,9 +795,6 @@ fun DetailsRating( .ifOrElse( condition = animated, isTrue = Modifier, - isFalse = Modifier.animateContentSize( - animationSpec = tween(200, delayMillis = 250), - ), ), ) { if (visible) { diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsState.kt index 3b0981d9d..c8b227a73 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsState.kt @@ -1,6 +1,7 @@ package tv.trakt.trakt.core.summary.movies import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.core.translations.model.MediaTranslation import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.helpers.StringResource @@ -9,6 +10,7 @@ import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Person import tv.trakt.trakt.common.model.User import tv.trakt.trakt.common.model.ratings.UserRating +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class MovieDetailsState( @@ -25,6 +27,7 @@ internal data class MovieDetailsState( val info: StringResource? = null, val error: Exception? = null, val user: User? = null, + val visibility: ImmutableMap? = null, ) { data class ProgressState( val plays: Int, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsViewModel.kt index f0f0d6524..75917b5d0 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/MovieDetailsViewModel.kt @@ -8,6 +8,8 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.async @@ -80,6 +82,9 @@ import tv.trakt.trakt.core.user.usecases.lists.LoadUserListsUseCase import tv.trakt.trakt.core.user.usecases.lists.LoadUserWatchlistUseCase import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase import tv.trakt.trakt.core.user.usecases.ratings.LoadUserRatingsUseCase +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.MovieKeys import tv.trakt.trakt.resources.R import java.util.Locale @@ -112,6 +117,7 @@ internal class MovieDetailsViewModel( private val checkInManager: CheckInManager, private val errorsManager: GlobalErrorsManager, private val analytics: Analytics, + private val editScreenManager: EditScreenManager, ) : ViewModel() { private val destination = savedStateHandle.toRoute() private val movieId = destination.movieId.toTraktId() @@ -131,6 +137,7 @@ internal class MovieDetailsViewModel( private val infoState = MutableStateFlow(initialState.info) private val errorState = MutableStateFlow(initialState.error) private val userState = MutableStateFlow(initialState.user) + private val visibilityState = MutableStateFlow(initialState.visibility) private var ratingJob: Job? = null @@ -142,12 +149,21 @@ internal class MovieDetailsViewModel( loadUserRatingData() observeCheckIn() + observeVisibility() analytics.logScreenView( screenName = "movie_details", ) } + private fun observeVisibility() { + editScreenManager.observe(MovieKeys) + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) + } + private fun observeCheckIn() { checkInUpdates.observeUpdates() .distinctUntilChanged() @@ -985,6 +1001,7 @@ internal class MovieDetailsViewModel( infoState, errorState, userState, + visibilityState, ) { state -> MovieDetailsState( movie = state[0] as Movie?, @@ -1000,6 +1017,7 @@ internal class MovieDetailsViewModel( info = state[10] as StringResource?, error = state[11] as Exception?, user = state[12] as User?, + visibility = state[13] as ImmutableMap?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/di/MovieDetailsModule.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/di/MovieDetailsModule.kt index d49dab3e9..af62f4afe 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/di/MovieDetailsModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/di/MovieDetailsModule.kt @@ -185,6 +185,7 @@ internal val movieDetailsModule = module { checkInManager = get(), errorsManager = get(), analytics = get(), + editScreenManager = get(), ) } @@ -202,7 +203,6 @@ internal val movieDetailsModule = module { movie = movie, sessionManager = get(), getStreamingsUseCase = get(), - collapsingManager = get(), ) } @@ -210,7 +210,6 @@ internal val movieDetailsModule = module { MovieExtrasViewModel( movie = movie, getExtrasUseCase = get(), - collapsingManager = get(), ) } @@ -218,7 +217,6 @@ internal val movieDetailsModule = module { MovieActorsViewModel( movie = movie, getActorsUseCase = get(), - collapsingManager = get(), ) } @@ -227,7 +225,6 @@ internal val movieDetailsModule = module { movie = movie, getRelatedMoviesUseCase = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } @@ -236,7 +233,6 @@ internal val movieDetailsModule = module { movie = movie, getListsUseCase = get(), loadUserLikedListsUseCase = get(), - collapsingManager = get(), ) } @@ -252,7 +248,6 @@ internal val movieDetailsModule = module { MovieSentimentViewModel( movie = movie, getSentimentUseCase = get(), - collapsingManager = get(), sessionManager = get(), ) } @@ -261,7 +256,6 @@ internal val movieDetailsModule = module { MovieTriviaViewModel( movie = movie, getTriviaUseCase = get(), - collapsingManager = get(), sessionManager = get(), ) } @@ -277,7 +271,6 @@ internal val movieDetailsModule = module { reactionsUpdates = get(), sessionManager = get(), commentsUpdates = get(), - collapsingManager = get(), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsState.kt index e27754796..0e7ad6f5b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsState.kt @@ -9,6 +9,5 @@ import tv.trakt.trakt.common.model.CastPerson internal data class MovieActorsState( val items: ImmutableList? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsView.kt index 072f5b9e9..078807f34 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.actors import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -23,9 +20,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -69,7 +63,6 @@ internal fun MovieActorsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onPersonClick = onPersonClick, ) } @@ -80,58 +73,44 @@ private fun MovieActorsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onPersonClick: ((Person) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_actors), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onPersonClick = onPersonClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onPersonClick = onPersonClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsViewModel.kt index 715ac5381..d750c9f64 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/actors/MovieActorsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -19,23 +18,17 @@ import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.CastPerson import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.core.summary.movies.features.actors.usecases.GetMovieActorsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class MovieActorsViewModel( private val movie: Movie, private val getActorsUseCase: GetMovieActorsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = MovieActorsState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_ACTORS)) private val errorState = MutableStateFlow(initialState.error) - private var collapseJob: kotlinx.coroutines.Job? = null - init { loadData() } @@ -59,30 +52,16 @@ internal class MovieActorsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_ACTORS) - else -> collapsingManager.expand(CollapsingKey.MOVIE_ACTORS) - } - } - } - @Suppress("UNCHECKED_CAST") - val state: StateFlow = combine( + val state = combine( itemsState, loadingState, - collapseState, errorState, ) { state -> MovieActorsState( items = state[0] as ImmutableList?, loading = state[1] as LoadingState, - collapsed = state[2] as Boolean, - error = state[3] as Exception?, + error = state[2] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsState.kt index ce868b133..245d5f819 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsState.kt @@ -21,5 +21,4 @@ internal data class MovieCommentsState( val loading: LoadingState = LoadingState.Idle, val user: User? = null, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsView.kt index c06c90f49..1797b2d0e 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.comments import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -34,7 +31,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier @@ -122,7 +118,6 @@ internal fun MovieCommentsView( onReactionClick = { reaction, comment -> viewModel.setReaction(reaction, comment.id) }, - onCollapse = viewModel::setCollapsed, onUserClick = onUserClick, ) @@ -167,15 +162,11 @@ private fun MovieCommentsContent( onAddCommentClick: (() -> Unit)? = null, onDeleteCommentClick: ((Comment) -> Unit)? = null, onMoreClick: (() -> Unit)? = null, - onCollapse: ((Boolean) -> Unit)? = null, onUserClick: ((User) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { Row( modifier = Modifier @@ -187,12 +178,6 @@ private fun MovieCommentsContent( TraktSectionHeader( title = stringResource(R.string.list_title_comments), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse?.invoke(!current) - }, extraIcon = when { state.user != null -> { { @@ -227,51 +212,49 @@ private fun MovieCommentsContent( ) } - if (state.collapsed != true) { - if (!state.items.isNullOrEmpty() || state.loading.isLoading || state.user != null) { - ContentFilters( - selectedFilter = state.filter, - headerPadding = headerPadding, - onFilterClick = onFilterClick, - ) - } else { - Spacer(modifier = Modifier.height(TraktTheme.spacing.mainRowHeaderSpace)) - } + if (!state.items.isNullOrEmpty() || state.loading.isLoading || state.user != null) { + ContentFilters( + selectedFilter = state.filter, + headerPadding = headerPadding, + onFilterClick = onFilterClick, + ) + } else { + Spacer(modifier = Modifier.height(TraktTheme.spacing.mainRowHeaderSpace)) + } - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listReactions = (state.reactions ?: emptyMap()).toImmutableMap(), - user = state.user, - userReactions = (state.userReactions ?: emptyMap()).toImmutableMap(), - contentPadding = contentPadding, - onCommentClick = onCommentClick, - onDeleteCommentClick = onDeleteCommentClick, - onCommentLoaded = onCommentLoaded, - onReactionClick = onReactionClick, - onUserClick = onUserClick, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listReactions = (state.reactions ?: emptyMap()).toImmutableMap(), + user = state.user, + userReactions = (state.userReactions ?: emptyMap()).toImmutableMap(), + contentPadding = contentPadding, + onCommentClick = onCommentClick, + onDeleteCommentClick = onDeleteCommentClick, + onCommentLoaded = onCommentLoaded, + onReactionClick = onReactionClick, + onUserClick = onUserClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsViewModel.kt index d08a71465..fe3d9afd6 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/comments/MovieCommentsViewModel.kt @@ -49,8 +49,6 @@ import tv.trakt.trakt.core.reactions.data.work.DeleteReactionWorker import tv.trakt.trakt.core.reactions.data.work.PostReactionWorker import tv.trakt.trakt.core.summary.movies.features.comments.usecases.GetMovieCommentsUseCase import tv.trakt.trakt.core.user.usecases.reactions.LoadUserReactionsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import kotlin.time.Duration.Companion.seconds @OptIn(FlowPreview::class) @@ -64,7 +62,6 @@ internal class MovieCommentsViewModel( private val loadUserReactionsUseCase: LoadUserReactionsUseCase, private val reactionsUpdates: ReactionsUpdates, private val commentsUpdates: CommentsUpdates, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = MovieCommentsState() @@ -76,10 +73,8 @@ internal class MovieCommentsViewModel( private val loadingState = MutableStateFlow(initialState.loading) private val userState = MutableStateFlow(initialState.user) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_COMMENTS)) private var reactionJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -346,16 +341,6 @@ internal class MovieCommentsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_COMMENTS) - else -> collapsingManager.expand(CollapsingKey.MOVIE_COMMENTS) - } - } - } @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( @@ -367,7 +352,6 @@ internal class MovieCommentsViewModel( loadingState, userState, errorState, - collapseState, ) { state -> MovieCommentsState( movie = state[0] as Movie?, @@ -378,7 +362,6 @@ internal class MovieCommentsViewModel( loading = state[5] as LoadingState, user = state[6] as User?, error = state[7] as Exception?, - collapsed = state[8] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextSheet.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextSheet.kt index 99079768f..b6ae50b28 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextSheet.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextSheet.kt @@ -30,6 +30,7 @@ internal fun MovieDetailsContextSheet( onRemoveClick: (() -> Unit)? = null, onListsClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, onDismiss: () -> Unit, ) { val sheetScope = rememberCoroutineScope() @@ -92,6 +93,15 @@ internal fun MovieDetailsContextSheet( } } }, + onEditScreenClick = { + onEditScreenClick?.invoke() + sheetScope.launch { state.hide() } + .invokeOnCompletion { + if (!state.isVisible) { + onDismiss() + } + } + }, modifier = Modifier .padding(bottom = 24.dp) .padding(horizontal = 24.dp), diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextView.kt index 86d9e3413..ac674c225 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/context/more/MovieDetailsContextView.kt @@ -62,6 +62,7 @@ internal fun MovieDetailsContextView( onCheckClick: (() -> Unit)? = null, onListsClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -75,6 +76,7 @@ internal fun MovieDetailsContextView( onRemoveClick = onRemoveClick, onListsClick = onListsClick, onCoverClick = onCoverClick, + onEditScreenClick = onEditScreenClick, modifier = modifier, ) } @@ -91,6 +93,7 @@ private fun MovieDetailsContextViewContent( onShareClick: (() -> Unit)? = null, onListsClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, ) { val context = LocalContext.current @@ -174,6 +177,7 @@ private fun MovieDetailsContextViewContent( onShareClick = onShareClick ?: {}, onListsClick = onListsClick ?: {}, onCoverClick = onCoverClick ?: {}, + onEditScreenClick = onEditScreenClick ?: {}, modifier = Modifier .padding(top = 14.dp), ) @@ -233,6 +237,7 @@ private fun ActionButtons( onRemoveClick: () -> Unit, onListsClick: () -> Unit, onCoverClick: () -> Unit, + onEditScreenClick: () -> Unit, ) { Column( verticalArrangement = spacedBy(TraktTheme.spacing.contextItemsSpace), @@ -310,17 +315,17 @@ private fun ActionButtons( } } -// GhostButton( -// text = stringResource(R.string.button_text_share), -// icon = painterResource(R.drawable.ic_share), -// iconSize = 22.dp, -// iconSpace = 15.dp, -// modifier = Modifier -// .graphicsLayer { -// translationX = -5.dp.toPx() -// }, -// onClick = onShareClick, -// ) + GhostButton( + text = stringResource(R.string.text_edit_screen), + icon = painterResource(R.drawable.ic_edit_screen), + iconSize = 22.dp, + iconSpace = 15.dp, + modifier = Modifier + .graphicsLayer { + translationX = -5.dp.toPx() + }, + onClick = onEditScreenClick, + ) } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasState.kt index b74436c91..f9f7a50d2 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasState.kt @@ -11,7 +11,6 @@ internal data class MovieExtrasState( val items: ImmutableList? = null, val filters: FiltersState = FiltersState(), val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) { data class FiltersState( diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasView.kt index 242b5a596..2f71d2edc 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.extras import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -25,10 +22,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -72,7 +66,6 @@ internal fun MovieExtrasView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onVideoClick = onVideoClick, onFilterClick = { viewModel.toggleFilter(it) }, ) @@ -84,68 +77,54 @@ private fun MovieExtrasContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onVideoClick: ((ExtraVideo) -> Unit)? = null, onFilterClick: ((String) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_extras), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.filters.filters.size > 1) { - ContentFilters( - state = state.filters, - onFilterClick = onFilterClick ?: {}, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.filters.filters.size > 1) { + ContentFilters( + state = state.filters, + onFilterClick = onFilterClick ?: {}, + ) + } - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onVideoClick, - ) - } + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onClick = onVideoClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasViewModel.kt index 848a67520..cae2b5f09 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/extras/MovieExtrasViewModel.kt @@ -20,13 +20,10 @@ import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.ExtraVideo import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.core.summary.movies.features.extras.usecases.GetMovieExtrasUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class MovieExtrasViewModel( private val movie: Movie, private val getExtrasUseCase: GetMovieExtrasUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = MovieExtrasState() @@ -34,11 +31,8 @@ internal class MovieExtrasViewModel( private val filterItemsState = MutableStateFlow(initialState.items) private val filtersState = MutableStateFlow(initialState.filters) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_EXTRAS)) private val errorState = MutableStateFlow(initialState.error) - private var collapseJob: kotlinx.coroutines.Job? = null - init { loadData() } @@ -104,32 +98,18 @@ internal class MovieExtrasViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_EXTRAS) - else -> collapsingManager.expand(CollapsingKey.MOVIE_EXTRAS) - } - } - } - @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( filterItemsState, filtersState, loadingState, - collapseState, errorState, ) { state -> MovieExtrasState( items = state[0] as ImmutableList?, filters = state[1] as MovieExtrasState.FiltersState, loading = state[2] as LoadingState, - collapsed = state[3] as Boolean, - error = state[4] as Exception?, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsState.kt index aabd636c3..393e61f37 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsState.kt @@ -12,6 +12,5 @@ internal data class MovieListsState( val items: ImmutableList? = null, val likedItems: ImmutableSet? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsView.kt index a72bb5dac..2fa62e3ac 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.lists import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -26,10 +23,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -73,7 +67,6 @@ internal fun MovieListsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onClick = onClick, ) } @@ -84,61 +77,47 @@ private fun MovieListsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onClick: ((CustomList) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_popular_lists), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listLikedItems = state.likedItems ?: EmptyImmutableSet, - contentPadding = contentPadding, - onClick = onClick, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listLikedItems = state.likedItems ?: EmptyImmutableSet, + contentPadding = contentPadding, + onClick = onClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsViewModel.kt index a3aa1665e..191666d7d 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/lists/MovieListsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -25,25 +24,19 @@ import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.TraktId import tv.trakt.trakt.core.summary.movies.features.lists.usecases.GetMovieListsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class MovieListsViewModel( private val movie: Movie, private val getListsUseCase: GetMovieListsUseCase, private val loadUserLikedListsUseCase: LoadUserLikedListsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = MovieListsState() private val itemsState = MutableStateFlow(initialState.items) private val likedItemsState = MutableStateFlow(initialState.likedItems) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_LISTS)) private val errorState = MutableStateFlow(initialState.error) - private var collapseJob: Job? = null - init { loadData() loadUserLikedLists() @@ -90,32 +83,18 @@ internal class MovieListsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_LISTS) - else -> collapsingManager.expand(CollapsingKey.MOVIE_LISTS) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, likedItemsState, loadingState, - collapseState, errorState, ) { state -> MovieListsState( items = state[0] as ImmutableList?, likedItems = state[1] as ImmutableSet?, loading = state[2] as LoadingState, - collapsed = state[3] as Boolean, - error = state[4] as Exception?, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedState.kt index beba86e5e..8b8d96b77 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedState.kt @@ -11,6 +11,5 @@ internal data class MovieRelatedState( val items: ImmutableList? = null, val collection: UserCollectionState = UserCollectionState.Default, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedView.kt index a07206686..52d8438e5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.related import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -24,7 +21,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -74,7 +70,6 @@ internal fun MovieRelatedView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onClick = onClick, onLongClick = { contextSheet = it }, ) @@ -91,62 +86,48 @@ private fun MovieRelatedContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onClick: ((Movie) -> Unit)? = null, onLongClick: ((Movie) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_related_movies), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 4.dp), + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = state.items ?: EmptyImmutableList, + collection = state.collection, contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 4.dp), + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = state.items ?: EmptyImmutableList, - collection = state.collection, - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedViewModel.kt index c821822a9..433fe631b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/related/MovieRelatedViewModel.kt @@ -19,20 +19,16 @@ import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.core.summary.movies.features.related.usecases.GetMovieRelatedUseCase import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class MovieRelatedViewModel( private val movie: Movie, private val getRelatedMoviesUseCase: GetMovieRelatedUseCase, private val collectionStateProvider: CollectionStateProvider, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = MovieRelatedState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_RELATED)) private val errorState = MutableStateFlow(initialState.error) private var collapseJob: kotlinx.coroutines.Job? = null @@ -66,32 +62,19 @@ internal class MovieRelatedViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_RELATED) - else -> collapsingManager.expand(CollapsingKey.MOVIE_RELATED) - } - } - } @Suppress("UNCHECKED_CAST") val state = combine( itemsState, collectionStateProvider.stateFlow, loadingState, - collapseState, errorState, ) { state -> MovieRelatedState( items = state[0] as ImmutableList?, collection = state[1] as UserCollectionState, loading = state[2] as LoadingState, - collapsed = state[3] as Boolean, - error = state[4] as Exception?, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentState.kt index 1b4e8ce0c..4a3ae9a88 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentState.kt @@ -10,6 +10,5 @@ internal data class MovieSentimentState( val user: User? = null, val sentiment: Sentiments? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentView.kt index 514a75064..524e88daf 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.sentiment import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -20,7 +17,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -62,7 +58,6 @@ internal fun MovieSentimentView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onNotAvailable = { visible = false }, onSentimentClick = onSentimentClick, ) @@ -76,28 +71,17 @@ private fun MovieSentimentContent( headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), onSentimentClick: (Sentiments) -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, onNotAvailable: (() -> Unit)? = null, ) { val windowClass = currentWindowAdaptiveInfo().windowSizeClass - var animateCollapse by rememberSaveable { mutableStateOf(false) } Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.header_community_sentiment), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -106,34 +90,32 @@ private fun MovieSentimentContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - val maxWidth = if (windowClass.isAtLeastLarge()) 0.45f else 1f - when (loading) { - Idle, Loading -> { - DetailsSentimentSkeleton( + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + val maxWidth = if (windowClass.isAtLeastLarge()) 0.45f else 1f + when (loading) { + Idle, Loading -> { + DetailsSentimentSkeleton( + modifier = Modifier + .fillMaxWidth(maxWidth) + .padding(contentPadding), + ) + } + + Done -> { + if (state.sentiment != null) { + DetailsSentiment( + vip = state.user?.isAnyVip == true, + sentiments = state.sentiment, modifier = Modifier .fillMaxWidth(maxWidth) - .padding(contentPadding), + .padding(contentPadding) + .onClick { onSentimentClick(state.sentiment) }, ) - } - - Done -> { - if (state.sentiment != null) { - DetailsSentiment( - vip = state.user?.isAnyVip == true, - sentiments = state.sentiment, - modifier = Modifier - .fillMaxWidth(maxWidth) - .padding(contentPadding) - .onClick { onSentimentClick(state.sentiment) }, - ) - } else { - onNotAvailable?.invoke() - } + } else { + onNotAvailable?.invoke() } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentViewModel.kt index c4da2ac18..962562f78 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/sentiment/MovieSentimentViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.sentiment import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -26,13 +25,10 @@ import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.Sentiments import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.summary.movies.features.sentiment.usecases.GetMovieSentimentUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class MovieSentimentViewModel( private val movie: Movie, private val getSentimentUseCase: GetMovieSentimentUseCase, - private val collapsingManager: CollapsingManager, private val sessionManager: SessionManager, ) : ViewModel() { private val initialState = MovieSentimentState() @@ -40,11 +36,8 @@ internal class MovieSentimentViewModel( private val userState = MutableStateFlow(initialState.user) private val sentimentState = MutableStateFlow(initialState.sentiment) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_SENTIMENT)) private val errorState = MutableStateFlow(initialState.error) - private var collapseJob: Job? = null - init { loadData() observeUser() @@ -98,32 +91,18 @@ internal class MovieSentimentViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_SENTIMENT) - else -> collapsingManager.expand(CollapsingKey.MOVIE_SENTIMENT) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( userState, sentimentState, loadingState, - collapseState, errorState, ) { state -> MovieSentimentState( user = state[0] as User?, sentiment = state[1] as Sentiments?, loading = state[2] as LoadingState, - collapsed = state[3] as Boolean, - error = state[4] as Exception?, + error = state[3] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsState.kt index 84d6d548d..5f31bf845 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsState.kt @@ -8,6 +8,5 @@ import tv.trakt.trakt.core.streamings.model.StreamingsResult internal data class MovieStreamingsState( val items: StreamingsResult? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsView.kt index 226d2b55f..24c8d5715 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.streaming import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -22,9 +19,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -73,7 +67,6 @@ internal fun MovieStreamingsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onClick = { service -> openExternalAppLink( packageId = StreamingServiceApp.findFromSource(service.source)?.packageId, @@ -91,17 +84,11 @@ private fun MovieStreamingsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onClick: ((StreamingService) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(12.dp), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { Column( verticalArrangement = spacedBy(3.dp), @@ -112,12 +99,6 @@ private fun MovieStreamingsContent( TraktSectionHeader( title = stringResource(R.string.page_title_where_to_watch), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, ) JustWatchRanksStrip( @@ -126,32 +107,30 @@ private fun MovieStreamingsContent( ) } - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.streamings?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items?.streamings ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onClick = onClick, ) } - - Done -> { - if (state.items?.streamings?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items?.streamings ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsViewModel.kt index aea9cf171..15ede92cd 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/streaming/MovieStreamingsViewModel.kt @@ -18,20 +18,16 @@ import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.core.streamings.model.StreamingsResult import tv.trakt.trakt.core.summary.movies.features.streaming.usecases.GetMovieStreamingsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class MovieStreamingsViewModel( private val movie: Movie, private val sessionManager: SessionManager, private val getStreamingsUseCase: GetMovieStreamingsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = MovieStreamingsState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.MOVIE_WHERE_TO_WATCH)) private val errorState = MutableStateFlow(initialState.error) private var collapseJob: kotlinx.coroutines.Job? = null @@ -69,30 +65,17 @@ internal class MovieStreamingsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.MOVIE_WHERE_TO_WATCH) - else -> collapsingManager.expand(CollapsingKey.MOVIE_WHERE_TO_WATCH) - } - } - } @Suppress("UNCHECKED_CAST") val state = combine( itemsState, loadingState, - collapseState, errorState, ) { state -> MovieStreamingsState( items = state[0] as StreamingsResult?, loading = state[1] as LoadingState, - collapsed = state[2] as Boolean, - error = state[3] as Exception?, + error = state[2] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaState.kt index a63626c5b..821036485 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaState.kt @@ -11,7 +11,6 @@ internal data class MovieTriviaState( val user: User? = null, val summary: ImmutableList? = null, val items: ImmutableList? = null, - val collapsed: Boolean? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaView.kt index 28973103c..fc1053f0b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.movies.features.trivia import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -19,7 +16,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -60,7 +56,6 @@ internal fun MovieTriviaView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onNotAvailable = { visible = false }, onVipClick = onVipClick, onTriviaClick = onTriviaClick, @@ -74,29 +69,17 @@ private fun MovieTriviaContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onVipClick: () -> Unit = {}, onTriviaClick: () -> Unit = {}, onNotAvailable: (() -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_trivia), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -107,35 +90,33 @@ private fun MovieTriviaContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - DetailsTriviaSkeleton( - modifier = Modifier.padding(contentPadding), + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + DetailsTriviaSkeleton( + modifier = Modifier.padding(contentPadding), + ) + } + Done -> { + if (state.summary.isNullOrEmpty()) { + onNotAvailable?.invoke() + } else { + DetailsTrivia( + items = state.summary, + vip = state.user?.isAnyVip == true, + onVipClick = onVipClick, + modifier = Modifier + .padding(contentPadding) + .onClick { + if (state.user?.isAnyVip == true) { + onTriviaClick() + } + }, ) } - Done -> { - if (state.summary.isNullOrEmpty()) { - onNotAvailable?.invoke() - } else { - DetailsTrivia( - items = state.summary, - vip = state.user?.isAnyVip == true, - onVipClick = onVipClick, - modifier = Modifier - .padding(contentPadding) - .onClick { - if (state.user?.isAnyVip == true) { - onTriviaClick() - } - }, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaViewModel.kt index 27fa027d2..6a07ed3d5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/movies/features/trivia/MovieTriviaViewModel.kt @@ -28,15 +28,10 @@ import tv.trakt.trakt.common.model.Movie import tv.trakt.trakt.common.model.User import tv.trakt.trakt.common.model.trivia.TriviaFact import tv.trakt.trakt.core.summary.movies.features.trivia.usecases.GetMovieTriviaUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey - -private val collapsingKey = CollapsingKey.MOVIE_TRIVIA internal class MovieTriviaViewModel( private val movie: Movie, private val getTriviaUseCase: GetMovieTriviaUseCase, - private val collapsingManager: CollapsingManager, private val sessionManager: SessionManager, ) : ViewModel() { private val initialState = MovieTriviaState() @@ -45,11 +40,8 @@ internal class MovieTriviaViewModel( private val summaryState = MutableStateFlow(initialState.summary) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(collapsingKey)) private val errorState = MutableStateFlow(initialState.error) - private var collapseJob: kotlinx.coroutines.Job? = null - init { loadData() observeUser() @@ -107,24 +99,11 @@ internal class MovieTriviaViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(collapsingKey) - else -> collapsingManager.expand(collapsingKey) - } - } - } - val state = combine( userState, itemsState, summaryState, loadingState, - collapseState, errorState, ) { state -> @Suppress("UNCHECKED_CAST") @@ -133,8 +112,7 @@ internal class MovieTriviaViewModel( items = state[1] as ImmutableList?, summary = state[2] as ImmutableList?, loading = state[3] as LoadingState, - collapsed = state[4] as Boolean, - error = state[5] as Exception?, + error = state[4] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsScreen.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsScreen.kt index 17cda0a06..477db077d 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsScreen.kt @@ -5,7 +5,6 @@ package tv.trakt.trakt.core.summary.shows import android.content.Context import android.content.Intent import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -101,6 +100,18 @@ import tv.trakt.trakt.core.summary.ui.DetailsActions import tv.trakt.trakt.core.summary.ui.DetailsBackground import tv.trakt.trakt.core.summary.ui.header.DetailsHeader import tv.trakt.trakt.helpers.SimpleScrollConnection +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.ShowKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowActors +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowExtras +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowHistory +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowLists +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowRelated +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowReviews +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowSeasons +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowSentiment +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowTrivia +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.ShowWhereToWatch import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.components.confirmation.ConfirmationSheet import tv.trakt.trakt.ui.components.confirmation.RemoveConfirmationSheet @@ -146,6 +157,7 @@ internal fun ShowDetailsScreen( var dateSheet by remember { mutableStateOf(false) } var shareSheet by remember { mutableStateOf(false) } var coverImageSheet by remember { mutableStateOf(null) } + var sectionsSheet by remember { mutableStateOf(false) } DisposableEffect(Unit) { localRateVisibility.value = false @@ -297,11 +309,22 @@ internal fun ShowDetailsScreen( onCoverClick = { coverImageSheet = state.show }, + onEditScreenClick = { + sectionsSheet = true + }, onDismiss = { contextSheet = null }, ) + EditScreenSheet( + active = sectionsSheet, + enabledValues = ShowKeys, + onDismiss = { + sectionsSheet = false + }, + ) + ShowDetailsHistorySheet( sheetItem = historySheet, onRemovePlay = { @@ -621,7 +644,7 @@ internal fun ShowDetailsContent( if (!previewMode) { val showStreamings = (state.user != null) && isReleased - if (showStreamings) { + if (showStreamings && state.visibility?.get(ShowWhereToWatch) == true) { item { ShowStreamingsView( viewModel = koinViewModel( @@ -636,7 +659,7 @@ internal fun ShowDetailsContent( } } - if (isReleased) { + if (isReleased && state.visibility?.get(ShowSentiment) == true) { item { ShowSentimentView( viewModel = koinViewModel( @@ -659,94 +682,106 @@ internal fun ShowDetailsContent( } } - item { - ShowCommentsView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onMoreClick = onMoreCommentsClick, - onUserClick = onNavigateToUser, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowReviews) == true) { + item { + ShowCommentsView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onMoreClick = onMoreCommentsClick, + onUserClick = onNavigateToUser, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - ShowActorsView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onPersonClick = onPersonClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowActors) == true) { + item { + ShowActorsView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onPersonClick = onPersonClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - ShowSeasonsView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - user = state.user, - onEpisodeClick = onEpisodeClick ?: {}, - onAllSeasonsClick = { onAllSeasonsClick?.invoke(show, it) }, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowSeasons) == true) { + item { + ShowSeasonsView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + user = state.user, + onEpisodeClick = onEpisodeClick ?: {}, + onAllSeasonsClick = { onAllSeasonsClick?.invoke(show, it) }, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - ShowExtrasView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onVideoClick = onExtraClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowExtras) == true) { + item { + ShowExtrasView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onVideoClick = onExtraClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - ShowRelatedView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onClick = onShowClick, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowRelated) == true) { + item { + ShowRelatedView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onClick = onShowClick, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - item { - ShowListsView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onClick = onListClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowLists) == true) { + item { + ShowListsView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onClick = onListClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } - if (isWatched) { + if (isWatched && state.visibility?.get(ShowHistory) == true) { item { ShowHistoryView( viewModel = koinViewModel( @@ -763,19 +798,21 @@ internal fun ShowDetailsContent( } } - item { - ShowTriviaView( - viewModel = koinViewModel( - parameters = { parametersOf(show) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onVipClick = onVipClick ?: {}, - onTriviaClick = onTriviaClick ?: {}, - modifier = Modifier - .alpha(ratingAlphaMask) - .padding(top = 32.dp), - ) + if (state.visibility?.get(ShowTrivia) == true) { + item { + ShowTriviaView( + viewModel = koinViewModel( + parameters = { parametersOf(show) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onVipClick = onVipClick ?: {}, + onTriviaClick = onTriviaClick ?: {}, + modifier = Modifier + .alpha(ratingAlphaMask) + .padding(top = 32.dp), + ) + } } } } @@ -803,9 +840,6 @@ fun DetailsRating( .ifOrElse( condition = animated, isTrue = Modifier, - isFalse = Modifier.animateContentSize( - animationSpec = tween(200, delayMillis = 250), - ), ), ) { if (visible) { diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsState.kt index 2da2863a3..da944fe10 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsState.kt @@ -1,6 +1,7 @@ package tv.trakt.trakt.core.summary.shows import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.core.translations.model.MediaTranslation import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.helpers.StringResource @@ -11,6 +12,7 @@ import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId import tv.trakt.trakt.common.model.User import tv.trakt.trakt.common.model.ratings.UserRating +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class ShowDetailsState( @@ -28,6 +30,7 @@ internal data class ShowDetailsState( val info: StringResource? = null, val error: Exception? = null, val user: User? = null, + val visibility: ImmutableMap? = null, ) { data class ProgressState( val aired: Int, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsViewModel.kt index db70a70d1..4bec8fe29 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/ShowDetailsViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job import kotlinx.coroutines.async @@ -78,6 +80,9 @@ import tv.trakt.trakt.core.user.usecases.lists.LoadUserListsUseCase import tv.trakt.trakt.core.user.usecases.lists.LoadUserWatchlistUseCase import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase import tv.trakt.trakt.core.user.usecases.ratings.LoadUserRatingsUseCase +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.ShowKeys import tv.trakt.trakt.resources.R import java.util.Locale @@ -110,6 +115,7 @@ internal class ShowDetailsViewModel( private val watchlistUpdates: WatchlistUpdates, private val sessionManager: SessionManager, private val errorsManager: GlobalErrorsManager, + private val editScreenManager: EditScreenManager, private val analytics: Analytics, ) : ViewModel() { private val destination = savedStateHandle.toRoute() @@ -131,6 +137,7 @@ internal class ShowDetailsViewModel( private val infoState = MutableStateFlow(initialState.info) private val errorState = MutableStateFlow(initialState.error) private val userState = MutableStateFlow(initialState.user) + private val visibilityState = MutableStateFlow(initialState.visibility) private var ratingJob: Job? = null @@ -140,6 +147,7 @@ internal class ShowDetailsViewModel( loadProgressData() loadUserRatingData() observeData() + observeVisibility() analytics.logScreenView( screenName = "show_details", @@ -158,6 +166,15 @@ internal class ShowDetailsViewModel( .launchIn(viewModelScope) } + private fun observeVisibility() { + editScreenManager.observe(ShowKeys) + .distinctUntilChanged() + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) + } + private fun loadUser() { viewModelScope.launch { try { @@ -963,6 +980,7 @@ internal class ShowDetailsViewModel( infoState, errorState, userState, + visibilityState, ) { state -> ShowDetailsState( show = state[0] as Show?, @@ -979,6 +997,7 @@ internal class ShowDetailsViewModel( info = state[11] as StringResource?, error = state[12] as Exception?, user = state[13] as User?, + visibility = state[14] as ImmutableMap?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/di/ShowDetailsModule.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/di/ShowDetailsModule.kt index bc690a7e2..00b4b97a6 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/di/ShowDetailsModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/di/ShowDetailsModule.kt @@ -40,7 +40,6 @@ import tv.trakt.trakt.core.summary.shows.features.trivia.usecases.GetShowTriviaU import tv.trakt.trakt.core.summary.shows.usecases.GetShowDetailsUseCase import tv.trakt.trakt.core.summary.shows.usecases.GetShowRatingsUseCase import tv.trakt.trakt.core.summary.shows.usecases.GetShowStreamingUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager internal val showDetailsDataModule = module { single { @@ -195,6 +194,7 @@ internal val showDetailsModule = module { watchlistUpdates = get(), sessionManager = get(), errorsManager = get(), + editScreenManager = get(), analytics = get(), ) } @@ -213,7 +213,6 @@ internal val showDetailsModule = module { show = show, sessionManager = get(), getStreamingsUseCase = get(), - collapsingManager = get(), ) } @@ -221,7 +220,6 @@ internal val showDetailsModule = module { ShowExtrasViewModel( show = show, getExtrasUseCase = get(), - collapsingManager = get(), ) } @@ -235,7 +233,6 @@ internal val showDetailsModule = module { episodeDetailsUpdates = get(), sessionManager = get(), analytics = get(), - collapsingManager = get(), ) } @@ -258,7 +255,6 @@ internal val showDetailsModule = module { ShowActorsViewModel( show = show, getActorsUseCase = get(), - collapsingManager = get(), ) } @@ -267,7 +263,6 @@ internal val showDetailsModule = module { show = show, getRelatedShowsUseCase = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } @@ -276,7 +271,6 @@ internal val showDetailsModule = module { show = show, getListsUseCase = get(), loadUserLikedListsUseCase = get(), - collapsingManager = get(), ) } @@ -284,7 +278,6 @@ internal val showDetailsModule = module { ShowSentimentViewModel( show = show, getSentimentUseCase = get(), - collapsingManager = get(), sessionManager = get(), ) } @@ -300,7 +293,6 @@ internal val showDetailsModule = module { reactionsUpdates = get(), sessionManager = get(), commentsUpdates = get(), - collapsingManager = get(), ) } @@ -326,7 +318,6 @@ internal val showDetailsModule = module { getHistoryUseCase = get(), showDetailsUpdates = get(), episodeDetailsUpdates = get(), - collapsingManager = get(), ) } @@ -334,7 +325,6 @@ internal val showDetailsModule = module { ShowTriviaViewModel( show = show, getTriviaUseCase = get(), - collapsingManager = get(), sessionManager = get(), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsState.kt index 4c18a3dad..c286aac31 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsState.kt @@ -10,5 +10,4 @@ internal data class ShowActorsState( val items: ImmutableList? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsView.kt index 7027800eb..7160e58c3 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.actors import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -23,9 +20,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -70,7 +64,6 @@ internal fun ShowActorsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onPersonClick = onPersonClick, ) } @@ -81,58 +74,44 @@ private fun ShowActorsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onPersonClick: ((Person) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_actors), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onPersonClick = onPersonClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onPersonClick = onPersonClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsViewModel.kt index c072e135d..9f6fd40e2 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/actors/ShowActorsViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.actors import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -20,22 +19,16 @@ import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.CastPerson import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.summary.shows.features.actors.usecases.GetShowActorsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ShowActorsViewModel( private val show: Show, private val getActorsUseCase: GetShowActorsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowActorsState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_ACTORS)) - - private var collapseJob: Job? = null init { loadData() @@ -60,29 +53,16 @@ internal class ShowActorsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_ACTORS) - else -> collapsingManager.expand(CollapsingKey.SHOW_ACTORS) - } - } - } - @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( itemsState, loadingState, errorState, - collapseState, ) { state -> ShowActorsState( items = state[0] as ImmutableList?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsState.kt index 4fe31411a..1bac047a5 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsState.kt @@ -21,5 +21,4 @@ internal data class ShowCommentsState( val loading: LoadingState = LoadingState.Idle, val user: User? = null, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsView.kt index e19d802cf..ec178fd08 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.comments import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -34,7 +31,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier @@ -121,7 +117,6 @@ internal fun ShowCommentsView( onReactionClick = { reaction, comment -> viewModel.setReaction(reaction, comment.id) }, - onCollapse = viewModel::setCollapsed, onUserClick = onUserClick, ) @@ -166,15 +161,11 @@ private fun ShowCommentsContent( onAddCommentClick: (() -> Unit)? = null, onDeleteCommentClick: ((Comment) -> Unit)? = null, onMoreClick: (() -> Unit)? = null, - onCollapse: ((Boolean) -> Unit)? = null, onUserClick: ((User) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize(animationSpec = if (animateCollapse) spring() else snap()), + modifier = modifier, ) { Row( modifier = Modifier @@ -186,12 +177,6 @@ private fun ShowCommentsContent( TraktSectionHeader( title = stringResource(R.string.list_title_comments), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse?.invoke(!current) - }, extraIcon = when { state.user != null -> { { @@ -226,51 +211,49 @@ private fun ShowCommentsContent( ) } - if (state.collapsed != true) { - if (!state.items.isNullOrEmpty() || state.loading.isLoading || state.user != null) { - ContentFilters( - selectedFilter = state.filter, - headerPadding = headerPadding, - onFilterClick = onFilterClick, - ) - } else { - Spacer(modifier = Modifier.height(TraktTheme.spacing.mainRowHeaderSpace)) - } + if (!state.items.isNullOrEmpty() || state.loading.isLoading || state.user != null) { + ContentFilters( + selectedFilter = state.filter, + headerPadding = headerPadding, + onFilterClick = onFilterClick, + ) + } else { + Spacer(modifier = Modifier.height(TraktTheme.spacing.mainRowHeaderSpace)) + } - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listReactions = (state.reactions ?: emptyMap()).toImmutableMap(), - user = state.user, - userReactions = (state.userReactions ?: emptyMap()).toImmutableMap(), - contentPadding = contentPadding, - onCommentClick = onCommentClick, - onDeleteCommentClick = onDeleteCommentClick, - onCommentLoaded = onCommentLoaded, - onReactionClick = onReactionClick, - onUserClick = onUserClick, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listReactions = (state.reactions ?: emptyMap()).toImmutableMap(), + user = state.user, + userReactions = (state.userReactions ?: emptyMap()).toImmutableMap(), + contentPadding = contentPadding, + onCommentClick = onCommentClick, + onDeleteCommentClick = onDeleteCommentClick, + onCommentLoaded = onCommentLoaded, + onReactionClick = onReactionClick, + onUserClick = onUserClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsViewModel.kt index 4ac8f351a..74736a1ae 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/comments/ShowCommentsViewModel.kt @@ -49,8 +49,6 @@ import tv.trakt.trakt.core.reactions.data.work.DeleteReactionWorker import tv.trakt.trakt.core.reactions.data.work.PostReactionWorker import tv.trakt.trakt.core.summary.shows.features.comments.usecases.GetShowCommentsUseCase import tv.trakt.trakt.core.user.usecases.reactions.LoadUserReactionsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import kotlin.time.Duration.Companion.seconds @OptIn(FlowPreview::class) @@ -64,7 +62,6 @@ internal class ShowCommentsViewModel( private val loadUserReactionsUseCase: LoadUserReactionsUseCase, private val reactionsUpdates: ReactionsUpdates, private val commentsUpdates: CommentsUpdates, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowCommentsState() @@ -76,10 +73,8 @@ internal class ShowCommentsViewModel( private val loadingState = MutableStateFlow(initialState.loading) private val userState = MutableStateFlow(initialState.user) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_COMMENTS)) private var reactionJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -345,16 +340,6 @@ internal class ShowCommentsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_COMMENTS) - else -> collapsingManager.expand(CollapsingKey.SHOW_COMMENTS) - } - } - } override fun onCleared() { reactionJob?.cancel() @@ -372,7 +357,6 @@ internal class ShowCommentsViewModel( loadingState, userState, errorState, - collapseState, ) { state -> ShowCommentsState( show = state[0] as Show?, @@ -383,7 +367,6 @@ internal class ShowCommentsViewModel( loading = state[5] as LoadingState, user = state[6] as User?, error = state[7] as Exception?, - collapsed = state[8] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextSheet.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextSheet.kt index f1fd8a1e8..1bd9dc910 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextSheet.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextSheet.kt @@ -30,6 +30,7 @@ internal fun ShowDetailsContextSheet( onRemoveClick: (() -> Unit)? = null, onListsClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, onDismiss: () -> Unit, ) { val sheetScope = rememberCoroutineScope() @@ -92,6 +93,15 @@ internal fun ShowDetailsContextSheet( } } }, + onEditScreenClick = { + onEditScreenClick?.invoke() + sheetScope.launch { state.hide() } + .invokeOnCompletion { + if (!state.isVisible) { + onDismiss() + } + } + }, modifier = Modifier .padding(bottom = 24.dp) .padding(horizontal = 24.dp), diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextView.kt index 373b4228d..ec8091c84 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/context/more/ShowDetailsContextView.kt @@ -62,6 +62,7 @@ internal fun ShowDetailsContextView( onCheckClick: (() -> Unit)? = null, onListsClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -75,6 +76,7 @@ internal fun ShowDetailsContextView( onCheckClick = onCheckClick, onListsClick = onListsClick, onCoverClick = onCoverClick, + onEditScreenClick = onEditScreenClick, modifier = modifier, ) } @@ -91,6 +93,7 @@ private fun ShowDetailsContextViewContent( onShareClick: (() -> Unit)? = null, onListsClick: (() -> Unit)? = null, onCoverClick: (() -> Unit)? = null, + onEditScreenClick: (() -> Unit)? = null, ) { val context = LocalContext.current @@ -175,6 +178,7 @@ private fun ShowDetailsContextViewContent( onShareClick = onShareClick ?: {}, onListsClick = onListsClick ?: {}, onCoverClick = onCoverClick ?: {}, + onEditScreenClick = onEditScreenClick ?: {}, modifier = Modifier .padding(top = 14.dp), ) @@ -234,6 +238,7 @@ private fun ActionButtons( onCoverClick: () -> Unit, onRemoveClick: () -> Unit, onListsClick: () -> Unit, + onEditScreenClick: () -> Unit, ) { Column( verticalArrangement = spacedBy(TraktTheme.spacing.contextItemsSpace), @@ -311,17 +316,17 @@ private fun ActionButtons( } } -// GhostButton( -// text = stringResource(R.string.button_text_share), -// icon = painterResource(R.drawable.ic_share), -// iconSize = 22.dp, -// iconSpace = 15.dp, -// modifier = Modifier -// .graphicsLayer { -// translationX = -5.dp.toPx() -// }, -// onClick = onShareClick, -// ) + GhostButton( + text = stringResource(R.string.text_edit_screen), + icon = painterResource(R.drawable.ic_edit_screen), + iconSize = 22.dp, + iconSpace = 15.dp, + modifier = Modifier + .graphicsLayer { + translationX = -5.dp.toPx() + }, + onClick = onEditScreenClick, + ) } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasState.kt index 31d721f60..c8a7f99b4 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasState.kt @@ -12,7 +12,6 @@ internal data class ShowExtrasState( val filters: FiltersState = FiltersState(), val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) { data class FiltersState( val filters: ImmutableList = emptyList().toImmutableList(), diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasView.kt index cbb0e826a..a5daac8c1 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.extras import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -25,10 +22,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -74,7 +68,6 @@ internal fun ShowExtrasView( contentPadding = contentPadding, onClick = onVideoClick, onFilterClick = { viewModel.toggleFilter(it) }, - onCollapse = viewModel::setCollapsed, ) } @@ -86,66 +79,52 @@ private fun ShowExtrasContent( contentPadding: PaddingValues = PaddingValues(), onClick: ((ExtraVideo) -> Unit)? = null, onFilterClick: ((String) -> Unit)? = null, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_extras), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.filters.filters.size > 1) { - ContentFilters( - state = state.filters, - onFilterClick = onFilterClick ?: {}, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.filters.filters.size > 1) { + ContentFilters( + state = state.filters, + onFilterClick = onFilterClick ?: {}, + ) + } - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - ) - } + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onClick = onClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasViewModel.kt index 4f07cda83..3af29539a 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/extras/ShowExtrasViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -21,13 +20,10 @@ import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.ExtraVideo import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.summary.shows.features.extras.usecases.GetShowExtrasUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ShowExtrasViewModel( private val show: Show, private val getExtrasUseCase: GetShowExtrasUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowExtrasState() @@ -36,9 +32,6 @@ internal class ShowExtrasViewModel( private val filtersState = MutableStateFlow(initialState.filters) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_EXTRAS)) - - private var collapseJob: Job? = null init { loadData() @@ -105,31 +98,18 @@ internal class ShowExtrasViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_EXTRAS) - else -> collapsingManager.expand(CollapsingKey.SHOW_EXTRAS) - } - } - } - @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( filterItemsState, filtersState, loadingState, errorState, - collapseState, ) { state -> ShowExtrasState( items = state[0] as ImmutableList?, filters = state[1] as ShowExtrasState.FiltersState, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryState.kt index cc15e15d4..742a4e25b 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryState.kt @@ -10,5 +10,4 @@ internal data class ShowHistoryState( val items: ImmutableList? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryView.kt index 7e6eb6cbf..7ea7b8910 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.history import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -22,9 +19,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -73,7 +67,6 @@ internal fun ShowHistoryView( onLongClick = { if (!loading) onClick?.invoke(it) }, - onCollapse = viewModel::setCollapsed, ) } @@ -86,26 +79,14 @@ private fun ShowHistoryContent( contentPadding: PaddingValues = PaddingValues(), onClick: ((HomeActivityItem.EpisodeItem) -> Unit)? = null, onLongClick: ((HomeActivityItem.EpisodeItem) -> Unit)? = null, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_history), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, extraIcon = if (loading) { { FilmProgressIndicator( @@ -120,33 +101,31 @@ private fun ShowHistoryContent( .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryViewModel.kt index a63f86957..88bcc0c57 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/history/ShowHistoryViewModel.kt @@ -30,8 +30,6 @@ import tv.trakt.trakt.core.summary.episodes.data.EpisodeDetailsUpdates.Source.SE import tv.trakt.trakt.core.summary.shows.data.ShowDetailsUpdates import tv.trakt.trakt.core.summary.shows.data.ShowDetailsUpdates.Source import tv.trakt.trakt.core.summary.shows.features.history.usecases.GetShowHistoryUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey @OptIn(FlowPreview::class) internal class ShowHistoryViewModel( @@ -39,14 +37,12 @@ internal class ShowHistoryViewModel( private val getHistoryUseCase: GetShowHistoryUseCase, private val showDetailsUpdates: ShowDetailsUpdates, private val episodeDetailsUpdates: EpisodeDetailsUpdates, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowHistoryState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_HISTORY)) private var collapseJob: kotlinx.coroutines.Job? = null @@ -93,29 +89,17 @@ internal class ShowHistoryViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_HISTORY) - else -> collapsingManager.expand(CollapsingKey.SHOW_HISTORY) - } - } - } @Suppress("UNCHECKED_CAST") val state: StateFlow = combine( itemsState, loadingState, errorState, - collapseState, ) { state -> ShowHistoryState( items = state[0] as ImmutableList?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsState.kt index 932da27f4..76599d098 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsState.kt @@ -13,5 +13,4 @@ internal data class ShowListsState( val likedItems: ImmutableSet? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsView.kt index 802c22cde..f8c25b597 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.lists import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -26,10 +23,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color @@ -74,7 +68,6 @@ internal fun ShowListsView( headerPadding = headerPadding, contentPadding = contentPadding, onClick = onClick, - onCollapse = viewModel::setCollapsed, ) } @@ -85,60 +78,46 @@ private fun ShowListsContent( headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), onClick: ((CustomList) -> Unit)? = null, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_popular_lists), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - Column( - verticalArrangement = spacedBy(0.dp), - ) { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - listLikedItems = state.likedItems ?: EmptyImmutableSet, - contentPadding = contentPadding, - onClick = onClick, - ) - } + Done -> { + Column( + verticalArrangement = spacedBy(0.dp), + ) { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + listLikedItems = state.likedItems ?: EmptyImmutableSet, + contentPadding = contentPadding, + onClick = onClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsViewModel.kt index dfc88ef69..f3383389e 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/lists/ShowListsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -25,14 +24,11 @@ import tv.trakt.trakt.common.model.CustomList import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.TraktId import tv.trakt.trakt.core.summary.shows.features.lists.usecases.GetShowListsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ShowListsViewModel( private val show: Show, private val getListsUseCase: GetShowListsUseCase, private val loadUserLikedListsUseCase: LoadUserLikedListsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowListsState() @@ -40,9 +36,6 @@ internal class ShowListsViewModel( private val likedItemsState = MutableStateFlow(initialState.likedItems) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_LISTS)) - - private var collapseJob: Job? = null init { loadData() @@ -90,31 +83,18 @@ internal class ShowListsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_LISTS) - else -> collapsingManager.expand(CollapsingKey.SHOW_LISTS) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, likedItemsState, loadingState, errorState, - collapseState, ) { state -> ShowListsState( items = state[0] as ImmutableList?, likedItems = state[1] as ImmutableSet?, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedState.kt index f7e32e175..859be61c6 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedState.kt @@ -12,5 +12,4 @@ internal data class ShowRelatedState( val collection: UserCollectionState = UserCollectionState.Default, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedView.kt index 73b18e51a..e5594e9a1 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.related import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -24,7 +21,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -76,7 +72,6 @@ internal fun ShowRelatedView( contentPadding = contentPadding, onClick = onClick, onLongClick = { contextSheet = it }, - onCollapse = viewModel::setCollapsed, ) ShowContextSheet( @@ -93,60 +88,46 @@ private fun ShowRelatedContent( contentPadding: PaddingValues = PaddingValues(), onClick: ((Show) -> Unit)? = null, onLongClick: ((Show) -> Unit)? = null, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_related_shows), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding), ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + modifier = Modifier.padding(bottom = 4.dp), + ) + } + + Done -> { + if (state.items?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = state.items ?: EmptyImmutableList, + collection = state.collection, contentPadding = contentPadding, - modifier = Modifier.padding(bottom = 4.dp), + onClick = onClick, + onLongClick = onLongClick, ) } - - Done -> { - if (state.items?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = state.items ?: EmptyImmutableList, - collection = state.collection, - contentPadding = contentPadding, - onClick = onClick, - onLongClick = onLongClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedViewModel.kt index 9d7d607c8..07648013f 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/related/ShowRelatedViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.related import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -20,23 +19,17 @@ import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.summary.shows.features.related.usecases.GetShowRelatedUseCase import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ShowRelatedViewModel( private val show: Show, private val getRelatedShowsUseCase: GetShowRelatedUseCase, private val collectionStateProvider: CollectionStateProvider, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowRelatedState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_RELATED)) - - private var collapseJob: Job? = null init { loadData() @@ -66,31 +59,18 @@ internal class ShowRelatedViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_RELATED) - else -> collapsingManager.expand(CollapsingKey.SHOW_RELATED) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, collectionStateProvider.stateFlow, loadingState, errorState, - collapseState, ) { state -> ShowRelatedState( items = state[0] as ImmutableList?, collection = state[1] as UserCollectionState, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsState.kt index f2fb919bf..b24b73820 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsState.kt @@ -15,5 +15,4 @@ internal data class ShowSeasonsState( val loadingSeason: LoadingState = LoadingState.Idle, val info: StringResource? = null, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsView.kt index d9a829630..08a9891cf 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.seasons import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -32,13 +29,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.hapticfeedback.HapticFeedbackType.Companion.Confirm @@ -146,7 +141,6 @@ internal fun ShowSeasonsView( onAllSeasonsClick = { onAllSeasonsClick(state.items.selectedSeason?.number) }, - onCollapse = viewModel::setCollapsed, ) RemoveConfirmationSheet( @@ -263,16 +257,10 @@ private fun ShowSeasonsContent( onCheckSeasonClick: (() -> Unit)? = null, onRemoveSeasonClick: (() -> Unit)? = null, onAllSeasonsClick: (() -> Unit)? = null, - onCollapse: (collapsed: Boolean) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { val headerSeasons = stringResource(R.string.list_title_seasons) val headerCurrentSeason = state.items.selectedSeason?.let { @@ -294,7 +282,7 @@ private fun ShowSeasonsContent( verticalAlignment = CenterVertically, modifier = Modifier .weight(1f) - .onClick(enabled = !state.loading.isLoading && state.collapsed != true) { + .onClick(enabled = !state.loading.isLoading) { onAllSeasonsClick?.invoke() }, ) { @@ -302,12 +290,11 @@ private fun ShowSeasonsContent( title = headerSeasons, titleColor = when { !state.loading.isLoading && state.items.seasons.isEmpty() -> TraktTheme.colors.textPrimary - state.collapsed == true -> TraktTheme.colors.textPrimary else -> TraktTheme.colors.textSecondary }, ) - if (state.items.selectedSeason != null && state.collapsed != true) { + if (state.items.selectedSeason != null && true) { TraktHeader( title = "/", titleColor = TraktTheme.colors.textSecondary, @@ -317,14 +304,14 @@ private fun ShowSeasonsContent( ) } - if (state.items.selectedSeason != null && state.collapsed != true) { + if (state.items.selectedSeason != null && true) { AllSeasonsDropdown( state = state, onSeasonClick = onSeasonClick, ) } - if (user != null && state.collapsed != true && !state.loading.isLoading) { + if (user != null && true && !state.loading.isLoading) { Icon( painter = painterResource(R.drawable.ic_chevron_right), contentDescription = null, @@ -346,64 +333,47 @@ private fun ShowSeasonsContent( if (state.loadingSeason.isLoading) { FilmProgressIndicator(size = 16.dp) } - - Icon( - painter = painterResource(R.drawable.ic_arrow_dropdown), - contentDescription = null, - tint = TraktTheme.colors.textSecondary, - modifier = Modifier - .padding(start = 4.dp) - .rotate(if (state.collapsed == true) -180F else 0F) - .size(16.dp) - .onClick { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, - ) } } - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(300), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(300), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items.seasons.isEmpty()) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + show = state.show, + seasons = state.items, contentPadding = contentPadding, + onEpisodeClick = onEpisodeClick, + onSeasonClick = onSeasonClick, + onSeasonLongClick = { + if (state.loadingSeason.isLoading) { + return@ContentList + } + when (state.items.isSelectedSeasonWatched) { + true -> onRemoveSeasonClick?.invoke() + else -> onCheckSeasonClick?.invoke() + } + }, + onCheckEpisodeClick = onCheckEpisodeClick, + onCheckEpisodeLongClick = onCheckEpisodeLongClick, + onRemoveEpisodeClick = onRemoveEpisodeClick, ) } - - Done -> { - if (state.items.seasons.isEmpty()) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - show = state.show, - seasons = state.items, - contentPadding = contentPadding, - onEpisodeClick = onEpisodeClick, - onSeasonClick = onSeasonClick, - onSeasonLongClick = { - if (state.loadingSeason.isLoading) { - return@ContentList - } - when (state.items.isSelectedSeasonWatched) { - true -> onRemoveSeasonClick?.invoke() - else -> onCheckSeasonClick?.invoke() - } - }, - onCheckEpisodeClick = onCheckEpisodeClick, - onCheckEpisodeLongClick = onCheckEpisodeLongClick, - onRemoveEpisodeClick = onRemoveEpisodeClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsViewModel.kt index 4ec0a2fd2..879f9fad7 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/seasons/ShowSeasonsViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -44,8 +43,6 @@ import tv.trakt.trakt.core.summary.shows.features.seasons.model.ShowSeasons.Help import tv.trakt.trakt.core.summary.shows.features.seasons.usecases.GetShowSeasonsUseCase import tv.trakt.trakt.core.sync.usecases.UpdateEpisodeHistoryUseCase import tv.trakt.trakt.core.user.usecases.progress.LoadUserProgressUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey import tv.trakt.trakt.resources.R @OptIn(FlowPreview::class) @@ -58,7 +55,6 @@ internal class ShowSeasonsViewModel( private val episodeDetailsUpdates: EpisodeDetailsUpdates, private val sessionManager: SessionManager, private val analytics: Analytics, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowSeasonsState() @@ -69,9 +65,6 @@ internal class ShowSeasonsViewModel( private val loadingSeasonState = MutableStateFlow(initialState.loadingSeason) private val infoState = MutableStateFlow(initialState.info) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_SEASONS)) - - private var collapseJob: Job? = null init { loadData() @@ -475,17 +468,6 @@ internal class ShowSeasonsViewModel( infoState.update { null } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_SEASONS) - else -> collapsingManager.expand(CollapsingKey.SHOW_SEASONS) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( showState, @@ -495,7 +477,6 @@ internal class ShowSeasonsViewModel( loadingSeasonState, infoState, errorState, - collapseState, ) { state -> ShowSeasonsState( show = state[0] as Show, @@ -505,7 +486,6 @@ internal class ShowSeasonsViewModel( loadingSeason = state[4] as LoadingState, info = state[5] as StringResource?, error = state[6] as Exception?, - collapsed = state[7] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentState.kt index a75a05896..ff9dfeebe 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentState.kt @@ -11,5 +11,4 @@ internal data class ShowSentimentState( val sentiment: Sentiments? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentView.kt index fca55c1a8..564175ddc 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.sentiment import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -20,7 +17,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -62,7 +58,6 @@ internal fun ShowSentimentView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onNotAvailable = { visible = false }, onSentimentClick = onSentimentClick, ) @@ -76,28 +71,17 @@ private fun ShowSentimentContent( headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), onSentimentClick: (Sentiments) -> Unit = {}, - onCollapse: (collapsed: Boolean) -> Unit = {}, onNotAvailable: (() -> Unit)? = null, ) { val windowClass = currentWindowAdaptiveInfo().windowSizeClass - var animateCollapse by rememberSaveable { mutableStateOf(false) } Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.header_community_sentiment), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -106,33 +90,31 @@ private fun ShowSentimentContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - val maxWidth = if (windowClass.isAtLeastLarge()) 0.45f else 1f - when (loading) { - Idle, Loading -> { - DetailsSentimentSkeleton( + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + val maxWidth = if (windowClass.isAtLeastLarge()) 0.45f else 1f + when (loading) { + Idle, Loading -> { + DetailsSentimentSkeleton( + modifier = Modifier + .fillMaxWidth(maxWidth) + .padding(contentPadding), + ) + } + Done -> { + if (state.sentiment != null) { + DetailsSentiment( + vip = state.user?.isAnyVip == true, + sentiments = state.sentiment, modifier = Modifier .fillMaxWidth(maxWidth) - .padding(contentPadding), + .padding(contentPadding) + .onClick { onSentimentClick(state.sentiment) }, ) - } - Done -> { - if (state.sentiment != null) { - DetailsSentiment( - vip = state.user?.isAnyVip == true, - sentiments = state.sentiment, - modifier = Modifier - .fillMaxWidth(maxWidth) - .padding(contentPadding) - .onClick { onSentimentClick(state.sentiment) }, - ) - } else { - onNotAvailable?.invoke() - } + } else { + onNotAvailable?.invoke() } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentViewModel.kt index 1948d7833..03c5c6bda 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/sentiment/ShowSentimentViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.sentiment import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -26,13 +25,10 @@ import tv.trakt.trakt.common.model.Sentiments import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.summary.shows.features.sentiment.usecases.GetShowSentimentUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ShowSentimentViewModel( private val show: Show, private val getSentimentUseCase: GetShowSentimentUseCase, - private val collapsingManager: CollapsingManager, private val sessionManager: SessionManager, ) : ViewModel() { private val initialState = ShowSentimentState() @@ -41,9 +37,6 @@ internal class ShowSentimentViewModel( private val sentimentState = MutableStateFlow(initialState.sentiment) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_SENTIMENT)) - - private var collapseJob: Job? = null init { loadData() @@ -98,31 +91,18 @@ internal class ShowSentimentViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_SENTIMENT) - else -> collapsingManager.expand(CollapsingKey.SHOW_SENTIMENT) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( userState, sentimentState, loadingState, errorState, - collapseState, ) { state -> ShowSentimentState( user = state[0] as User?, sentiment = state[1] as Sentiments?, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsState.kt index 7b013ec0d..d619df290 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsState.kt @@ -9,5 +9,4 @@ internal data class ShowStreamingsState( val items: StreamingsResult? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, - val collapsed: Boolean? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsView.kt index 451506e8c..817b55823 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.streaming import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -22,9 +19,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -73,7 +67,6 @@ internal fun ShowStreamingsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onClick = { service -> openExternalAppLink( packageId = StreamingServiceApp.findFromSource(service.source)?.packageId, @@ -91,17 +84,11 @@ private fun ShowStreamingsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onClick: ((StreamingService) -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(12.dp), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { Column( verticalArrangement = spacedBy(3.dp), @@ -112,12 +99,6 @@ private fun ShowStreamingsContent( TraktSectionHeader( title = stringResource(R.string.page_title_where_to_watch), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, ) JustWatchRanksStrip( @@ -126,32 +107,30 @@ private fun ShowStreamingsContent( ) } - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoading( - visible = loading.isLoading, + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoading( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } + + Done -> { + if (state.items?.streamings?.isEmpty() == true) { + ContentEmpty( + contentPadding = headerPadding, + ) + } else { + ContentList( + listItems = (state.items?.streamings ?: emptyList()).toImmutableList(), contentPadding = contentPadding, + onClick = onClick, ) } - - Done -> { - if (state.items?.streamings?.isEmpty() == true) { - ContentEmpty( - contentPadding = headerPadding, - ) - } else { - ContentList( - listItems = (state.items?.streamings ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onClick = onClick, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsViewModel.kt index 21ee6c8ab..9d7b815b9 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/streaming/ShowStreamingsViewModel.kt @@ -2,7 +2,6 @@ package tv.trakt.trakt.core.summary.shows.features.streaming import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -19,23 +18,17 @@ import tv.trakt.trakt.common.helpers.extensions.rethrowCancellation import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.core.streamings.model.StreamingsResult import tv.trakt.trakt.core.summary.shows.features.streaming.usecases.GetShowStreamingsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class ShowStreamingsViewModel( private val show: Show, private val sessionManager: SessionManager, private val getStreamingsUseCase: GetShowStreamingsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = ShowStreamingsState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.SHOW_WHERE_TO_WATCH)) - - private var collapseJob: Job? = null init { loadData() @@ -70,29 +63,16 @@ internal class ShowStreamingsViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.SHOW_WHERE_TO_WATCH) - else -> collapsingManager.expand(CollapsingKey.SHOW_WHERE_TO_WATCH) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, loadingState, errorState, - collapseState, ) { state -> ShowStreamingsState( items = state[0] as StreamingsResult?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaState.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaState.kt index 7b05d3350..35a30ea94 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaState.kt @@ -11,7 +11,6 @@ internal data class ShowTriviaState( val user: User? = null, val summary: ImmutableList? = null, val items: ImmutableList? = null, - val collapsed: Boolean? = null, val loading: LoadingState = LoadingState.Idle, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaView.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaView.kt index 7ae84c6cc..04f0c1d4c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.summary.shows.features.trivia import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -19,7 +16,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -60,7 +56,6 @@ internal fun ShowTriviaView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onNotAvailable = { visible = false }, onVipClick = onVipClick, onTriviaClick = onTriviaClick, @@ -74,29 +69,17 @@ private fun ShowTriviaContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (collapsed: Boolean) -> Unit = {}, onVipClick: () -> Unit = {}, onTriviaClick: () -> Unit = {}, onNotAvailable: (() -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_trivia), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -107,35 +90,33 @@ private fun ShowTriviaContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - DetailsTriviaSkeleton( - modifier = Modifier.padding(contentPadding), + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + DetailsTriviaSkeleton( + modifier = Modifier.padding(contentPadding), + ) + } + Done -> { + if (state.summary.isNullOrEmpty()) { + onNotAvailable?.invoke() + } else { + DetailsTrivia( + items = state.summary, + vip = state.user?.isAnyVip == true, + onVipClick = onVipClick, + modifier = Modifier + .padding(contentPadding) + .onClick { + if (state.user?.isAnyVip == true) { + onTriviaClick() + } + }, ) } - Done -> { - if (state.summary.isNullOrEmpty()) { - onNotAvailable?.invoke() - } else { - DetailsTrivia( - items = state.summary, - vip = state.user?.isAnyVip == true, - onVipClick = onVipClick, - modifier = Modifier - .padding(contentPadding) - .onClick { - if (state.user?.isAnyVip == true) { - onTriviaClick() - } - }, - ) - } - } } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaViewModel.kt index 50343e2ca..9d7268540 100644 --- a/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/summary/shows/features/trivia/ShowTriviaViewModel.kt @@ -27,15 +27,11 @@ import tv.trakt.trakt.common.model.Show import tv.trakt.trakt.common.model.User import tv.trakt.trakt.common.model.trivia.TriviaFact import tv.trakt.trakt.core.summary.shows.features.trivia.usecases.GetShowTriviaUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey -private val collapsingKey = CollapsingKey.SHOW_TRIVIA internal class ShowTriviaViewModel( private val show: Show, private val getTriviaUseCase: GetShowTriviaUseCase, - private val collapsingManager: CollapsingManager, private val sessionManager: SessionManager, ) : ViewModel() { private val initialState = ShowTriviaState() @@ -44,7 +40,6 @@ internal class ShowTriviaViewModel( private val summaryState = MutableStateFlow(initialState.summary) private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(collapsingKey)) private val errorState = MutableStateFlow(initialState.error) private var collapseJob: kotlinx.coroutines.Job? = null @@ -106,24 +101,12 @@ internal class ShowTriviaViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(collapsingKey) - else -> collapsingManager.expand(collapsingKey) - } - } - } val state = combine( userState, itemsState, summaryState, loadingState, - collapseState, errorState, ) { state -> @Suppress("UNCHECKED_CAST") @@ -132,8 +115,7 @@ internal class ShowTriviaViewModel( items = state[1] as ImmutableList?, summary = state[2] as ImmutableList?, loading = state[3] as LoadingState, - collapsed = state[4] as Boolean, - error = state[5] as Exception?, + error = state[4] as Exception?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/user/usecases/LogoutUserUseCase.kt b/app/src/main/java/tv/trakt/trakt/core/user/usecases/LogoutUserUseCase.kt index a888c6527..044271f39 100644 --- a/app/src/main/java/tv/trakt/trakt/core/user/usecases/LogoutUserUseCase.kt +++ b/app/src/main/java/tv/trakt/trakt/core/user/usecases/LogoutUserUseCase.kt @@ -38,12 +38,10 @@ import tv.trakt.trakt.core.user.data.local.ratings.UserRatingsLocalDataSource import tv.trakt.trakt.core.user.data.local.reactions.UserReactionsLocalDataSource import tv.trakt.trakt.core.user.data.local.watchlist.UserWatchlistLocalDataSource import tv.trakt.trakt.core.user.data.local.watchlist.minimal.UserWatchlistMinimalLocalDataSource -import tv.trakt.trakt.helpers.collapsing.CollapsingManager internal class LogoutUserUseCase( private val appContext: Context, private val sessionManager: SessionManager, - private val collapsingManager: CollapsingManager, private val checkInManager: CheckInManager, private val apiClients: Array, private val younifyApiClient: YounifyRemoteDataSource, @@ -78,7 +76,6 @@ internal class LogoutUserUseCase( ) { suspend fun logoutUser() { sessionManager.clear() - collapsingManager.clear() checkInManager.stop( source = CheckInUpdates.Source.Default, context = appContext, diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileScreen.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileScreen.kt index 2fd1dd4cb..c53495753 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileScreen.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileScreen.kt @@ -83,6 +83,12 @@ import tv.trakt.trakt.core.userprofile.sections.history.UserProfileHistoryView import tv.trakt.trakt.core.userprofile.sections.lists.UserProfileListsView import tv.trakt.trakt.core.userprofile.sections.social.UserProfileSocialView import tv.trakt.trakt.helpers.SimpleScrollConnection +import tv.trakt.trakt.helpers.editscreen.EditScreenSheet +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.UserProfileKeys +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.UserProfileFavorites +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.UserProfileHistory +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.UserProfileLists +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.UserProfileSocial import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.components.ScrollableBackdropImage import tv.trakt.trakt.ui.components.TraktHeader @@ -111,6 +117,13 @@ internal fun UserProfileScreen( var confirmBlockSheet by remember { mutableStateOf(false) } var confirmUnfollowSheet by remember { mutableStateOf(false) } + var sectionsSheet by remember { mutableStateOf(false) } + + EditScreenSheet( + active = sectionsSheet, + enabledValues = UserProfileKeys, + onDismiss = { sectionsSheet = false }, + ) LaunchedEffect(state.info) { state.info?.let { info -> @@ -154,6 +167,7 @@ internal fun UserProfileScreen( else -> confirmBlockSheet = true } }, + onEditScreenClick = { sectionsSheet = true }, onNavigateToShow = viewModel::navigateToShow, onNavigateToMovie = viewModel::navigateToMovie, onNavigateToEpisode = viewModel::navigateToEpisode, @@ -202,6 +216,7 @@ private fun UserProfileContent( onNavigateBack: () -> Unit = {}, onFollowClick: () -> Unit = {}, onBlockClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { val windowClass = currentWindowAdaptiveInfoV2().windowSizeClass @@ -257,6 +272,7 @@ private fun UserProfileContent( onBack = onNavigateBack, onFollowClick = onFollowClick, onBlockClick = onBlockClick, + onEditScreenClick = onEditScreenClick, modifier = Modifier .padding(bottom = 8.dp), ) @@ -360,62 +376,70 @@ private fun LazyListScope.userProfilePublicContent( } } - item { - UserProfileHistoryView( - viewModel = koinViewModel( - parameters = { parametersOf(state.user.ids.trakt) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onNavigateToShow, - onEpisodeClick = onNavigateToEpisode, - onMovieClick = onNavigateToMovie, - onMoreClick = { onNavigateToAllHistory(state.user.ids.trakt) }, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(UserProfileHistory) == true) { + item { + UserProfileHistoryView( + viewModel = koinViewModel( + parameters = { parametersOf(state.user.ids.trakt) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onNavigateToShow, + onEpisodeClick = onNavigateToEpisode, + onMovieClick = onNavigateToMovie, + onMoreClick = { onNavigateToAllHistory(state.user.ids.trakt) }, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - UserProfileFavoritesView( - viewModel = koinViewModel( - parameters = { parametersOf(state.user.ids.trakt) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onShowClick = onNavigateToShow, - onMovieClick = onNavigateToMovie, - onMoreClick = { onNavigateToAllFavorites(state.user.ids.trakt) }, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(UserProfileFavorites) == true) { + item { + UserProfileFavoritesView( + viewModel = koinViewModel( + parameters = { parametersOf(state.user.ids.trakt) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onShowClick = onNavigateToShow, + onMovieClick = onNavigateToMovie, + onMoreClick = { onNavigateToAllFavorites(state.user.ids.trakt) }, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - UserProfileListsView( - viewModel = koinViewModel( - parameters = { parametersOf(state.user.ids.trakt) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onListClick = onNavigateToList, - onMoreClick = { onNavigateToAllLists(state.user, it) }, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(UserProfileLists) == true) { + item { + UserProfileListsView( + viewModel = koinViewModel( + parameters = { parametersOf(state.user.ids.trakt) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onListClick = onNavigateToList, + onMoreClick = { onNavigateToAllLists(state.user, it) }, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } - item { - UserProfileSocialView( - viewModel = koinViewModel( - parameters = { parametersOf(state.user.ids.trakt) }, - ), - headerPadding = sectionPadding, - contentPadding = sectionPadding, - onUserClick = onNavigateToUser, - modifier = Modifier - .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), - ) + if (state.visibility?.get(UserProfileSocial) == true) { + item { + UserProfileSocialView( + viewModel = koinViewModel( + parameters = { parametersOf(state.user.ids.trakt) }, + ), + headerPadding = sectionPadding, + contentPadding = sectionPadding, + onUserClick = onNavigateToUser, + modifier = Modifier + .padding(bottom = TraktTheme.spacing.mainSectionVerticalSpace), + ) + } } } @@ -473,6 +497,7 @@ private fun TitleBar( onBack: () -> Unit = {}, onFollowClick: () -> Unit = {}, onBlockClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { Row( verticalAlignment = CenterVertically, @@ -659,6 +684,28 @@ private fun TitleBar( ) }, ) + + DropdownMenuItem( + text = { + Text( + text = stringResource(R.string.text_edit_screen), + style = TraktTheme.typography.buttonTertiary, + color = TraktTheme.colors.textPrimary, + ) + }, + onClick = { + onEditScreenClick() + showMenu = false + }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.ic_edit_screen), + contentDescription = null, + tint = TraktTheme.colors.textPrimary, + modifier = Modifier.size(22.dp), + ) + }, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileState.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileState.kt index 3f79a3a23..9ae310b19 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileState.kt @@ -1,12 +1,14 @@ package tv.trakt.trakt.core.userprofile import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap import tv.trakt.trakt.common.helpers.LoadingState import tv.trakt.trakt.common.helpers.StringResource import tv.trakt.trakt.common.model.Episode import tv.trakt.trakt.common.model.TraktId import tv.trakt.trakt.common.model.User import tv.trakt.trakt.core.profile.sections.thismonth.model.ThisMonthStats +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey @Immutable internal data class UserProfileState( @@ -19,6 +21,7 @@ internal data class UserProfileState( val navigateEpisode: Pair? = null, val loading: LoadingState = LoadingState.Idle, val info: StringResource? = null, + val visibility: ImmutableMap? = null, ) { data class MonthlyStats( val stats: ThisMonthStats?, diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileViewModel.kt index 087bef412..789e6fdd7 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/UserProfileViewModel.kt @@ -8,10 +8,14 @@ import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.google.firebase.Firebase import com.google.firebase.remoteconfig.remoteConfig +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -40,6 +44,9 @@ import tv.trakt.trakt.core.user.usecases.social.LoadUserSocialUseCase import tv.trakt.trakt.core.userprofile.navigation.UserProfileDestination import tv.trakt.trakt.core.userprofile.sections.thismonth.GetUserProfileMonthUseCase import tv.trakt.trakt.core.userprofile.usecases.GetUserProfileDetailsUseCase +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey.Companion.UserProfileKeys import tv.trakt.trakt.resources.R internal class UserProfileViewModel( @@ -53,6 +60,7 @@ internal class UserProfileViewModel( private val showLocalDataSource: ShowLocalDataSource, private val episodeLocalDataSource: EpisodeLocalDataSource, private val movieLocalDataSource: MovieLocalDataSource, + private val editScreenManager: EditScreenManager, ) : ViewModel() { private val destination = savedStateHandle.toRoute() private val user = Json.decodeFromString(destination.userJson) @@ -65,6 +73,7 @@ internal class UserProfileViewModel( private val userMonthState = MutableStateFlow(initialState.monthStats) private val loadingState = MutableStateFlow(initialState.loading) private val infoState = MutableStateFlow(initialState.info) + private val visibilityState = MutableStateFlow(initialState.visibility) private val navigateShowState = MutableStateFlow(initialState.navigateShow) private val navigateMovieState = MutableStateFlow(initialState.navigateMovie) @@ -74,6 +83,15 @@ internal class UserProfileViewModel( init { loadData() + observeVisibility() + } + + private fun observeVisibility() { + editScreenManager.observe(UserProfileKeys) + .onEach { map -> + visibilityState.update { map.toImmutableMap() } + } + .launchIn(viewModelScope) } private fun loadData() { @@ -344,6 +362,7 @@ internal class UserProfileViewModel( navigateMovieState, navigateEpisodeState, infoState, + visibilityState, ) { states -> UserProfileState( user = states[0] as User, @@ -355,6 +374,7 @@ internal class UserProfileViewModel( navigateMovie = states[6] as? TraktId, navigateEpisode = states[7] as? Pair, info = states[8] as? StringResource, + visibility = states[9] as ImmutableMap?, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/di/UserProfileModule.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/di/UserProfileModule.kt index ef1598d5d..30cbd4c21 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/di/UserProfileModule.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/di/UserProfileModule.kt @@ -36,7 +36,6 @@ internal val userProfileModule = module { UserProfileHistoryViewModel( userId = userId, getHistoryUseCase = get(), - collapsingManager = get(), ) } @@ -45,7 +44,6 @@ internal val userProfileModule = module { userId = userId, getFavoritesUseCase = get(), collectionStateProvider = get(), - collapsingManager = get(), ) } @@ -53,7 +51,6 @@ internal val userProfileModule = module { UserProfileSocialViewModel( userId = userId, getSocialUseCase = get(), - collapsingManager = get(), ) } @@ -61,7 +58,6 @@ internal val userProfileModule = module { UserProfileListsViewModel( userId = userId, getListsUseCase = get(), - collapsingManager = get(), ) } diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesState.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesState.kt index 69e3a1bdb..e625a254c 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesState.kt @@ -11,6 +11,5 @@ internal data class UserProfileFavoritesState( val items: ImmutableList? = null, val collection: UserCollectionState = UserCollectionState.Default, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesView.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesView.kt index 12d5431cd..1c3c6b965 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.userprofile.sections.favorites import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -20,9 +17,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource @@ -61,7 +55,6 @@ internal fun UserProfileFavoritesView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { onShowClick?.invoke(it) }, onMovieClick = { onMovieClick?.invoke(it) }, onMoreClick = onMoreClick, @@ -74,29 +67,17 @@ internal fun UserProfileFavoritesContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (Boolean) -> Unit = {}, onShowClick: (Show) -> Unit = {}, onMovieClick: (Movie) -> Unit = {}, onMoreClick: (() -> Unit)? = null, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_favorites), chevron = onMoreClick != null && !state.items.isNullOrEmpty(), - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -105,53 +86,51 @@ internal fun UserProfileFavoritesContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - collectionState = state.collection, - contentPadding = contentPadding, - onShowClick = onShowClick, - onMovieClick = onMovieClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + collectionState = state.collection, + contentPadding = contentPadding, + onShowClick = onShowClick, + onMovieClick = onMovieClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesViewModel.kt index 1919eb7c0..1a3ba78d0 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/favorites/UserProfileFavoritesViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -24,23 +23,17 @@ import tv.trakt.trakt.core.user.CollectionStateProvider import tv.trakt.trakt.core.user.UserCollectionState import tv.trakt.trakt.core.userprofile.UserProfileConfig.FAVORITES_SECTION_LIMIT import tv.trakt.trakt.core.userprofile.sections.favorites.usecases.GetUserProfileFavoritesUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class UserProfileFavoritesViewModel( private val userId: TraktId, private val getFavoritesUseCase: GetUserProfileFavoritesUseCase, private val collectionStateProvider: CollectionStateProvider, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = UserProfileFavoritesState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.USER_PROFILE_FAVORITES)) - - private var collapseJob: Job? = null init { loadData() @@ -80,31 +73,18 @@ internal class UserProfileFavoritesViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.USER_PROFILE_FAVORITES) - else -> collapsingManager.expand(CollapsingKey.USER_PROFILE_FAVORITES) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, loadingState, errorState, - collapseState, collectionStateProvider.stateFlow, ) { state -> UserProfileFavoritesState( items = state[0] as ImmutableList?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, - collection = state[4] as UserCollectionState, + collection = state[3] as UserCollectionState, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryState.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryState.kt index 4bf54e2d6..3f8aa2c83 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryState.kt @@ -9,6 +9,5 @@ import tv.trakt.trakt.core.home.sections.activity.model.HomeActivityItem internal data class UserProfileHistoryState( val items: ImmutableList? = null, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryView.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryView.kt index 8ec763902..7e51e3bb2 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.userprofile.sections.history import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -21,9 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource @@ -64,7 +58,6 @@ internal fun UserProfileHistoryView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onShowClick = { onShowClick?.invoke(it) }, onEpisodeClick = { show, episode -> onEpisodeClick?.invoke(show, episode) }, onMovieClick = { onMovieClick?.invoke(it) }, @@ -81,30 +74,18 @@ internal fun UserProfileHistoryContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (Boolean) -> Unit = {}, onShowClick: (Show) -> Unit = {}, onEpisodeClick: (Show, Episode) -> Unit = { _, _ -> }, onMovieClick: (Movie) -> Unit = {}, onMoreClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( verticalArrangement = spacedBy(TraktTheme.spacing.mainRowHeaderSpace), - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_history), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .fillMaxWidth() .padding(headerPadding) @@ -113,51 +94,49 @@ internal fun UserProfileHistoryContent( }, ) - if (state.collapsed != true) { - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = "${stringResource( - R.string.error_text_unexpected_error_short, - )}\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = "${stringResource( + R.string.error_text_unexpected_error_short, + )}\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onShowClick = onShowClick, - onEpisodeClick = onEpisodeClick, - onMovieClick = onMovieClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onShowClick = onShowClick, + onEpisodeClick = onEpisodeClick, + onMovieClick = onMovieClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryViewModel.kt index b7b79df94..0e4130760 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/history/UserProfileHistoryViewModel.kt @@ -3,7 +3,6 @@ package tv.trakt.trakt.core.userprofile.sections.history import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -21,22 +20,16 @@ import tv.trakt.trakt.common.model.pagination.Pagination import tv.trakt.trakt.core.home.sections.activity.model.HomeActivityItem import tv.trakt.trakt.core.userprofile.UserProfileConfig.HORIZONTAL_SECTION_LIMIT import tv.trakt.trakt.core.userprofile.sections.history.usecases.GetUserProfileHistoryUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class UserProfileHistoryViewModel( private val userId: TraktId, private val getHistoryUseCase: GetUserProfileHistoryUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = UserProfileHistoryState() private val itemsState = MutableStateFlow(initialState.items) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.USER_PROFILE_HISTORY)) - - private var collapseJob: Job? = null init { loadData() @@ -71,29 +64,16 @@ internal class UserProfileHistoryViewModel( } } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.USER_PROFILE_HISTORY) - else -> collapsingManager.expand(CollapsingKey.USER_PROFILE_HISTORY) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, loadingState, errorState, - collapseState, ) { state -> UserProfileHistoryState( items = state[0] as ImmutableList?, loading = state[1] as LoadingState, error = state[2] as Exception?, - collapsed = state[3] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsState.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsState.kt index 6650fd933..11f3673b9 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsState.kt @@ -12,6 +12,5 @@ internal data class UserProfileListsState( val items: ImmutableList? = null, val filter: PersonalListType = PersonalListType.Personal, val loading: LoadingState = Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsView.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsView.kt index 29c9ca02e..c3d3f5310 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.userprofile.sections.lists import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -22,9 +19,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource @@ -63,7 +57,6 @@ internal fun UserProfileListsView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onFilterClick = viewModel::setFilter, onListClick = onListClick, onMoreClick = { @@ -81,28 +74,16 @@ internal fun UserProfileListsContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (Boolean) -> Unit = {}, onFilterClick: (PersonalListType) -> Unit = {}, onListClick: (CustomList) -> Unit = {}, onMoreClick: () -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_user_lists), chevron = !state.items.isNullOrEmpty() || state.loading != Done, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding) .onClick(enabled = state.loading == Done && !state.items.isNullOrEmpty()) { @@ -110,59 +91,57 @@ internal fun UserProfileListsContent( }, ) - if (state.collapsed != true) { - ListsFilters( - options = listOf(PersonalListType.Personal, PersonalListType.Collaborations).toImmutableList(), - selected = state.filter, - paddingHorizontal = headerPadding, - paddingVertical = PaddingValues(top = 13.dp, bottom = 16.dp), - onClick = onFilterClick, - ) + ListsFilters( + options = listOf(PersonalListType.Personal, PersonalListType.Collaborations).toImmutableList(), + selected = state.filter, + paddingHorizontal = headerPadding, + paddingVertical = PaddingValues(top = 13.dp, bottom = 16.dp), + onClick = onFilterClick, + ) - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onListClick = onListClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onListClick = onListClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsViewModel.kt index a8281d503..b66c2c853 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/lists/UserProfileListsViewModel.kt @@ -26,13 +26,10 @@ import tv.trakt.trakt.core.lists.sections.personal.model.PersonalListType.Liked import tv.trakt.trakt.core.lists.sections.personal.model.PersonalListType.Personal import tv.trakt.trakt.core.userprofile.UserProfileConfig.LISTS_SECTION_LIMIT import tv.trakt.trakt.core.userprofile.sections.lists.usecases.GetUserProfileListsUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class UserProfileListsViewModel( private val userId: TraktId, private val getListsUseCase: GetUserProfileListsUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = UserProfileListsState() @@ -40,10 +37,8 @@ internal class UserProfileListsViewModel( private val filterState = MutableStateFlow(initialState.filter) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.USER_PROFILE_LISTS)) private var loadDataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -88,31 +83,18 @@ internal class UserProfileListsViewModel( loadData() } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.USER_PROFILE_LISTS) - else -> collapsingManager.expand(CollapsingKey.USER_PROFILE_LISTS) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, filterState, loadingState, errorState, - collapseState, ) { state -> UserProfileListsState( items = state[0] as ImmutableList?, filter = state[1] as PersonalListType, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialState.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialState.kt index 227c0f13f..bb2a5af7e 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialState.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialState.kt @@ -11,6 +11,5 @@ internal data class UserProfileSocialState( val items: ImmutableList? = null, val filter: SocialFilter = SocialFilter.FOLLOWING, val loading: LoadingState = LoadingState.Idle, - val collapsed: Boolean? = null, val error: Exception? = null, ) diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialView.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialView.kt index 191ae2a12..d9b9028c0 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialView.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialView.kt @@ -3,9 +3,6 @@ package tv.trakt.trakt.core.userprofile.sections.social import androidx.compose.animation.Crossfade -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column @@ -22,9 +19,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.painterResource @@ -63,7 +57,6 @@ internal fun UserProfileSocialView( modifier = modifier, headerPadding = headerPadding, contentPadding = contentPadding, - onCollapse = viewModel::setCollapsed, onFilterClick = viewModel::setFilter, onUserClick = { onUserClick?.invoke(it) }, ) @@ -75,82 +68,68 @@ internal fun UserProfileSocialContent( modifier: Modifier = Modifier, headerPadding: PaddingValues = PaddingValues(), contentPadding: PaddingValues = PaddingValues(), - onCollapse: (Boolean) -> Unit = {}, onFilterClick: (SocialFilter) -> Unit = {}, onUserClick: (User) -> Unit = {}, ) { - var animateCollapse by rememberSaveable { mutableStateOf(false) } - Column( - modifier = modifier - .animateContentSize( - animationSpec = if (animateCollapse) spring() else snap(), - ), + modifier = modifier, ) { TraktSectionHeader( title = stringResource(R.string.list_title_social), chevron = false, - collapsed = state.collapsed ?: false, - onCollapseClick = { - animateCollapse = true - val current = (state.collapsed ?: false) - onCollapse(!current) - }, modifier = Modifier .padding(headerPadding), ) - if (state.collapsed != true) { - ContentFilters( - state = state, - headerPadding = headerPadding, - onFilterClick = onFilterClick, - ) + ContentFilters( + state = state, + headerPadding = headerPadding, + onFilterClick = onFilterClick, + ) - Crossfade( - targetState = state.loading, - animationSpec = tween(200), - ) { loading -> - when (loading) { - Idle, Loading -> { - ContentLoadingList( - visible = loading.isLoading, - contentPadding = contentPadding, - ) - } + Crossfade( + targetState = state.loading, + animationSpec = tween(200), + ) { loading -> + when (loading) { + Idle, Loading -> { + ContentLoadingList( + visible = loading.isLoading, + contentPadding = contentPadding, + ) + } - Done -> { - when { - state.error != null -> { - Text( - text = "${ - stringResource( - R.string.error_text_unexpected_error_short, - ) - }\n\n${state.error}", - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.meta, - maxLines = 10, - modifier = Modifier.padding(contentPadding), - ) - } + Done -> { + when { + state.error != null -> { + Text( + text = "${ + stringResource( + R.string.error_text_unexpected_error_short, + ) + }\n\n${state.error}", + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.meta, + maxLines = 10, + modifier = Modifier.padding(contentPadding), + ) + } - state.items?.isEmpty() == true -> { - Text( - text = stringResource(R.string.list_placeholder_empty), - color = TraktTheme.colors.textSecondary, - style = TraktTheme.typography.heading6, - modifier = Modifier.padding(contentPadding), - ) - } + state.items?.isEmpty() == true -> { + Text( + text = stringResource(R.string.list_placeholder_empty), + color = TraktTheme.colors.textSecondary, + style = TraktTheme.typography.heading6, + modifier = Modifier.padding(contentPadding), + ) + } - else -> { - ContentList( - listItems = (state.items ?: emptyList()).toImmutableList(), - contentPadding = contentPadding, - onUserClick = onUserClick, - ) - } + else -> { + ContentList( + listItems = (state.items ?: emptyList()).toImmutableList(), + contentPadding = contentPadding, + onUserClick = onUserClick, + ) } } } diff --git a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialViewModel.kt b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialViewModel.kt index 171c8e8eb..5efd58b7f 100644 --- a/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialViewModel.kt +++ b/app/src/main/java/tv/trakt/trakt/core/userprofile/sections/social/UserProfileSocialViewModel.kt @@ -22,13 +22,10 @@ import tv.trakt.trakt.core.profile.sections.social.model.SocialFilter import tv.trakt.trakt.core.profile.sections.social.model.SocialFilter.FOLLOWERS import tv.trakt.trakt.core.profile.sections.social.model.SocialFilter.FOLLOWING import tv.trakt.trakt.core.userprofile.sections.social.usecases.GetUserProfileSocialUseCase -import tv.trakt.trakt.helpers.collapsing.CollapsingManager -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey internal class UserProfileSocialViewModel( private val userId: TraktId, private val getSocialUseCase: GetUserProfileSocialUseCase, - private val collapsingManager: CollapsingManager, ) : ViewModel() { private val initialState = UserProfileSocialState() @@ -36,10 +33,8 @@ internal class UserProfileSocialViewModel( private val filterState = MutableStateFlow(initialState.filter) private val loadingState = MutableStateFlow(initialState.loading) private val errorState = MutableStateFlow(initialState.error) - private val collapseState = MutableStateFlow(collapsingManager.isCollapsed(CollapsingKey.USER_PROFILE_SOCIAL)) private var loadDataJob: Job? = null - private var collapseJob: Job? = null init { loadData() @@ -76,31 +71,18 @@ internal class UserProfileSocialViewModel( loadData() } - fun setCollapsed(collapsed: Boolean) { - collapseState.update { collapsed } - collapseJob?.cancel() - collapseJob = viewModelScope.launch { - when { - collapsed -> collapsingManager.collapse(CollapsingKey.USER_PROFILE_SOCIAL) - else -> collapsingManager.expand(CollapsingKey.USER_PROFILE_SOCIAL) - } - } - } - @Suppress("UNCHECKED_CAST") val state = combine( itemsState, filterState, loadingState, errorState, - collapseState, ) { state -> UserProfileSocialState( items = state[0] as ImmutableList?, filter = state[1] as SocialFilter, loading = state[2] as LoadingState, error = state[3] as Exception?, - collapsed = state[4] as Boolean, ) }.stateIn( scope = viewModelScope, diff --git a/app/src/main/java/tv/trakt/trakt/helpers/collapsing/CollapsingManager.kt b/app/src/main/java/tv/trakt/trakt/helpers/collapsing/CollapsingManager.kt deleted file mode 100644 index 41705a61a..000000000 --- a/app/src/main/java/tv/trakt/trakt/helpers/collapsing/CollapsingManager.kt +++ /dev/null @@ -1,43 +0,0 @@ -package tv.trakt.trakt.helpers.collapsing - -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey - -/** - * Manages the collapsed/expanded state of various sections in the app. - */ -internal interface CollapsingManager { - /** - * Returns true if the section identified by [key] is collapsed, false otherwise. - */ - fun isCollapsed(key: CollapsingKey): Boolean - - /** - * Returns true if the section identified by [keyId] is collapsed, false otherwise. - */ - fun isCollapsed(keyId: String): Boolean - - /** - * Marks the section identified by [key] as collapsed. - */ - suspend fun collapse(key: CollapsingKey) - - /** - * Marks the section identified by [keyId] as collapsed. - */ - suspend fun collapse(keyId: String) - - /** - * Marks the section identified by [key] as expanded. - */ - suspend fun expand(key: CollapsingKey) - - /** - * Marks the section identified by [keyId] as expanded. - */ - suspend fun expand(keyId: String) - - /** - * Clears all collapsing states. - */ - suspend fun clear() -} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/collapsing/DefaultCollapsingManager.kt b/app/src/main/java/tv/trakt/trakt/helpers/collapsing/DefaultCollapsingManager.kt deleted file mode 100644 index ff8608000..000000000 --- a/app/src/main/java/tv/trakt/trakt/helpers/collapsing/DefaultCollapsingManager.kt +++ /dev/null @@ -1,85 +0,0 @@ -package tv.trakt.trakt.helpers.collapsing - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import tv.trakt.trakt.helpers.collapsing.model.CollapsingKey - -internal class DefaultCollapsingManager( - private val dataStore: DataStore, -) : CollapsingManager { - private val cache = mutableMapOf() - private val mutex = Mutex() - - init { - runBlocking { - val prefs = dataStore.data.first() - CollapsingKey.entries.forEach { key -> - val preferenceKey = booleanPreferencesKey(key.preferenceKey) - cache[key.preferenceKey] = prefs[preferenceKey] ?: false - } - } - } - - override fun isCollapsed(key: CollapsingKey): Boolean { - return cache[key.preferenceKey] ?: false - } - - override suspend fun collapse(key: CollapsingKey) { - mutex.withLock { - cache[key.preferenceKey] = true - dataStore.edit { prefs -> - val preferenceKey = booleanPreferencesKey(key.preferenceKey) - prefs[preferenceKey] = true - } - } - } - - override suspend fun expand(key: CollapsingKey) { - mutex.withLock { - cache[key.preferenceKey] = false - dataStore.edit { prefs -> - val preferenceKey = booleanPreferencesKey(key.preferenceKey) - prefs[preferenceKey] = false - } - } - } - - override fun isCollapsed(keyId: String): Boolean { - return cache[keyId] ?: false - } - - override suspend fun collapse(keyId: String) { - mutex.withLock { - cache[keyId] = true - dataStore.edit { prefs -> - val preferenceKey = booleanPreferencesKey(keyId) - prefs[preferenceKey] = true - } - } - } - - override suspend fun expand(keyId: String) { - mutex.withLock { - cache[keyId] = false - dataStore.edit { prefs -> - val preferenceKey = booleanPreferencesKey(keyId) - prefs[preferenceKey] = false - } - } - } - - override suspend fun clear() { - mutex.withLock { - cache.clear() - dataStore.edit { store -> - store.clear() - } - } - } -} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/collapsing/model/CollapsingKey.kt b/app/src/main/java/tv/trakt/trakt/helpers/collapsing/model/CollapsingKey.kt deleted file mode 100644 index 5904a89fa..000000000 --- a/app/src/main/java/tv/trakt/trakt/helpers/collapsing/model/CollapsingKey.kt +++ /dev/null @@ -1,100 +0,0 @@ -package tv.trakt.trakt.helpers.collapsing.model - -internal enum class CollapsingKey( - val preferenceKey: String, -) { - // Home Screen - HOME_MEDIA_UP_NEXT("key_home_media_up_next"), - HOME_MEDIA_START_WATCHING("key_home_media_start_watching"), - HOME_MEDIA_UPCOMING("key_home_media_personal"), - HOME_MEDIA_HISTORY("key_home_media_history"), - HOME_MEDIA_SOCIAL("key_home_media_social"), - - HOME_SHOWS_UP_NEXT("key_home_shows_up_next"), - HOME_SHOWS_START_WATCHING("key_home_shows_start_watching"), - HOME_SHOWS_UPCOMING("key_home_shows_personal"), - HOME_SHOWS_HISTORY("key_home_shows_history"), - HOME_SHOWS_SOCIAL("key_home_shows_social"), - - HOME_MOVIES_UP_NEXT("key_home_movies_up_next"), - HOME_MOVIES_START_WATCHING("key_home_movies_start_watching"), - HOME_MOVIES_UPCOMING("key_home_movies_personal"), - HOME_MOVIES_HISTORY("key_home_movies_history"), - HOME_MOVIES_SOCIAL("key_home_movies_social"), - - // Discover Screen - DISCOVER_MEDIA_TRENDING("key_discover_media_trending"), - DISCOVER_MEDIA_POPULAR("key_discover_media_popular"), - DISCOVER_MEDIA_ANTICIPATED("key_discover_media_anticipated"), - DISCOVER_MEDIA_RECOMMENDED("key_discover_media_recommended"), - - DISCOVER_SHOWS_TRENDING("key_discover_shows_trending"), - DISCOVER_SHOWS_POPULAR("key_discover_shows_popular"), - DISCOVER_SHOWS_ANTICIPATED("key_discover_shows_anticipated"), - DISCOVER_SHOWS_RECOMMENDED("key_discover_shows_recommended"), - - DISCOVER_MOVIES_TRENDING("key_discover_movies_trending"), - DISCOVER_MOVIES_POPULAR("key_discover_movies_popular"), - DISCOVER_MOVIES_ANTICIPATED("key_discover_movies_anticipated"), - DISCOVER_MOVIES_RECOMMENDED("key_discover_movies_recommended"), - - // Lists Screen - LISTS_MEDIA_WATCHLIST("key_lists_media_watchlist"), - LISTS_SHOWS_WATCHLIST("key_lists_shows_watchlist"), - LISTS_MOVIES_WATCHLIST("key_lists_movies_watchlist"), - - LISTS_MEDIA_PERSONAL("key_lists_media_personal"), - LISTS_SHOWS_PERSONAL("key_lists_shows_personal"), - LISTS_MOVIES_PERSONAL("key_lists_movies_personal"), - - LISTS_MEDIA_LIKED("key_lists_media_liked"), - LISTS_SHOWS_LIKED("key_lists_shows_liked"), - LISTS_MOVIES_LIKED("key_lists_movies_liked"), - - LISTS_MEDIA_COLLABORATIONS("key_lists_media_collaborations"), - LISTS_SHOWS_COLLABORATIONS("key_lists_shows_collaborations"), - LISTS_MOVIES_COLLABORATIONS("key_lists_movies_collaborations"), - - // Movie Details Screen - MOVIE_WHERE_TO_WATCH("key_movie_where_to_watch"), - MOVIE_SENTIMENT("key_movie_sentiment"), - MOVIE_ACTORS("key_movie_actors"), - MOVIE_EXTRAS("key_movie_extras"), - MOVIE_RELATED("key_movie_related"), - MOVIE_LISTS("key_movie_lists"), - MOVIE_COMMENTS("key_movie_comments"), - MOVIE_HISTORY("key_movie_history"), - MOVIE_TRIVIA("key_movie_trivia"), - - // Episode Details Screen - EPISODE_WHERE_TO_WATCH("key_episode_where_to_watch"), - EPISODE_COMMENTS("key_episode_comments"), - EPISODE_ACTORS("key_episode_actors"), - EPISODE_SEASON("key_episode_season"), - EPISODE_RELATED("key_episode_related"), - - // Show Details Screen - SHOW_WHERE_TO_WATCH("key_show_where_to_watch"), - SHOW_SENTIMENT("key_show_sentiment"), - SHOW_COMMENTS("key_show_comments"), - SHOW_ACTORS("key_show_actors"), - SHOW_SEASONS("key_show_seasons"), - SHOW_EXTRAS("key_show_extras"), - SHOW_RELATED("key_show_related"), - SHOW_LISTS("key_show_lists"), - SHOW_HISTORY("key_show_history"), - SHOW_TRIVIA("key_show_trivia"), - - // Profile Screen - PROFILE_HISTORY("key_profile_history"), - PROFILE_FAVORITES("key_profile_favorites"), - PROFILE_PROGRESS("key_profile_progress"), - PROFILE_LIBRARY("key_profile_library"), - PROFILE_SOCIAL("key_profile_social"), - - // User Profile Screen - USER_PROFILE_HISTORY("key_user_profile_history"), - USER_PROFILE_FAVORITES("key_user_profile_favorites"), - USER_PROFILE_SOCIAL("key_user_profile_social"), - USER_PROFILE_LISTS("key_user_profile_lists"), -} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenSheet.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenSheet.kt new file mode 100644 index 000000000..b98009065 --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenSheet.kt @@ -0,0 +1,51 @@ +package tv.trakt.trakt.helpers.editscreen + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue +import androidx.compose.material3.rememberBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.ui.components.TraktBottomSheet +import kotlin.random.Random.Default.nextInt + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun EditScreenSheet( + state: SheetState = rememberBottomSheetState( + initialValue = SheetValue.Hidden, + enabledValues = setOf(SheetValue.Hidden, SheetValue.Expanded), + ), + active: Boolean, + enabledValues: Set, + onDismiss: () -> Unit, +) { + val viewModelKey = remember(active, enabledValues) { + nextInt().toString() + } + + if (active) { + TraktBottomSheet( + sheetState = state, + onDismiss = { + onDismiss() + }, + ) { + EditScreenView( + viewModel = koinViewModel( + key = viewModelKey, + parameters = { parametersOf(enabledValues) }, + ), + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(bottom = 24.dp), + ) + } + } +} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenState.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenState.kt new file mode 100644 index 000000000..aa2411d2c --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenState.kt @@ -0,0 +1,10 @@ +package tv.trakt.trakt.helpers.editscreen + +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableMap +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey + +@Immutable +internal data class EditScreenState( + val values: ImmutableMap? = null, +) diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenView.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenView.kt new file mode 100644 index 000000000..e13103e98 --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenView.kt @@ -0,0 +1,174 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package tv.trakt.trakt.helpers.editscreen + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.collections.immutable.toImmutableMap +import tv.trakt.trakt.common.helpers.extensions.onClick +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import tv.trakt.trakt.resources.R +import tv.trakt.trakt.ui.components.TraktHeader +import tv.trakt.trakt.ui.theme.TraktTheme + +@Composable +internal fun EditScreenView( + viewModel: EditScreenViewModel, + modifier: Modifier = Modifier, +) { + val state by viewModel.state.collectAsStateWithLifecycle() + + EditScreenView( + state = state, + onToggle = viewModel::toggle, + modifier = modifier.fillMaxWidth(), + ) +} + +@Composable +internal fun EditScreenView( + state: EditScreenState, + modifier: Modifier = Modifier, + onToggle: (EditScreenKey) -> Unit = {}, +) { + Column( + verticalArrangement = spacedBy(16.dp), + modifier = modifier, + ) { + TraktHeader( + title = stringResource(R.string.text_edit_screen), + subtitle = stringResource(R.string.text_edit_screen_description), + modifier = Modifier.padding(bottom = 8.dp), + ) + + Column( + verticalArrangement = spacedBy(10.dp), + ) { + state.values?.let { + it.forEach { (key, visible) -> + SectionToggleRow( + title = stringResource(key.displayStringRes), + visible = visible, + onToggle = { onToggle(key) }, + ) + } + } + } + } +} + +@Composable +private fun SectionToggleRow( + title: String, + visible: Boolean, + onToggle: () -> Unit, +) { + val animatedAlpha: Float by animateFloatAsState( + targetValue = if (visible) 1f else 0.25F, + animationSpec = tween(100), + label = "alpha", + ) + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .alpha(animatedAlpha) + .height(42.dp) + .fillMaxWidth() + .background( + color = TraktTheme.colors.dialogOnContainer, + shape = RoundedCornerShape(16.dp), + ) + .padding(horizontal = 16.dp) + .onClick( + throttle = false, + onClick = onToggle, + ), + ) { + Text( + text = title, + color = TraktTheme.colors.textPrimary, + style = TraktTheme.typography.buttonSecondary, + maxLines = 1, + overflow = Ellipsis, + modifier = Modifier + .weight(1F) + .padding(end = 16.dp), + ) + + Icon( + painter = painterResource( + when (visible) { + true -> R.drawable.ic_eye + false -> R.drawable.ic_eye_off + }, + ), + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = TraktTheme.colors.textPrimary, + ) + } +} + +@Preview( + device = "id:pixel_5", + showBackground = true, + backgroundColor = 0xFF212427, +) +@Composable +private fun Preview() { + TraktTheme { + EditScreenView( + state = EditScreenState( + values = mapOf( + EditScreenKey.DiscoverPopular to true, + EditScreenKey.DiscoverTrending to true, + ).toImmutableMap(), + ), + ) + } +} + +@Preview( + device = "id:pixel_5", + showBackground = true, + backgroundColor = 0xFF212427, +) +@Composable +private fun PreviewPartial() { + TraktTheme { + EditScreenView( + state = EditScreenState( + values = mapOf( + EditScreenKey.DiscoverTrending to false, + EditScreenKey.DiscoverAnticipated to true, + ).toImmutableMap(), + ), + ) + } +} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenViewModel.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenViewModel.kt new file mode 100644 index 000000000..dd1c159d2 --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/EditScreenViewModel.kt @@ -0,0 +1,66 @@ +package tv.trakt.trakt.helpers.editscreen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.collections.immutable.toImmutableMap +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import tv.trakt.trakt.helpers.editscreen.data.EditScreenManager +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey + +internal class EditScreenViewModel( + enabledValues: Set, + private val editScreenManager: EditScreenManager, +) : ViewModel() { + val initialState = EditScreenState( + values = enabledValues + .associateWith { editScreenManager.isVisible(setOf(it)) } + .toImmutableMap(), + ) + + private val valuesState = MutableStateFlow(initialState.values) + + init { + editScreenManager.observe(enabledValues) + .onEach { values -> + valuesState.update { + values.toImmutableMap() + } + }.launchIn(viewModelScope) + } + + fun toggle(key: EditScreenKey) { + val currentValues = valuesState.value ?: return + val isVisible = currentValues[key] ?: return + + viewModelScope.launch { + if (isVisible) { + // Only hide if there's more than 1 visible, to prevent hiding all sections. + val visibleCount = currentValues.values.count { it } + if (visibleCount > 1) { + editScreenManager.hide(key) + } + } else { + editScreenManager.show(key) + } + } + } + + val state = combine( + valuesState, + ) { state -> + EditScreenState( + values = state[0], + ) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = EditScreenState(), + ) +} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/DefaultEditScreenManager.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/DefaultEditScreenManager.kt new file mode 100644 index 000000000..beae677e8 --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/DefaultEditScreenManager.kt @@ -0,0 +1,73 @@ +package tv.trakt.trakt.helpers.editscreen.data + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey +import java.util.concurrent.ConcurrentHashMap + +internal class DefaultEditScreenManager( + private val dataStore: DataStore, + scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), +) : EditScreenManager { + private val cache = ConcurrentHashMap() + private val mutex = Mutex() + + init { + scope.launch { + val prefs = dataStore.data.first() + EditScreenKey.entries.forEach { key -> + cache[key.preferenceKey] = prefs[booleanPreferencesKey(key.preferenceKey)] ?: true + } + } + } + + override fun isVisible(keys: Set): Boolean { + return keys.all { key -> + cache[key.preferenceKey] ?: true + } + } + + override fun observe(keys: Set): Flow> = + dataStore.data + .map { prefs -> + keys.associateWith { key -> prefs[booleanPreferencesKey(key.preferenceKey)] ?: true } + } + .distinctUntilChanged() + + override suspend fun hide(key: EditScreenKey) { + mutex.withLock { + cache[key.preferenceKey] = false + dataStore.edit { prefs -> + prefs[booleanPreferencesKey(key.preferenceKey)] = false + } + } + } + + override suspend fun show(key: EditScreenKey) { + mutex.withLock { + cache[key.preferenceKey] = true + dataStore.edit { prefs -> + prefs[booleanPreferencesKey(key.preferenceKey)] = true + } + } + } + + override suspend fun clear() { + mutex.withLock { + cache.clear() + dataStore.edit { it.clear() } + } + } +} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/EditScreenManager.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/EditScreenManager.kt new file mode 100644 index 000000000..71cab81a1 --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/EditScreenManager.kt @@ -0,0 +1,16 @@ +package tv.trakt.trakt.helpers.editscreen.data + +import kotlinx.coroutines.flow.Flow +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey + +internal interface EditScreenManager { + fun isVisible(keys: Set): Boolean + + fun observe(keys: Set): Flow> + + suspend fun hide(key: EditScreenKey) + + suspend fun show(key: EditScreenKey) + + suspend fun clear() +} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/model/EditScreenKey.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/model/EditScreenKey.kt new file mode 100644 index 000000000..d58066df0 --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/data/model/EditScreenKey.kt @@ -0,0 +1,136 @@ +package tv.trakt.trakt.helpers.editscreen.data.model + +import kotlinx.collections.immutable.toImmutableSet +import tv.trakt.trakt.resources.R + +internal enum class EditScreenKey( + val preferenceKey: String, + val displayStringRes: Int, +) { + HomeUpNext("key_edit_home_up_next", R.string.list_title_up_next), + HomeWatchlist("key_edit_home_watchlist", R.string.list_title_start_watching), + HomeStreaks("key_edit_home_streaks", R.string.label_stats_current_streak), + HomeUpcoming("key_edit_home_upcoming", R.string.page_title_calendar), + HomeHistory("key_edit_home_history", R.string.list_title_history), + HomeSocial("key_edit_home_social", R.string.list_title_social_activity), + + ProfileHistory("key_edit_profile_history", R.string.list_title_history), + ProfileProgress("key_edit_profile_progress", R.string.list_title_progress), + ProfileFavorites("key_edit_profile_favorites", R.string.list_title_favorites), + ProfileLibrary("key_edit_profile_library", R.string.list_title_library), + ProfileSocial("key_edit_profile_social", R.string.list_title_social), + + ListsWatchlist("key_edit_lists_watchlist", R.string.list_title_watchlist), + ListsMyLists("key_edit_lists_my_lists", R.string.list_title_personal_lists), + + ShowWhereToWatch("key_edit_show_where_to_watch", R.string.page_title_where_to_watch), + ShowSentiment("key_edit_show_sentiment", R.string.header_community_sentiment), + ShowReviews("key_edit_show_reviews", R.string.list_title_comments), + ShowActors("key_edit_show_actors", R.string.list_title_actors), + ShowSeasons("key_edit_show_seasons", R.string.list_title_seasons), + ShowExtras("key_edit_show_extras", R.string.list_title_extras), + ShowRelated("key_edit_show_related", R.string.list_title_related_shows), + ShowLists("key_edit_show_lists", R.string.list_title_popular_lists), + ShowHistory("key_edit_show_history", R.string.list_title_history), + ShowTrivia("key_edit_show_trivia", R.string.list_title_trivia), + + MovieWhereToWatch("key_edit_movie_where_to_watch", R.string.page_title_where_to_watch), + MovieSentiment("key_edit_movie_sentiment", R.string.header_community_sentiment), + MovieReviews("key_edit_movie_reviews", R.string.list_title_comments), + MovieActors("key_edit_movie_actors", R.string.list_title_actors), + MovieExtras("key_edit_movie_extras", R.string.list_title_extras), + MovieRelated("key_edit_movie_related", R.string.list_title_related_movies), + MovieLists("key_edit_movie_lists", R.string.list_title_popular_lists), + MovieHistory("key_edit_movie_history", R.string.list_title_history), + MovieTrivia("key_edit_movie_trivia", R.string.list_title_trivia), + + EpisodeWhereToWatch("key_edit_episode_where_to_watch", R.string.page_title_where_to_watch), + EpisodeReviews("key_edit_episode_reviews", R.string.list_title_comments), + EpisodeActors("key_edit_episode_actors", R.string.list_title_actors), + EpisodeSeason("key_edit_episode_season", R.string.list_title_seasons), + EpisodeRelated("key_edit_episode_related", R.string.list_title_related_shows), + EpisodeHistory("key_edit_episode_history", R.string.list_title_history), + + UserProfileHistory("key_edit_user_profile_history", R.string.list_title_history), + UserProfileFavorites("key_edit_user_profile_favorites", R.string.list_title_favorites), + UserProfileLists("key_edit_user_profile_lists", R.string.list_title_user_lists), + UserProfileSocial("key_edit_user_profile_social", R.string.list_title_social), + + DiscoverTrending("key_edit_discover_trending", R.string.list_title_trending), + DiscoverPopular("key_edit_discover_popular", R.string.list_title_most_popular), + DiscoverAnticipated("key_edit_discover_anticipated", R.string.list_title_most_anticipated), + DiscoverRecommended("key_edit_discover_recommended", R.string.list_title_recommended), + ; + + companion object { + val DiscoverKeys = setOf( + DiscoverTrending, + DiscoverAnticipated, + DiscoverPopular, + DiscoverRecommended, + ).toImmutableSet() + + val HomeKeys = setOf( + HomeUpNext, + HomeWatchlist, + HomeStreaks, + HomeUpcoming, + HomeHistory, + HomeSocial, + ).toImmutableSet() + + val ProfileKeys = setOf( + ProfileHistory, + ProfileProgress, + ProfileFavorites, + ProfileLibrary, + ProfileSocial, + ).toImmutableSet() + + val ListsKeys = setOf( + ListsWatchlist, + ListsMyLists, + ).toImmutableSet() + + val ShowKeys = setOf( + ShowWhereToWatch, + ShowSentiment, + ShowReviews, + ShowActors, + ShowSeasons, + ShowExtras, + ShowRelated, + ShowLists, + ShowHistory, + ShowTrivia, + ).toImmutableSet() + + val MovieKeys = setOf( + MovieWhereToWatch, + MovieSentiment, + MovieReviews, + MovieActors, + MovieExtras, + MovieRelated, + MovieLists, + MovieHistory, + MovieTrivia, + ).toImmutableSet() + + val EpisodeKeys = setOf( + EpisodeWhereToWatch, + EpisodeReviews, + EpisodeActors, + EpisodeSeason, + EpisodeRelated, + EpisodeHistory, + ).toImmutableSet() + + val UserProfileKeys = setOf( + UserProfileHistory, + UserProfileFavorites, + UserProfileLists, + UserProfileSocial, + ).toImmutableSet() + } +} diff --git a/app/src/main/java/tv/trakt/trakt/helpers/editscreen/di/EditScreenModule.kt b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/di/EditScreenModule.kt new file mode 100644 index 000000000..1d94ea1ae --- /dev/null +++ b/app/src/main/java/tv/trakt/trakt/helpers/editscreen/di/EditScreenModule.kt @@ -0,0 +1,15 @@ +package tv.trakt.trakt.helpers.editscreen.di + +import org.koin.core.module.dsl.viewModel +import org.koin.dsl.module +import tv.trakt.trakt.helpers.editscreen.EditScreenViewModel +import tv.trakt.trakt.helpers.editscreen.data.model.EditScreenKey + +internal val editScreenModule = module { + viewModel { (values: Set) -> + EditScreenViewModel( + enabledValues = values, + editScreenManager = get(), + ) + } +} diff --git a/app/src/main/java/tv/trakt/trakt/ui/components/TraktSectionHeader.kt b/app/src/main/java/tv/trakt/trakt/ui/components/TraktSectionHeader.kt index f30d28476..a008b37a9 100644 --- a/app/src/main/java/tv/trakt/trakt/ui/components/TraktSectionHeader.kt +++ b/app/src/main/java/tv/trakt/trakt/ui/components/TraktSectionHeader.kt @@ -1,7 +1,5 @@ package tv.trakt.trakt.ui.components -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Row @@ -10,14 +8,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import tv.trakt.trakt.common.helpers.extensions.onClick import tv.trakt.trakt.resources.R import tv.trakt.trakt.ui.theme.TraktTheme @@ -28,17 +23,8 @@ internal fun TraktSectionHeader( subtitle: String? = null, maxSubtitleLength: Int = Int.MAX_VALUE, chevron: Boolean = true, - collapsed: Boolean = false, - collapsable: Boolean = true, extraIcon: @Composable (() -> Unit)? = null, - onCollapseClick: () -> Unit = {}, ) { - val animateRotation by animateFloatAsState( - targetValue = if (collapsed) -180F else 0F, - animationSpec = tween(200), - label = "rotation", - ) - Row( modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -51,19 +37,16 @@ internal fun TraktSectionHeader( ) { TraktHeader( title = title, - subtitle = when { - !collapsed && subtitle != null -> subtitle - else -> null - }, + subtitle = subtitle, maxSubtitleLength = maxSubtitleLength, modifier = Modifier.weight(1F, false), ) - if (extraIcon != null && !collapsed) { + if (extraIcon != null) { extraIcon() } - if (!collapsed && chevron) { + if (chevron) { Icon( painter = painterResource(R.drawable.ic_chevron_right), contentDescription = null, @@ -72,19 +55,6 @@ internal fun TraktSectionHeader( ) } } - - if (collapsable) { - Icon( - painter = painterResource(R.drawable.ic_arrow_dropdown), - contentDescription = null, - tint = TraktTheme.colors.textSecondary, - modifier = Modifier - .padding(start = 48.dp) - .rotate(animateRotation) - .size(16.dp) - .onClick(onClick = onCollapseClick), - ) - } } } @@ -99,7 +69,6 @@ private fun Preview() { TraktTheme { TraktSectionHeader( title = "Trending Movies", - collapsed = false, ) } } @@ -116,7 +85,6 @@ private fun Preview2() { TraktSectionHeader( title = "Trending Movies", subtitle = "Subtitle Lorem Ipsum", - collapsed = false, ) } } @@ -133,7 +101,6 @@ private fun Preview3() { TraktSectionHeader( title = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec suscipit auctor dui.", subtitle = "Subtitle Lorem Ipsum", - collapsed = false, extraIcon = { Icon( painter = painterResource(R.drawable.ic_more_vertical), diff --git a/app/src/main/java/tv/trakt/trakt/ui/components/headerbar/HeaderBar.kt b/app/src/main/java/tv/trakt/trakt/ui/components/headerbar/HeaderBar.kt index 0477a6571..516a7820e 100644 --- a/app/src/main/java/tv/trakt/trakt/ui/components/headerbar/HeaderBar.kt +++ b/app/src/main/java/tv/trakt/trakt/ui/components/headerbar/HeaderBar.kt @@ -12,13 +12,20 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -32,6 +39,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.launch import org.koin.compose.koinInject import tv.trakt.trakt.MainActivity +import tv.trakt.trakt.common.helpers.extensions.onClick import tv.trakt.trakt.common.model.MediaMode import tv.trakt.trakt.common.model.globalfilter.GlobalFilter import tv.trakt.trakt.core.auth.ConfigAuth @@ -52,11 +60,13 @@ internal fun HeaderBar( containerColor: Color = TraktTheme.colors.navigationHeaderContainer, containerAlpha: Float = 0.98F, showFilters: Boolean = false, + showEditScreen: Boolean = false, showLogin: Boolean = false, showVip: Boolean = false, userLoading: Boolean = false, onVipClick: () -> Unit = {}, onFilterClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { val scope = rememberCoroutineScope() val localActivity = LocalActivity.current @@ -78,10 +88,12 @@ internal fun HeaderBar( showLogin = showLogin, showVip = showVip, showFilters = showFilters, + showEditScreen = showEditScreen, userLoading = userLoading, customTheme = customThemeConfig, onVipClick = onVipClick, onFilterClick = onFilterClick, + onEditScreenClick = onEditScreenClick, onCustomThemeChange = { (localActivity as? MainActivity)?.toggleCustomTheme(it) }, @@ -102,12 +114,14 @@ private fun HeaderBar( showLogin: Boolean = false, showVip: Boolean = false, showFilters: Boolean = false, + showEditScreen: Boolean = false, userLoading: Boolean = false, customTheme: CustomThemeUseCase.CustomThemeConfig? = null, onMediaModeSelect: (MediaMode) -> Unit = {}, onCustomThemeChange: (Boolean) -> Unit = {}, onVipClick: () -> Unit = {}, onFilterClick: () -> Unit = {}, + onEditScreenClick: () -> Unit = {}, ) { val uriHandler = LocalUriHandler.current @@ -155,7 +169,7 @@ private fun HeaderBar( Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp), ) { if (showLogin) { TertiaryButton( @@ -188,11 +202,65 @@ private fun HeaderBar( onClick = onFilterClick, ) } + + if (showEditScreen) { + HeaderBarMoreButton( + onEditScreenClick = onEditScreenClick, + ) + } } } } } +@Composable +fun HeaderBarMoreButton(onEditScreenClick: () -> Unit = {}) { + var showMenu by remember { mutableStateOf(false) } + + Box { + Icon( + painter = painterResource(R.drawable.ic_more_vertical), + contentDescription = null, + tint = TraktTheme.colors.textPrimary, + modifier = Modifier + .size(18.dp) + .onClick { + showMenu = true + }, + ) + DropdownMenu( + expanded = showMenu, + containerColor = TraktTheme.colors.dialogContainer, + shape = RoundedCornerShape(16.dp), + onDismissRequest = { + showMenu = false + }, + ) { + DropdownMenuItem( + text = { + Text( + text = stringResource(R.string.text_edit_screen), + style = TraktTheme.typography.buttonTertiary, + color = TraktTheme.colors.textPrimary, + ) + }, + onClick = { + onEditScreenClick() + showMenu = false + }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.ic_edit_screen), + contentDescription = null, + tint = TraktTheme.colors.textPrimary, + modifier = Modifier.size(21.dp), + ) + }, + ) + } + } +} + @Preview(widthDp = 400) @Composable private fun Preview() { @@ -247,6 +315,7 @@ private fun Preview4() { ), showVip = true, showFilters = true, + showEditScreen = true, ) } } diff --git a/resources/src/main/res/drawable/ic_edit_screen.xml b/resources/src/main/res/drawable/ic_edit_screen.xml new file mode 100644 index 000000000..f9ce2c6f7 --- /dev/null +++ b/resources/src/main/res/drawable/ic_edit_screen.xml @@ -0,0 +1,24 @@ + + + + diff --git a/resources/src/main/res/drawable/ic_eye_off.xml b/resources/src/main/res/drawable/ic_eye_off.xml new file mode 100644 index 000000000..8c66bfe22 --- /dev/null +++ b/resources/src/main/res/drawable/ic_eye_off.xml @@ -0,0 +1,24 @@ + + + + diff --git a/resources/src/main/res/values/strings-todo.xml b/resources/src/main/res/values/strings-todo.xml index 3ea04e700..e0848cb81 100644 --- a/resources/src/main/res/values/strings-todo.xml +++ b/resources/src/main/res/values/strings-todo.xml @@ -1,2 +1,5 @@ - + + Edit Screen + Adjust the visibility of screen sections. + From 37108958e97a1b2b0832e72f40d1d85c745e7089 Mon Sep 17 00:00:00 2001 From: Michal Date: Sat, 13 Jun 2026 11:23:18 +0200 Subject: [PATCH 2/2] chore: update icon --- resources/src/main/res/drawable/ic_flame_1.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/src/main/res/drawable/ic_flame_1.xml b/resources/src/main/res/drawable/ic_flame_1.xml index 965fae66d..de654ba55 100644 --- a/resources/src/main/res/drawable/ic_flame_1.xml +++ b/resources/src/main/res/drawable/ic_flame_1.xml @@ -35,7 +35,7 @@ - +