From aa53da6449c7cfdd40bfd6a20fe2bb63456019a3 Mon Sep 17 00:00:00 2001 From: maxrave-dev Date: Sat, 1 Feb 2025 15:47:16 +0700 Subject: [PATCH] feat(LibraryDynamicPlaylistScreen): Implement search functionality for dynamic playlists --- .../library/LibraryDynamicPlaylistScreen.kt | 158 +++++++++++++++--- 1 file changed, 133 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt b/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt index 94b84087..64837706 100644 --- a/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt +++ b/app/src/main/java/com/maxrave/simpmusic/ui/screen/library/LibraryDynamicPlaylistScreen.kt @@ -1,30 +1,46 @@ package com.maxrave.simpmusic.ui.screen.library import android.os.Bundle +import android.util.Log import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets 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.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Search import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.media3.common.util.UnstableApi import androidx.navigation.NavController import com.maxrave.simpmusic.R +import com.maxrave.simpmusic.data.db.entities.ArtistEntity import com.maxrave.simpmusic.data.db.entities.SongEntity import com.maxrave.simpmusic.extension.navigateSafe import com.maxrave.simpmusic.ui.component.ArtistFullWidthItems @@ -49,18 +65,48 @@ fun LibraryDynamicPlaylistScreen( var chosenSong: SongEntity? by remember { mutableStateOf(null) } var showBottomSheet by rememberSaveable { mutableStateOf(false) } + var showSearchBar by rememberSaveable { mutableStateOf(false) } + var query by rememberSaveable { mutableStateOf("") } val favorite by viewModel.listFavoriteSong.collectAsState() + var tempFavorite by rememberSaveable { mutableStateOf(emptyList()) } val followed by viewModel.listFollowedArtist.collectAsState() + var tempFollowed by rememberSaveable { mutableStateOf(emptyList()) } val mostPlayed by viewModel.listMostPlayedSong.collectAsState() + var tempMostPlayed by rememberSaveable { mutableStateOf(emptyList()) } val downloaded by viewModel.listDownloadedSong.collectAsState() + var tempDownloaded by rememberSaveable { mutableStateOf(emptyList()) } + + LaunchedEffect(query) { + Log.w("LibraryDynamicPlaylistScreen", "Check query: $query") + tempFavorite = favorite.filter { it.title.contains(query, ignoreCase = true) } + Log.w("LibraryDynamicPlaylistScreen", "Check tempFavorite: $tempFavorite") + tempFollowed = followed.filter { it.name.contains(query, ignoreCase = true) } + Log.w("LibraryDynamicPlaylistScreen", "Check tempFollowed: $tempFollowed") + tempMostPlayed = mostPlayed.filter { it.title.contains(query, ignoreCase = true) } + Log.w("LibraryDynamicPlaylistScreen", "Check tempMostPlayed: $tempMostPlayed") + tempDownloaded = downloaded.filter { it.title.contains(query, ignoreCase = true) } + Log.w("LibraryDynamicPlaylistScreen", "Check tempDownloaded: $tempDownloaded") + } LazyColumn( modifier = Modifier.padding(top = 64.dp), contentPadding = innerPadding, ) { + item { + AnimatedVisibility(showSearchBar) { + Spacer(Modifier.height(55.dp)) + } + } if (type == LibraryDynamicPlaylistType.Followed) { - items(followed, key = { it.channelId }) { artist -> + items( + if (query.isNotEmpty() && showSearchBar) { + tempFollowed + } else { + followed + }, + key = { it.channelId }, + ) { artist -> ArtistFullWidthItems( artist, onClickListener = { @@ -76,9 +122,24 @@ fun LibraryDynamicPlaylistScreen( } else { items( when (type) { - LibraryDynamicPlaylistType.Downloaded -> downloaded - LibraryDynamicPlaylistType.Favorite -> favorite - LibraryDynamicPlaylistType.MostPlayed -> mostPlayed + LibraryDynamicPlaylistType.Downloaded -> + if (query.isNotEmpty() && showSearchBar) { + tempDownloaded + } else { + downloaded + } + LibraryDynamicPlaylistType.Favorite -> + if (query.isNotEmpty() && showSearchBar) { + tempFavorite + } else { + favorite + } + LibraryDynamicPlaylistType.MostPlayed -> + if (query.isNotEmpty() && showSearchBar) { + tempMostPlayed + } else { + mostPlayed + } else -> emptyList() }, key = { it.hashCode() }, @@ -111,29 +172,76 @@ fun LibraryDynamicPlaylistScreen( song = chosenSong ?: return, ) } - TopAppBar( - title = { - Text( - text = - stringResource( - type.name(), - ), - style = typo.titleMedium, - ) - }, - navigationIcon = { - Box(Modifier.padding(horizontal = 5.dp)) { - RippleIconButton( - R.drawable.baseline_arrow_back_ios_new_24, - Modifier - .size(32.dp), - true, - ) { - navController.navigateUp() + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TopAppBar( + title = { + Text( + text = + stringResource( + type.name(), + ), + style = typo.titleMedium, + ) + }, + navigationIcon = { + Box(Modifier.padding(horizontal = 5.dp)) { + RippleIconButton( + R.drawable.baseline_arrow_back_ios_new_24, + Modifier + .size(32.dp), + true, + ) { + navController.navigateUp() + } } + }, + actions = { + Box(Modifier.padding(horizontal = 5.dp)) { + RippleIconButton( + if (showSearchBar) R.drawable.baseline_close_24 else R.drawable.baseline_search_24, + Modifier + .size(32.dp), + true, + ) { + showSearchBar = !showSearchBar + } + } + }, + ) + androidx.compose.animation.AnimatedVisibility(visible = showSearchBar) { + SearchBar( + modifier = + Modifier + .fillMaxWidth() + .height(45.dp) + .padding(horizontal = 12.dp), + inputField = { + CompositionLocalProvider(LocalTextStyle provides typo.bodySmall) { + SearchBarDefaults.InputField( + query = query, + onQueryChange = { query = it }, + onSearch = { showSearchBar = false }, + expanded = showSearchBar, + onExpandedChange = { showSearchBar = it }, + placeholder = { + Text( + stringResource(R.string.search), + style = typo.bodySmall, + ) + }, + leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = null) }, + ) + } + }, + expanded = false, + onExpandedChange = {}, + windowInsets = WindowInsets(0, 0, 0, 0), + ) { } - }, - ) + } + } } sealed class LibraryDynamicPlaylistType {