├── .github └── workflows │ └── build-ci.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hoc081098 │ │ └── datastoresample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── hoc081098 │ │ │ └── datastoresample │ │ │ ├── App.kt │ │ │ ├── Locator.kt │ │ │ ├── data │ │ │ ├── TaskRepositoryImpl.kt │ │ │ └── UserPreferencesRepositoryImpl.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── SortOrder.kt │ │ │ │ ├── Task.kt │ │ │ │ ├── TaskPriority.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── UserPreferences.kt │ │ │ ├── repo │ │ │ │ ├── TaskRepository.kt │ │ │ │ └── UserPreferencesRepository.kt │ │ │ └── usecase │ │ │ │ ├── ChangeShowCompleted.kt │ │ │ │ ├── ChangeTheme.kt │ │ │ │ ├── EnableSortByDeadline.kt │ │ │ │ ├── EnableSortByPriority.kt │ │ │ │ ├── FilterSortTasks.kt │ │ │ │ └── GetTheme.kt │ │ │ └── ui │ │ │ ├── MainActivity.kt │ │ │ ├── MainScreen.kt │ │ │ ├── MainViewModel.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── hoc081098 │ └── datastoresample │ └── ExampleUnitTest.kt ├── build.gradle ├── demo.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── renovate.json └── settings.gradle /.github/workflows/build-ci.yml: -------------------------------------------------------------------------------- 1 | name: Build CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up JDKd 16 | uses: actions/setup-java@v2 17 | with: 18 | distribution: 'zulu' 19 | java-version: '11' 20 | 21 | - name: Make gradlew executable 22 | run: chmod +x ./gradlew 23 | 24 | # - name: Spotless check 25 | # run: ./gradlew spotlessCheck 26 | 27 | - name: Build debug APK 28 | run: ./gradlew assembleDebug --warning-mode all --stacktrace 29 | 30 | - name: Upload APK 31 | uses: actions/upload-artifact@v2 32 | with: 33 | name: app-debug 34 | path: app/build/outputs/apk/debug/app-debug.apk 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | DataStore Sample -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 121 | 122 | 124 | 125 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kotlin Android Open Source - Petrus Nguyễn Thái Học 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DataStore-sample 2 | DataStore-sample 3 | 4 | - Preferences DataStore [Working with Preferences DataStore Codelab](https://developer.android.com/codelabs/android-preferences-datastore#0). 5 | - Using [Jetpack Compose](https://developer.android.com/jetpack/compose) for UI. 6 | - Toggle dark mode / light mode. 7 | - StateFlow, Coroutines Flow. 8 | 9 | > DataStore is a new and improved data storage solution aimed at replacing SharedPreferences. Built on Kotlin coroutines and Flow, DataStore provides two different implementations: Proto DataStore, that lets you store typed objects (backed by protocol buffers) and Preferences DataStore, that stores key-value pairs. Data is stored asynchronously, consistently, and transactionally, overcoming some of the drawbacks of SharedPreferences. 10 | 11 |

12 | 13 |

14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 30 8 | buildToolsVersion "30.0.3" 9 | 10 | defaultConfig { 11 | applicationId "com.hoc081098.datastoresample" 12 | minSdkVersion 23 13 | targetSdkVersion 30 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | useIR = true 33 | 34 | // Opt-in to experimental compose APIs 35 | freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' 36 | 37 | // Enable experimental coroutines APIs, including collectAsState() 38 | freeCompilerArgs += '-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi' 39 | } 40 | buildFeatures { 41 | compose true 42 | } 43 | composeOptions { 44 | kotlinCompilerExtensionVersion compose_version 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 50 | implementation 'com.google.android.material:material:1.4.0' 51 | 52 | implementation 'androidx.core:core-ktx:1.3.2' 53 | implementation 'androidx.appcompat:appcompat:1.2.0' 54 | implementation 'androidx.activity:activity-compose:1.3.0-alpha02' 55 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0' 56 | implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha01' 57 | 58 | implementation "androidx.compose.foundation:foundation-layout:$compose_version" 59 | implementation "androidx.compose.foundation:foundation:$compose_version" 60 | implementation "androidx.compose.ui:ui:$compose_version" 61 | implementation "androidx.compose.material:material:$compose_version" 62 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 63 | implementation "androidx.compose.runtime:runtime:$compose_version" 64 | implementation "androidx.ui:ui-tooling:1.0.0-alpha07" 65 | 66 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 67 | debugImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 68 | 69 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 70 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" 71 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" 72 | 73 | implementation "androidx.datastore:datastore-preferences:1.0.0" 74 | 75 | testImplementation 'junit:junit:4.13.2' 76 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 78 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/hoc081098/datastoresample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.hoc081098.datastoresample", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/App.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample 2 | 3 | import android.app.Application 4 | 5 | class App : Application() { 6 | override fun onCreate() { 7 | super.onCreate() 8 | Locator.initWith(this) 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/Locator.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.datastore.preferences.preferencesDataStore 6 | import com.hoc081098.datastoresample.data.TaskRepositoryImpl 7 | import com.hoc081098.datastoresample.data.UserPreferencesRepositoryImpl 8 | import com.hoc081098.datastoresample.domain.usecase.ChangeShowCompleted 9 | import com.hoc081098.datastoresample.domain.usecase.ChangeTheme 10 | import com.hoc081098.datastoresample.domain.usecase.EnableSortByDeadline 11 | import com.hoc081098.datastoresample.domain.usecase.EnableSortByPriority 12 | import com.hoc081098.datastoresample.domain.usecase.FilterSortTasks 13 | import com.hoc081098.datastoresample.domain.usecase.GetTheme 14 | import com.hoc081098.datastoresample.ui.MainViewModel 15 | 16 | object Locator { 17 | private var application: Application? = null 18 | 19 | private inline val requireApplication 20 | get() = application ?: error("Missing call: initWith(application)") 21 | 22 | fun initWith(application: Application) { 23 | this.application = application 24 | } 25 | 26 | val mainViewModelFactory 27 | get() = MainViewModel.Factory( 28 | filterSortTasks = filterSortTasks, 29 | getTheme = getTheme, 30 | changeShowCompleted = changeShowCompleted, 31 | enableSortByDeadline = enableSortByDeadline, 32 | enableSortByPriority = enableSortByPriority, 33 | changeTheme = changeTheme, 34 | ) 35 | 36 | private val filterSortTasks 37 | get() = FilterSortTasks( 38 | taskRepository = taskRepository, 39 | userPreferencesRepository = userPreferencesRepository 40 | ) 41 | 42 | private val changeTheme get() = ChangeTheme(userPreferencesRepository) 43 | 44 | private val getTheme get() = GetTheme(userPreferencesRepository) 45 | 46 | private val changeShowCompleted get() = ChangeShowCompleted(userPreferencesRepository) 47 | 48 | private val enableSortByDeadline get() = EnableSortByDeadline(userPreferencesRepository) 49 | 50 | private val enableSortByPriority get() = EnableSortByPriority(userPreferencesRepository) 51 | 52 | private val Context.dataStore by preferencesDataStore(name = "user_preferences") 53 | 54 | private val taskRepository by lazy { TaskRepositoryImpl() } 55 | private val userPreferencesRepository by lazy { 56 | UserPreferencesRepositoryImpl(requireApplication.dataStore) 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/data/TaskRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.data 2 | 3 | import com.hoc081098.datastoresample.domain.model.Task 4 | import com.hoc081098.datastoresample.domain.model.TaskPriority 5 | import com.hoc081098.datastoresample.domain.repo.TaskRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.flowOf 8 | import java.text.SimpleDateFormat 9 | import java.util.* 10 | 11 | class TaskRepositoryImpl : TaskRepository { 12 | 13 | private val simpleDateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) 14 | private fun parseDate(date: String? = null): Date = date?.let(simpleDateFormat::parse) ?: Date() 15 | 16 | override fun tasks(): Flow> { 17 | return flowOf( 18 | listOf( 19 | Task( 20 | name = "Complete graduate project", 21 | deadline = parseDate("25-12-2020"), 22 | priority = TaskPriority.HIGH, 23 | ), 24 | Task( 25 | name = "Learning Jetpack Compose", 26 | deadline = parseDate("01-01-2021"), 27 | priority = TaskPriority.MEDIUM, 28 | completed = true, 29 | ), 30 | Task( 31 | name = "Learning NestJs", 32 | deadline = parseDate("02-01-2021"), 33 | priority = TaskPriority.LOW, 34 | ), 35 | Task( 36 | name = "Learn about Polymer", 37 | deadline = parseDate("10-10-2020"), 38 | priority = TaskPriority.LOW, 39 | completed = true, 40 | ), 41 | Task( 42 | name = "Learning Functional programming with Λrrow", 43 | deadline = parseDate("01-01-2022"), 44 | priority = TaskPriority.MEDIUM, 45 | ), 46 | Task( 47 | name = "Learning Functional programming with Bow Swift", 48 | deadline = parseDate("01-01-2022"), 49 | priority = TaskPriority.MEDIUM, 50 | completed = true, 51 | ), 52 | Task( 53 | name = "Understand how to migrate to DataStore", 54 | deadline = parseDate(), 55 | priority = TaskPriority.HIGH, 56 | ), 57 | Task( 58 | name = "Compose is awesome", 59 | deadline = parseDate(), 60 | priority = TaskPriority.HIGH, 61 | completed = true, 62 | ), 63 | Task( 64 | name = "RxDart", 65 | deadline = parseDate("25-12-2020"), 66 | priority = TaskPriority.HIGH, 67 | ), 68 | Task( 69 | name = "RxSwift", 70 | deadline = parseDate("01-01-2021"), 71 | priority = TaskPriority.MEDIUM, 72 | ), 73 | ) 74 | ) 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/data/UserPreferencesRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.data 2 | 3 | import android.util.Log 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.booleanPreferencesKey 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.core.emptyPreferences 9 | import androidx.datastore.preferences.core.stringPreferencesKey 10 | import com.hoc081098.datastoresample.domain.model.SortOrder 11 | import com.hoc081098.datastoresample.domain.model.SortOrder.* 12 | import com.hoc081098.datastoresample.domain.model.Theme 13 | import com.hoc081098.datastoresample.domain.model.UserPreferences 14 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 15 | import kotlinx.coroutines.flow.Flow 16 | import kotlinx.coroutines.flow.catch 17 | import kotlinx.coroutines.flow.distinctUntilChanged 18 | import kotlinx.coroutines.flow.map 19 | import java.io.IOException 20 | 21 | class UserPreferencesRepositoryImpl( 22 | private val dataStore: DataStore, 23 | ) : UserPreferencesRepository { 24 | 25 | private object Keys { 26 | val showCompleted = booleanPreferencesKey("show_completed") 27 | val sortOrder = stringPreferencesKey("sort_order") 28 | val theme = booleanPreferencesKey("theme") 29 | } 30 | 31 | private inline val Preferences.showCompleted 32 | get() = this[Keys.showCompleted] ?: false 33 | 34 | private inline val Preferences.sortOrder 35 | get() = this[Keys.sortOrder]?.let(SortOrder::valueOf) ?: NONE 36 | 37 | override val userPreferences: Flow = dataStore.data 38 | .catch { 39 | // throws an IOException when an error is encountered when reading data 40 | if (it is IOException) { 41 | emit(emptyPreferences()) 42 | } else { 43 | throw it 44 | } 45 | } 46 | .map { preferences -> 47 | UserPreferences( 48 | showCompleted = preferences.showCompleted, 49 | sortOrder = preferences.sortOrder, 50 | ) 51 | } 52 | .distinctUntilChanged() 53 | 54 | override suspend fun enableSortByDeadline(enabled: Boolean) { 55 | dataStore.edit { 56 | val sortOrder = it.sortOrder 57 | 58 | val newSortOrder = if (enabled) { 59 | when (sortOrder) { 60 | NONE -> BY_DEADLINE 61 | BY_DEADLINE -> BY_DEADLINE 62 | BY_PRIORITY -> BY_DEADLINE_AND_PRIORITY 63 | BY_DEADLINE_AND_PRIORITY -> BY_DEADLINE 64 | } 65 | } else { 66 | when (sortOrder) { 67 | NONE -> NONE 68 | BY_DEADLINE -> NONE 69 | BY_PRIORITY -> NONE 70 | BY_DEADLINE_AND_PRIORITY -> BY_PRIORITY 71 | } 72 | } 73 | 74 | it[Keys.sortOrder] = newSortOrder.name 75 | } 76 | } 77 | 78 | override suspend fun enableSortByPriority(enabled: Boolean) { 79 | dataStore.edit { 80 | val sortOrder = it.sortOrder 81 | 82 | val newSortOrder = if (enabled) { 83 | when (sortOrder) { 84 | NONE -> BY_PRIORITY 85 | BY_DEADLINE -> BY_DEADLINE_AND_PRIORITY 86 | BY_PRIORITY -> BY_PRIORITY 87 | BY_DEADLINE_AND_PRIORITY -> BY_PRIORITY 88 | } 89 | } else { 90 | when (sortOrder) { 91 | NONE -> NONE 92 | BY_DEADLINE -> NONE 93 | BY_PRIORITY -> NONE 94 | BY_DEADLINE_AND_PRIORITY -> BY_DEADLINE 95 | } 96 | } 97 | 98 | it[Keys.sortOrder] = newSortOrder.name 99 | } 100 | } 101 | 102 | override suspend fun updateShowCompleted(showCompleted: Boolean) { 103 | dataStore.edit { it[Keys.showCompleted] = showCompleted } 104 | Log.d("UserPreferencesRepo", "updateShowCompleted $showCompleted") 105 | } 106 | 107 | override val theme: Flow = dataStore.data 108 | .catch { 109 | // throws an IOException when an error is encountered when reading data 110 | if (it is IOException) { 111 | emit(emptyPreferences()) 112 | } else { 113 | throw it 114 | } 115 | } 116 | .map { 117 | when (it[Keys.theme]) { 118 | true -> Theme.NIGHT_YES 119 | false -> Theme.NIGHT_NO 120 | null -> Theme.NIGHT_UNSPECIFIED 121 | } 122 | } 123 | .distinctUntilChanged() 124 | 125 | override suspend fun changeTheme(lightTheme: Boolean) { 126 | dataStore.edit { it[Keys.theme] = lightTheme } 127 | Log.d("UserPreferencesRepo", "changeTheme $lightTheme") 128 | } 129 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/model/SortOrder.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.model 2 | 3 | enum class SortOrder { 4 | NONE, 5 | BY_DEADLINE, 6 | BY_PRIORITY, 7 | BY_DEADLINE_AND_PRIORITY; 8 | 9 | val comparator: Comparator by lazy { 10 | when (this) { 11 | NONE -> Comparator { _, _ -> 0 } 12 | BY_DEADLINE -> compareByDescending { it.deadline } 13 | BY_PRIORITY -> compareBy { it.priority } 14 | BY_DEADLINE_AND_PRIORITY -> compareByDescending { it.deadline }.thenBy { it.priority } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/model/Task.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.model 2 | 3 | import java.util.* 4 | 5 | data class Task( 6 | val name: String, 7 | val deadline: Date, 8 | val priority: TaskPriority, 9 | val completed: Boolean = false, 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/model/TaskPriority.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.model 2 | 3 | enum class TaskPriority { 4 | HIGH, 5 | MEDIUM, 6 | LOW 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/model/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.model 2 | 3 | enum class Theme { 4 | NIGHT_YES, 5 | NIGHT_NO, 6 | NIGHT_UNSPECIFIED, 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/model/UserPreferences.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.model 2 | 3 | data class UserPreferences( 4 | val showCompleted: Boolean, 5 | val sortOrder: SortOrder, 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/repo/TaskRepository.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.repo 2 | 3 | import com.hoc081098.datastoresample.domain.model.Task 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface TaskRepository { 7 | fun tasks(): Flow> 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/repo/UserPreferencesRepository.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.repo 2 | 3 | import com.hoc081098.datastoresample.domain.model.Theme 4 | import com.hoc081098.datastoresample.domain.model.UserPreferences 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface UserPreferencesRepository { 8 | val userPreferences: Flow 9 | 10 | suspend fun enableSortByDeadline(enabled: Boolean) 11 | 12 | suspend fun enableSortByPriority(enabled: Boolean) 13 | 14 | suspend fun updateShowCompleted(showCompleted: Boolean) 15 | 16 | suspend fun changeTheme(lightTheme: Boolean) 17 | 18 | val theme: Flow 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/usecase/ChangeShowCompleted.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.usecase 2 | 3 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 4 | 5 | class ChangeShowCompleted( 6 | private val userPreferencesRepository: UserPreferencesRepository, 7 | ) { 8 | suspend operator fun invoke(showCompleted: Boolean) = 9 | userPreferencesRepository.updateShowCompleted(showCompleted) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/usecase/ChangeTheme.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.usecase 2 | 3 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 4 | 5 | class ChangeTheme(private val userPreferencesRepository: UserPreferencesRepository) { 6 | suspend operator fun invoke(lightTheme: Boolean) = 7 | userPreferencesRepository.changeTheme(lightTheme) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/usecase/EnableSortByDeadline.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.usecase 2 | 3 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 4 | 5 | class EnableSortByDeadline( 6 | private val userPreferencesRepository: UserPreferencesRepository, 7 | ) { 8 | suspend operator fun invoke(enabled: Boolean) = 9 | userPreferencesRepository.enableSortByDeadline(enabled) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/usecase/EnableSortByPriority.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.usecase 2 | 3 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 4 | 5 | class EnableSortByPriority(private val userPreferencesRepository: UserPreferencesRepository) { 6 | suspend operator fun invoke(enabled: Boolean) = 7 | userPreferencesRepository.enableSortByPriority(enabled) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/usecase/FilterSortTasks.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.usecase 2 | 3 | import com.hoc081098.datastoresample.domain.model.SortOrder 4 | import com.hoc081098.datastoresample.domain.model.Task 5 | import com.hoc081098.datastoresample.domain.repo.TaskRepository 6 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.combine 9 | 10 | data class FilteredSortedTasks( 11 | val tasks: List, 12 | val showCompleted: Boolean, 13 | val sortOrder: SortOrder, 14 | ) 15 | 16 | class FilterSortTasks( 17 | private val taskRepository: TaskRepository, 18 | private val userPreferencesRepository: UserPreferencesRepository, 19 | ) { 20 | operator fun invoke(): Flow { 21 | return combine( 22 | taskRepository.tasks(), 23 | userPreferencesRepository.userPreferences, 24 | ) { tasks, (showCompleted, sortOrder) -> 25 | val filtered = if (showCompleted) tasks.filter { it.completed } else tasks 26 | 27 | FilteredSortedTasks( 28 | tasks = filtered.sortedWith(sortOrder.comparator), 29 | showCompleted = showCompleted, 30 | sortOrder = sortOrder 31 | ) 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/domain/usecase/GetTheme.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.domain.usecase 2 | 3 | import com.hoc081098.datastoresample.domain.model.Theme 4 | import com.hoc081098.datastoresample.domain.repo.UserPreferencesRepository 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | class GetTheme(private val userPreferencesRepository: UserPreferencesRepository) { 8 | operator fun invoke(): Flow = userPreferencesRepository.theme 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.activity.viewModels 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.compose.foundation.isSystemInDarkTheme 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Surface 10 | import androidx.compose.runtime.collectAsState 11 | import androidx.compose.runtime.getValue 12 | import com.hoc081098.datastoresample.Locator 13 | import com.hoc081098.datastoresample.domain.model.Theme 14 | import com.hoc081098.datastoresample.ui.theme.DataStoreSampleTheme 15 | 16 | class MainActivity : AppCompatActivity() { 17 | private val viewModel by viewModels(factoryProducer = { Locator.mainViewModelFactory }) 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | 22 | setContent { 23 | val theme by viewModel.theme.collectAsState() 24 | 25 | val darkTheme = when (theme) { 26 | Theme.NIGHT_YES -> false 27 | Theme.NIGHT_NO -> true 28 | Theme.NIGHT_UNSPECIFIED -> isSystemInDarkTheme() 29 | null -> return@setContent 30 | } 31 | 32 | DataStoreSampleTheme(darkTheme = darkTheme) { 33 | // A surface container using the 'background' color from the theme 34 | Surface(color = MaterialTheme.colors.background) { 35 | val state by viewModel.state.collectAsState() 36 | 37 | MainScreen( 38 | state = state, 39 | changeShowCompleted = viewModel::changeShowCompleted, 40 | enableSortByDeadline = viewModel::enableSortByDeadline, 41 | enableSortByPriority=viewModel::enableSortByPriority, 42 | lightTheme = !darkTheme, 43 | changeTheme = viewModel::changeTheme, 44 | ) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.ExperimentalAnimationApi 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.Row 10 | import androidx.compose.foundation.layout.Spacer 11 | import androidx.compose.foundation.layout.fillMaxHeight 12 | import androidx.compose.foundation.layout.fillMaxSize 13 | import androidx.compose.foundation.layout.fillMaxWidth 14 | import androidx.compose.foundation.layout.height 15 | import androidx.compose.foundation.layout.padding 16 | import androidx.compose.foundation.layout.requiredHeight 17 | import androidx.compose.foundation.layout.requiredWidth 18 | import androidx.compose.foundation.layout.width 19 | import androidx.compose.foundation.layout.wrapContentWidth 20 | import androidx.compose.foundation.lazy.LazyColumn 21 | import androidx.compose.foundation.lazy.itemsIndexed 22 | import androidx.compose.foundation.selection.toggleable 23 | import androidx.compose.foundation.shape.RoundedCornerShape 24 | import androidx.compose.material.Checkbox 25 | import androidx.compose.material.CircularProgressIndicator 26 | import androidx.compose.material.Divider 27 | import androidx.compose.material.Icon 28 | import androidx.compose.material.IconToggleButton 29 | import androidx.compose.material.LocalContentColor 30 | import androidx.compose.material.MaterialTheme 31 | import androidx.compose.material.Scaffold 32 | import androidx.compose.material.Surface 33 | import androidx.compose.material.Text 34 | import androidx.compose.material.TopAppBar 35 | import androidx.compose.material.icons.Icons 36 | import androidx.compose.material.icons.filled.Check 37 | import androidx.compose.material.icons.filled.DateRange 38 | import androidx.compose.material.icons.filled.LightMode 39 | import androidx.compose.material.icons.filled.Reorder 40 | import androidx.compose.material.icons.filled.Sort 41 | import androidx.compose.material.icons.outlined.LightMode 42 | import androidx.compose.runtime.Composable 43 | import androidx.compose.ui.Alignment 44 | import androidx.compose.ui.Modifier 45 | import androidx.compose.ui.graphics.Color 46 | import androidx.compose.ui.graphics.Shape 47 | import androidx.compose.ui.semantics.onClick 48 | import androidx.compose.ui.semantics.semantics 49 | import androidx.compose.ui.unit.dp 50 | import com.hoc081098.datastoresample.domain.model.SortOrder 51 | import com.hoc081098.datastoresample.domain.model.Task 52 | import com.hoc081098.datastoresample.domain.model.TaskPriority 53 | import com.hoc081098.datastoresample.domain.model.TaskPriority.* 54 | import com.hoc081098.datastoresample.domain.usecase.FilteredSortedTasks 55 | import java.text.SimpleDateFormat 56 | import java.util.* 57 | 58 | @Composable 59 | fun MainScreen( 60 | state: FilteredSortedTasks?, 61 | changeShowCompleted: (Boolean) -> Unit, 62 | enableSortByDeadline: (Boolean) -> Unit, 63 | enableSortByPriority: (Boolean) -> Unit, 64 | lightTheme: Boolean, 65 | changeTheme: (Boolean) -> Unit, 66 | ) { 67 | Scaffold( 68 | topBar = { 69 | TopAppBar( 70 | title = { 71 | Text(text = "Jetpack DataStore sample") 72 | }, 73 | actions = { 74 | IconToggleButton( 75 | checked = lightTheme, 76 | onCheckedChange = changeTheme, 77 | modifier = Modifier.semantics { 78 | // Use a custom click label that accessibility services can communicate to the user. 79 | // We only want to override the label, not the actual action, so for the action we pass null. 80 | onClick( 81 | label = if (lightTheme) "To dark mode" else "To night mode", 82 | action = null 83 | ) 84 | } 85 | ) { 86 | Icon( 87 | imageVector = if (lightTheme) { 88 | Icons.Filled.LightMode 89 | } else { 90 | Icons.Outlined.LightMode 91 | }, 92 | contentDescription = null, // handled by click label of parent 93 | ) 94 | } 95 | } 96 | ) 97 | }, 98 | ) { innerPadding -> 99 | if (state == null) { 100 | Column( 101 | modifier = Modifier 102 | .fillMaxHeight() 103 | .fillMaxWidth(), 104 | verticalArrangement = Arrangement.Center, 105 | horizontalAlignment = Alignment.CenterHorizontally 106 | ) { 107 | CircularProgressIndicator() 108 | } 109 | } else { 110 | Column( 111 | modifier = Modifier 112 | .fillMaxHeight() 113 | .fillMaxWidth(), 114 | ) { 115 | MainTasksList(state.tasks, 116 | Modifier 117 | .weight(1f) 118 | .padding(innerPadding)) 119 | 120 | Row(modifier = Modifier.padding(all = 16.dp)) { 121 | Icon( 122 | imageVector = Icons.Default.Sort, 123 | contentDescription = null, 124 | tint = LocalContentColor.current.copy(alpha = 0.5f), 125 | ) 126 | 127 | Spacer(modifier = Modifier.width(16.dp)) 128 | 129 | Text( 130 | modifier = Modifier.wrapContentWidth(), 131 | text = "Show completed tasks", 132 | style = MaterialTheme.typography.subtitle1 133 | ) 134 | 135 | Spacer(modifier = Modifier.width(16.dp)) 136 | 137 | Checkbox( 138 | checked = state.showCompleted, 139 | onCheckedChange = changeShowCompleted 140 | ) 141 | } 142 | 143 | Row(modifier = Modifier.padding(all = 16.dp)) { 144 | Icon( 145 | imageVector = Icons.Default.Reorder, 146 | contentDescription = null, 147 | tint = LocalContentColor.current.copy(alpha = 0.5f), 148 | ) 149 | 150 | Spacer(modifier = Modifier.width(16.dp)) 151 | 152 | SortChip( 153 | text = "Priority", 154 | selected = state.sortOrder == SortOrder.BY_PRIORITY 155 | || state.sortOrder == SortOrder.BY_DEADLINE_AND_PRIORITY, 156 | setSelected = enableSortByPriority, 157 | shape = RoundedCornerShape(14.dp) 158 | ) 159 | 160 | Spacer(modifier = Modifier.width(16.dp)) 161 | 162 | SortChip( 163 | text = "Deadline", 164 | selected = state.sortOrder == SortOrder.BY_DEADLINE 165 | || state.sortOrder == SortOrder.BY_DEADLINE_AND_PRIORITY, 166 | setSelected = enableSortByDeadline, 167 | shape = RoundedCornerShape(14.dp) 168 | ) 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | @Composable 176 | fun MainTasksList(tasks: List, modifier: Modifier = Modifier) { 177 | LazyColumn( 178 | modifier = modifier, 179 | ) { 180 | itemsIndexed(tasks) { index, task -> 181 | TaskRow(task = task) 182 | if (index < tasks.lastIndex) { 183 | Divider( 184 | thickness = 0.7.dp 185 | ) 186 | } 187 | } 188 | } 189 | } 190 | 191 | private val TaskPriority.color: Color 192 | get() { 193 | return when (this) { 194 | HIGH -> Color.Red.copy(alpha = 0.8f) 195 | MEDIUM -> Color.Magenta.copy(alpha = 0.8f) 196 | LOW -> Color.Green.copy(alpha = 0.8f) 197 | } 198 | } 199 | private val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.US) 200 | 201 | @Composable 202 | fun TaskRow(task: Task) { 203 | val bgColor = if (task.completed) { 204 | LocalContentColor.current.copy(alpha = 0.07f) 205 | } else { 206 | Color.Unspecified 207 | } 208 | 209 | Column( 210 | verticalArrangement = Arrangement.SpaceBetween, 211 | modifier = Modifier 212 | .fillMaxSize() 213 | .background(bgColor) 214 | .padding(all = 12.dp) 215 | ) { 216 | Text( 217 | text = task.name, 218 | style = MaterialTheme.typography.subtitle1, 219 | ) 220 | Spacer(modifier = Modifier.requiredHeight(8.dp)) 221 | Text( 222 | text = "Priority ${task.priority.name}", 223 | style = MaterialTheme.typography.subtitle2.copy( 224 | color = task.priority.color 225 | ), 226 | ) 227 | Spacer(modifier = Modifier.requiredHeight(8.dp)) 228 | Row( 229 | verticalAlignment = Alignment.CenterVertically 230 | ) { 231 | Icon( 232 | imageVector = Icons.Default.DateRange, 233 | contentDescription = null, 234 | tint = LocalContentColor.current.copy(alpha = 0.5f), 235 | ) 236 | Spacer(modifier = Modifier.requiredWidth(8.dp)) 237 | Text( 238 | dateFormat.format(task.deadline), 239 | style = MaterialTheme.typography.caption, 240 | ) 241 | } 242 | } 243 | } 244 | 245 | @OptIn(ExperimentalAnimationApi::class) 246 | @Composable 247 | fun SortChip( 248 | text: String, 249 | selected: Boolean, 250 | setSelected: (Boolean) -> Unit, 251 | modifier: Modifier = Modifier, 252 | shape: Shape = MaterialTheme.shapes.small, 253 | ) { 254 | Surface( 255 | modifier = modifier.height(28.dp), 256 | color = MaterialTheme.colors.secondary, 257 | shape = shape, 258 | elevation = 2.dp 259 | ) { 260 | Box( 261 | modifier = Modifier.toggleable( 262 | value = selected, 263 | onValueChange = setSelected, 264 | ) 265 | ) { 266 | Row( 267 | verticalAlignment = Alignment.CenterVertically, 268 | modifier = Modifier.padding( 269 | horizontal = 8.dp, 270 | vertical = 6.dp 271 | ) 272 | ) { 273 | AnimatedVisibility(visible = selected) { 274 | Icon( 275 | imageVector = Icons.Default.Check, 276 | contentDescription = null, 277 | modifier = Modifier.width(24.dp), 278 | ) 279 | Spacer(modifier = Modifier.width(2.dp)) 280 | } 281 | 282 | Text( 283 | text = text, 284 | style = MaterialTheme.typography.caption, 285 | maxLines = 1, 286 | 287 | ) 288 | } 289 | 290 | } 291 | } 292 | } 293 | 294 | //@Preview 295 | //@Composable 296 | //fun MainScreenPreview() { 297 | // DataStoreSampleTheme { 298 | // MainScreen( 299 | // state = null, 300 | // changeShowCompleted = {}, 301 | // enableSortByDeadline = {}, 302 | // enableSortByPriority = {}, 303 | // lightTheme = false, 304 | // changeTheme = {} 305 | // ) 306 | // } 307 | //} 308 | // 309 | //@Preview 310 | //@Composable 311 | //fun TaskRowPreview() { 312 | // TaskRow( 313 | // task = Task( 314 | // name = "Complete graduate project", 315 | // deadline = Date(), 316 | // priority = HIGH, 317 | // ), 318 | // ) 319 | //} -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.viewModelScope 7 | import com.hoc081098.datastoresample.domain.usecase.ChangeShowCompleted 8 | import com.hoc081098.datastoresample.domain.usecase.ChangeTheme 9 | import com.hoc081098.datastoresample.domain.usecase.EnableSortByDeadline 10 | import com.hoc081098.datastoresample.domain.usecase.EnableSortByPriority 11 | import com.hoc081098.datastoresample.domain.usecase.GetTheme 12 | import com.hoc081098.datastoresample.domain.usecase.FilterSortTasks 13 | import com.hoc081098.datastoresample.domain.usecase.FilteredSortedTasks 14 | import com.hoc081098.datastoresample.domain.model.Theme 15 | import kotlinx.coroutines.flow.SharingStarted 16 | import kotlinx.coroutines.flow.StateFlow 17 | import kotlinx.coroutines.flow.stateIn 18 | import kotlinx.coroutines.launch 19 | 20 | class MainViewModel( 21 | filterSortTasks: FilterSortTasks, 22 | getTheme: GetTheme, 23 | private val _changeShowCompleted: ChangeShowCompleted, 24 | private val _enableSortByDeadline: EnableSortByDeadline, 25 | private val _enableSortByPriority: EnableSortByPriority, 26 | private val _changeTheme: ChangeTheme, 27 | ) : ViewModel() { 28 | val state: StateFlow = filterSortTasks() 29 | .stateIn( 30 | viewModelScope, 31 | SharingStarted.Eagerly, 32 | null, 33 | ) 34 | 35 | val theme: StateFlow = getTheme().stateIn( 36 | viewModelScope, 37 | SharingStarted.Eagerly, 38 | null, 39 | ) 40 | 41 | init { 42 | Log.d("MainViewModel", "$this::init") 43 | } 44 | 45 | override fun onCleared() { 46 | super.onCleared() 47 | Log.d("MainViewModel", "$this::onCleared") 48 | } 49 | 50 | fun changeShowCompleted(showCompleted: Boolean) { 51 | viewModelScope.launch { _changeShowCompleted(showCompleted) } 52 | } 53 | 54 | fun enableSortByDeadline(enabled: Boolean) { 55 | viewModelScope.launch { _enableSortByDeadline(enabled) } 56 | } 57 | 58 | fun enableSortByPriority(enabled: Boolean) { 59 | viewModelScope.launch { _enableSortByPriority(enabled) } 60 | } 61 | 62 | fun changeTheme(lightTheme: Boolean) { 63 | viewModelScope.launch { _changeTheme(lightTheme) } 64 | } 65 | 66 | class Factory( 67 | private val filterSortTasks: FilterSortTasks, 68 | private val getTheme: GetTheme, 69 | private val changeShowCompleted: ChangeShowCompleted, 70 | private val enableSortByDeadline: EnableSortByDeadline, 71 | private val enableSortByPriority: EnableSortByPriority, 72 | private val changeTheme: ChangeTheme, 73 | 74 | ) : ViewModelProvider.Factory { 75 | override fun create(modelClass: Class): T { 76 | if (modelClass.isAssignableFrom(MainViewModel::class.java)) { 77 | @Suppress("UNCHECKED_CAST") 78 | return MainViewModel( 79 | filterSortTasks = filterSortTasks, 80 | getTheme = getTheme, 81 | _changeShowCompleted = changeShowCompleted, 82 | _enableSortByDeadline = enableSortByDeadline, 83 | _enableSortByPriority = enableSortByPriority, 84 | _changeTheme = changeTheme, 85 | ) as T 86 | } 87 | error("Unknown ViewModel class: $modelClass") 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val purple200 = Color(0xFFBB86FC) 6 | val purple500 = Color(0xFF6200EE) 7 | val purple700 = Color(0xFF3700B3) 8 | val teal200 = Color(0xFF03DAC5) -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = purple200, 11 | primaryVariant = purple700, 12 | secondary = teal200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = purple500, 17 | primaryVariant = purple700, 18 | secondary = teal200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun DataStoreSampleTheme( 32 | darkTheme: Boolean = isSystemInDarkTheme(), 33 | content: @Composable() () -> Unit, 34 | ) { 35 | val colors = if (darkTheme) { 36 | DarkColorPalette 37 | } else { 38 | LightColorPalette 39 | } 40 | 41 | MaterialTheme( 42 | colors = colors, 43 | typography = typography, 44 | shapes = shapes, 45 | content = content 46 | ) 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/hoc081098/datastoresample/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.hoc081098.datastoresample.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin-Android-Open-Source/DataStore-sample/5477a8fe787e13c0af04f7fa281e8c4e2a77a341/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DataStore Sample 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |