feat: save patch options and selected patches in bundle (#50)

This commit is contained in:
Ax333l 2023-07-04 11:09:16 +02:00 committed by GitHub
parent a0ab7a0e26
commit 370276dcab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 39 deletions

View File

@ -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

View File

@ -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>>

View File

@ -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) }
)
}

View File

@ -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) }
)

View File

@ -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()
}
)