diff --git a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreen.kt b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreen.kt index 2f6d70d..d0d27eb 100644 --- a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreen.kt +++ b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreen.kt @@ -40,6 +40,7 @@ import dev.hyuwah.imusic.features.search.domain.model.SearchModel import dev.hyuwah.imusic.features.search.domain.model.SearchResultModel import dev.hyuwah.imusic.features.search.domain.model.TrackPlaybackState import dev.hyuwah.imusic.features.search.presentation.component.MiniPlaybackControl +import dev.hyuwah.imusic.features.search.presentation.component.RecentSearchQueries import dev.hyuwah.imusic.features.search.presentation.component.SearchResultItem import dev.hyuwah.imusic.ui.theme.IMusicTheme @@ -72,12 +73,12 @@ fun SearchScreen( searchQuery = it }, onSearch = { - active = false if (searchQuery.isNotBlank()) { - onEvent(SearchScreenEvent.Search(it)) + onEvent(SearchScreenEvent.Search(it.trim())) } else { searchQuery = "" } + active = false }, active = active, onActiveChange = { @@ -111,7 +112,26 @@ fun SearchScreen( .fillMaxWidth() .padding(12.dp) ) { - + RecentSearchQueries( + searchHistories = screenState.searchHistories, + onItemClick = { query -> + if (query != searchQuery) { + searchQuery = query + if (searchQuery.isNotBlank()) { + onEvent(SearchScreenEvent.Search(query)) + } else { + searchQuery = "" + } + } + active = false + }, + onClearAll = { + onEvent(SearchScreenEvent.ClearRecentSearch) + }, + onRemoveItem = { query -> + onEvent(SearchScreenEvent.RemoveRecentSearch(query)) + }, + ) } Box( modifier = Modifier.weight(1f), @@ -154,22 +174,6 @@ fun SearchScreen( } } } - - with(trackPlaybackState) { - if (playerState != null && playerState != PlayerState.STOPPED) { - MiniPlaybackControl( - modifier = Modifier.align(Alignment.BottomCenter).padding(horizontal = 12.dp, vertical = 16.dp), - track = currentTrack, - playerState = playerState, - seekbarPos = currentPosition, - seekbarDuration = totalDuration, - onSeekbarChanged = { onEvent(SearchScreenEvent.SeekTrackPosition(it)) }, - onResumeClicked = { onEvent(SearchScreenEvent.ResumeTrack) }, - onPauseClicked = { onEvent(SearchScreenEvent.PauseTrack) }) { - // [enhancement] Open Playback Control Detail - } - } - } } else { Text( text = "No result found for \"$searchQuery\"", @@ -196,6 +200,24 @@ fun SearchScreen( } } } + + with(trackPlaybackState) { + if (playerState != null && playerState != PlayerState.STOPPED) { + MiniPlaybackControl( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(horizontal = 12.dp, vertical = 16.dp), + track = currentTrack, + playerState = playerState, + seekbarPos = currentPosition, + seekbarDuration = totalDuration, + onSeekbarChanged = { onEvent(SearchScreenEvent.SeekTrackPosition(it)) }, + onResumeClicked = { onEvent(SearchScreenEvent.ResumeTrack) }, + onPauseClicked = { onEvent(SearchScreenEvent.PauseTrack) }) { + // [enhancement] Open Playback Control Detail + } + } + } } } diff --git a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenEvent.kt b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenEvent.kt index 9d2322f..2bc6ada 100644 --- a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenEvent.kt +++ b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenEvent.kt @@ -9,4 +9,6 @@ sealed interface SearchScreenEvent { data class SeekTrackPosition(val pos: Long): SearchScreenEvent data class OnTrackSelected(val selectedTrack: SearchModel) : SearchScreenEvent data class Search(val query: String) : SearchScreenEvent + data class RemoveRecentSearch(val query: String) : SearchScreenEvent + data object ClearRecentSearch : SearchScreenEvent } \ No newline at end of file diff --git a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenState.kt b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenState.kt index 7d29746..08c7132 100644 --- a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenState.kt +++ b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchScreenState.kt @@ -7,6 +7,7 @@ import dev.hyuwah.imusic.features.search.domain.model.SearchResultModel data class SearchScreenState( val isLoading: Boolean? = null, val searchResult: SearchResultModel? = null, + val searchHistories: List = listOf(), val selectedTrack: SearchModel? = null, val searchError: ErrorType? = null ) \ No newline at end of file diff --git a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchViewModel.kt b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchViewModel.kt index eb31cc6..36e485f 100644 --- a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchViewModel.kt +++ b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/SearchViewModel.kt @@ -38,6 +38,19 @@ class SearchViewModel @Inject constructor( when (event) { is SearchScreenEvent.Search -> { search(event.query) + addQueryToRecentSearch(event.query) + } + is SearchScreenEvent.RemoveRecentSearch -> { + val recentQuery = screenState.searchHistories.toMutableList() + recentQuery.remove(event.query) + screenState = screenState.copy( + searchHistories = recentQuery + ) + } + SearchScreenEvent.ClearRecentSearch -> { + screenState = screenState.copy( + searchHistories = emptyList() + ) } is SearchScreenEvent.OnTrackSelected -> { screenState = screenState.copy(selectedTrack = event.selectedTrack) @@ -81,6 +94,20 @@ class SearchViewModel @Inject constructor( } } + private fun addQueryToRecentSearch(query: String) { + viewModelScope.launch { + delay(200) + val newHistories = screenState.searchHistories.toMutableList() + if (screenState.searchHistories.contains(query)) { + newHistories.remove(query) + } + newHistories.add(0, query) + screenState = screenState.copy( + searchHistories = newHistories + ) + } + } + private fun setMediaControllerCallback() { playbackControllerUseCase.setMediaControllerCallback { playerState, currentTrack, currentPosition, totalDuration -> trackPlaybackState = trackPlaybackState.copy( diff --git a/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/component/RecentSearchQueries.kt b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/component/RecentSearchQueries.kt new file mode 100644 index 0000000..19b1e5d --- /dev/null +++ b/app/src/main/java/dev/hyuwah/imusic/features/search/presentation/component/RecentSearchQueries.kt @@ -0,0 +1,73 @@ +package dev.hyuwah.imusic.features.search.presentation.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.DeleteSweep +import androidx.compose.material.icons.filled.History +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun RecentSearchQueries( + modifier: Modifier = Modifier, + searchHistories: List, + onItemClick: (query: String) -> Unit, + onRemoveItem: (query: String) -> Unit, + onClearAll: () -> Unit +) { + if (searchHistories.isNotEmpty()) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp, top = 12.dp, bottom = 8.dp) + ) { + Text( + text = "Recent Search", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface + ) + IconButton(onClick = { onClearAll() }) { + Icon( + imageVector = Icons.Default.DeleteSweep, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + LazyColumn { + items(searchHistories) { query -> + ListItem( + headlineContent = { + Text(text = query) + }, + leadingContent = { + Icon(imageVector = Icons.Default.History, contentDescription = null) + }, + trailingContent = { + IconButton(onClick = { onRemoveItem(query) }) { + Icon(imageVector = Icons.Default.Delete, contentDescription = null) + } + }, + modifier = Modifier.clickable { + onItemClick(query) + }) + } + } + } +} \ No newline at end of file