mirror of
https://github.com/revanced/revanced-manager-compose
synced 2025-02-20 18:16:48 +01:00
feat: save patch options and selected patches in bundle (#50)
This commit is contained in:
parent
a0ab7a0e26
commit
370276dcab
@ -26,7 +26,7 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.util.PathSaver
|
import app.revanced.manager.util.saver.PathSaver
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.isDirectory
|
import kotlin.io.path.isDirectory
|
||||||
import kotlin.io.path.isRegularFile
|
import kotlin.io.path.isRegularFile
|
||||||
|
@ -5,10 +5,14 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
|
||||||
|
import androidx.lifecycle.viewmodel.compose.saveable
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
||||||
import app.revanced.manager.domain.repository.SourceRepository
|
import app.revanced.manager.domain.repository.SourceRepository
|
||||||
@ -19,6 +23,8 @@ import app.revanced.manager.util.PatchesSelection
|
|||||||
import app.revanced.manager.util.SnapshotStateSet
|
import app.revanced.manager.util.SnapshotStateSet
|
||||||
import app.revanced.manager.util.flatMapLatestAndCombine
|
import app.revanced.manager.util.flatMapLatestAndCombine
|
||||||
import app.revanced.manager.util.mutableStateSetOf
|
import app.revanced.manager.util.mutableStateSetOf
|
||||||
|
import app.revanced.manager.util.saver.snapshotStateMapSaver
|
||||||
|
import app.revanced.manager.util.saver.snapshotStateSetSaver
|
||||||
import app.revanced.manager.util.toMutableStateSet
|
import app.revanced.manager.util.toMutableStateSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@ -29,11 +35,13 @@ import org.koin.core.component.KoinComponent
|
|||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
@OptIn(SavedStateHandleSaveableApi::class)
|
||||||
class PatchesSelectorViewModel(
|
class PatchesSelectorViewModel(
|
||||||
val appInfo: AppInfo
|
val appInfo: AppInfo
|
||||||
) : ViewModel(), KoinComponent {
|
) : ViewModel(), KoinComponent {
|
||||||
private val selectionRepository: PatchSelectionRepository = get()
|
private val selectionRepository: PatchSelectionRepository = get()
|
||||||
private val prefs: PreferencesManager = get()
|
private val prefs: PreferencesManager = get()
|
||||||
|
private val savedStateHandle: SavedStateHandle = get()
|
||||||
val allowExperimental get() = prefs.allowExperimental
|
val allowExperimental get() = prefs.allowExperimental
|
||||||
|
|
||||||
val bundlesFlow = get<SourceRepository>().sources.flatMapLatestAndCombine(
|
val bundlesFlow = get<SourceRepository>().sources.flatMapLatestAndCombine(
|
||||||
@ -59,9 +67,30 @@ class PatchesSelectorViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val selectedPatches = mutableStateMapOf<Int, SnapshotStateSet<String>>()
|
private val selectedPatches: SnapshotStatePatchesSelection by savedStateHandle.saveable(saver = patchesSelectionSaver, init = {
|
||||||
private val patchOptions =
|
val map: SnapshotStatePatchesSelection = mutableStateMapOf()
|
||||||
mutableStateMapOf<Int, SnapshotStateMap<String, SnapshotStateMap<String, Any?>>>()
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
val bundles = bundlesFlow.first()
|
||||||
|
val filteredSelection =
|
||||||
|
selectionRepository.getSelection(appInfo.packageName).mapValues { (uid, patches) ->
|
||||||
|
// Filter out patches that don't exist.
|
||||||
|
val filteredPatches = bundles.singleOrNull { it.uid == uid }
|
||||||
|
?.let { bundle ->
|
||||||
|
val allPatches = bundle.all.map { it.name }
|
||||||
|
patches.filter { allPatches.contains(it) }
|
||||||
|
}
|
||||||
|
?: patches
|
||||||
|
|
||||||
|
filteredPatches.toMutableStateSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
map.putAll(filteredSelection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@saveable map
|
||||||
|
})
|
||||||
|
private val patchOptions: SnapshotStateOptions by savedStateHandle.saveable(saver = optionsSaver, init = ::mutableStateMapOf)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the patch options dialog for this patch.
|
* Show the patch options dialog for this patch.
|
||||||
@ -91,39 +120,17 @@ class PatchesSelectorViewModel(
|
|||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
selectionRepository.updateSelection(appInfo.packageName, it)
|
selectionRepository.updateSelection(appInfo.packageName, it)
|
||||||
}
|
}
|
||||||
}.mapValues { it.value.toMutableList() }.apply {
|
}.mapValues { it.value.toMutableSet() }.apply {
|
||||||
if (allowExperimental) {
|
if (allowExperimental) {
|
||||||
return@apply
|
return@apply
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out unsupported patches that may have gotten selected through the database if the setting is not enabled.
|
// Filter out unsupported patches that may have gotten selected through the database if the setting is not enabled.
|
||||||
bundlesFlow.first().forEach {
|
bundlesFlow.first().forEach {
|
||||||
this[it.uid]?.removeAll(it.unsupported.map { patch -> patch.name })
|
this[it.uid]?.removeAll(it.unsupported.map { patch -> patch.name }.toSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
|
||||||
val bundles = bundlesFlow.first()
|
|
||||||
val filteredSelection =
|
|
||||||
selectionRepository.getSelection(appInfo.packageName).mapValues { (uid, patches) ->
|
|
||||||
// Filter out patches that don't exist.
|
|
||||||
val filteredPatches = bundles.singleOrNull { it.uid == uid }
|
|
||||||
?.let { bundle ->
|
|
||||||
val allPatches = bundle.all.map { it.name }
|
|
||||||
patches.filter { allPatches.contains(it) }
|
|
||||||
}
|
|
||||||
?: patches
|
|
||||||
|
|
||||||
filteredPatches.toMutableStateSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
selectedPatches.putAll(filteredSelection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOptions(): Options = patchOptions
|
fun getOptions(): Options = patchOptions
|
||||||
fun getOptions(bundle: Int, patch: PatchInfo) = patchOptions[bundle]?.get(patch.name)
|
fun getOptions(bundle: Int, patch: PatchInfo) = patchOptions[bundle]?.get(patch.name)
|
||||||
|
|
||||||
@ -164,6 +171,17 @@ class PatchesSelectorViewModel(
|
|||||||
|
|
||||||
private fun <K, K2, V> SnapshotStateMap<K, SnapshotStateMap<K2, V>>.getOrCreate(key: K) =
|
private fun <K, K2, V> SnapshotStateMap<K, SnapshotStateMap<K2, V>>.getOrCreate(key: K) =
|
||||||
getOrPut(key, ::mutableStateMapOf)
|
getOrPut(key, ::mutableStateMapOf)
|
||||||
|
|
||||||
|
private val optionsSaver: Saver<SnapshotStateOptions, Options> = snapshotStateMapSaver(
|
||||||
|
// Patch name -> Options
|
||||||
|
valueSaver = snapshotStateMapSaver(
|
||||||
|
// Option key -> Option value
|
||||||
|
valueSaver = snapshotStateMapSaver()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val patchesSelectionSaver: Saver<SnapshotStatePatchesSelection, PatchesSelection> =
|
||||||
|
snapshotStateMapSaver(valueSaver = snapshotStateSetSaver())
|
||||||
}
|
}
|
||||||
|
|
||||||
data class BundleInfo(
|
data class BundleInfo(
|
||||||
@ -174,4 +192,14 @@ class PatchesSelectorViewModel(
|
|||||||
val unsupported: List<PatchInfo>,
|
val unsupported: List<PatchInfo>,
|
||||||
val universal: List<PatchInfo>
|
val universal: List<PatchInfo>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Options] but with observable collection types.
|
||||||
|
*/
|
||||||
|
private typealias SnapshotStateOptions = SnapshotStateMap<Int, SnapshotStateMap<String, SnapshotStateMap<String, Any?>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [PatchesSelection] but with observable collection types.
|
||||||
|
*/
|
||||||
|
private typealias SnapshotStatePatchesSelection = SnapshotStateMap<Int, SnapshotStateSet<String>>
|
@ -7,7 +7,6 @@ import android.graphics.drawable.Drawable
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
@ -20,10 +19,8 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.Path
|
|
||||||
|
|
||||||
typealias PatchesSelection = Map<Int, List<String>>
|
typealias PatchesSelection = Map<Int, Set<String>>
|
||||||
typealias Options = Map<Int, Map<String, Map<String, Any?>>>
|
typealias Options = Map<Int, Map<String, Map<String, Any?>>>
|
||||||
|
|
||||||
fun Context.openUrl(url: String) {
|
fun Context.openUrl(url: String) {
|
||||||
@ -96,9 +93,4 @@ inline fun <T, reified R, C> Flow<Iterable<T>>.flatMapLatestAndCombine(
|
|||||||
combine(iterable.map(transformer)) {
|
combine(iterable.map(transformer)) {
|
||||||
combiner(it)
|
combiner(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val PathSaver = Saver<Path, String>(
|
|
||||||
save = { it.toString() },
|
|
||||||
restore = { Path(it) }
|
|
||||||
)
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.manager.util.saver
|
||||||
|
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Saver] that can save [Path]s. Only works with the default filesystem.
|
||||||
|
*/
|
||||||
|
val PathSaver = Saver<Path, String>(
|
||||||
|
save = { it.toString() },
|
||||||
|
restore = { Path(it) }
|
||||||
|
)
|
@ -0,0 +1,75 @@
|
|||||||
|
package app.revanced.manager.util.saver
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
|
import androidx.compose.runtime.toMutableStateMap
|
||||||
|
import app.revanced.manager.util.SnapshotStateSet
|
||||||
|
import app.revanced.manager.util.toMutableStateSet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Saver] for [SnapshotStateList]s.
|
||||||
|
*/
|
||||||
|
fun <T> snapshotStateListSaver() = Saver<SnapshotStateList<T>, List<T>>(
|
||||||
|
save = {
|
||||||
|
it.toMutableList()
|
||||||
|
},
|
||||||
|
restore = {
|
||||||
|
it.toMutableStateList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Saver] for [SnapshotStateSet]s.
|
||||||
|
*/
|
||||||
|
fun <T> snapshotStateSetSaver() = Saver<SnapshotStateSet<T>, Set<T>>(
|
||||||
|
save = {
|
||||||
|
it.toMutableSet()
|
||||||
|
},
|
||||||
|
restore = {
|
||||||
|
it.toMutableStateSet()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [Saver] for [SnapshotStateMap]s.
|
||||||
|
*/
|
||||||
|
fun <K, V> snapshotStateMapSaver() = Saver<SnapshotStateMap<K, V>, Map<K, V>>(
|
||||||
|
save = {
|
||||||
|
it.toMutableMap()
|
||||||
|
},
|
||||||
|
restore = {
|
||||||
|
mutableStateMapOf<K, V>().apply {
|
||||||
|
this.putAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a saver for [SnapshotStateMap]s with a custom [Saver] used for the values.
|
||||||
|
* Null values will not be saved by this [Saver].
|
||||||
|
*
|
||||||
|
* @param valueSaver The [Saver] used for the values of the [Map].
|
||||||
|
*/
|
||||||
|
fun <K, Original, Saveable : Any> snapshotStateMapSaver(
|
||||||
|
valueSaver: Saver<Original, Saveable>
|
||||||
|
) = Saver<SnapshotStateMap<K, Original>, Map<K, Saveable>>(
|
||||||
|
save = {
|
||||||
|
buildMap {
|
||||||
|
it.forEach { (key, value) ->
|
||||||
|
with(valueSaver) {
|
||||||
|
save(value)?.let {
|
||||||
|
this@buildMap[key] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restore = {
|
||||||
|
it.mapNotNull { (key, value) ->
|
||||||
|
valueSaver.restore(value)?.let { restored -> key to restored }
|
||||||
|
}.toMutableStateMap()
|
||||||
|
}
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user