From 137aa5ae9fefe67a307db3ad405e530813b8a51d Mon Sep 17 00:00:00 2001 From: reocat Date: Sun, 12 Jan 2025 14:24:14 +0300 Subject: [PATCH] Bump deps --- .../outertune/ui/screens/HistoryScreen.kt | 460 +++++++++--------- app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 6 +- 3 files changed, 243 insertions(+), 224 deletions(-) diff --git a/app/src/main/java/com/dd3boh/outertune/ui/screens/HistoryScreen.kt b/app/src/main/java/com/dd3boh/outertune/ui/screens/HistoryScreen.kt index e54f65201..34a45d8c4 100644 --- a/app/src/main/java/com/dd3boh/outertune/ui/screens/HistoryScreen.kt +++ b/app/src/main/java/com/dd3boh/outertune/ui/screens/HistoryScreen.kt @@ -66,7 +66,6 @@ import com.dd3boh.outertune.LocalDownloadUtil import com.dd3boh.outertune.LocalIsNetworkConnected import com.dd3boh.outertune.LocalPlayerAwareWindowInsets import com.dd3boh.outertune.LocalPlayerConnection -import com.dd3boh.outertune.LocalIsNetworkConnected import com.dd3boh.outertune.R import com.dd3boh.outertune.constants.HistorySource import com.dd3boh.outertune.constants.InnerTubeCookieKey @@ -192,256 +191,275 @@ fun HistoryScreen( val lazyListState = rememberLazyListState() Box(Modifier.fillMaxSize()) { - LazyColumn( - state = lazyListState, - contentPadding = LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) - .union(WindowInsets.ime) - .asPaddingValues(), - modifier = Modifier.windowInsetsPadding( - LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Top) - ) - ) { - stickyHeader( - key = "searchbar" + if (filteredEventsMap.isNotEmpty()) { + LazyColumn( + state = lazyListState, + contentPadding = LocalPlayerAwareWindowInsets.current + .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) + .union(WindowInsets.ime) + .asPaddingValues(), + modifier = Modifier.windowInsetsPadding( + LocalPlayerAwareWindowInsets.current + .only(WindowInsetsSides.Top) + ) ) { - if (isSearching) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.background(MaterialTheme.colorScheme.background) - ) { - IconButton( - onClick = { isSearching = true } + stickyHeader( + key = "searchbar" + ) { + if (isSearching) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.background(MaterialTheme.colorScheme.background) ) { - Icon( - Icons.Rounded.Search, - contentDescription = null + IconButton( + onClick = { isSearching = true } + ) { + Icon( + Icons.Rounded.Search, + contentDescription = null + ) + } + TextField( + value = query, + onValueChange = { query = it }, + placeholder = { + Text( + text = stringResource(R.string.search), + style = MaterialTheme.typography.titleLarge + ) + }, + singleLine = true, + textStyle = MaterialTheme.typography.titleLarge, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + ), + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) ) } - TextField( - value = query, - onValueChange = { query = it }, - placeholder = { - Text( - text = stringResource(R.string.search), - style = MaterialTheme.typography.titleLarge - ) - }, - singleLine = true, - textStyle = MaterialTheme.typography.titleLarge, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - ), - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester) - ) } } - } - item { - ChipsRow( - chips = if (isLoggedIn) listOf( - HistorySource.LOCAL to stringResource(R.string.local_history), - HistorySource.REMOTE to stringResource(R.string.remote_history), - ) else { - listOf(HistorySource.LOCAL to stringResource(R.string.local_history)) - }, - currentValue = historySource, - onValueUpdate = { - viewModel.historySource.value = it - if (it == HistorySource.REMOTE) { - viewModel.fetchRemoteHistory() + item { + ChipsRow( + chips = if (isLoggedIn) listOf( + HistorySource.LOCAL to stringResource(R.string.local_history), + HistorySource.REMOTE to stringResource(R.string.remote_history), + ) else { + listOf(HistorySource.LOCAL to stringResource(R.string.local_history)) + }, + currentValue = historySource, + onValueUpdate = { + viewModel.historySource.value = it + if (it == HistorySource.REMOTE) { + viewModel.fetchRemoteHistory() + } } - } - ) - } + ) + } - if (historySource == HistorySource.REMOTE && isLoggedIn) { - historyPage?.sections?.forEach { section -> - stickyHeader { - NavigationTitle( - title = section.title, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - ) - } + if (historySource == HistorySource.REMOTE && isLoggedIn) { + historyPage?.sections?.forEach { section -> + if (section.songs.isNotEmpty()) { // Ensure the section is not empty + stickyHeader { + NavigationTitle( + title = section.title, + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + ) + } - items( - items = section.songs, - key = { it.id } - ) { song -> - val available = downloads[song.id]?.isAvailableOffline() ?: false || isNetworkConnected + items( + items = section.songs, + key = { it.id } + ) { song -> + val available = downloads[song.id]?.isAvailableOffline() ?: false || isNetworkConnected - val content: @Composable () -> Unit = { - YouTubeListItem( - item = song, - isActive = song.id == mediaMetadata?.id, - isPlaying = isPlaying, - trailingContent = { - if (available) { - IconButton( - onClick = { - menuState.show { - YouTubeSongMenu( - song = song, - navController = navController, - onDismiss = menuState::dismiss - ) - } - } - ) { - Icon( - Icons.Rounded.MoreVert, - contentDescription = null - ) - } - } - }, - modifier = Modifier - .fillMaxWidth() - .combinedClickable( - onClick = { + val content: @Composable () -> Unit = { + YouTubeListItem( + item = song, + isActive = song.id == mediaMetadata?.id, + isPlaying = isPlaying, + trailingContent = { if (available) { - if (song.id == mediaMetadata?.id) { - playerConnection.player.togglePlayPause() - } else if (song.id.startsWith("LA")) { - playerConnection.playQueue( - ListQueue( - title = "History", - items = section.songs.map { it.toMediaMetadata() } - ) - ) - } else { - playerConnection.playQueue( - if (isNetworkConnected) { - YouTubeQueue( - endpoint = WatchEndpoint(videoId = song.id), - preloadItem = song.toMediaMetadata() - ) - } else { - ListQueue( - title = "${context.getString(R.string.queue_searched_songs)} $viewModel.query", - items = listOf(song.toMediaMetadata()) + IconButton( + onClick = { + menuState.show { + YouTubeSongMenu( + song = song, + navController = navController, + onDismiss = menuState::dismiss ) } + } + ) { + Icon( + Icons.Rounded.MoreVert, + contentDescription = null ) } } }, - onLongClick = { - if (available) { - menuState.show { - YouTubeSongMenu( - song = song, - navController = navController, - onDismiss = menuState::dismiss - ) + modifier = Modifier + .fillMaxWidth() + .combinedClickable( + onClick = { + if (available) { + if (song.id == mediaMetadata?.id) { + playerConnection.player.togglePlayPause() + } else if (song.id.startsWith("LA")) { + playerConnection.playQueue( + ListQueue( + title = "History", + items = section.songs.map { it.toMediaMetadata() } + ) + ) + } else { + playerConnection.playQueue( + if (isNetworkConnected) { + YouTubeQueue( + endpoint = WatchEndpoint(videoId = song.id), + preloadItem = song.toMediaMetadata() + ) + } else { + ListQueue( + title = "${context.getString(R.string.queue_searched_songs)} $viewModel.query", + items = listOf(song.toMediaMetadata()) + ) + } + ) + } + } + }, + onLongClick = { + if (available) { + menuState.show { + YouTubeSongMenu( + song = song, + navController = navController, + onDismiss = menuState::dismiss + ) + } + } } - } - } + ) ) - ) - } + } - if (available) { - SwipeToQueueBox( - item = song.toMediaItem(), - content = { content() }, - snackbarHostState = snackbarHostState - ) - } else { - content() + if (available) { + SwipeToQueueBox( + item = song.toMediaItem(), + content = { content() }, + snackbarHostState = snackbarHostState + ) + } else { + content() + } + } } } - } - } else { - eventsMap.forEach { (dateAgo, eventsGroup) -> - stickyHeader { - NavigationTitle( - title = dateAgoToString(dateAgo), - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface) - ) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface) - ) { - Spacer(modifier = Modifier.width(16.dp)) // why compose no margin... - - if (inSelectMode) { - SelectHeader( - selectedItems = eventsMap.flatMap { - group -> group.value.filter{ it.event.id in selection } - }.map { it.song.toMediaMetadata() }, - totalItemCount = eventsMap.flatMap { group -> group.value.map { it.song }.getAvailableSongs(isNetworkConnected)}.size, - onSelectAll = { - selection.clear() - selection.addAll(eventsMap.flatMap { group -> - group.value.filter{ it.song.song.isAvailableOffline() || isNetworkConnected }.map { it.event.id } - }) - }, - onDeselectAll = { selection.clear() }, - menuState = menuState, - onDismiss = onExitSelectionMode, - onRemoveFromHistory = { - val sel = selection.mapNotNull { eventId -> - filteredEventIndex[eventId]?.event - } - database.query { - sel.forEach { - delete(it) - } - } - }, + } else { + filteredEventsMap.forEach { (dateAgo, eventsGroup) -> + if (eventsGroup.isNotEmpty()) { // Ensure the group is not empty + stickyHeader { + NavigationTitle( + title = dateAgoToString(dateAgo), + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface) ) - } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface) + ) { + Spacer(modifier = Modifier.width(16.dp)) // why compose no margin... - Spacer(modifier = Modifier.width(16.dp)) - } - } + if (inSelectMode) { + SelectHeader( + selectedItems = eventsMap.flatMap { + group -> group.value.filter{ it.event.id in selection } + }.map { it.song.toMediaMetadata() }, + totalItemCount = eventsMap.flatMap { group -> group.value.map { it.song }.getAvailableSongs(isNetworkConnected)}.size, + onSelectAll = { + selection.clear() + selection.addAll(eventsMap.flatMap { group -> + group.value.filter{ it.song.song.isAvailableOffline() || isNetworkConnected }.map { it.event.id } + }) + }, + onDeselectAll = { selection.clear() }, + menuState = menuState, + onDismiss = onExitSelectionMode, + onRemoveFromHistory = { + val sel = selection.mapNotNull { eventId -> + filteredEventIndex[eventId]?.event + } + database.query { + sel.forEach { + delete(it) + } + } + }, + ) + } - itemsIndexed( - items = eventsGroup, - ) { index, event -> - SongListItem( - song = event.song, - onPlay = { - playerConnection.playQueue( - ListQueue( - title = dateAgoToString(dateAgo), - items = eventsGroup.map { it.song.toMediaMetadata() }, - startIndex = index + Spacer(modifier = Modifier.width(16.dp)) + } + } + + itemsIndexed( + items = eventsGroup, + ) { index, event -> + if (index < eventsGroup.size) { // Ensure the index is within bounds + SongListItem( + song = event.song, + onPlay = { + playerConnection.playQueue( + ListQueue( + title = dateAgoToString(dateAgo), + items = eventsGroup.map { it.song.toMediaMetadata() }, + startIndex = index + ) + ) + }, + onSelectedChange = { + inSelectMode = true + if (it) { + selection.add(event.event.id) + } else { + selection.remove(event.event.id) + } + }, + inSelectMode = inSelectMode, + isSelected = selection.contains(event.event.id), + navController = navController, + modifier = Modifier.fillMaxWidth().animateItem() ) - ) - }, - onSelectedChange = { - inSelectMode = true - if (it) { - selection.add(event.event.id) - } else { - selection.remove(event.event.id) } - }, - inSelectMode = inSelectMode, - isSelected = selection.contains(event.event.id), - navController = navController, - modifier = Modifier.fillMaxWidth().animateItem() - ) + } + } } } } + } else { + // Handle empty state, e.g., show a message or a placeholder + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.no_history), + style = MaterialTheme.typography.titleLarge + ) + } } HideOnScrollFAB( @@ -498,4 +516,4 @@ fun HistoryScreen( } } ) -} +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 035292475..d246bf4f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -469,4 +469,5 @@ Clear translation models OFF ON + No listening history yet diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0aa0ed61a..60ccd0633 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ androidGradlePlugin = "8.9.0-alpha09" annotation = "1.9.1" json = "20250107" kotlin = "2.1.0" -compose = "1.7.6" +compose = "1.8.0-alpha07" lifecycle = "2.8.7" material3 = "1.3.1" media3 = "1.5.1" @@ -29,13 +29,13 @@ hilt-navigation = { group = "androidx.hilt", name = "hilt-navigation-compose", v datastore = { group = "androidx.datastore", name = "datastore-preferences", version = "1.1.1" } compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "compose" } -compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version = "1.7.6" } +compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version = "compose" } compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" } compose-ui-util = { group = "androidx.compose.ui", name = "ui-util", version.ref = "compose" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" } compose-animation = { group = "androidx.compose.animation", name = "animation-graphics", version.ref = "compose" } compose-reorderable = { module = "sh.calvin.reorderable:reorderable", version = "2.4.2" } -compose-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version = "1.7.6" } +compose-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version = "compose" } taglib = { module = "com.github.Kyant0:taglib", version.ref = "taglib" } viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }