WIP: Make :core:data as the multiplatform module

pull/1323/head
lihenggui 2 years ago
parent e505cae541
commit 3fc3452304

@ -14,9 +14,9 @@
* limitations under the License.
*/
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.kmp.library)
alias(libs.plugins.nowinandroid.kotlin.inject)
alias(libs.plugins.nowinandroid.android.library.jacoco)
alias(libs.plugins.nowinandroid.android.hilt)
id("kotlinx-serialization")
}
@ -30,17 +30,30 @@ android {
}
}
dependencies {
api(projects.core.common)
api(projects.core.database)
api(projects.core.datastore)
api(projects.core.network)
kotlin {
sourceSets {
commonMain.dependencies {
api(projects.core.common)
api(projects.core.database)
api(projects.core.datastore)
api(projects.core.network)
implementation(projects.core.analytics)
implementation(projects.core.notifications)
implementation(projects.core.analytics)
implementation(projects.core.notifications)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.kotlinx.serialization.json)
// implementation(projects.core.datastoreTest)
// implementation(projects.core.testing)
}
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.serialization.json)
testImplementation(projects.core.datastoreTest)
testImplementation(projects.core.testing)
androidMain.dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.tracing.ktx)
}
}
}

@ -0,0 +1,35 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.data.di
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@Component
internal actual abstract class PlatformDependentDataModule {
@Provides
internal actual fun bindsNetworkMonitor(): NetworkMonitor {
TODO()
}
@Provides
internal actual fun bindsTimeZoneMonitor(): TimeZoneMonitor {
TODO()
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,15 +26,15 @@ import android.net.NetworkRequest.Builder
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.core.content.getSystemService
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
internal class ConnectivityManagerNetworkMonitor @Inject constructor(
@ApplicationContext private val context: Context,
@Inject
internal class ConnectivityManagerNetworkMonitor(
private val context: Context,
) : NetworkMonitor {
override val isOnline: Flow<Boolean> = callbackFlow {
val connectivityManager = context.getSystemService<ConnectivityManager>()

@ -23,14 +23,9 @@ import android.content.IntentFilter
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import com.google.samples.apps.nowinandroid.core.di.IODispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
@ -40,23 +35,14 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.shareIn
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toKotlinTimeZone
import me.tatarka.inject.annotations.Inject
import java.time.ZoneId
import javax.inject.Inject
import javax.inject.Singleton
/**
* Utility for reporting current timezone the device has set.
* It always emits at least once with default setting and then for each TZ change.
*/
interface TimeZoneMonitor {
val currentTimeZone: Flow<TimeZone>
}
@Singleton
internal class TimeZoneBroadcastMonitor @Inject constructor(
@ApplicationContext private val context: Context,
@ApplicationScope appScope: CoroutineScope,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
@Inject
internal class TimeZoneBroadcastMonitor(
private val context: Context,
appScope: CoroutineScope,
private val ioDispatcher: IODispatcher,
) : TimeZoneMonitor {
override val currentTimeZone: SharedFlow<TimeZone> =

@ -16,7 +16,7 @@
package com.google.samples.apps.nowinandroid.core.data
import android.util.Log
import co.touchlab.kermit.Logger
import com.google.samples.apps.nowinandroid.core.datastore.ChangeListVersions
import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList
import kotlinx.coroutines.flow.Flow
@ -59,11 +59,10 @@ private suspend fun <T> suspendRunCatching(block: suspend () -> T): Result<T> =
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
Log.i(
"suspendRunCatching",
"Failed to evaluate a suspendRunCatchingBlock. Returning failure Result",
exception,
)
Logger.i {
"suspendRunCatching" +
"Failed to evaluate a suspendRunCatchingBlock. Returning failure Result"
}
Result.failure(exception)
}

@ -26,49 +26,34 @@ import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRep
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.ConnectivityManagerNetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneBroadcastMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@Module
@InstallIn(SingletonComponent::class)
@Component
abstract class DataModule {
@Binds
internal abstract fun bindsTopicRepository(
@Provides
internal fun bindsTopicRepository(
topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository
): TopicsRepository = topicsRepository
@Binds
internal abstract fun bindsNewsResourceRepository(
@Provides
internal fun bindsNewsResourceRepository(
newsRepository: OfflineFirstNewsRepository,
): NewsRepository
): NewsRepository = newsRepository
@Binds
internal abstract fun bindsUserDataRepository(
@Provides
internal fun bindsUserDataRepository(
userDataRepository: OfflineFirstUserDataRepository,
): UserDataRepository
): UserDataRepository = userDataRepository
@Binds
internal abstract fun bindsRecentSearchRepository(
@Provides
internal fun bindsRecentSearchRepository(
recentSearchRepository: DefaultRecentSearchRepository,
): RecentSearchRepository
): RecentSearchRepository = recentSearchRepository
@Binds
internal abstract fun bindsSearchContentsRepository(
@Provides
internal fun bindsSearchContentsRepository(
searchContentsRepository: DefaultSearchContentsRepository,
): SearchContentsRepository
@Binds
internal abstract fun bindsNetworkMonitor(
networkMonitor: ConnectivityManagerNetworkMonitor,
): NetworkMonitor
@Binds
internal abstract fun binds(impl: TimeZoneBroadcastMonitor): TimeZoneMonitor
): SearchContentsRepository = searchContentsRepository
}

@ -0,0 +1,29 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.data.di
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import me.tatarka.inject.annotations.Provides
internal expect abstract class PlatformDependentDataModule {
@Provides
internal fun bindsNetworkMonitor(): NetworkMonitor
@Provides
internal fun bindsTimeZoneMonitor(): TimeZoneMonitor
}

@ -18,16 +18,13 @@ package com.google.samples.apps.nowinandroid.core.data.di
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@Module
@InstallIn(SingletonComponent::class)
internal interface UserNewsResourceRepositoryModule {
@Binds
@Component
internal abstract class UserNewsResourceRepositoryModule {
@Provides
fun bindsUserNewsResourceRepository(
userDataRepository: CompositeUserNewsResourceRepository,
): UserNewsResourceRepository
): UserNewsResourceRepository = userDataRepository
}

@ -24,13 +24,14 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
/**
* Implements a [UserNewsResourceRepository] by combining a [NewsRepository] with a
* [UserDataRepository].
*/
class CompositeUserNewsResourceRepository @Inject constructor(
@Inject
class CompositeUserNewsResourceRepository(
val newsRepository: NewsRepository,
val userDataRepository: UserDataRepository,
) : UserNewsResourceRepository {

@ -23,9 +23,10 @@ import com.google.samples.apps.nowinandroid.core.database.model.RecentSearchQuer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Clock
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
internal class DefaultRecentSearchRepository @Inject constructor(
@Inject
internal class DefaultRecentSearchRepository(
private val recentSearchQueryDao: RecentSearchQueryDao,
) : RecentSearchRepository {
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) {

@ -23,10 +23,8 @@ import com.google.samples.apps.nowinandroid.core.database.dao.TopicFtsDao
import com.google.samples.apps.nowinandroid.core.database.model.PopulatedNewsResource
import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.database.model.asFtsEntity
import com.google.samples.apps.nowinandroid.core.di.IODispatcher
import com.google.samples.apps.nowinandroid.core.model.data.SearchResult
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@ -34,14 +32,15 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
internal class DefaultSearchContentsRepository @Inject constructor(
@Inject
internal class DefaultSearchContentsRepository(
private val newsResourceDao: NewsResourceDao,
private val newsResourceFtsDao: NewsResourceFtsDao,
private val topicDao: TopicDao,
private val topicFtsDao: TopicFtsDao,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val ioDispatcher: IODispatcher,
) : SearchContentsRepository {
override suspend fun populateFtsData() {

@ -35,7 +35,7 @@ import com.google.samples.apps.nowinandroid.core.notifications.Notifier
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
// Heuristic value to optimize for serialization and deserialization cost on client and server
// for each news resource batch.
@ -45,7 +45,8 @@ private const val SYNC_BATCH_SIZE = 40
* Disk storage backed implementation of the [NewsRepository].
* Reads are exclusively from local storage to support offline access.
*/
internal class OfflineFirstNewsRepository @Inject constructor(
@Inject
internal class OfflineFirstNewsRepository(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val newsResourceDao: NewsResourceDao,
private val topicDao: TopicDao,

@ -28,13 +28,14 @@ import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
/**
* Disk storage backed implementation of the [TopicsRepository].
* Reads are exclusively from local storage to support offline access.
*/
internal class OfflineFirstTopicsRepository @Inject constructor(
@Inject
internal class OfflineFirstTopicsRepository(
private val topicDao: TopicDao,
private val network: NiaNetworkDataSource,
) : TopicsRepository {

@ -16,16 +16,16 @@
package com.google.samples.apps.nowinandroid.core.data.repository
import androidx.annotation.VisibleForTesting
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import me.tatarka.inject.annotations.Inject
internal class OfflineFirstUserDataRepository @Inject constructor(
@Inject
internal class OfflineFirstUserDataRepository(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val analyticsHelper: AnalyticsHelper,
) : UserDataRepository {
@ -33,7 +33,6 @@ internal class OfflineFirstUserDataRepository @Inject constructor(
override val userData: Flow<UserData> =
niaPreferencesDataSource.userData
@VisibleForTesting
override suspend fun setFollowedTopicIds(followedTopicIds: Set<String>) =
niaPreferencesDataSource.setFollowedTopicIds(followedTopicIds)

@ -0,0 +1,28 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.data.util
import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.TimeZone
/**
* Utility for reporting current timezone the device has set.
* It always emits at least once with default setting and then for each TZ change.
*/
interface TimeZoneMonitor {
val currentTimeZone: Flow<TimeZone>
}

@ -27,7 +27,7 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.emptyUserDat
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
class CompositeUserNewsResourceRepositoryTest {

@ -24,9 +24,9 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import kotlinx.datetime.Clock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class UserNewsResourceTest {

@ -20,7 +20,7 @@ import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResour
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResourceExpanded
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
import kotlinx.datetime.Instant
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
class NetworkEntityKtTest {

@ -32,10 +32,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
class OfflineFirstTopicsRepositoryTest {
@ -55,7 +53,7 @@ class OfflineFirstTopicsRepositoryTest {
@get:Rule
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
@Before
@BeforeTest
fun setup() {
topicDao = TestTopicDao()
network = TestNiaNetworkDataSource()

@ -27,10 +27,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -48,7 +46,7 @@ class OfflineFirstUserDataRepositoryTest {
@get:Rule
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
@Before
@BeforeTest
fun setup() {
niaPreferencesDataSource = NiaPreferencesDataSource(
tmpFolder.testUserPreferencesDataStore(testScope),

@ -0,0 +1,48 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.data.di
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.datetime.TimeZone
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
/**
* JVM module that provides platform dependent data
* Leave empty for now
*/
@Component
internal actual abstract class PlatformDependentDataModule {
@Provides
internal actual fun bindsNetworkMonitor(): NetworkMonitor {
return object : NetworkMonitor {
override val isOnline: Flow<Boolean>
get() = flowOf(true)
}
}
@Provides
internal actual fun bindsTimeZoneMonitor(): TimeZoneMonitor {
return object : TimeZoneMonitor {
override val currentTimeZone: Flow<TimeZone>
get() = flowOf(TimeZone.UTC)
}
}
}

@ -0,0 +1,48 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.data.di
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.datetime.TimeZone
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
/**
* Native module that provides platform dependent data
* Leave empty for now
*/
@Component
internal actual abstract class PlatformDependentDataModule {
@Provides
internal actual fun bindsNetworkMonitor(): NetworkMonitor {
return object : NetworkMonitor {
override val isOnline: Flow<Boolean>
get() = flowOf(true)
}
}
@Provides
internal actual fun bindsTimeZoneMonitor(): TimeZoneMonitor {
return object : TimeZoneMonitor {
override val currentTimeZone: Flow<TimeZone>
get() = flowOf(TimeZone.UTC)
}
}
}

@ -19,12 +19,15 @@ package com.google.samples.apps.nowinandroid.core.datastore
import com.google.samples.apps.nowinandroid.core.di.IODispatcher
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.Settings
import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
@ -37,7 +40,7 @@ class NiaPreferencesDataSource(
) {
// FlowSettings did not support JS, use a workaround instead
// https://github.com/russhwolf/multiplatform-settings/issues/139
val userData = MutableStateFlow(
private val _userData = MutableStateFlow(
settings.decodeValue(
key = USER_DATA_KEY,
serializer = UserPreferences.serializer(),
@ -48,12 +51,24 @@ class NiaPreferencesDataSource(
),
)
val userData: Flow<UserData> = _userData.map {
UserData(
bookmarkedNewsResources = it.bookmarkedNewsResourceIds,
viewedNewsResources = it.viewedNewsResourceIds,
followedTopics = it.followedTopicIds,
themeBrand = it.themeBrand.toThemeBrand(),
darkThemeConfig = it.darkThemeConfig.toDarkThemeConfig(),
useDynamicColor = it.useDynamicColor,
shouldHideOnboarding = it.shouldHideOnboarding,
)
}
suspend fun setFollowedTopicIds(topicIds: Set<String>) = withContext(dispatcher) {
val preference = settings.getUserPreference()
.copy(followedTopicIds = topicIds)
.updateShouldHideOnboardingIfNecessary()
settings.putUserPreference(preference)
userData.value = preference
_userData.value = preference
}
suspend fun setTopicIdFollowed(topicId: String, followed: Boolean) = withContext(dispatcher) {
@ -68,28 +83,28 @@ class NiaPreferencesDataSource(
)
.updateShouldHideOnboardingIfNecessary()
settings.putUserPreference(newPreference)
userData.value = newPreference
_userData.value = newPreference
}
suspend fun setThemeBrand(themeBrand: ThemeBrand) = withContext(dispatcher) {
val newPreference = settings.getUserPreference()
.copy(themeBrand = themeBrand.toThemeBrandProto())
settings.putUserPreference(newPreference)
userData.value = newPreference
_userData.value = newPreference
}
suspend fun setDynamicColorPreference(useDynamicColor: Boolean) = withContext(dispatcher) {
val newPreference = settings.getUserPreference()
.copy(useDynamicColor = useDynamicColor)
settings.putUserPreference(newPreference)
userData.value = newPreference
_userData.value = newPreference
}
suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig) = withContext(dispatcher) {
val newPreference = settings.getUserPreference()
.copy(darkThemeConfig = darkThemeConfig.toDarkThemeConfigProto())
settings.putUserPreference(newPreference)
userData.value = newPreference
_userData.value = newPreference
}
suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) =
@ -104,7 +119,7 @@ class NiaPreferencesDataSource(
},
)
settings.putUserPreference(newPreferences)
userData.value = newPreferences
_userData.value = newPreferences
}
suspend fun setNewsResourceViewed(newsResourceId: String, viewed: Boolean) {
@ -123,7 +138,7 @@ class NiaPreferencesDataSource(
},
)
settings.putUserPreference(newPreferences)
userData.value = newPreferences
_userData.value = newPreferences
}
suspend fun getChangeListVersions(): ChangeListVersions = withContext(dispatcher) {
@ -151,14 +166,14 @@ class NiaPreferencesDataSource(
newsResourceChangeListVersion = updatedChangeListVersions.newsResourceVersion,
)
settings.putUserPreference(updatedPreference)
userData.value = updatedPreference
_userData.value = updatedPreference
}
suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) = withContext(dispatcher) {
val newPreference = settings.getUserPreference()
.copy(shouldHideOnboarding = shouldHideOnboarding)
settings.putUserPreference(newPreference)
userData.value = newPreference
_userData.value = newPreference
}
}
@ -212,3 +227,13 @@ private fun DarkThemeConfig.toDarkThemeConfigProto(): DarkThemeConfigProto {
DarkThemeConfig.LIGHT -> DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT
}
}
private fun DarkThemeConfigProto.toDarkThemeConfig(): DarkThemeConfig {
return when (this) {
DarkThemeConfigProto.DARK_THEME_CONFIG_UNSPECIFIED,
DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM,
-> DarkThemeConfig.FOLLOW_SYSTEM
DarkThemeConfigProto.DARK_THEME_CONFIG_DARK -> DarkThemeConfig.DARK
DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT -> DarkThemeConfig.LIGHT
}
}

Loading…
Cancel
Save