From 7a79d86a62b531370f1a460c8c45a73dede8ed61 Mon Sep 17 00:00:00 2001 From: Ericgacoki Date: Sat, 13 Dec 2025 17:00:25 +0300 Subject: [PATCH 1/5] feat: add settings module for grid count Adds a new feature module `feature:settings` to manage application-wide settings. This initial implementation uses DataStore to persist the grid layout count for the Albums and Artists screens. Key changes: - Created `AppSettings` DataStore wrapper. - Implemented `AppSettingsRepository` to abstract data access. - Injected the repository into `AllAlbumsViewModel` and `ArtistsViewModel`. - Grid count state is now fetched from and updated to the DataStore, ensuring persistence. --- feature/album/build.gradle.kts | 1 + .../viewmodel/AllAlbumsViewModel.kt | 24 +++-- feature/artist/build.gradle.kts | 1 + .../viewmodel/ArtistsViewModel.kt | 24 ++++- feature/settings/.gitignore | 1 + feature/settings/build.gradle.kts | 99 +++++++++++++++++++ feature/settings/consumer-rules.pro | 0 feature/settings/proguard-rules.pro | 21 ++++ .../settings/ExampleInstrumentedTest.kt | 24 +++++ feature/settings/src/main/AndroidManifest.xml | 4 + .../settings/data/datastore/AppSettings.kt | 43 ++++++++ .../repository/AppSettingsDataRepository.kt | 25 +++++ .../settings/di/AppSettingsModule.kt | 23 +++++ .../settings/di/RepositoryModule.kt | 20 ++++ .../repository/AppSettingsRepository.kt | 11 +++ .../swingmusic/settings/ExampleUnitTest.kt | 17 ++++ settings.gradle.kts | 1 + 17 files changed, 328 insertions(+), 11 deletions(-) create mode 100644 feature/settings/.gitignore create mode 100644 feature/settings/build.gradle.kts create mode 100644 feature/settings/consumer-rules.pro create mode 100644 feature/settings/proguard-rules.pro create mode 100644 feature/settings/src/androidTest/java/com/android/swingmusic/settings/ExampleInstrumentedTest.kt create mode 100644 feature/settings/src/main/AndroidManifest.xml create mode 100644 feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt create mode 100644 feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt create mode 100644 feature/settings/src/main/java/com/android/swingmusic/settings/di/AppSettingsModule.kt create mode 100644 feature/settings/src/main/java/com/android/swingmusic/settings/di/RepositoryModule.kt create mode 100644 feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt create mode 100644 feature/settings/src/test/java/com/android/swingmusic/settings/ExampleUnitTest.kt diff --git a/feature/album/build.gradle.kts b/feature/album/build.gradle.kts index da9ecf0f..98d17449 100644 --- a/feature/album/build.gradle.kts +++ b/feature/album/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(project(":uicomponent")) implementation(project(":feature:player")) implementation(project(":feature:artist")) + implementation(project(":feature:settings")) // Common implementation(project(":feature:common")) diff --git a/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt b/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt index 4af35d6a..36a64a5a 100644 --- a/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt +++ b/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt @@ -12,17 +12,21 @@ import com.android.swingmusic.auth.domain.repository.AuthRepository import com.android.swingmusic.core.data.util.Resource import com.android.swingmusic.core.domain.util.SortBy import com.android.swingmusic.core.domain.util.SortOrder +import com.android.swingmusic.settings.domain.repository.AppSettingsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class AllAlbumsViewModel @Inject constructor( private val artistRepository: AlbumRepository, - private val authRepository: AuthRepository + private val authRepository: AuthRepository, + private val settingsRepository: AppSettingsRepository ) : ViewModel() { private val _baseUrl: MutableStateFlow = MutableStateFlow(null) @@ -68,8 +72,13 @@ class AllAlbumsViewModel @Inject constructor( init { getBaseUrl() - } + settingsRepository.albumGridCount + .onEach { newCount -> + _allAlbumsUiState.value = _allAlbumsUiState.value.copy(gridCount = newCount) + } + .launchIn(viewModelScope) + } private fun getBaseUrl() { viewModelScope.launch { @@ -86,6 +95,11 @@ class AllAlbumsViewModel @Inject constructor( getAlbumCount() } + private fun updateGridCount(count: Int) { + viewModelScope.launch { + settingsRepository.setAlbumGridCount(count) + } + } fun onAlbumsUiEvent(event: AlbumsUiEvent) { when (event) { @@ -117,13 +131,11 @@ class AllAlbumsViewModel @Inject constructor( } is AlbumsUiEvent.OnClickAlbum -> { - // TODO: Navigate from the UI (apparently not in the VM) + // TODO: Navigate from the UI (handled by UI navigator) } is AlbumsUiEvent.OnUpdateGridCount -> { - _allAlbumsUiState.value = _allAlbumsUiState.value.copy( - gridCount = event.newCount - ) + updateGridCount(event.newCount) } is AlbumsUiEvent.OnRetry -> { diff --git a/feature/artist/build.gradle.kts b/feature/artist/build.gradle.kts index 718a847a..33d554ec 100644 --- a/feature/artist/build.gradle.kts +++ b/feature/artist/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(project(":network")) implementation(project(":uicomponent")) implementation(project(":feature:player")) + implementation(project(":feature:settings")) // Common Feature implementation(project(":feature:common")) diff --git a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt index 59c86cd0..8b672442 100644 --- a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt +++ b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt @@ -12,15 +12,19 @@ import com.android.swingmusic.auth.domain.repository.AuthRepository import com.android.swingmusic.core.data.util.Resource import com.android.swingmusic.core.domain.util.SortBy import com.android.swingmusic.core.domain.util.SortOrder +import com.android.swingmusic.settings.domain.repository.AppSettingsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class ArtistsViewModel @Inject constructor( private val artistRepository: ArtistRepository, - private val authRepository: AuthRepository + private val authRepository: AuthRepository, + private val settingsRepository: AppSettingsRepository ) : ViewModel() { private var baseUrl: MutableState = mutableStateOf(null) val artistsUiState: MutableState = mutableStateOf(ArtistsUiState()) @@ -61,6 +65,12 @@ class ArtistsViewModel @Inject constructor( init { getBaseUrl() + + settingsRepository.artistGridCount + .onEach { newCount -> + artistsUiState.value = artistsUiState.value.copy(gridCount = newCount) + } + .launchIn(viewModelScope) } fun baseUrl() = baseUrl @@ -79,6 +89,12 @@ class ArtistsViewModel @Inject constructor( getArtistsCount() } + private fun updateGridCount(count: Int) { + viewModelScope.launch { + settingsRepository.setArtistGridCount(count) + } + } + fun onArtistUiEvent(event: ArtistUiEvent) { when (event) { is ArtistUiEvent.OnSortBy -> { @@ -109,13 +125,11 @@ class ArtistsViewModel @Inject constructor( } is ArtistUiEvent.OnClickArtist -> { - // TODO: Navigate from the UI (apparently not in the VM) + // TODO: Navigate from the UI (handled by UI navigator) } is ArtistUiEvent.OnUpdateGridCount -> { - artistsUiState.value = artistsUiState.value.copy( - gridCount = event.newCount - ) + updateGridCount(event.newCount) } is ArtistUiEvent.OnRetry -> { diff --git a/feature/settings/.gitignore b/feature/settings/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/feature/settings/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts new file mode 100644 index 00000000..66353581 --- /dev/null +++ b/feature/settings/build.gradle.kts @@ -0,0 +1,99 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + id("com.google.dagger.hilt.android") + id("com.google.devtools.ksp") +} + +android { + namespace = "com.android.swingmusic.settings" + compileSdk = 35 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = true + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.0" + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + // Core + implementation("androidx.core:core-ktx:1.15.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") + + // Compose + implementation(platform("androidx.compose:compose-bom:2024.10.01")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") + + // Hilt DI + implementation("com.google.dagger:hilt-android:2.50") + ksp("com.google.dagger:hilt-android-compiler:2.50") + + // Hilt Navigation-Compose + implementation("androidx.hilt:hilt-navigation-compose:1.2.0") + + + //Prefs Datastore + implementation("androidx.datastore:datastore-preferences:1.1.1") + + // Timber + implementation("com.jakewharton.timber:timber:5.0.1") + + // Navigation + implementation("io.github.raamcosta.compose-destinations:core:1.9.63") + ksp("io.github.raamcosta.compose-destinations:ksp:1.9.63") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") +} + +kotlin { + sourceSets { + debug { + kotlin.srcDir("build/generated/ksp/debug/kotlin") + } + release { + kotlin.srcDir("build/generated/ksp/release/kotlin") + } + } +} + +ksp { + arg("compose-destinations.mode", "destinations") + arg("compose-destinations.moduleName", "settings") +} diff --git a/feature/settings/consumer-rules.pro b/feature/settings/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/feature/settings/proguard-rules.pro b/feature/settings/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/feature/settings/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/settings/src/androidTest/java/com/android/swingmusic/settings/ExampleInstrumentedTest.kt b/feature/settings/src/androidTest/java/com/android/swingmusic/settings/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..e678b60f --- /dev/null +++ b/feature/settings/src/androidTest/java/com/android/swingmusic/settings/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.android.swingmusic.settings + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.android.swingmusic.settings.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/settings/src/main/AndroidManifest.xml b/feature/settings/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5918e68 --- /dev/null +++ b/feature/settings/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt new file mode 100644 index 00000000..cb82ba0d --- /dev/null +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt @@ -0,0 +1,43 @@ +package com.android.swingmusic.settings.data.datastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppSettings @Inject constructor( + private val context: Context +) { + private companion object { + private val Context.dataStore: DataStore by preferencesDataStore(name = "app_settings") + + private val ALBUM_GRID_COUNT = intPreferencesKey("album_grid_count") + private val ARTIST_GRID_COUNT = intPreferencesKey("artist_grid_count") + } + + val getAlbumGridCount: Flow = context.dataStore.data.map { preferences -> + preferences[ALBUM_GRID_COUNT] ?: 2 + } + val getArtistGridCount: Flow = context.dataStore.data.map { preferences -> + preferences[ARTIST_GRID_COUNT] ?: 2 + } + + suspend fun updateAlbumGridCount(count: Int) { + context.dataStore.edit { preferences -> + preferences[ALBUM_GRID_COUNT] = count + } + } + + suspend fun updateArtistGridCount(count: Int) { + context.dataStore.edit { preferences -> + preferences[ARTIST_GRID_COUNT] = count + } + } +} diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt new file mode 100644 index 00000000..233482b6 --- /dev/null +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt @@ -0,0 +1,25 @@ +package com.android.swingmusic.settings.data.repository + +import com.android.swingmusic.settings.data.datastore.AppSettings +import com.android.swingmusic.settings.domain.repository.AppSettingsRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppSettingsDataRepository @Inject constructor( + private val appSettings: AppSettings +) : AppSettingsRepository { + + override val albumGridCount: Flow = appSettings.getAlbumGridCount + + override val artistGridCount: Flow = appSettings.getArtistGridCount + + override suspend fun setAlbumGridCount(count: Int) { + appSettings.updateAlbumGridCount(count) + } + + override suspend fun setArtistGridCount(count: Int) { + appSettings.updateArtistGridCount(count) + } +} diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/di/AppSettingsModule.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/di/AppSettingsModule.kt new file mode 100644 index 00000000..279c9ed7 --- /dev/null +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/di/AppSettingsModule.kt @@ -0,0 +1,23 @@ +package com.android.swingmusic.settings.di + +import android.content.Context +import com.android.swingmusic.settings.data.datastore.AppSettings +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppSettingsModule { + + @Provides + @Singleton + fun provideAppSettingsDataStore( + @ApplicationContext context: Context + ): AppSettings { + return AppSettings(context) + } +} diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/di/RepositoryModule.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/di/RepositoryModule.kt new file mode 100644 index 00000000..ad8195c9 --- /dev/null +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/di/RepositoryModule.kt @@ -0,0 +1,20 @@ +package com.android.swingmusic.settings.di + +import com.android.swingmusic.settings.data.repository.AppSettingsDataRepository +import com.android.swingmusic.settings.domain.repository.AppSettingsRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + + @Binds + @Singleton + abstract fun bindAppSettingsRepository( + impl: AppSettingsDataRepository + ): AppSettingsRepository +} diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt new file mode 100644 index 00000000..ab7fce5f --- /dev/null +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt @@ -0,0 +1,11 @@ +package com.android.swingmusic.settings.domain.repository + +import kotlinx.coroutines.flow.Flow + +interface AppSettingsRepository { + val albumGridCount: Flow + val artistGridCount: Flow + + suspend fun setAlbumGridCount(count: Int) + suspend fun setArtistGridCount(count: Int) +} diff --git a/feature/settings/src/test/java/com/android/swingmusic/settings/ExampleUnitTest.kt b/feature/settings/src/test/java/com/android/swingmusic/settings/ExampleUnitTest.kt new file mode 100644 index 00000000..c16564ec --- /dev/null +++ b/feature/settings/src/test/java/com/android/swingmusic/settings/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.android.swingmusic.settings + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index bc94b0b0..99fa134b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,3 +36,4 @@ include(":feature:home") include(":feature:album") include(":feature:common") include(":feature:search") +include(":feature:settings") From 2cab2cfec2ad622ea454d23089bddc41e52bcd2a Mon Sep 17 00:00:00 2001 From: Eric Gacoki Date: Sat, 13 Dec 2025 17:13:29 +0300 Subject: [PATCH 2/5] encapsulate artistsUiState in ArtistsViewModel --- .../viewmodel/ArtistsViewModel.kt | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt index 8b672442..39cef1e0 100644 --- a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt +++ b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt @@ -1,6 +1,7 @@ package com.android.swingmusic.artist.presentation.viewmodel import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -27,7 +28,8 @@ class ArtistsViewModel @Inject constructor( private val settingsRepository: AppSettingsRepository ) : ViewModel() { private var baseUrl: MutableState = mutableStateOf(null) - val artistsUiState: MutableState = mutableStateOf(ArtistsUiState()) + private val _artistsUiState: MutableState = mutableStateOf(ArtistsUiState()) + val artistsUiState: State = _artistsUiState val sortArtistsByEntries: List> = listOf( Pair(SortBy.LAST_PLAYED, "lastplayed"), @@ -43,7 +45,7 @@ class ArtistsViewModel @Inject constructor( private fun getArtistsCount() { viewModelScope.launch { artistRepository.getArtistsCount().collectLatest { - artistsUiState.value = artistsUiState.value.copy(totalArtists = it) + _artistsUiState.value = _artistsUiState.value.copy(totalArtists = it) } } } @@ -54,7 +56,7 @@ class ArtistsViewModel @Inject constructor( SortOrder.ASCENDING -> 0 } viewModelScope.launch { - artistsUiState.value = artistsUiState.value.copy( + _artistsUiState.value = _artistsUiState.value.copy( pagingArtists = artistRepository.getPagingArtists( sortBy = sortBy, sortOrder = order @@ -68,7 +70,7 @@ class ArtistsViewModel @Inject constructor( settingsRepository.artistGridCount .onEach { newCount -> - artistsUiState.value = artistsUiState.value.copy(gridCount = newCount) + _artistsUiState.value = _artistsUiState.value.copy(gridCount = newCount) } .launchIn(viewModelScope) } @@ -83,8 +85,8 @@ class ArtistsViewModel @Inject constructor( init { getPagingArtists( - sortBy = artistsUiState.value.sortBy.second, - sortOrder = artistsUiState.value.sortOrder + sortBy = _artistsUiState.value.sortBy.second, + sortOrder = _artistsUiState.value.sortOrder ) getArtistsCount() } @@ -99,21 +101,21 @@ class ArtistsViewModel @Inject constructor( when (event) { is ArtistUiEvent.OnSortBy -> { // Retry fetching artist count if the previous sorting resulted to Error - if (artistsUiState.value.totalArtists is Resource.Error) { + if (_artistsUiState.value.totalArtists is Resource.Error) { getArtistsCount() } - if (event.sortByPair == artistsUiState.value.sortBy) { - val newOrder = if (artistsUiState.value.sortOrder == SortOrder.ASCENDING) + if (event.sortByPair == _artistsUiState.value.sortBy) { + val newOrder = if (_artistsUiState.value.sortOrder == SortOrder.ASCENDING) SortOrder.DESCENDING else SortOrder.ASCENDING - artistsUiState.value = artistsUiState.value.copy(sortOrder = newOrder) + _artistsUiState.value = _artistsUiState.value.copy(sortOrder = newOrder) getPagingArtists( sortBy = event.sortByPair.second, sortOrder = newOrder ) } else { - artistsUiState.value = artistsUiState.value.copy( + _artistsUiState.value = _artistsUiState.value.copy( sortBy = event.sortByPair, sortOrder = SortOrder.DESCENDING ) @@ -133,14 +135,14 @@ class ArtistsViewModel @Inject constructor( } is ArtistUiEvent.OnRetry -> { - if (artistsUiState.value.totalArtists is Resource.Error) { + if (_artistsUiState.value.totalArtists is Resource.Error) { getArtistsCount() } } is ArtistUiEvent.OnPullToRefresh -> { - val sortBy = artistsUiState.value.sortBy - val sortOrder = artistsUiState.value.sortOrder + val sortBy = _artistsUiState.value.sortBy + val sortOrder = _artistsUiState.value.sortOrder getPagingArtists( sortBy = sortBy.second, From 598f86316947b8cf46ddf747cbd051d71f6f90aa Mon Sep 17 00:00:00 2001 From: Ericgacoki Date: Sat, 13 Dec 2025 18:55:49 +0300 Subject: [PATCH 3/5] feat: persist album and artist sort preferences This commit introduces the ability to persist sorting preferences for albums and artists in the app settings. - Added `SortBy` and `SortOrder` to `AppSettingsRepository` and `AppSettingsDataRepository`. - Implemented storing and retrieving sort preferences using `DataStore`. - Updated `AllAlbumsViewModel` and `ArtistsViewModel` to use and update the persisted sort settings. - Refactored ViewModels to use `combine` for observing multiple settings flows. --- .../viewmodel/AllAlbumsViewModel.kt | 76 +++++++++++-------- .../viewmodel/ArtistsViewModel.kt | 75 +++++++++++------- feature/settings/build.gradle.kts | 3 + .../settings/data/datastore/AppSettings.kt | 60 +++++++++++---- .../repository/AppSettingsDataRepository.kt | 39 +++++++++- .../repository/AppSettingsRepository.kt | 14 +++- 6 files changed, 192 insertions(+), 75 deletions(-) diff --git a/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt b/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt index 36a64a5a..6c4589da 100644 --- a/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt +++ b/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt @@ -17,6 +17,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -70,21 +71,34 @@ class AllAlbumsViewModel @Inject constructor( } } + // Settings init { - getBaseUrl() + combine( + settingsRepository.albumGridCount, + settingsRepository.albumSortOrder, + settingsRepository.albumSortBy + ) { gridCount, sortOrder, sortBy -> + val sortByPair = sortAlbumsByEntries.find { it.first == sortBy } + ?: Pair(SortBy.LAST_PLAYED, "lastplayed") + + Triple(gridCount, sortOrder, sortByPair) + }.onEach { (gridCount, sortOrder, sortByPair) -> + _allAlbumsUiState.value = _allAlbumsUiState.value.copy( + gridCount = gridCount, + sortOrder = sortOrder, + sortBy = sortByPair + ) + }.launchIn(viewModelScope) + } - settingsRepository.albumGridCount - .onEach { newCount -> - _allAlbumsUiState.value = _allAlbumsUiState.value.copy(gridCount = newCount) - } - .launchIn(viewModelScope) + init { + getBaseUrl() } private fun getBaseUrl() { viewModelScope.launch { _baseUrl.value = authRepository.getBaseUrl() } - } init { @@ -104,29 +118,31 @@ class AllAlbumsViewModel @Inject constructor( fun onAlbumsUiEvent(event: AlbumsUiEvent) { when (event) { is AlbumsUiEvent.OnSortBy -> { - // Retry fetching artist count if the previous sorting resulted to Error - if (_allAlbumsUiState.value.totalAlbums is Resource.Error) { - getAlbumCount() - } - - if (event.sortByPair == _allAlbumsUiState.value.sortBy) { - val newOrder = if (_allAlbumsUiState.value.sortOrder == SortOrder.ASCENDING) - SortOrder.DESCENDING else SortOrder.ASCENDING - - _allAlbumsUiState.value = _allAlbumsUiState.value.copy(sortOrder = newOrder) - getPagingAlbums( - sortBy = event.sortByPair.second, - sortOrder = newOrder - ) - } else { - _allAlbumsUiState.value = _allAlbumsUiState.value.copy( - sortBy = event.sortByPair, - sortOrder = SortOrder.DESCENDING - ) - getPagingAlbums( - sortBy = event.sortByPair.second, - sortOrder = SortOrder.DESCENDING - ) + viewModelScope.launch { + // Retry fetching artist count if the previous sorting resulted to Error + if (_allAlbumsUiState.value.totalAlbums is Resource.Error) { + getAlbumCount() + } + + if (event.sortByPair == _allAlbumsUiState.value.sortBy) { + val newOrder = if (_allAlbumsUiState.value.sortOrder == SortOrder.ASCENDING) + SortOrder.DESCENDING else SortOrder.ASCENDING + + settingsRepository.setAlbumSortOrder(newOrder) + + getPagingAlbums( + sortBy = event.sortByPair.second, + sortOrder = newOrder + ) + } else { + settingsRepository.setAlbumSortOrder(SortOrder.DESCENDING) + settingsRepository.setAlbumSortBy(event.sortByPair.first) + + getPagingAlbums( + sortBy = event.sortByPair.second, + sortOrder = SortOrder.DESCENDING + ) + } } } diff --git a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt index 39cef1e0..6cee7e8c 100644 --- a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt +++ b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt @@ -16,6 +16,7 @@ import com.android.swingmusic.core.domain.util.SortOrder import com.android.swingmusic.settings.domain.repository.AppSettingsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -66,13 +67,26 @@ class ArtistsViewModel @Inject constructor( } init { - getBaseUrl() + combine( + settingsRepository.artistGridCount, + settingsRepository.artistSortOrder, + settingsRepository.artistSortBy + ) { gridCount, sortOrder, sortBy -> + val sortByPair = sortArtistsByEntries.find { it.first == sortBy } + ?: Pair(SortBy.LAST_PLAYED, "lastplayed") + + Triple(gridCount, sortOrder, sortByPair) + }.onEach { (gridCount, sortOrder, sortByPair) -> + _artistsUiState.value = _artistsUiState.value.copy( + gridCount = gridCount, + sortOrder = sortOrder, + sortBy = sortByPair + ) + }.launchIn(viewModelScope) + } - settingsRepository.artistGridCount - .onEach { newCount -> - _artistsUiState.value = _artistsUiState.value.copy(gridCount = newCount) - } - .launchIn(viewModelScope) + init { + getBaseUrl() } fun baseUrl() = baseUrl @@ -100,29 +114,32 @@ class ArtistsViewModel @Inject constructor( fun onArtistUiEvent(event: ArtistUiEvent) { when (event) { is ArtistUiEvent.OnSortBy -> { - // Retry fetching artist count if the previous sorting resulted to Error - if (_artistsUiState.value.totalArtists is Resource.Error) { - getArtistsCount() - } - - if (event.sortByPair == _artistsUiState.value.sortBy) { - val newOrder = if (_artistsUiState.value.sortOrder == SortOrder.ASCENDING) - SortOrder.DESCENDING else SortOrder.ASCENDING - - _artistsUiState.value = _artistsUiState.value.copy(sortOrder = newOrder) - getPagingArtists( - sortBy = event.sortByPair.second, - sortOrder = newOrder - ) - } else { - _artistsUiState.value = _artistsUiState.value.copy( - sortBy = event.sortByPair, - sortOrder = SortOrder.DESCENDING - ) - getPagingArtists( - sortBy = event.sortByPair.second, - sortOrder = SortOrder.DESCENDING - ) + viewModelScope.launch { + // Retry fetching artist count if the previous sorting resulted to Error + if (_artistsUiState.value.totalArtists is Resource.Error) { + getArtistsCount() + } + + if (event.sortByPair == _artistsUiState.value.sortBy) { + val newOrder = if (_artistsUiState.value.sortOrder == SortOrder.ASCENDING) + SortOrder.DESCENDING else SortOrder.ASCENDING + + settingsRepository.setArtistSortOrder(newOrder) + + _artistsUiState.value = _artistsUiState.value.copy(sortOrder = newOrder) + getPagingArtists( + sortBy = event.sortByPair.second, + sortOrder = newOrder + ) + } else { + settingsRepository.setArtistSortOrder(SortOrder.DESCENDING) + settingsRepository.setArtistSortBy(event.sortByPair.first) + + getPagingArtists( + sortBy = event.sortByPair.second, + sortOrder = SortOrder.DESCENDING + ) + } } } diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 66353581..9d3b3a94 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -47,6 +47,9 @@ dependencies { // Core implementation("androidx.core:core-ktx:1.15.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") + // Project Core + implementation(project(":core")) + // Compose implementation(platform("androidx.compose:compose-bom:2024.10.01")) diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt index cb82ba0d..614e17ea 100644 --- a/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/data/datastore/AppSettings.kt @@ -5,7 +5,11 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import com.android.swingmusic.core.domain.util.SortBy +import com.android.swingmusic.core.domain.util.SortOrder +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -13,31 +17,59 @@ import javax.inject.Singleton @Singleton class AppSettings @Inject constructor( - private val context: Context + @ApplicationContext private val context: Context ) { private companion object { private val Context.dataStore: DataStore by preferencesDataStore(name = "app_settings") private val ALBUM_GRID_COUNT = intPreferencesKey("album_grid_count") + private val ALBUM_SORT_BY = stringPreferencesKey("album_sort_by") + private val ALBUM_SORT_ORDER = stringPreferencesKey("album_sort_order") + private val ARTIST_GRID_COUNT = intPreferencesKey("artist_grid_count") + private val ARTIST_SORT_BY = stringPreferencesKey("artist_sort_by") + private val ARTIST_SORT_ORDER = stringPreferencesKey("artist_sort_order") } - val getAlbumGridCount: Flow = context.dataStore.data.map { preferences -> - preferences[ALBUM_GRID_COUNT] ?: 2 + // Album Flows + val getAlbumGridCount: Flow = context.dataStore.data.map { + it[ALBUM_GRID_COUNT] ?: 2 } - val getArtistGridCount: Flow = context.dataStore.data.map { preferences -> - preferences[ARTIST_GRID_COUNT] ?: 2 + val getAlbumSortBy: Flow = context.dataStore.data.map { + it[ALBUM_SORT_BY] ?: SortBy.LAST_PLAYED.name } - - suspend fun updateAlbumGridCount(count: Int) { - context.dataStore.edit { preferences -> - preferences[ALBUM_GRID_COUNT] = count - } + val getAlbumSortOrder: Flow = context.dataStore.data.map { + it[ALBUM_SORT_ORDER] ?: SortOrder.DESCENDING.name } - suspend fun updateArtistGridCount(count: Int) { - context.dataStore.edit { preferences -> - preferences[ARTIST_GRID_COUNT] = count - } + // Artist Flows + val getArtistGridCount: Flow = context.dataStore.data.map { + it[ARTIST_GRID_COUNT] ?: 2 + } + val getArtistSortBy: Flow = context.dataStore.data.map { + it[ARTIST_SORT_BY] ?: SortBy.LAST_PLAYED.name } + val getArtistSortOrder: Flow = context.dataStore.data.map { + it[ARTIST_SORT_ORDER] ?: SortOrder.DESCENDING.name + } + + // Album Update methods + suspend fun updateAlbumGridCount(count: Int) = + context.dataStore.edit { it[ALBUM_GRID_COUNT] = count } + + suspend fun updateAlbumSortBy(value: String) = + context.dataStore.edit { it[ALBUM_SORT_BY] = value } + + suspend fun updateAlbumSortOrder(value: String) = + context.dataStore.edit { it[ALBUM_SORT_ORDER] = value } + + // Artist Update Methods + suspend fun updateArtistGridCount(count: Int) = + context.dataStore.edit { it[ARTIST_GRID_COUNT] = count } + + suspend fun updateArtistSortBy(value: String) = + context.dataStore.edit { it[ARTIST_SORT_BY] = value } + + suspend fun updateArtistSortOrder(value: String) = + context.dataStore.edit { it[ARTIST_SORT_ORDER] = value } } diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt index 233482b6..9294f7b8 100644 --- a/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/data/repository/AppSettingsDataRepository.kt @@ -1,8 +1,11 @@ package com.android.swingmusic.settings.data.repository +import com.android.swingmusic.core.domain.util.SortBy +import com.android.swingmusic.core.domain.util.SortOrder import com.android.swingmusic.settings.data.datastore.AppSettings import com.android.swingmusic.settings.domain.repository.AppSettingsRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @@ -11,15 +14,49 @@ class AppSettingsDataRepository @Inject constructor( private val appSettings: AppSettings ) : AppSettingsRepository { + // --- Album Settings --- override val albumGridCount: Flow = appSettings.getAlbumGridCount - override val artistGridCount: Flow = appSettings.getArtistGridCount + override val albumSortBy: Flow = appSettings.getAlbumSortBy.map { + SortBy.valueOf(it) + } + + override val albumSortOrder: Flow = appSettings.getAlbumSortOrder.map { + SortOrder.valueOf(it) + } override suspend fun setAlbumGridCount(count: Int) { appSettings.updateAlbumGridCount(count) } + override suspend fun setAlbumSortBy(sortBy: SortBy) { + appSettings.updateAlbumSortBy(sortBy.name) + } + + override suspend fun setAlbumSortOrder(order: SortOrder) { + appSettings.updateAlbumSortOrder(order.name) + } + + // --- Artist Settings --- + override val artistGridCount: Flow = appSettings.getArtistGridCount + + override val artistSortBy: Flow = appSettings.getArtistSortBy.map { + SortBy.valueOf(it) + } + + override val artistSortOrder: Flow = appSettings.getArtistSortOrder.map { + SortOrder.valueOf(it) + } + override suspend fun setArtistGridCount(count: Int) { appSettings.updateArtistGridCount(count) } + + override suspend fun setArtistSortBy(sortBy: SortBy) { + appSettings.updateArtistSortBy(sortBy.name) + } + + override suspend fun setArtistSortOrder(order: SortOrder) { + appSettings.updateArtistSortOrder(order.name) + } } diff --git a/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt b/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt index ab7fce5f..d94d656b 100644 --- a/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt +++ b/feature/settings/src/main/java/com/android/swingmusic/settings/domain/repository/AppSettingsRepository.kt @@ -1,11 +1,23 @@ package com.android.swingmusic.settings.domain.repository +import com.android.swingmusic.core.domain.util.SortBy +import com.android.swingmusic.core.domain.util.SortOrder import kotlinx.coroutines.flow.Flow interface AppSettingsRepository { val albumGridCount: Flow + val albumSortBy: Flow + val albumSortOrder: Flow + val artistGridCount: Flow - + val artistSortBy: Flow + val artistSortOrder: Flow + suspend fun setAlbumGridCount(count: Int) + suspend fun setAlbumSortBy(sortBy: SortBy) + suspend fun setAlbumSortOrder(order: SortOrder) + suspend fun setArtistGridCount(count: Int) + suspend fun setArtistSortBy(sortBy: SortBy) + suspend fun setArtistSortOrder(order: SortOrder) } From 0500a2fcf7626610899f2d03f742aaee9667e232 Mon Sep 17 00:00:00 2001 From: Ericgacoki Date: Sat, 13 Dec 2025 19:06:47 +0300 Subject: [PATCH 4/5] try fix: order reset on first composition --- .../viewmodel/AllAlbumsViewModel.kt | 56 +++++++++---------- .../viewmodel/ArtistsViewModel.kt | 56 +++++++++---------- 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt b/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt index 6c4589da..d11714ae 100644 --- a/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt +++ b/feature/album/src/main/java/com/android/swingmusic/album/presentation/viewmodel/AllAlbumsViewModel.kt @@ -48,29 +48,6 @@ class AllAlbumsViewModel @Inject constructor( Pair(SortBy.DURATION, "duration"), ) - private fun getAlbumCount() { - viewModelScope.launch { - artistRepository.getAlbumCount().collectLatest { - _allAlbumsUiState.value = _allAlbumsUiState.value.copy(totalAlbums = it) - } - } - } - - private fun getPagingAlbums(sortBy: String, sortOrder: SortOrder) { - val order = when (sortOrder) { - SortOrder.DESCENDING -> 1 - SortOrder.ASCENDING -> 0 - } - viewModelScope.launch { - _allAlbumsUiState.value = _allAlbumsUiState.value.copy( - pagingAlbums = artistRepository.getPagingAlbums( - sortBy = sortBy, - sortOrder = order - ).cachedIn(viewModelScope) - ) - } - } - // Settings init { combine( @@ -89,6 +66,12 @@ class AllAlbumsViewModel @Inject constructor( sortBy = sortByPair ) }.launchIn(viewModelScope) + + getPagingAlbums( + sortBy = _allAlbumsUiState.value.sortBy.second, + sortOrder = _allAlbumsUiState.value.sortOrder + ) + getAlbumCount() } init { @@ -101,12 +84,27 @@ class AllAlbumsViewModel @Inject constructor( } } - init { - getPagingAlbums( - sortBy = _allAlbumsUiState.value.sortBy.second, - sortOrder = _allAlbumsUiState.value.sortOrder - ) - getAlbumCount() + private fun getAlbumCount() { + viewModelScope.launch { + artistRepository.getAlbumCount().collectLatest { + _allAlbumsUiState.value = _allAlbumsUiState.value.copy(totalAlbums = it) + } + } + } + + private fun getPagingAlbums(sortBy: String, sortOrder: SortOrder) { + val order = when (sortOrder) { + SortOrder.DESCENDING -> 1 + SortOrder.ASCENDING -> 0 + } + viewModelScope.launch { + _allAlbumsUiState.value = _allAlbumsUiState.value.copy( + pagingAlbums = artistRepository.getPagingAlbums( + sortBy = sortBy, + sortOrder = order + ).cachedIn(viewModelScope) + ) + } } private fun updateGridCount(count: Int) { diff --git a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt index 6cee7e8c..2c74d1d4 100644 --- a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt +++ b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt @@ -43,29 +43,6 @@ class ArtistsViewModel @Inject constructor( Pair(SortBy.DURATION, "duration"), ) - private fun getArtistsCount() { - viewModelScope.launch { - artistRepository.getArtistsCount().collectLatest { - _artistsUiState.value = _artistsUiState.value.copy(totalArtists = it) - } - } - } - - private fun getPagingArtists(sortBy: String, sortOrder: SortOrder) { - val order = when (sortOrder) { - SortOrder.DESCENDING -> 1 - SortOrder.ASCENDING -> 0 - } - viewModelScope.launch { - _artistsUiState.value = _artistsUiState.value.copy( - pagingArtists = artistRepository.getPagingArtists( - sortBy = sortBy, - sortOrder = order - ).cachedIn(viewModelScope) - ) - } - } - init { combine( settingsRepository.artistGridCount, @@ -83,6 +60,12 @@ class ArtistsViewModel @Inject constructor( sortBy = sortByPair ) }.launchIn(viewModelScope) + + getPagingArtists( + sortBy = _artistsUiState.value.sortBy.second, + sortOrder = _artistsUiState.value.sortOrder + ) + getArtistsCount() } init { @@ -97,12 +80,27 @@ class ArtistsViewModel @Inject constructor( } } - init { - getPagingArtists( - sortBy = _artistsUiState.value.sortBy.second, - sortOrder = _artistsUiState.value.sortOrder - ) - getArtistsCount() + private fun getArtistsCount() { + viewModelScope.launch { + artistRepository.getArtistsCount().collectLatest { + _artistsUiState.value = _artistsUiState.value.copy(totalArtists = it) + } + } + } + + private fun getPagingArtists(sortBy: String, sortOrder: SortOrder) { + val order = when (sortOrder) { + SortOrder.DESCENDING -> 1 + SortOrder.ASCENDING -> 0 + } + viewModelScope.launch { + _artistsUiState.value = _artistsUiState.value.copy( + pagingArtists = artistRepository.getPagingArtists( + sortBy = sortBy, + sortOrder = order + ).cachedIn(viewModelScope) + ) + } } private fun updateGridCount(count: Int) { From 290a5d0b3e0020f20ac75ed9c37df0f626660ec1 Mon Sep 17 00:00:00 2001 From: Eric Gacoki Date: Sat, 13 Dec 2025 23:02:54 +0300 Subject: [PATCH 5/5] refactor: use distinctUntilChanged for sorting flows and add Chucker interceptor - Refactor `ArtistsViewModel` and `AllAlbumsViewModel` to observe grid count separately from sort settings. - Apply `distinctUntilChanged()` to sort order and sort by flows to prevent unnecessary updates. - Remove redundant calls to `getPagingArtists` and `getPagingAlbums` inside event handlers, as they are now reactive to setting changes. - Add Chucker interceptor to `AuthModule` for HTTP inspection in debug builds. - Add `tools:node="replace"` to WAKE_LOCK permission in AndroidManifest. This prevents Chucker from removing this permission on Android 8+ --- app/src/main/AndroidManifest.xml | 2 +- auth/build.gradle.kts | 4 ++ .../swingmusic/auth/data/di/AuthModule.kt | 6 ++- .../viewmodel/AllAlbumsViewModel.kt | 39 ++++++++----------- .../viewmodel/ArtistsViewModel.kt | 39 +++++++------------ 5 files changed, 41 insertions(+), 49 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6f7fb286..3afb92a7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ - + + _allAlbumsUiState.value = _allAlbumsUiState.value.copy(gridCount = gridCount) + }.launchIn(viewModelScope) + combine( - settingsRepository.albumGridCount, - settingsRepository.albumSortOrder, - settingsRepository.albumSortBy - ) { gridCount, sortOrder, sortBy -> + settingsRepository.albumSortOrder.distinctUntilChanged(), + settingsRepository.albumSortBy.distinctUntilChanged() + ) { sortOrder, sortBy -> val sortByPair = sortAlbumsByEntries.find { it.first == sortBy } ?: Pair(SortBy.LAST_PLAYED, "lastplayed") - Triple(gridCount, sortOrder, sortByPair) - }.onEach { (gridCount, sortOrder, sortByPair) -> + Pair(sortOrder, sortByPair) + }.onEach { (sortOrder, sortByPair) -> _allAlbumsUiState.value = _allAlbumsUiState.value.copy( - gridCount = gridCount, sortOrder = sortOrder, sortBy = sortByPair ) + getPagingAlbums( + sortBy = sortByPair.second, + sortOrder = sortOrder + ) }.launchIn(viewModelScope) - - getPagingAlbums( - sortBy = _allAlbumsUiState.value.sortBy.second, - sortOrder = _allAlbumsUiState.value.sortOrder - ) - getAlbumCount() } init { getBaseUrl() + getAlbumCount() } private fun getBaseUrl() { @@ -127,19 +130,9 @@ class AllAlbumsViewModel @Inject constructor( SortOrder.DESCENDING else SortOrder.ASCENDING settingsRepository.setAlbumSortOrder(newOrder) - - getPagingAlbums( - sortBy = event.sortByPair.second, - sortOrder = newOrder - ) } else { settingsRepository.setAlbumSortOrder(SortOrder.DESCENDING) settingsRepository.setAlbumSortBy(event.sortByPair.first) - - getPagingAlbums( - sortBy = event.sortByPair.second, - sortOrder = SortOrder.DESCENDING - ) } } } diff --git a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt index 2c74d1d4..be007d1e 100644 --- a/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt +++ b/feature/artist/src/main/java/com/android/swingmusic/artist/presentation/viewmodel/ArtistsViewModel.kt @@ -17,6 +17,7 @@ import com.android.swingmusic.settings.domain.repository.AppSettingsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -44,32 +45,33 @@ class ArtistsViewModel @Inject constructor( ) init { + settingsRepository.artistGridCount.onEach { gridCount -> + _artistsUiState.value = _artistsUiState.value.copy(gridCount = gridCount) + }.launchIn(viewModelScope) + combine( - settingsRepository.artistGridCount, - settingsRepository.artistSortOrder, - settingsRepository.artistSortBy - ) { gridCount, sortOrder, sortBy -> + settingsRepository.artistSortOrder.distinctUntilChanged(), + settingsRepository.artistSortBy.distinctUntilChanged() + ) { sortOrder, sortBy -> val sortByPair = sortArtistsByEntries.find { it.first == sortBy } ?: Pair(SortBy.LAST_PLAYED, "lastplayed") - Triple(gridCount, sortOrder, sortByPair) - }.onEach { (gridCount, sortOrder, sortByPair) -> + Pair(sortOrder, sortByPair) + }.onEach { (sortOrder, sortByPair) -> _artistsUiState.value = _artistsUiState.value.copy( - gridCount = gridCount, sortOrder = sortOrder, sortBy = sortByPair ) + getPagingArtists( + sortBy = _artistsUiState.value.sortBy.second, + sortOrder = _artistsUiState.value.sortOrder + ) }.launchIn(viewModelScope) - - getPagingArtists( - sortBy = _artistsUiState.value.sortBy.second, - sortOrder = _artistsUiState.value.sortOrder - ) - getArtistsCount() } init { getBaseUrl() + getArtistsCount() } fun baseUrl() = baseUrl @@ -123,20 +125,9 @@ class ArtistsViewModel @Inject constructor( SortOrder.DESCENDING else SortOrder.ASCENDING settingsRepository.setArtistSortOrder(newOrder) - - _artistsUiState.value = _artistsUiState.value.copy(sortOrder = newOrder) - getPagingArtists( - sortBy = event.sortByPair.second, - sortOrder = newOrder - ) } else { settingsRepository.setArtistSortOrder(SortOrder.DESCENDING) settingsRepository.setArtistSortBy(event.sortByPair.first) - - getPagingArtists( - sortBy = event.sortByPair.second, - sortOrder = SortOrder.DESCENDING - ) } } }