diff --git a/feature/explore/build.gradle b/feature/explore/build.gradle index 284264ef..3f4f4bf5 100644 --- a/feature/explore/build.gradle +++ b/feature/explore/build.gradle @@ -21,4 +21,5 @@ dependencies { implementation(libs.coil.compose) implementation(libs.bundles.koin) implementation(libs.compose.constraintLayout) + implementation(libs.timber) } \ No newline at end of file diff --git a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/data/ExploreRepositoryImpl.kt b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/data/ExploreRepositoryImpl.kt index 8ccb5ec0..5dd0b002 100644 --- a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/data/ExploreRepositoryImpl.kt +++ b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/data/ExploreRepositoryImpl.kt @@ -51,6 +51,10 @@ internal class ExploreRepositoryImpl( } } + override suspend fun refresh() { + songRandomStorage.refresh() + } + private fun PlayingSource?.isSongPlaying(serverId: Long, songId: String): Boolean { if (this == null) { return false diff --git a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/domain/ExploreRepository.kt b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/domain/ExploreRepository.kt index d07ca5ee..d5566f71 100644 --- a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/domain/ExploreRepository.kt +++ b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/domain/ExploreRepository.kt @@ -4,4 +4,5 @@ import kotlinx.coroutines.flow.Flow internal interface ExploreRepository { fun getExplore(): Flow + suspend fun refresh() } \ No newline at end of file diff --git a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreScreen.kt b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreScreen.kt index b39d245d..6930b469 100644 --- a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreScreen.kt +++ b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.derivedStateOf @@ -54,6 +55,7 @@ fun ExploreScreen( onSearchClick = onSearchClick, onSongClick = onSongClick, onRandomSongsClick = onRandomSongsClick, + onRefresh = viewModel::refresh, onRetry = viewModel::retry, onPlayPauseAudioSource = viewModel::onPlayPauseAudioSource, ) @@ -66,6 +68,7 @@ private fun ExploreScreen( onSongClick: (id: String) -> Unit, onRandomSongsClick: () -> Unit, onRetry: () -> Unit, + onRefresh: () -> Unit, onPlayPauseAudioSource: (source: AudioSource) -> Unit, modifier: Modifier = Modifier ) { @@ -78,28 +81,33 @@ private fun ExploreScreen( } } } - StateLayout( - state = layoutState, - content = { - Content( - state = state, - modifier = modifier, - onSearchClick = onSearchClick, - onSongClick = onSongClick, - onRandomSongsClick = onRandomSongsClick, - onPlayPauseAudioSource = onPlayPauseAudioSource - ) - }, - progress = { - Progress() - }, - error = { - ErrorLayout(onRetry = onRetry) - }, - modifier = Modifier - .fillMaxSize() - .background(color = MaterialTheme.colorScheme.background) - ) + PullToRefreshBox( + isRefreshing = state.refreshing, + onRefresh = onRefresh + ) { + StateLayout( + state = layoutState, + content = { + Content( + state = state, + modifier = modifier, + onSearchClick = onSearchClick, + onSongClick = onSongClick, + onRandomSongsClick = onRandomSongsClick, + onPlayPauseAudioSource = onPlayPauseAudioSource + ) + }, + progress = { + Progress() + }, + error = { + ErrorLayout(onRetry = onRetry) + }, + modifier = Modifier + .fillMaxSize() + .background(color = MaterialTheme.colorScheme.background) + ) + } } @Composable @@ -227,10 +235,10 @@ private fun Progress( } } -@Immutable internal data class StateUi( val progress: Boolean = true, val error: Boolean = false, + val refreshing: Boolean = false, val data: DataUi? = null ) @@ -246,6 +254,7 @@ private fun ExploreScreenPreview() { ExploreScreen( state = StateUi(), onRetry = {}, + onRefresh = {}, onSongClick = {}, onRandomSongsClick = {}, onPlayPauseAudioSource = {}, diff --git a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreViewModel.kt b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreViewModel.kt index 734df93e..3422c1b4 100644 --- a/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreViewModel.kt +++ b/feature/explore/src/main/java/ru/stresh/youamp/feature/explore/ui/ExploreViewModel.kt @@ -19,6 +19,7 @@ import ru.stersh.youamp.shared.player.queue.PlayerQueueAudioSourceManager import ru.stersh.youamp.shared.player.queue.equals import ru.stersh.youamp.shared.player.state.PlayStateStore import ru.stresh.youamp.feature.explore.domain.ExploreRepository +import timber.log.Timber internal class ExploreViewModel( private val repository: ExploreRepository, @@ -46,6 +47,7 @@ internal class ExploreViewModel( .catch { _state.update { it.copy( + refreshing = false, progress = false, error = true ) @@ -54,6 +56,7 @@ internal class ExploreViewModel( .collect { explore -> _state.update { it.copy( + refreshing = false, progress = false, data = explore ) @@ -62,6 +65,16 @@ internal class ExploreViewModel( } } + fun refresh() = viewModelScope.launch { + _state.update { + it.copy(refreshing = true) + } + runCatching { repository.refresh() } + .onFailure { Timber.w(it) } + stateJob?.cancel() + subscribeState() + } + fun retry() { stateJob?.cancel() _state.update { diff --git a/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryScreen.kt b/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryScreen.kt index c9c48376..987627e6 100644 --- a/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryScreen.kt +++ b/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -57,6 +58,7 @@ fun LibraryScreen( onAlbumsClick = onAlbumsClick, onArtistsClick = onArtistsClick, onRetry = viewModel::retry, + onRefresh = viewModel::refresh, onPlayPauseAudioSource = viewModel::onPlayPauseAudioSource ) } @@ -70,6 +72,7 @@ internal fun LibraryScreen( onArtistsClick: () -> Unit, onPlayPauseAudioSource: (source: AudioSource) -> Unit, onRetry: () -> Unit, + onRefresh: () -> Unit, ) { val layoutState by remember(state) { derivedStateOf { @@ -80,28 +83,33 @@ internal fun LibraryScreen( } } } - StateLayout( - state = layoutState, - content = { - Content( - data = state.data, - onAlbumClick = onAlbumClick, - onArtistClick = onArtistClick, - onAlbumsClick = onAlbumsClick, - onArtistsClick = onArtistsClick, - onPlayPauseAudioSource = onPlayPauseAudioSource - ) - }, - progress = { - Progress() - }, - error = { - ErrorLayout(onRetry = onRetry) - }, - modifier = Modifier - .fillMaxSize() - .background(color = MaterialTheme.colorScheme.background) - ) + PullToRefreshBox( + isRefreshing = state.refreshing, + onRefresh = onRefresh + ) { + StateLayout( + state = layoutState, + content = { + Content( + data = state.data, + onAlbumClick = onAlbumClick, + onArtistClick = onArtistClick, + onAlbumsClick = onAlbumsClick, + onArtistsClick = onArtistsClick, + onPlayPauseAudioSource = onPlayPauseAudioSource + ) + }, + progress = { + Progress() + }, + error = { + ErrorLayout(onRetry = onRetry) + }, + modifier = Modifier + .fillMaxSize() + .background(color = MaterialTheme.colorScheme.background) + ) + } } @Composable @@ -278,6 +286,7 @@ private fun LibraryScreenPreview() { LibraryScreen( state = StateUi(), onRetry = {}, + onRefresh = {}, onAlbumClick = {}, onArtistClick = {}, onArtistsClick = {}, diff --git a/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryViewModel.kt b/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryViewModel.kt index 797f1475..45e46513 100644 --- a/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryViewModel.kt +++ b/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/LibraryViewModel.kt @@ -48,7 +48,8 @@ internal class LibraryViewModel( _state.update { it.copy( progress = false, - error = true + error = true, + refreshing = false ) } } @@ -56,6 +57,8 @@ internal class LibraryViewModel( _state.update { it.copy( progress = false, + refreshing = false, + error = false, data = explore ) } @@ -75,6 +78,14 @@ internal class LibraryViewModel( subscribeState() } + fun refresh() { + stateJob?.cancel() + _state.update { + it.copy(refreshing = true) + } + subscribeState() + } + fun onPlayPauseAudioSource(source: AudioSource) { viewModelScope.launch { val playingSource = playerQueueAudioSourceManager diff --git a/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/State.kt b/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/State.kt index 40791faf..6a968237 100644 --- a/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/State.kt +++ b/feature/library/src/main/java/ru/stresh/youamp/feature/library/ui/State.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.Immutable internal data class StateUi( val progress: Boolean = true, val error: Boolean = false, + val refreshing: Boolean = false, val data: DataUi? = null ) diff --git a/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalScreen.kt b/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalScreen.kt index 1707dbcf..89c3c0b7 100644 --- a/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalScreen.kt +++ b/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue @@ -60,6 +61,7 @@ fun PersonalScreen( PersonalScreen( state = state, onRetry = viewModel::retry, + onRefresh = viewModel::refresh, onPlayPauseAudioSource = viewModel::onPlayPauseAudioSource, onSongClick = onSongClick, onAlbumClick = onAlbumClick, @@ -76,6 +78,7 @@ fun PersonalScreen( private fun PersonalScreen( state: PersonalScreenStateUi, onRetry: () -> Unit, + onRefresh: () -> Unit, onPlayPauseAudioSource: (source: AudioSource) -> Unit, onPlaylistsClick: () -> Unit, onFavoriteSongsClick: () -> Unit, @@ -92,35 +95,40 @@ private fun PersonalScreen( state.data?.isEmpty == true -> LayoutStateUi.Empty else -> LayoutStateUi.Content } - StateLayout( - state = layoutState, - content = { - Content( - data = state.data, - onPlayPauseAudioSource = onPlayPauseAudioSource, - onSongClick = onSongClick, - onAlbumClick = onAlbumClick, - onArtistClick = onArtistClick, - onPlaylistClick = onPlaylistClick, - onPlaylistsClick = onPlaylistsClick, - onFavoriteSongsClick = onFavoriteSongsClick, - onFavoriteAlbumsClick = onFavoriteAlbumsClick, - onFavoriteArtistsClick = onFavoriteArtistsClick - ) - }, - progress = { - Progress() - }, - error = { - ErrorLayout(onRetry = onRetry) - }, - empty = { - EmptyLayout() - }, - modifier = Modifier - .fillMaxSize() - .background(color = MaterialTheme.colorScheme.background) - ) + PullToRefreshBox( + isRefreshing = state.refreshing, + onRefresh = onRefresh + ) { + StateLayout( + state = layoutState, + content = { + Content( + data = state.data, + onPlayPauseAudioSource = onPlayPauseAudioSource, + onSongClick = onSongClick, + onAlbumClick = onAlbumClick, + onArtistClick = onArtistClick, + onPlaylistClick = onPlaylistClick, + onPlaylistsClick = onPlaylistsClick, + onFavoriteSongsClick = onFavoriteSongsClick, + onFavoriteAlbumsClick = onFavoriteAlbumsClick, + onFavoriteArtistsClick = onFavoriteArtistsClick + ) + }, + progress = { + Progress() + }, + error = { + ErrorLayout(onRetry = onRetry) + }, + empty = { + EmptyLayout() + }, + modifier = Modifier + .fillMaxSize() + .background(color = MaterialTheme.colorScheme.background) + ) + } } @Composable @@ -377,6 +385,7 @@ private fun Content( internal data class PersonalScreenStateUi( val progress: Boolean = true, val error: Boolean = false, + val refreshing: Boolean = false, val data: PersonalDataUi? = null ) @@ -502,6 +511,7 @@ private fun PersonalScreenPreview() { PersonalScreen( state = state, onRetry = {}, + onRefresh = {}, onPlayPauseAudioSource = {}, onSongClick = {}, onAlbumClick = {}, diff --git a/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalViewModel.kt b/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalViewModel.kt index 26f8ab55..ce4c06b5 100644 --- a/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalViewModel.kt +++ b/feature/personal/src/main/java/ru/stersh/youamp/feature/personal/ui/PersonalViewModel.kt @@ -48,6 +48,7 @@ internal class PersonalViewModel( _state.update { it.copy( progress = false, + refreshing = false, error = true ) } @@ -56,6 +57,7 @@ internal class PersonalViewModel( _state.update { it.copy( progress = false, + refreshing = false, data = personal ) } @@ -75,6 +77,14 @@ internal class PersonalViewModel( subscribeState() } + fun refresh() { + _state.update { + it.copy(refreshing = true) + } + stateJob?.cancel() + subscribeState() + } + fun onPlayPauseAudioSource(source: AudioSource) { viewModelScope.launch { val playingSource = playerQueueAudioSourceManager