mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
feat: save patch options and selected patches in bundle (#50)
This commit is contained in:
parent
01fd4c8ffa
commit
8dd8f88d2b
@ -26,7 +26,7 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import app.revanced.manager.R
|
||||
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 kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
@ -5,10 +5,14 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.repository.PatchSelectionRepository
|
||||
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.flatMapLatestAndCombine
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
@ -29,11 +35,13 @@ import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
|
||||
@Stable
|
||||
@OptIn(SavedStateHandleSaveableApi::class)
|
||||
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 bundlesFlow = get<SourceRepository>().sources.flatMapLatestAndCombine(
|
||||
@ -59,9 +67,30 @@ class PatchesSelectorViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private val selectedPatches = mutableStateMapOf<Int, SnapshotStateSet<String>>()
|
||||
private val patchOptions =
|
||||
mutableStateMapOf<Int, SnapshotStateMap<String, SnapshotStateMap<String, Any?>>>()
|
||||
private val selectedPatches: SnapshotStatePatchesSelection by savedStateHandle.saveable(saver = patchesSelectionSaver, init = {
|
||||
val map: SnapshotStatePatchesSelection = mutableStateMapOf()
|
||||
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.
|
||||
@ -91,39 +120,17 @@ class PatchesSelectorViewModel(
|
||||
withContext(Dispatchers.Default) {
|
||||
selectionRepository.updateSelection(appInfo.packageName, it)
|
||||
}
|
||||
}.mapValues { it.value.toMutableList() }.apply {
|
||||
}.mapValues { it.value.toMutableSet() }.apply {
|
||||
if (allowExperimental) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
// Filter out unsupported patches that may have gotten selected through the database if the setting is not enabled.
|
||||
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(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) =
|
||||
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(
|
||||
@ -174,4 +192,14 @@ class PatchesSelectorViewModel(
|
||||
val unsupported: 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.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
@ -20,10 +19,8 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
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?>>>
|
||||
|
||||
fun Context.openUrl(url: String) {
|
||||
@ -96,9 +93,4 @@ inline fun <T, reified R, C> Flow<Iterable<T>>.flatMapLatestAndCombine(
|
||||
combine(iterable.map(transformer)) {
|
||||
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