Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Commit

Permalink
Use DataStore to store user preferences
Browse files Browse the repository at this point in the history
  • Loading branch information
SanmerDev committed May 31, 2023
1 parent b064691 commit fc58f5f
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 6 deletions.
39 changes: 39 additions & 0 deletions app/src/main/kotlin/com/sanmer/geomag/datastore/UserData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.sanmer.geomag.datastore

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import com.sanmer.geomag.app.Const
import com.sanmer.geomag.ui.theme.Colors

data class UserData(
val darkMode: DarkMode,
val themeColor: Int,
val enableNavigationAnimation: Boolean
) {
companion object {
fun default() = UserData(
darkMode = DarkMode.FOLLOW_SYSTEM,
themeColor = if (Const.atLeastS) Colors.Dynamic.id else Colors.Sakura.id,
enableNavigationAnimation = false
)
}
}

@Composable
fun UserData.isDarkMode() = when (darkMode) {
DarkMode.ALWAYS_OFF -> false
DarkMode.ALWAYS_ON -> true
else -> isSystemInDarkTheme()
}

fun UserData.toPreferences(): UserPreferences = UserPreferences.newBuilder()
.setDarkMode(darkMode)
.setThemeColor(themeColor)
.setEnableNavigationAnimation(enableNavigationAnimation)
.build()

fun UserPreferences.toUserData() = UserData(
darkMode = darkMode,
themeColor = themeColor,
enableNavigationAnimation = enableNavigationAnimation
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.sanmer.geomag.datastore

import androidx.datastore.core.DataStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import javax.inject.Inject

class UserPreferencesDataSource @Inject constructor(
private val userPreferences: DataStore<UserPreferences>
) {
val userData get() = userPreferences.data.map { it.toUserData() }

suspend fun setDarkTheme(value: DarkMode) = withContext(Dispatchers.IO) {
userPreferences.updateData {
it.copy {
darkMode = value
}
}
}

suspend fun setThemeColor(value: Int) = withContext(Dispatchers.IO) {
userPreferences.updateData {
it.copy {
themeColor = value
}
}
}

suspend fun setEnableNavigationAnimation(value: Boolean) = withContext(Dispatchers.IO) {
userPreferences.updateData {
it.copy {
enableNavigationAnimation = value
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sanmer.geomag.datastore

import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.google.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject


class UserPreferencesSerializer @Inject constructor() : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserData.default().toPreferences()

override suspend fun readFrom(input: InputStream): UserPreferences =
try {
UserPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("cannot read proto", exception)
}

override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
t.writeTo(output)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.sanmer.geomag.datastore.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.sanmer.geomag.datastore.UserPreferences
import com.sanmer.geomag.datastore.UserPreferencesSerializer
import com.sanmer.geomag.di.ApplicationScope
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {

@Provides
@Singleton
fun providesUserPreferencesDataStore(
@ApplicationContext context: Context,
userPreferencesSerializer: UserPreferencesSerializer,
@ApplicationScope applicationScope: CoroutineScope
): DataStore<UserPreferences> =
DataStoreFactory.create(
serializer = userPreferencesSerializer,
scope = applicationScope,
) {
context.dataStoreFile("user_preferences.pb")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.sanmer.geomag.repository

import com.sanmer.geomag.datastore.DarkMode
import com.sanmer.geomag.datastore.UserData
import com.sanmer.geomag.datastore.UserPreferencesDataSource
import com.sanmer.geomag.di.ApplicationScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class UserDataRepository @Inject constructor(
private val userPreferencesDataSource: UserPreferencesDataSource,
@ApplicationScope private val applicationScope: CoroutineScope
) {
val userData get() = userPreferencesDataSource.userData

private var _value = UserData.default()
val value get() = _value

init {
userPreferencesDataSource.userData
.distinctUntilChanged()
.onEach {
_value = it
}.launchIn(applicationScope)
}

fun setDarkTheme(value: DarkMode) = applicationScope.launch {
userPreferencesDataSource.setDarkTheme(value)
}

fun setThemeColor(value: Int) = applicationScope.launch {
userPreferencesDataSource.setThemeColor(value)
}

fun setEnableNavigationAnimation(value: Boolean) = applicationScope.launch {
userPreferencesDataSource.setEnableNavigationAnimation(value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,37 @@ package com.sanmer.geomag.ui.activity.base
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sanmer.geomag.datastore.UserData
import com.sanmer.geomag.datastore.isDarkMode
import com.sanmer.geomag.repository.UserDataRepository
import com.sanmer.geomag.ui.theme.AppTheme
import com.sanmer.geomag.ui.theme.Colors
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
abstract class BaseActivity : ComponentActivity() {
@Inject
lateinit var userDataRepository: UserDataRepository

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
}

fun setActivityContent(
content: @Composable () -> Unit
content: @Composable (UserData) -> Unit
) = setContent {
val userData by userDataRepository.userData.collectAsStateWithLifecycle(UserData.default())

AppTheme(
darkMode = isSystemInDarkTheme(),
themeColor = Colors.Dynamic.id
darkMode = userData.isDarkMode(),
themeColor = userData.themeColor
) {
content()
content(userData)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";

option java_package = "com.sanmer.geomag.datastore";
option java_multiple_files = true;

enum DarkMode {
FOLLOW_SYSTEM = 0;
ALWAYS_OFF = 1;
ALWAYS_ON = 2;
}

message UserPreferences {
DarkMode darkMode = 1;
int32 themeColor = 2;
bool enableNavigationAnimation = 3; // TODO: Waiting for https://github.com/google/accompanist/issues/1487 to be fixed
}

0 comments on commit fc58f5f

Please sign in to comment.