feat: switch to Preferences DataStore (#60)

This commit is contained in:
Ax333l 2023-07-15 11:52:12 +02:00 committed by GitHub
parent 5d3b963682
commit 879884a9fa
18 changed files with 299 additions and 192 deletions

View File

@ -68,6 +68,7 @@ dependencies {
implementation(libs.compose.activity)
implementation(libs.paging.common.ktx)
implementation(libs.work.runtime.ktx)
implementation(libs.preferences.datastore)
// Compose
implementation(platform(libs.compose.bom))

View File

@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.getValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.destination.Destination
@ -56,9 +57,12 @@ class MainActivity : ComponentActivity() {
)
setContent {
val theme by prefs.theme.getAsState()
val dynamicColor by prefs.dynamicColor.getAsState()
ReVancedManagerTheme(
darkTheme = prefs.theme == Theme.SYSTEM && isSystemInDarkTheme() || prefs.theme == Theme.DARK,
dynamicColor = prefs.dynamicColor
darkTheme = theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK,
dynamicColor = dynamicColor
) {
val navController =
rememberNavController<Destination>(startDestination = Destination.Dashboard)

View File

@ -2,12 +2,18 @@ package app.revanced.manager
import android.app.Application
import app.revanced.manager.di.*
import app.revanced.manager.domain.manager.PreferencesManager
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.core.context.startKoin
class ManagerApplication : Application() {
private val scope = MainScope()
private val prefs: PreferencesManager by inject()
override fun onCreate() {
super.onCreate()
@ -26,5 +32,9 @@ class ManagerApplication : Application() {
databaseModule,
)
}
scope.launch {
prefs.preload()
}
}
}

View File

@ -1,14 +1,9 @@
package app.revanced.manager.di
import android.content.Context
import app.revanced.manager.domain.manager.PreferencesManager
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
val preferencesModule = module {
fun providePreferences(
context: Context
) = PreferencesManager(context.getSharedPreferences("preferences", Context.MODE_PRIVATE))
singleOf(::providePreferences)
singleOf(::PreferencesManager)
}

View File

@ -4,8 +4,9 @@ import android.app.Application
import android.content.Context
import app.revanced.manager.util.signing.Signer
import app.revanced.manager.util.signing.SigningOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
@ -23,39 +24,46 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
private val keystorePath =
app.getDir("signing", Context.MODE_PRIVATE).resolve("manager.keystore").toPath()
private fun options(
cn: String = prefs.keystoreCommonName!!,
pass: String = prefs.keystorePass!!,
) = SigningOptions(cn, pass, keystorePath)
private fun updatePrefs(cn: String, pass: String) {
prefs.keystoreCommonName = cn
prefs.keystorePass = pass
private suspend fun updatePrefs(cn: String, pass: String) = prefs.edit {
prefs.keystoreCommonName.value = cn
prefs.keystorePass.value = pass
}
fun sign(input: File, output: File) = Signer(options()).signApk(input, output)
init {
if (!keystorePath.exists()) {
regenerate()
}
suspend fun sign(input: File, output: File) = withContext(Dispatchers.Default) {
Signer(
SigningOptions(
prefs.keystoreCommonName.get(),
prefs.keystorePass.get(),
keystorePath
)
).signApk(
input,
output
)
}
fun regenerate() = Signer(options(DEFAULT, DEFAULT)).regenerateKeystore().also {
suspend fun regenerate() = withContext(Dispatchers.Default) {
Signer(SigningOptions(DEFAULT, DEFAULT, keystorePath)).regenerateKeystore()
updatePrefs(DEFAULT, DEFAULT)
}
fun import(cn: String, pass: String, keystore: Path): Boolean {
suspend fun import(cn: String, pass: String, keystore: Path): Boolean {
if (!Signer(SigningOptions(cn, pass, keystore)).canUnlock()) {
return false
}
Files.copy(keystore, keystorePath, StandardCopyOption.REPLACE_EXISTING)
withContext(Dispatchers.IO) {
Files.copy(keystore, keystorePath, StandardCopyOption.REPLACE_EXISTING)
}
updatePrefs(cn, pass)
return true
}
fun export(target: OutputStream) {
Files.copy(keystorePath, target)
fun hasKeystore() = keystorePath.exists()
suspend fun export(target: OutputStream) {
withContext(Dispatchers.IO) {
Files.copy(keystorePath, target)
}
}
}

View File

@ -1,22 +1,19 @@
package app.revanced.manager.domain.manager
import android.content.SharedPreferences
import app.revanced.manager.domain.manager.base.BasePreferenceManager
import android.content.Context
import app.revanced.manager.domain.manager.base.BasePreferencesManager
import app.revanced.manager.ui.theme.Theme
/**
* @author Hyperion Authors, zt64
*/
class PreferencesManager(
sharedPreferences: SharedPreferences
) : BasePreferenceManager(sharedPreferences) {
var dynamicColor by booleanPreference("dynamic_color", true)
var theme by enumPreference("theme", Theme.SYSTEM)
context: Context
) : BasePreferencesManager(context, "settings") {
val dynamicColor = booleanPreference("dynamic_color", true)
val theme = enumPreference("theme", Theme.SYSTEM)
var allowExperimental by booleanPreference("allow_experimental", false)
val allowExperimental = booleanPreference("allow_experimental", false)
var preferSplits by booleanPreference("prefer_splits", false)
val keystoreCommonName = stringPreference("keystore_cn", KeystoreManager.DEFAULT)
val keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
var keystoreCommonName by stringPreference("keystore_cn", KeystoreManager.DEFAULT)
var keystorePass by stringPreference("keystore_pass", KeystoreManager.DEFAULT)
val preferSplits = booleanPreference("prefer_splits", false)
}

View File

@ -1,98 +1,133 @@
package app.revanced.manager.domain.manager.base
import android.content.SharedPreferences
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.edit
import kotlin.reflect.KProperty
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStore
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.domain.manager.base.BasePreferencesManager.Companion.editor
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
/**
* @author Hyperion Authors, zt64
*/
abstract class BasePreferenceManager(
private val prefs: SharedPreferences
) {
protected fun getString(key: String, defaultValue: String?) =
prefs.getString(key, defaultValue)!!
abstract class BasePreferencesManager(private val context: Context, name: String) {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = name)
protected val dataStore get() = context.dataStore
private fun getBoolean(key: String, defaultValue: Boolean) = prefs.getBoolean(key, defaultValue)
private fun getInt(key: String, defaultValue: Int) = prefs.getInt(key, defaultValue)
private fun getFloat(key: String, defaultValue: Float) = prefs.getFloat(key, defaultValue)
protected inline fun <reified E : Enum<E>> getEnum(key: String, defaultValue: E) =
enumValueOf<E>(getString(key, defaultValue.name))
protected fun putString(key: String, value: String?) = prefs.edit { putString(key, value) }
private fun putBoolean(key: String, value: Boolean) = prefs.edit { putBoolean(key, value) }
private fun putInt(key: String, value: Int) = prefs.edit { putInt(key, value) }
private fun putFloat(key: String, value: Float) = prefs.edit { putFloat(key, value) }
protected inline fun <reified E : Enum<E>> putEnum(key: String, value: E) =
putString(key, value.name)
protected class Preference<T>(
private val key: String,
defaultValue: T,
getter: (key: String, defaultValue: T) -> T,
private val setter: (key: String, newValue: T) -> Unit
) {
@Suppress("RedundantSetter")
var value by mutableStateOf(getter(key, defaultValue))
private set
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
value = newValue
setter(key, newValue)
}
suspend fun preload() {
dataStore.data.first()
}
protected fun stringPreference(
key: String,
defaultValue: String?
) = Preference(
key = key,
defaultValue = defaultValue,
getter = ::getString,
setter = ::putString
)
suspend fun edit(block: EditorContext.() -> Unit) = dataStore.editor(block)
protected fun booleanPreference(
key: String,
defaultValue: Boolean
) = Preference(
key = key,
defaultValue = defaultValue,
getter = ::getBoolean,
setter = ::putBoolean
)
protected fun stringPreference(key: String, default: String) =
StringPreference(dataStore, key, default)
protected fun intPreference(
key: String,
defaultValue: Int
) = Preference(
key = key,
defaultValue = defaultValue,
getter = ::getInt,
setter = ::putInt
)
protected fun booleanPreference(key: String, default: Boolean) =
BooleanPreference(dataStore, key, default)
protected fun floatPreference(
key: String,
defaultValue: Float
) = Preference(
key = key,
defaultValue = defaultValue,
getter = ::getFloat,
setter = ::putFloat
)
protected fun intPreference(key: String, default: Int) = IntPreference(dataStore, key, default)
protected fun floatPreference(key: String, default: Float) =
FloatPreference(dataStore, key, default)
protected inline fun <reified E : Enum<E>> enumPreference(
key: String,
defaultValue: E
) = Preference(
key = key,
defaultValue = defaultValue,
getter = ::getEnum,
setter = ::putEnum
)
default: E
) = EnumPreference(dataStore, key, default, enumValues())
companion object {
suspend inline fun DataStore<Preferences>.editor(crossinline block: EditorContext.() -> Unit) {
edit {
EditorContext(it).run(block)
}
}
}
}
class EditorContext(private val prefs: MutablePreferences) {
var <T> Preference<T>.value
get() = prefs.run { read() }
set(value) = prefs.run { write(value) }
}
abstract class Preference<T>(
private val dataStore: DataStore<Preferences>,
protected val default: T
) {
internal abstract fun Preferences.read(): T
internal abstract fun MutablePreferences.write(value: T)
val flow = dataStore.data.map { with(it) { read() } ?: default }.distinctUntilChanged()
suspend fun get() = flow.first()
fun getBlocking() = runBlocking { get() }
@Composable
fun getAsState() = flow.collectAsStateWithLifecycle(initialValue = remember {
getBlocking()
})
suspend fun update(value: T) = dataStore.editor {
this@Preference.value = value
}
}
class EnumPreference<E : Enum<E>>(
dataStore: DataStore<Preferences>,
key: String,
default: E,
private val enumValues: Array<E>
) : Preference<E>(dataStore, default) {
private val key = stringPreferencesKey(key)
override fun Preferences.read() =
this[key]?.let { name ->
enumValues.find { it.name == name }
} ?: default
override fun MutablePreferences.write(value: E) {
this[key] = value.name
}
}
abstract class BasePreference<T>(dataStore: DataStore<Preferences>, default: T) :
Preference<T>(dataStore, default) {
protected abstract val key: Preferences.Key<T>
override fun Preferences.read() = this[key] ?: default
override fun MutablePreferences.write(value: T) {
this[key] = value
}
}
class StringPreference(
dataStore: DataStore<Preferences>,
key: String,
default: String
) : BasePreference<String>(dataStore, default) {
override val key = stringPreferencesKey(key)
}
class BooleanPreference(
dataStore: DataStore<Preferences>,
key: String,
default: Boolean
) : BasePreference<Boolean>(dataStore, default) {
override val key = booleanPreferencesKey(key)
}
class IntPreference(
dataStore: DataStore<Preferences>,
key: String,
default: Int
) : BasePreference<Int>(dataStore, default) {
override val key = intPreferencesKey(key)
}
class FloatPreference(
dataStore: DataStore<Preferences>,
key: String,
default: Float
) : BasePreference<Float>(dataStore, default) {
override val key = floatPreferencesKey(key)
}

View File

@ -0,0 +1,50 @@
package app.revanced.manager.ui.component.settings
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.material3.ListItem
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import app.revanced.manager.domain.manager.base.Preference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Composable
fun BooleanItem(
preference: Preference<Boolean>,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
@StringRes headline: Int,
@StringRes description: Int
) {
val value by preference.getAsState()
BooleanItem(
value = value,
onValueChange = { coroutineScope.launch { preference.update(it) } },
headline = headline,
description = description
)
}
@Composable
fun BooleanItem(
value: Boolean,
onValueChange: (Boolean) -> Unit,
@StringRes headline: Int,
@StringRes description: Int
) = ListItem(
modifier = Modifier.clickable { onValueChange(!value) },
headlineContent = { Text(stringResource(headline)) },
supportingContent = { Text(stringResource(description)) },
trailingContent = {
Switch(
checked = value,
onCheckedChange = onValueChange,
)
}
)

View File

@ -183,6 +183,8 @@ fun PatchesSelectorScreen(
)
}
val allowExperimental by vm.allowExperimental.getAsState()
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
@ -237,7 +239,7 @@ fun PatchesSelectorScreen(
patchList(
patches = bundle.unsupported,
filterFlag = SHOW_UNSUPPORTED,
supported = vm.allowExperimental
supported = allowExperimental
) {
ListHeader(
title = stringResource(R.string.unsupported_patches),

View File

@ -13,7 +13,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -24,6 +23,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
import org.koin.androidx.compose.getViewModel
@ -58,13 +58,10 @@ fun DownloadsSettingsScreen(
.padding(paddingValues)
.verticalScroll(rememberScrollState())
) {
ListItem(
modifier = Modifier.clickable { prefs.preferSplits = !prefs.preferSplits },
headlineContent = { Text(stringResource(R.string.prefer_splits)) },
supportingContent = { Text(stringResource(R.string.prefer_splits_description)) },
trailingContent = {
Switch(checked = prefs.preferSplits, onCheckedChange = { prefs.preferSplits = it })
}
BooleanItem(
preference = prefs.preferSplits,
headline = R.string.prefer_splits,
description = R.string.prefer_splits_description,
)
GroupHeader(stringResource(R.string.downloaded_apps))

View File

@ -15,12 +15,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.theme.Theme
import app.revanced.manager.ui.viewmodel.SettingsViewModel
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
@OptIn(ExperimentalMaterial3Api::class)
@ -30,6 +33,7 @@ fun GeneralSettingsScreen(
viewModel: SettingsViewModel
) {
val prefs = viewModel.prefs
val coroutineScope = viewModel.viewModelScope
var showThemePicker by rememberSaveable { mutableStateOf(false) }
if (showThemePicker) {
@ -53,22 +57,27 @@ fun GeneralSettingsScreen(
.padding(paddingValues)
.verticalScroll(rememberScrollState())
) {
GroupHeader(stringResource(R.string.appearance))
val theme by prefs.theme.getAsState()
ListItem(
modifier = Modifier.clickable { showThemePicker = true },
headlineContent = { Text(stringResource(R.string.theme)) },
supportingContent = { Text(stringResource(R.string.theme_description)) },
trailingContent = {
Button({
showThemePicker = true
}) { Text(stringResource(prefs.theme.displayName)) }
Button(
onClick = {
showThemePicker = true
}
) {
Text(stringResource(theme.displayName))
}
}
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BooleanItem(
value = prefs.dynamicColor,
onValueChange = { prefs.dynamicColor = it },
preference = prefs.dynamicColor,
coroutineScope = coroutineScope,
headline = R.string.dynamic_color,
description = R.string.dynamic_color_description
)
@ -76,8 +85,8 @@ fun GeneralSettingsScreen(
GroupHeader(stringResource(R.string.patcher))
BooleanItem(
value = prefs.allowExperimental,
onValueChange = { prefs.allowExperimental = it },
preference = prefs.allowExperimental,
coroutineScope = coroutineScope,
headline = R.string.experimental_patches,
description = R.string.experimental_patches_description
)
@ -91,7 +100,7 @@ private fun ThemePicker(
onConfirm: (Theme) -> Unit,
prefs: PreferencesManager = koinInject()
) {
var selectedTheme by rememberSaveable { mutableStateOf(prefs.theme) }
var selectedTheme by rememberSaveable { mutableStateOf(prefs.theme.getBlocking()) }
AlertDialog(
onDismissRequest = onDismiss,
@ -122,22 +131,4 @@ private fun ThemePicker(
}
}
)
}
@Composable
private fun BooleanItem(
value: Boolean,
onValueChange: (Boolean) -> Unit,
@StringRes headline: Int,
@StringRes description: Int
) = ListItem(
modifier = Modifier.clickable { onValueChange(!value) },
headlineContent = { Text(stringResource(headline)) },
supportingContent = { Text(stringResource(description)) },
trailingContent = {
Switch(
checked = value,
onCheckedChange = onValueChange,
)
}
)
}

View File

@ -25,6 +25,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.ui.viewmodel.ImportExportViewModel
import app.revanced.manager.ui.component.AppTopBar
@ -32,6 +33,7 @@ import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.PasswordField
import app.revanced.manager.ui.component.sources.SourceSelector
import app.revanced.manager.util.toast
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class)
@ -40,6 +42,8 @@ fun ImportExportSettingsScreen(
onBackClick: () -> Unit,
vm: ImportExportViewModel = getViewModel()
) {
val context = LocalContext.current
val importKeystoreLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
it?.let { uri -> vm.startKeystoreImport(uri) }
@ -74,7 +78,12 @@ fun ImportExportSettingsScreen(
if (vm.showCredentialsDialog) {
KeystoreCredentialsDialog(
onDismissRequest = vm::cancelKeystoreImport,
tryImport = vm::tryKeystoreImport
onSubmit = { cn, pass ->
vm.viewModelScope.launch {
val result = vm.tryKeystoreImport(cn, pass)
if (!result) context.toast(context.getString(R.string.import_keystore_wrong_credentials))
}
}
)
}
@ -102,6 +111,10 @@ fun ImportExportSettingsScreen(
)
GroupItem(
onClick = {
if (!vm.canExport()) {
context.toast(context.getString(R.string.export_keystore_unavailable))
return@GroupItem
}
exportKeystoreLauncher.launch("Manager.keystore")
},
headline = R.string.export_keystore,
@ -144,9 +157,8 @@ private fun GroupItem(onClick: () -> Unit, @StringRes headline: Int, @StringRes
@Composable
fun KeystoreCredentialsDialog(
onDismissRequest: () -> Unit,
tryImport: (String, String) -> Boolean
onSubmit: (String, String) -> Unit
) {
val context = LocalContext.current
var cn by rememberSaveable { mutableStateOf("") }
var pass by rememberSaveable { mutableStateOf("") }
@ -155,11 +167,7 @@ fun KeystoreCredentialsDialog(
confirmButton = {
TextButton(
onClick = {
if (!tryImport(
cn,
pass
)
) context.toast(context.getString(R.string.import_keystore_wrong_credentials))
onSubmit(cn, pass)
}
) {
Text(stringResource(R.string.import_keystore_dialog_button))

View File

@ -118,7 +118,7 @@ class AppDownloaderViewModel(
?: appDownloader.downloadApp(
version,
savePath,
preferSplit = prefs.preferSplits
preferSplit = prefs.preferSplits.get()
).also {
downloadedAppRepository.add(
selectedApp.packageName,

View File

@ -48,17 +48,20 @@ class ImportExportViewModel(
private var keystoreImportPath by mutableStateOf<Path?>(null)
val showCredentialsDialog by derivedStateOf { keystoreImportPath != null }
fun startKeystoreImport(content: Uri) {
val path = File.createTempFile("signing", "ks", app.cacheDir).toPath()
Files.copy(
contentResolver.openInputStream(content)!!,
path,
StandardCopyOption.REPLACE_EXISTING
)
fun startKeystoreImport(content: Uri) = viewModelScope.launch {
val path = withContext(Dispatchers.IO) {
File.createTempFile("signing", "ks", app.cacheDir).toPath().also {
Files.copy(
contentResolver.openInputStream(content)!!,
it,
StandardCopyOption.REPLACE_EXISTING
)
}
}
knownPasswords.forEach {
if (tryKeystoreImport(KeystoreManager.DEFAULT, it, path)) {
return
return@launch
}
}
@ -70,10 +73,10 @@ class ImportExportViewModel(
keystoreImportPath = null
}
fun tryKeystoreImport(cn: String, pass: String) =
suspend fun tryKeystoreImport(cn: String, pass: String) =
tryKeystoreImport(cn, pass, keystoreImportPath!!)
private fun tryKeystoreImport(cn: String, pass: String, path: Path): Boolean {
private suspend fun tryKeystoreImport(cn: String, pass: String, path: Path): Boolean {
if (keystoreManager.import(cn, pass, path)) {
cancelKeystoreImport()
return true
@ -88,10 +91,14 @@ class ImportExportViewModel(
cancelKeystoreImport()
}
fun exportKeystore(target: Uri) =
keystoreManager.export(contentResolver.openOutputStream(target)!!)
fun canExport() = keystoreManager.hasKeystore()
fun regenerateKeystore() = keystoreManager.regenerate().also {
fun exportKeystore(target: Uri) = viewModelScope.launch {
keystoreManager.export(contentResolver.openOutputStream(target)!!)
}
fun regenerateKeystore() = viewModelScope.launch {
keystoreManager.regenerate()
app.toast(app.getString(R.string.regenerate_keystore_success))
}

View File

@ -40,10 +40,9 @@ class PatchesSelectorViewModel(
val appInfo: AppInfo
) : ViewModel(), KoinComponent {
private val selectionRepository: PatchSelectionRepository = get()
private val prefs: PreferencesManager = get()
private val savedStateHandle: SavedStateHandle = get()
val allowExperimental get() = prefs.allowExperimental
val allowExperimental = get<PreferencesManager>().allowExperimental
val bundlesFlow = get<SourceRepository>().sources.flatMapLatestAndCombine(
combiner = { it }
) { source ->
@ -121,7 +120,7 @@ class PatchesSelectorViewModel(
selectionRepository.updateSelection(appInfo.packageName, it)
}
}.mapValues { it.value.toMutableSet() }.apply {
if (allowExperimental) {
if (allowExperimental.get()) {
return@apply
}

View File

@ -1,15 +1,15 @@
package app.revanced.manager.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.theme.Theme
import kotlinx.coroutines.launch
class SettingsViewModel(
val prefs: PreferencesManager
) : ViewModel() {
fun setTheme(theme: Theme) {
prefs.theme = theme
fun setTheme(theme: Theme) = viewModelScope.launch {
prefs.theme.update(theme)
}
}

View File

@ -45,6 +45,7 @@
<string name="import_keystore_wrong_credentials">Wrong keystore credentials</string>
<string name="export_keystore">Export keystore</string>
<string name="export_keystore_description">Export the current keystore</string>
<string name="export_keystore_unavailable">No keystore to export</string>
<string name="regenerate_keystore">Regenerate keystore</string>
<string name="regenerate_keystore_description">Generate a new keystore</string>
<string name="regenerate_keystore_success">The keystore has been successfully replaced</string>

View File

@ -4,6 +4,7 @@ viewmodel-lifecycle = "2.6.1"
splash-screen = "1.0.1"
compose-activity = "1.7.2"
paging = "3.1.1"
preferences-datastore = "1.0.0"
work-runtime = "2.8.1ō"
compose-bom = "2023.06.01"
accompanist = "0.30.1"
@ -35,6 +36,7 @@ splash-screen = { group = "androidx.core", name = "core-splashscreen", version.r
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
paging-common-ktx = { group = "androidx.paging", name = "paging-common-ktx", version.ref = "paging" }
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" }
preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" }
# Compose
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }