@@ -38,32 +38,35 @@ import androidx.compose.ui.platform.LocalDensity
3838import androidx.compose.ui.platform.LocalFocusManager
3939import androidx.compose.ui.unit.dp
4040import androidx.compose.ui.unit.sp
41+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
4142import com.example.xtreamplayer.auth.AuthConfig
4243import com.example.xtreamplayer.auth.AuthUiState
4344import com.example.xtreamplayer.content.CategoryItem
4445import com.example.xtreamplayer.content.ContentItem
4546import com.example.xtreamplayer.content.ContentRepository
4647import com.example.xtreamplayer.content.ContentType
47- import com.example.xtreamplayer.content.ContinueWatchingEntry
4848import com.example.xtreamplayer.content.ContinueWatchingRepository
4949import com.example.xtreamplayer.content.FavoritesRepository
50- import com.example.xtreamplayer.content.HistoryRepository
5150import com.example.xtreamplayer.content.ProgressiveSyncCoordinator
5251import com.example.xtreamplayer.content.ProgressiveSyncState
5352import com.example.xtreamplayer.content.SubtitleRepository
5453import com.example.xtreamplayer.settings.SettingsState
5554import com.example.xtreamplayer.settings.SettingsViewModel
55+ import com.example.xtreamplayer.ui.components.NAV_WIDTH
5656import com.example.xtreamplayer.ui.components.SideNav
5757import com.example.xtreamplayer.ui.theme.AppTheme
5858import java.util.Locale
5959import kotlinx.coroutines.CoroutineScope
6060import kotlinx.coroutines.Dispatchers
6161import kotlinx.coroutines.delay
62+ import kotlinx.coroutines.flow.flowOf
6263import kotlinx.coroutines.launch
6364import kotlinx.coroutines.withContext
6465import timber.log.Timber
6566import androidx.compose.runtime.withFrameNanos
6667
68+ private const val BROWSE_NAV_ANIM_DURATION_MS = 180
69+
6770@Composable
6871internal fun BrowseScreen (
6972 context : Context ,
@@ -76,10 +79,6 @@ internal fun BrowseScreen(
7679 appVersionName : String ,
7780 selectedSectionState : MutableState <Section >,
7881 navExpandedState : MutableState <Boolean >,
79- navLayoutExpanded : Boolean ,
80- navSlideExpanded : Boolean ,
81- navOffsetPx : Float ,
82- navProgress : Float ,
8382 moveFocusToNavState : MutableState <Boolean >,
8483 focusToContentTriggerState : MutableState <Int >,
8584 showManageListsState : MutableState <Boolean >,
@@ -97,7 +96,6 @@ internal fun BrowseScreen(
9796 cacheClearNonceState : MutableState <Int >,
9897 contentRepository : ContentRepository ,
9998 favoritesRepository : FavoritesRepository ,
100- historyRepository : HistoryRepository ,
10199 continueWatchingRepository : ContinueWatchingRepository ,
102100 subtitleRepository : SubtitleRepository ,
103101 playbackEngine : com.example.xtreamplayer.player.Media3PlaybackEngine ,
@@ -117,11 +115,6 @@ internal fun BrowseScreen(
117115 contentItemFocusRequester : FocusRequester ,
118116 resumeFocusId : String? ,
119117 resumeFocusRequester : FocusRequester ,
120- filteredContinueWatchingItems : List <ContinueWatchingEntry >,
121- filteredFavoriteContentItems : List <ContentItem >,
122- filteredFavoriteCategoryItems : List <CategoryItem >,
123- filteredFavoriteContentKeys : Set <String >,
124- filteredFavoriteCategoryKeys : Set <String >,
125118 isPlaybackActive : Boolean ,
126119 onItemFocused : (ContentItem ) -> Unit ,
127120 onPlay : (ContentItem , List <ContentItem >) -> Unit ,
@@ -134,8 +127,6 @@ internal fun BrowseScreen(
134127 localResumePositionMsForUri : (Uri ) -> Long? ,
135128 onToggleFavorite : (ContentItem ) -> Unit ,
136129 onToggleCategoryFavorite : (CategoryItem ) -> Unit ,
137- isItemFavorite : (ContentItem ) -> Boolean ,
138- isCategoryFavorite : (CategoryItem ) -> Boolean ,
139130 onSeriesPlaybackStart : (ContentItem ) -> Unit ,
140131 onTriggerSectionSync : (Section , AuthConfig ) -> Unit ,
141132 onEditList : () -> Unit ,
@@ -164,6 +155,101 @@ internal fun BrowseScreen(
164155 var cacheClearNonce by cacheClearNonceState
165156 val focusManager = LocalFocusManager .current
166157 var navMoveToContentTrigger by remember { mutableIntStateOf(0 ) }
158+ var navLayoutExpanded by remember { mutableStateOf(true ) }
159+ var navSlideExpanded by remember { mutableStateOf(true ) }
160+ LaunchedEffect (navExpanded) {
161+ if (navExpanded) {
162+ navLayoutExpanded = true
163+ navSlideExpanded = false
164+ withFrameNanos {}
165+ delay(16 )
166+ navSlideExpanded = true
167+ } else {
168+ navSlideExpanded = false
169+ delay(BROWSE_NAV_ANIM_DURATION_MS .toLong())
170+ if (! navExpanded) {
171+ navLayoutExpanded = false
172+ }
173+ }
174+ }
175+ val navProgress by animateFloatAsState(
176+ targetValue = if (navSlideExpanded) 1f else 0f ,
177+ animationSpec = tween(durationMillis = BROWSE_NAV_ANIM_DURATION_MS ),
178+ label = " browseNavSlide"
179+ )
180+ val navWidthPx = with (LocalDensity .current) { NAV_WIDTH .toPx() }
181+ val navOffsetPx = - navWidthPx * (1f - navProgress)
182+ val favoriteContentKeys by
183+ favoritesRepository.favoriteContentKeys.collectAsStateWithLifecycle(initialValue = emptySet())
184+ val favoriteCategoryKeys by
185+ favoritesRepository.favoriteCategoryKeys.collectAsStateWithLifecycle(initialValue = emptySet())
186+ val favoriteContentEntries by
187+ favoritesRepository.favoriteContentEntries.collectAsStateWithLifecycle(initialValue = emptyList())
188+ val favoriteCategoryEntries by
189+ favoritesRepository.favoriteCategoryEntries.collectAsStateWithLifecycle(initialValue = emptyList())
190+ val filteredFavoriteContentKeys =
191+ remember(favoriteContentKeys, activeConfig) {
192+ if (activeConfig == null ) {
193+ emptySet()
194+ } else {
195+ favoritesRepository.filterKeysForConfig(favoriteContentKeys, activeConfig)
196+ }
197+ }
198+ val filteredFavoriteCategoryKeys =
199+ remember(favoriteCategoryKeys, activeConfig) {
200+ if (activeConfig == null ) {
201+ emptySet()
202+ } else {
203+ favoritesRepository.filterKeysForConfig(favoriteCategoryKeys, activeConfig)
204+ }
205+ }
206+ val filteredFavoriteContentItems =
207+ remember(favoriteContentEntries, filteredFavoriteContentKeys, activeConfig) {
208+ if (activeConfig == null ) {
209+ emptyList()
210+ } else {
211+ favoriteContentEntries
212+ .filter {
213+ favoritesRepository.isKeyForConfig(it.key, activeConfig) &&
214+ filteredFavoriteContentKeys.contains(it.key)
215+ }
216+ .map { it.item }
217+ .distinctBy { " ${it.contentType.name} :${it.id} " }
218+ }
219+ }
220+ val filteredFavoriteCategoryItems =
221+ remember(favoriteCategoryEntries, filteredFavoriteCategoryKeys, activeConfig) {
222+ if (activeConfig == null ) {
223+ emptyList()
224+ } else {
225+ favoriteCategoryEntries
226+ .filter {
227+ favoritesRepository.isKeyForConfig(it.key, activeConfig) &&
228+ filteredFavoriteCategoryKeys.contains(it.key)
229+ }
230+ .map { it.category }
231+ .distinctBy { " ${it.type.name} :${it.id} " }
232+ }
233+ }
234+ val isItemFavorite: (ContentItem ) -> Boolean = { item ->
235+ val config = activeConfig
236+ config != null && favoritesRepository.isContentFavorite(favoriteContentKeys, config, item)
237+ }
238+ val isCategoryFavorite: (CategoryItem ) -> Boolean = { category ->
239+ val config = activeConfig
240+ config != null && favoritesRepository.isCategoryFavorite(favoriteCategoryKeys, config, category)
241+ }
242+ val filteredContinueWatchingFlow =
243+ remember(activeConfig) {
244+ val config = activeConfig
245+ if (config == null ) {
246+ flowOf(emptyList())
247+ } else {
248+ continueWatchingRepository.continueWatchingEntriesForConfig(config)
249+ }
250+ }
251+ val filteredContinueWatchingItems by
252+ filteredContinueWatchingFlow.collectAsStateWithLifecycle(initialValue = emptyList())
167253
168254 LaunchedEffect (navMoveToContentTrigger) {
169255 if (navMoveToContentTrigger <= 0 ) return @LaunchedEffect
0 commit comments