Skip to content

Commit 7596c81

Browse files
committed
Implement DataStore for configuration management and migrate from SharedPreferences
1 parent ce75eb2 commit 7596c81

File tree

10 files changed

+210
-74
lines changed

10 files changed

+210
-74
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.rosan.installer.data.settings.model.datastore
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.edit
6+
import androidx.datastore.preferences.core.intPreferencesKey
7+
import androidx.datastore.preferences.core.stringPreferencesKey
8+
import kotlinx.coroutines.flow.Flow
9+
import kotlinx.coroutines.flow.map
10+
11+
class AppDataStore(private val dataStore: DataStore<Preferences>) {
12+
13+
suspend fun putString(key: String, value: String) {
14+
dataStore.edit { it[stringPreferencesKey(key)] = value }
15+
}
16+
17+
fun getString(key: String, default: String = ""): Flow<String> {
18+
return dataStore.data.map { it[stringPreferencesKey(key)] ?: default }
19+
}
20+
21+
suspend fun putInt(key: String, value: Int) {
22+
dataStore.edit { it[intPreferencesKey(key)] = value }
23+
}
24+
25+
fun getInt(key: String, default: Int = 0): Flow<Int> {
26+
return dataStore.data.map { it[intPreferencesKey(key)] ?: default }
27+
}
28+
}

app/src/main/java/com/rosan/installer/data/settings/model/room/entity/converter/AuthorizerConverter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.rosan.installer.data.settings.model.room.entity.ConfigEntity
66
object AuthorizerConverter {
77
@TypeConverter
88
fun revert(value: String?): ConfigEntity.Authorizer =
9-
(if (value != null) ConfigEntity.Authorizer.values().find { it.value == value }
9+
(if (value != null) ConfigEntity.Authorizer.entries.find { it.value == value }
1010
else null) ?: ConfigEntity.Authorizer.Shizuku
1111

1212
@TypeConverter

app/src/main/java/com/rosan/installer/data/settings/util/ConfigUtil.kt

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,88 @@
11
package com.rosan.installer.data.settings.util
22

3-
import android.content.Context
3+
import com.rosan.installer.data.settings.model.datastore.AppDataStore
44
import com.rosan.installer.data.settings.model.room.entity.AppEntity
55
import com.rosan.installer.data.settings.model.room.entity.ConfigEntity
66
import com.rosan.installer.data.settings.model.room.entity.converter.AuthorizerConverter
77
import com.rosan.installer.data.settings.model.room.entity.converter.InstallModeConverter
88
import com.rosan.installer.data.settings.repo.AppRepo
99
import com.rosan.installer.data.settings.repo.ConfigRepo
1010
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.flow.first
1112
import kotlinx.coroutines.withContext
1213
import org.koin.core.component.KoinComponent
1314
import org.koin.core.component.get
1415
import org.koin.core.component.inject
1516

17+
/**
18+
* Utility class for managing configuration settings.
19+
*
20+
* DataStore 迁移注意事项
21+
* 由于 DataStore 的读写是异步操作,原本通过 `val` 属性(同步获取)的配置项,迁移后需改为 `suspend fun`(挂起函数)。调用时必须在协程或挂起环境下进行,否则会编译报错。
22+
*
23+
* 迁移前(同步)
24+
* val authorizer = ConfigUtil.globalAuthorizer
25+
*
26+
* 迁移后(异步)
27+
* val authorizer = withContext(Dispatchers.IO) {
28+
* ConfigUtil.getGlobalAuthorizer()
29+
* }
30+
**/
1631
class ConfigUtil {
1732
companion object : KoinComponent {
18-
private val context by inject<Context>()
33+
// SharedPreferences
34+
// private val context by inject<Context>()
35+
// private val sharedPreferences = context.getSharedPreferences("app", Context.MODE_PRIVATE)
1936

20-
private val sharedPreferences = context.getSharedPreferences("app", Context.MODE_PRIVATE)
37+
// DataStore
38+
private val appDataStore by inject<AppDataStore>()
2139

22-
val globalAuthorizer: ConfigEntity.Authorizer
23-
get() = AuthorizerConverter.revert(sharedPreferences.getString("authorizer", null))
40+
/*
41+
val globalAuthorizer: ConfigEntity.Authorizer
42+
get() = AuthorizerConverter.revert(sharedPreferences.getString("authorizer", null))
2443
25-
val globalCustomizeAuthorizer: String
26-
get() = sharedPreferences.getString("customize_authorizer", null) ?: ""
44+
val globalCustomizeAuthorizer: String
45+
get() = sharedPreferences.getString("customize_authorizer", null) ?: ""
2746
28-
val globalInstallMode: ConfigEntity.InstallMode
29-
get() = InstallModeConverter.revert(sharedPreferences.getString("install_mode", null))
47+
val globalInstallMode: ConfigEntity.InstallMode
48+
get() = InstallModeConverter.revert(sharedPreferences.getString("install_mode", null))
49+
*/
50+
51+
suspend fun getGlobalAuthorizer(): ConfigEntity.Authorizer {
52+
val str = appDataStore.getString("authorizer", "").first()
53+
return AuthorizerConverter.revert(str)
54+
}
55+
56+
suspend fun getGlobalCustomizeAuthorizer(): String {
57+
return appDataStore.getString("customize_authorizer", "").first()
58+
}
59+
60+
suspend fun getGlobalInstallMode(): ConfigEntity.InstallMode {
61+
val str = appDataStore.getString("install_mode", "").first()
62+
return InstallModeConverter.revert(str)
63+
}
64+
65+
/* suspend fun getByPackageName(packageName: String? = null): ConfigEntity {
66+
var entity = getByPackageNameInner(packageName)
67+
if (entity.authorizer == ConfigEntity.Authorizer.Global)
68+
entity = entity.copy(
69+
authorizer = globalAuthorizer,
70+
customizeAuthorizer = globalCustomizeAuthorizer
71+
)
72+
if (entity.installMode == ConfigEntity.InstallMode.Global)
73+
entity = entity.copy(installMode = globalInstallMode)
74+
return entity
75+
}*/
3076

3177
suspend fun getByPackageName(packageName: String? = null): ConfigEntity {
3278
var entity = getByPackageNameInner(packageName)
3379
if (entity.authorizer == ConfigEntity.Authorizer.Global)
3480
entity = entity.copy(
35-
authorizer = globalAuthorizer,
36-
customizeAuthorizer = globalCustomizeAuthorizer
81+
authorizer = getGlobalAuthorizer(),
82+
customizeAuthorizer = getGlobalCustomizeAuthorizer()
3783
)
3884
if (entity.installMode == ConfigEntity.InstallMode.Global)
39-
entity = entity.copy(installMode = globalInstallMode)
85+
entity = entity.copy(installMode = getGlobalInstallMode())
4086
return entity
4187
}
4288

@@ -61,4 +107,4 @@ class ConfigUtil {
61107
return null
62108
}
63109
}
64-
}
110+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.rosan.installer.di
2+
3+
import androidx.datastore.preferences.SharedPreferencesMigration
4+
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
5+
import androidx.datastore.preferences.preferencesDataStoreFile
6+
import com.rosan.installer.data.settings.model.datastore.AppDataStore
7+
import org.koin.android.ext.koin.androidContext
8+
import org.koin.dsl.module
9+
10+
val datastoreModule = module {
11+
single {
12+
PreferenceDataStoreFactory.create(
13+
migrations = listOf(
14+
// Migration from SharedPreferences to DataStore
15+
SharedPreferencesMigration(androidContext(), "app")
16+
)
17+
) {
18+
androidContext().preferencesDataStoreFile("app_settings")
19+
}
20+
}
21+
22+
single {
23+
AppDataStore(get())
24+
}
25+
}

app/src/main/java/com/rosan/installer/di/init/app_modules.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.rosan.installer.di.init
22

3+
import com.rosan.installer.di.datastoreModule
34
import com.rosan.installer.di.installerModule
45
import com.rosan.installer.di.reflectModule
56
import com.rosan.installer.di.roomModule
@@ -13,5 +14,6 @@ val appModules = listOf(
1314
serializationModule,
1415
workerModule,
1516
installerModule,
16-
reflectModule
17+
reflectModule,
18+
datastoreModule
1719
)

app/src/main/java/com/rosan/installer/di/viewmodel_module.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ val viewModelModule = module {
1616
}
1717

1818
viewModel {
19-
PreferredViewModel()
19+
PreferredViewModel(get())
2020
}
2121

2222
viewModel { (navController: NavController) ->

app/src/main/java/com/rosan/installer/ui/activity/SettingsActivity.kt

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,19 @@
11
package com.rosan.installer.ui.activity
22

3-
import android.content.Context
43
import android.os.Bundle
5-
import android.text.method.LinkMovementMethod
6-
import android.widget.TextView
74
import androidx.activity.ComponentActivity
85
import androidx.activity.compose.setContent
96
import androidx.activity.enableEdgeToEdge
107
import androidx.compose.foundation.layout.fillMaxSize
11-
import androidx.compose.material3.AlertDialog
12-
import androidx.compose.material3.AlertDialogDefaults
138
import androidx.compose.material3.Surface
14-
import androidx.compose.material3.Text
15-
import androidx.compose.material3.TextButton
16-
import androidx.compose.runtime.Composable
17-
import androidx.compose.runtime.getValue
18-
import androidx.compose.runtime.mutableStateOf
19-
import androidx.compose.runtime.remember
20-
import androidx.compose.runtime.setValue
219
import androidx.compose.ui.Modifier
22-
import androidx.compose.ui.graphics.toArgb
23-
import androidx.compose.ui.platform.LocalContext
24-
import androidx.compose.ui.res.stringResource
25-
import androidx.compose.ui.viewinterop.AndroidView
26-
import androidx.core.content.edit
27-
import androidx.core.text.HtmlCompat
28-
import com.rosan.installer.R
2910
import com.rosan.installer.ui.page.settings.SettingsPage
3011
import com.rosan.installer.ui.theme.InstallerTheme
3112
import org.koin.core.component.KoinComponent
3213

3314
class SettingsActivity : ComponentActivity(), KoinComponent {
3415
override fun onCreate(savedInstanceState: Bundle?) {
16+
// Enable edge-to-edge mode for immersive experience
3517
enableEdgeToEdge()
3618
super.onCreate(savedInstanceState)
3719
setContent {
@@ -41,16 +23,17 @@ class SettingsActivity : ComponentActivity(), KoinComponent {
4123
modifier = Modifier
4224
.fillMaxSize()
4325
) {
26+
// Disable AgreementDialog for now
4427
// AgreementDialog()
4528
SettingsPage()
4629
}
4730
}
4831
}
4932
}
5033

51-
@Composable
34+
/*@Composable
5235
private fun AgreementDialog() {
53-
val preferences = LocalContext.current.getSharedPreferences("app", Context.MODE_PRIVATE)
36+
val preferences = LocalContext.current.getSharedPreferences("app", MODE_PRIVATE)
5437
var agreed by remember {
5538
mutableStateOf(preferences.getBoolean("agreement", false))
5639
}
@@ -87,5 +70,5 @@ class SettingsActivity : ComponentActivity(), KoinComponent {
8770
Text(text = stringResource(id = R.string.agree))
8871
}
8972
})
90-
}
73+
}*/
9174
}

app/src/main/java/com/rosan/installer/ui/page/settings/config/edit/EditPage.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import androidx.compose.runtime.LaunchedEffect
4343
import androidx.compose.runtime.MutableState
4444
import androidx.compose.runtime.getValue
4545
import androidx.compose.runtime.mutableStateOf
46+
import androidx.compose.runtime.produceState
4647
import androidx.compose.runtime.remember
4748
import androidx.compose.ui.Modifier
4849
import androidx.compose.ui.geometry.Offset
@@ -297,7 +298,13 @@ fun DataInstallModeWidget(viewModel: EditViewModel) {
297298
fun DataDeclareInstallerWidget(viewModel: EditViewModel) {
298299
var authorizer = viewModel.state.data.authorizer
299300
if (authorizer == ConfigEntity.Authorizer.Global)
300-
authorizer = ConfigUtil.globalAuthorizer
301+
// authorizer = ConfigUtil.globalAuthorizer
302+
{
303+
val globalAuthorizer by produceState<ConfigEntity.Authorizer?>(null) {
304+
value = ConfigUtil.getGlobalAuthorizer()
305+
}
306+
authorizer = globalAuthorizer!!
307+
}
301308
val declareInstaller = viewModel.state.data.declareInstaller
302309
if (authorizer == ConfigEntity.Authorizer.Dhizuku) {
303310
viewModel.dispatch(EditViewAction.ChangeDataDeclareInstaller(false))

app/src/main/java/com/rosan/installer/ui/page/settings/main/MainPage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fun MainPage(navController: NavController) {
7272
modifier = Modifier
7373
.fillMaxSize()
7474
) {
75-
val isLandscapeScreen = maxHeight / maxWidth > 1.4
75+
val isLandscapeScreen = this.maxHeight.value / this.maxWidth.value > 1.4
7676

7777
val navigationSide =
7878
if (isLandscapeScreen) WindowInsetsSides.Bottom

0 commit comments

Comments
 (0)