feat: improved ui code

This commit is contained in:
Canny 2022-11-12 18:25:12 +03:00 committed by CnC-Robert
parent 98300cfb57
commit 0a5dfa906f
12 changed files with 221 additions and 188 deletions

View File

@ -11,4 +11,6 @@ val viewModelModule = module {
viewModelOf(::PatchesSelectorViewModel) viewModelOf(::PatchesSelectorViewModel)
viewModelOf(::PatchingScreenViewModel) viewModelOf(::PatchingScreenViewModel)
viewModelOf(::ContributorsViewModel) viewModelOf(::ContributorsViewModel)
viewModelOf(::PatcherScreenViewModel)
viewModelOf(::SourceSelectorViewModel)
} }

View File

@ -38,7 +38,7 @@ class ManagerAPI(
return out return out
} }
suspend fun downloadPatches() = withContext(Dispatchers.Main) { suspend fun downloadPatches() = withContext(Dispatchers.Default) {
try { try {
val asset = val asset =
if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar") if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar")

View File

@ -8,6 +8,7 @@ import android.util.Log
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.viewmodel.PatchClass
import app.revanced.manager.util.tag import app.revanced.manager.util.tag
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
@ -19,12 +20,13 @@ import java.util.*
class PatcherUtils(val app: Application) { class PatcherUtils(val app: Application) {
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading) val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading)
val filteredPatches = mutableStateListOf<PatchClass>()
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>()) val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
val selectedPatches = mutableStateListOf<String>() val selectedPatches = mutableStateListOf<String>()
lateinit var patchBundleFile: String lateinit var patchBundleFile: String
fun cleanup() { fun cleanup() {
patches.value = Resource.Success(emptyList()) patches.value = Resource.Loading
selectedAppPackage.value = Optional.empty() selectedAppPackage.value = Optional.empty()
selectedPatches.clear() selectedPatches.clear()
} }

View File

@ -1,6 +1,12 @@
package app.revanced.manager.ui.screen package app.revanced.manager.ui.screen
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Build
import androidx.compose.material3.* import androidx.compose.material3.*
@ -11,14 +17,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.network.api.ManagerAPI
import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.component.AppIcon import app.revanced.manager.ui.component.AppIcon
import app.revanced.manager.ui.component.FloatingActionButton import app.revanced.manager.ui.component.FloatingActionButton
import app.revanced.manager.ui.component.SplitAPKDialog import app.revanced.manager.ui.component.SplitAPKDialog
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
import org.koin.androidx.compose.get
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -28,27 +31,17 @@ fun PatcherScreen(
onClickPatchSelector: () -> Unit, onClickPatchSelector: () -> Unit,
onClickPatch: () -> Unit, onClickPatch: () -> Unit,
onClickSourceSelector: () -> Unit, onClickSourceSelector: () -> Unit,
patcherUtils: PatcherUtils = get(), vm: PatcherScreenViewModel = getViewModel(),
psvm: PatchesSelectorViewModel = getViewModel(),
managerAPI: ManagerAPI = get()
) { ) {
val selectedAmount = patcherUtils.selectedPatches.size
val selectedAppPackage by patcherUtils.selectedAppPackage
val hasAppSelected = selectedAppPackage.isPresent
val patchesLoaded = patcherUtils.patches.value is Resource.Success
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
val hasAppSelected by mutableStateOf(vm.selectedAppPackage.isPresent)
LaunchedEffect(patchesLoaded) { val patchesLoaded by mutableStateOf(vm.patchesLoaded is Resource.Success)
if (!patchesLoaded) {
managerAPI.downloadPatches()
}
}
Scaffold( Scaffold(
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
enabled = hasAppSelected && psvm.anyPatchSelected(), enabled = hasAppSelected && vm.selectedPatches.isNotEmpty(),
onClick = { onClickPatch(); patcherUtils.loadPatchBundle() }, // TODO: replace this with something better onClick = onClickPatch,
icon = { Icon(Icons.Default.Build, contentDescription = stringResource(R.string.patch)) }, icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
text = { Text(stringResource(R.string.patch)) } text = { Text(stringResource(R.string.patch)) }
) )
}) { paddingValues -> }) { paddingValues ->
@ -89,18 +82,18 @@ fun PatcherScreen(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
if (selectedAppPackage.isPresent) { if (vm.selectedAppPackage.isPresent) {
AppIcon( AppIcon(
LocalContext.current.packageManager.getApplicationIcon( LocalContext.current.packageManager.getApplicationIcon(
selectedAppPackage.get().packageName vm.selectedAppPackage.get().packageName
), contentDescription = null, size = 18 ), contentDescription = null, size = 18
) )
Spacer(Modifier.width(5.dp)) Spacer(Modifier.width(5.dp))
} }
Text( Text(
text = if (patchesLoaded) { text = if (patchesLoaded) {
if (selectedAppPackage.isPresent) { if (vm.selectedAppPackage.isPresent) {
selectedAppPackage.get().packageName vm.selectedAppPackage.get().packageName
} else { } else {
stringResource(R.string.card_application_not_selected) stringResource(R.string.card_application_not_selected)
} }
@ -128,8 +121,8 @@ fun PatcherScreen(
Text( Text(
text = if (!hasAppSelected) { text = if (!hasAppSelected) {
stringResource(R.string.select_an_application_first) stringResource(R.string.select_an_application_first)
} else if (psvm.anyPatchSelected()) { } else if (vm.selectedPatches.isNotEmpty()) {
"$selectedAmount patches selected." "${vm.selectedPatches.size} patches selected."
} else { } else {
stringResource(R.string.card_patches_body_patches) stringResource(R.string.card_patches_body_patches)
}, },

View File

@ -25,11 +25,11 @@ import org.koin.androidx.compose.getViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
viewModel: SettingsViewModel = getViewModel(), vm: SettingsViewModel = getViewModel(),
onClickContributors: () -> Unit, onClickContributors: () -> Unit,
onClickLicenses: () -> Unit, onClickLicenses: () -> Unit,
) { ) {
val prefs = viewModel.prefs val prefs = vm.prefs
Column( Column(
modifier = Modifier modifier = Modifier
@ -38,19 +38,19 @@ fun SettingsScreen(
.verticalScroll(state = rememberScrollState()), .verticalScroll(state = rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
if (viewModel.showThemePicker) { if (vm.showThemePicker) {
ThemePicker( ThemePicker(
onDismissRequest = viewModel::dismissThemePicker, onDismissRequest = vm::dismissThemePicker,
onConfirm = viewModel::setTheme onConfirm = vm::setTheme
) )
} }
GroupHeader(stringResource(R.string.appearance)) GroupHeader(stringResource(R.string.appearance))
ListItem( ListItem(
modifier = Modifier.clickable { viewModel.showThemePicker() }, modifier = Modifier.clickable { vm.showThemePicker() },
headlineText = { Text(stringResource(R.string.theme)) }, headlineText = { Text(stringResource(R.string.theme)) },
leadingContent = { Icon(Icons.Default.Style, contentDescription = null) }, leadingContent = { Icon(Icons.Default.Style, contentDescription = null) },
trailingContent = { trailingContent = {
FilledTonalButton(onClick = { viewModel.showThemePicker() }) { FilledTonalButton(onClick = { vm.showThemePicker() }) {
Text(text = prefs.theme.displayName) Text(text = prefs.theme.displayName)
} }
} }
@ -85,7 +85,7 @@ fun SettingsScreen(
} }
) )
Divider() Divider()
SocialItem(R.string.github, R.drawable.ic_github, viewModel::openGitHub) SocialItem(R.string.github, R.drawable.ic_github, vm::openGitHub)
SocialItem(R.string.opensource_licenses, Icons.Default.LibraryBooks, onClickLicenses) SocialItem(R.string.opensource_licenses, Icons.Default.LibraryBooks, onClickLicenses)
SocialItem(R.string.screen_contributors_title, Icons.Default.Group, onClickContributors) SocialItem(R.string.screen_contributors_title, Icons.Default.Group, onClickContributors)
} }

View File

@ -1,7 +1,6 @@
package app.revanced.manager.ui.screen.subscreens package app.revanced.manager.ui.screen.subscreens
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -12,9 +11,7 @@ import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.SdStorage import androidx.compose.material.icons.filled.SdStorage
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppIcon import app.revanced.manager.ui.component.AppIcon
@ -31,17 +28,12 @@ fun AppSelectorSubscreen(
navigator: BackstackNavigator<AppDestination>, navigator: BackstackNavigator<AppDestination>,
vm: AppSelectorViewModel = getViewModel(), vm: AppSelectorViewModel = getViewModel(),
) { ) {
val context = LocalContext.current
val filtered = mutableStateOf(vm.filteredApps.isNotEmpty())
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri -> it?.let { uri ->
vm.setSelectedAppPackageFromFile(uri) vm.setSelectedAppPackageFromFile(uri)
navigator.pop() navigator.pop()
return@rememberLauncherForActivityResult
} }
Toast.makeText(context, "Couldn't load APK file.", Toast.LENGTH_SHORT).show()
} }
Scaffold( Scaffold(
@ -63,7 +55,7 @@ fun AppSelectorSubscreen(
) )
}, },
) { paddingValues -> ) { paddingValues ->
if (filtered.value) { if (vm.filteredApps.isNotEmpty()) {
LazyColumn(modifier = Modifier.padding(paddingValues)) { LazyColumn(modifier = Modifier.padding(paddingValues)) {
items(count = vm.filteredApps.size) { int -> items(count = vm.filteredApps.size) { int ->
val app = vm.filteredApps[int] val app = vm.filteredApps[int]

View File

@ -13,65 +13,57 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.PatchCard
import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
import com.xinto.taxi.BackstackNavigator import com.xinto.taxi.BackstackNavigator
import org.koin.androidx.compose.get import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
import app.revanced.manager.ui.component.PatchCard
@SuppressLint("UnrememberedMutableState") @SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PatchesSelectorSubscreen( fun PatchesSelectorSubscreen(
navigator: BackstackNavigator<AppDestination>, navigator: BackstackNavigator<AppDestination>,
psvm: PatchesSelectorViewModel = getViewModel(), vm: PatchesSelectorViewModel = getViewModel(),
patcherUtils: PatcherUtils = get()
) { ) {
val patchesState by patcherUtils.patches val patches = vm.filteredPatches
val patches = psvm.getFilteredPatches()
var query by mutableStateOf("") var query by mutableStateOf("")
Scaffold( LaunchedEffect(null) {
topBar = { launch(Dispatchers.Default) {
MediumTopAppBar( vm.filterPatches()
title = { }
}
Scaffold(topBar = {
MediumTopAppBar(title = {
Text( Text(
text = stringResource(R.string.card_patches_header), text = stringResource(R.string.card_patches_header),
style = MaterialTheme.typography.headlineLarge style = MaterialTheme.typography.headlineLarge
) )
}, }, navigationIcon = {
navigationIcon = {
IconButton(onClick = navigator::pop) { IconButton(onClick = navigator::pop) {
Icon( Icon(
imageVector = Icons.Default.ArrowBack, imageVector = Icons.Default.ArrowBack, contentDescription = null
contentDescription = null
) )
} }
}, }, actions = {
actions = {
IconButton(onClick = { IconButton(onClick = {
psvm.selectAllPatches(patches, !psvm.anyPatchSelected()) vm.selectAllPatches(patches, vm.selectedPatches.isEmpty())
}) { }) {
if (!psvm.anyPatchSelected()) Icon( if (vm.selectedPatches.isEmpty()) Icon(
Icons.Default.SelectAll, Icons.Default.SelectAll, contentDescription = null
contentDescription = null
) else Icon(Icons.Default.Deselect, contentDescription = null) ) else Icon(Icons.Default.Deselect, contentDescription = null)
} }
} })
) }) { paddingValues ->
}
) { paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier.padding(paddingValues)
.padding(paddingValues)
) { ) {
when (patchesState) { if (!vm.loading) {
is Resource.Success -> {
if (patches.isNotEmpty()) { if (patches.isNotEmpty()) {
Box( Box(
modifier = Modifier modifier = Modifier
@ -91,14 +83,14 @@ fun PatchesSelectorSubscreen(
query = newValue query = newValue
}, },
leadingIcon = { leadingIcon = {
Icon(Icons.Default.Search, stringResource(R.string.search)) Icon(Icons.Default.Search, "Search")
}, },
trailingIcon = { trailingIcon = {
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
IconButton(onClick = { IconButton(onClick = {
query = "" query = ""
}) { }) {
Icon(Icons.Default.Clear, stringResource(R.string.clear)) Icon(Icons.Default.Clear, "Clear")
} }
} }
}, },
@ -111,8 +103,8 @@ fun PatchesSelectorSubscreen(
items(count = patches.size) { items(count = patches.size) {
val patch = patches[it] val patch = patches[it]
val name = patch.patch.patchName val name = patch.patch.patchName
PatchCard(patch, psvm.isPatchSelected(name)) { PatchCard(patch, vm.isPatchSelected(name)) {
psvm.selectPatch(name, !psvm.isPatchSelected(name)) vm.selectPatch(name, !vm.isPatchSelected(name))
} }
} }
} else { } else {
@ -120,8 +112,8 @@ fun PatchesSelectorSubscreen(
val patch = patches[it] val patch = patches[it]
val name = patch.patch.patchName val name = patch.patch.patchName
if (name.contains(query.lowercase())) { if (name.contains(query.lowercase())) {
PatchCard(patch, psvm.isPatchSelected(name)) { PatchCard(patch, vm.isPatchSelected(name)) {
psvm.selectPatch(name, !psvm.isPatchSelected(name)) vm.selectPatch(name, !vm.isPatchSelected(name))
} }
} }
} }
@ -136,9 +128,7 @@ fun PatchesSelectorSubscreen(
Text(stringResource(R.string.no_compatible_patches)) Text(stringResource(R.string.no_compatible_patches))
} }
} }
} } else LoadingIndicator(null)
else -> LoadingIndicator(null)
}
} }
} }
} }

View File

@ -20,19 +20,17 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.ui.component.SourceItem import app.revanced.manager.ui.component.SourceItem
import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.viewmodel.SourceSelectorViewModel
import com.xinto.taxi.BackstackNavigator import com.xinto.taxi.BackstackNavigator
import org.koin.androidx.compose.get import org.koin.androidx.compose.getViewModel
import java.nio.file.Files
import java.nio.file.StandardCopyOption
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SourceSelectorSubscreen( fun SourceSelectorSubscreen(
navigator: BackstackNavigator<AppDestination>, navigator: BackstackNavigator<AppDestination>,
patcherUtils: PatcherUtils = get() viewModel: SourceSelectorViewModel = getViewModel()
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
state = rememberTopAppBarState(), state = rememberTopAppBarState(),
@ -42,17 +40,7 @@ fun SourceSelectorSubscreen(
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri -> it?.let { uri ->
val patchesFile = context.cacheDir.resolve("patches.jar") viewModel.loadBundle(uri)
Files.copy(
context.contentResolver.openInputStream(uri),
patchesFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
patchesFile.absolutePath.also {
patcherUtils.patchBundleFile = it
patcherUtils.loadPatchBundle(it)
}
navigator.pop() navigator.pop()
return@rememberLauncherForActivityResult return@rememberLauncherForActivityResult
} }

View File

@ -6,6 +6,7 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.patcher.PatcherUtils
@ -23,8 +24,9 @@ class AppSelectorViewModel(
val app: Application, patcherUtils: PatcherUtils val app: Application, patcherUtils: PatcherUtils
) : ViewModel() { ) : ViewModel() {
val filteredApps = mutableListOf<ApplicationInfo>() val filteredApps = mutableStateListOf<ApplicationInfo>()
val patches = patcherUtils.patches val patches = patcherUtils.patches
private val filteredPatches = patcherUtils.filteredPatches
private val selectedAppPackage = patcherUtils.selectedAppPackage private val selectedAppPackage = patcherUtils.selectedAppPackage
private val selectedPatches = patcherUtils.selectedPatches private val selectedPatches = patcherUtils.selectedPatches
@ -32,7 +34,7 @@ class AppSelectorViewModel(
viewModelScope.launch { filterApps() } viewModelScope.launch { filterApps() }
} }
private suspend fun filterApps() = withContext(Dispatchers.Main) { private suspend fun filterApps() = withContext(Dispatchers.Default) {
try { try {
val (patches) = patches.value as Resource.Success val (patches) = patches.value as Resource.Success
patches.forEach patch@{ patch -> patches.forEach patch@{ patch ->
@ -59,13 +61,16 @@ class AppSelectorViewModel(
return app.packageManager.getApplicationLabel(info).toString() return app.packageManager.getApplicationLabel(info).toString()
} }
fun loadIcon(info: ApplicationInfo): Drawable? { fun loadIcon(info: ApplicationInfo): Drawable {
return info.loadIcon(app.packageManager) return app.packageManager.getApplicationIcon(info)
} }
fun setSelectedAppPackage(appId: ApplicationInfo) { fun setSelectedAppPackage(appId: ApplicationInfo) {
selectedAppPackage.value.ifPresent { s -> selectedAppPackage.value.ifPresent { s ->
if (s != appId) selectedPatches.clear() if (s != appId) {
selectedPatches.clear()
filteredPatches.clear()
}
} }
selectedAppPackage.value = Optional.of(appId) selectedAppPackage.value = Optional.of(appId)
} }

View File

@ -0,0 +1,25 @@
package app.revanced.manager.ui.viewmodel
import androidx.compose.runtime.getValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.network.api.ManagerAPI
import app.revanced.manager.patcher.PatcherUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class PatcherScreenViewModel(
patcherUtils: PatcherUtils,
api: ManagerAPI
) : ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO) {
api.downloadPatches()
}
}
val selectedPatches = patcherUtils.selectedPatches
val selectedAppPackage by patcherUtils.selectedAppPackage
val patchesLoaded by patcherUtils.patches
}

View File

@ -1,6 +1,9 @@
package app.revanced.manager.ui.viewmodel package app.revanced.manager.ui.viewmodel
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import app.revanced.manager.patcher.PatcherUtils import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
@ -13,16 +16,14 @@ import kotlinx.parcelize.Parcelize
class PatchesSelectorViewModel( class PatchesSelectorViewModel(
private val patcherUtils: PatcherUtils private val patcherUtils: PatcherUtils
) : ViewModel() { ) : ViewModel() {
private val selectedPatches = patcherUtils.selectedPatches val filteredPatches = patcherUtils.filteredPatches
val selectedPatches = patcherUtils.selectedPatches
var loading by mutableStateOf(true)
fun isPatchSelected(patchId: String): Boolean { fun isPatchSelected(patchId: String): Boolean {
return selectedPatches.contains(patchId) return selectedPatches.contains(patchId)
} }
fun anyPatchSelected(): Boolean {
return !selectedPatches.isEmpty()
}
fun selectPatch(patchId: String, state: Boolean) { fun selectPatch(patchId: String, state: Boolean) {
if (state) selectedPatches.add(patchId) if (state) selectedPatches.add(patchId)
else selectedPatches.remove(patchId) else selectedPatches.remove(patchId)
@ -36,10 +37,13 @@ class PatchesSelectorViewModel(
} }
} }
fun getFilteredPatches(): List<PatchClass> { fun filterPatches() {
return buildList { loading = true
val selected = patcherUtils.getSelectedPackageInfo() ?: return@buildList val selected = patcherUtils.getSelectedPackageInfo() ?: return
val (patches) = patcherUtils.patches.value as? Resource.Success ?: return@buildList val (patches) = patcherUtils.patches.value as? Resource.Success ?: return
if (filteredPatches.isNotEmpty()) {
loading = false; return
}
patches.forEach patch@{ patch -> patches.forEach patch@{ patch ->
var unsupported = false var unsupported = false
patch.compatiblePackages?.forEach { pkg -> patch.compatiblePackages?.forEach { pkg ->
@ -48,11 +52,11 @@ class PatchesSelectorViewModel(
if (!unsupported) if (!unsupported)
unsupported = unsupported =
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName } pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
add(PatchClass(patch, unsupported)) filteredPatches.add(PatchClass(patch, unsupported))
}
} }
} }
} }
loading = false
} }
} }

View File

@ -0,0 +1,32 @@
package app.revanced.manager.ui.viewmodel
import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import app.revanced.manager.patcher.PatcherUtils
import app.revanced.manager.util.tag
import io.sentry.Sentry
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
class SourceSelectorViewModel(val app: Application, val patcherUtils: PatcherUtils) : ViewModel() {
fun loadBundle(uri: Uri) {
try {
val patchesFile = app.cacheDir.resolve(File(uri.path!!).name)
Files.copy(
app.contentResolver.openInputStream(uri),
patchesFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
patchesFile.absolutePath.also {
patcherUtils.patchBundleFile = it
patcherUtils.loadPatchBundle(it)
}
} catch (e: Exception) {
Log.e(tag, "Failed to load bundle", e)
Sentry.captureException(e)
}
}
}