mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-04-29 22:14:28 +02:00
feat: improved ui code
This commit is contained in:
parent
98300cfb57
commit
0a5dfa906f
@ -11,4 +11,6 @@ val viewModelModule = module {
|
||||
viewModelOf(::PatchesSelectorViewModel)
|
||||
viewModelOf(::PatchingScreenViewModel)
|
||||
viewModelOf(::ContributorsViewModel)
|
||||
viewModelOf(::PatcherScreenViewModel)
|
||||
viewModelOf(::SourceSelectorViewModel)
|
||||
}
|
@ -38,7 +38,7 @@ class ManagerAPI(
|
||||
return out
|
||||
}
|
||||
|
||||
suspend fun downloadPatches() = withContext(Dispatchers.Main) {
|
||||
suspend fun downloadPatches() = withContext(Dispatchers.Default) {
|
||||
try {
|
||||
val asset =
|
||||
if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar")
|
||||
|
@ -8,6 +8,7 @@ import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
@ -19,12 +20,13 @@ import java.util.*
|
||||
|
||||
class PatcherUtils(val app: Application) {
|
||||
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading)
|
||||
val filteredPatches = mutableStateListOf<PatchClass>()
|
||||
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
|
||||
val selectedPatches = mutableStateListOf<String>()
|
||||
lateinit var patchBundleFile: String
|
||||
|
||||
fun cleanup() {
|
||||
patches.value = Resource.Success(emptyList())
|
||||
patches.value = Resource.Loading
|
||||
selectedAppPackage.value = Optional.empty()
|
||||
selectedPatches.clear()
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
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.filled.Build
|
||||
import androidx.compose.material3.*
|
||||
@ -11,14 +17,11 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.component.AppIcon
|
||||
import app.revanced.manager.ui.component.FloatingActionButton
|
||||
import app.revanced.manager.ui.component.SplitAPKDialog
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||
import org.koin.androidx.compose.get
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -28,27 +31,17 @@ fun PatcherScreen(
|
||||
onClickPatchSelector: () -> Unit,
|
||||
onClickPatch: () -> Unit,
|
||||
onClickSourceSelector: () -> Unit,
|
||||
patcherUtils: PatcherUtils = get(),
|
||||
psvm: PatchesSelectorViewModel = getViewModel(),
|
||||
managerAPI: ManagerAPI = get()
|
||||
vm: PatcherScreenViewModel = getViewModel(),
|
||||
) {
|
||||
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) }
|
||||
|
||||
LaunchedEffect(patchesLoaded) {
|
||||
if (!patchesLoaded) {
|
||||
managerAPI.downloadPatches()
|
||||
}
|
||||
}
|
||||
val hasAppSelected by mutableStateOf(vm.selectedAppPackage.isPresent)
|
||||
val patchesLoaded by mutableStateOf(vm.patchesLoaded is Resource.Success)
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
enabled = hasAppSelected && psvm.anyPatchSelected(),
|
||||
onClick = { onClickPatch(); patcherUtils.loadPatchBundle() }, // TODO: replace this with something better
|
||||
icon = { Icon(Icons.Default.Build, contentDescription = stringResource(R.string.patch)) },
|
||||
enabled = hasAppSelected && vm.selectedPatches.isNotEmpty(),
|
||||
onClick = onClickPatch,
|
||||
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
||||
text = { Text(stringResource(R.string.patch)) }
|
||||
)
|
||||
}) { paddingValues ->
|
||||
@ -89,18 +82,18 @@ fun PatcherScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (selectedAppPackage.isPresent) {
|
||||
if (vm.selectedAppPackage.isPresent) {
|
||||
AppIcon(
|
||||
LocalContext.current.packageManager.getApplicationIcon(
|
||||
selectedAppPackage.get().packageName
|
||||
vm.selectedAppPackage.get().packageName
|
||||
), contentDescription = null, size = 18
|
||||
)
|
||||
Spacer(Modifier.width(5.dp))
|
||||
}
|
||||
Text(
|
||||
text = if (patchesLoaded) {
|
||||
if (selectedAppPackage.isPresent) {
|
||||
selectedAppPackage.get().packageName
|
||||
if (vm.selectedAppPackage.isPresent) {
|
||||
vm.selectedAppPackage.get().packageName
|
||||
} else {
|
||||
stringResource(R.string.card_application_not_selected)
|
||||
}
|
||||
@ -128,8 +121,8 @@ fun PatcherScreen(
|
||||
Text(
|
||||
text = if (!hasAppSelected) {
|
||||
stringResource(R.string.select_an_application_first)
|
||||
} else if (psvm.anyPatchSelected()) {
|
||||
"$selectedAmount patches selected."
|
||||
} else if (vm.selectedPatches.isNotEmpty()) {
|
||||
"${vm.selectedPatches.size} patches selected."
|
||||
} else {
|
||||
stringResource(R.string.card_patches_body_patches)
|
||||
},
|
||||
|
@ -25,11 +25,11 @@ import org.koin.androidx.compose.getViewModel
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: SettingsViewModel = getViewModel(),
|
||||
vm: SettingsViewModel = getViewModel(),
|
||||
onClickContributors: () -> Unit,
|
||||
onClickLicenses: () -> Unit,
|
||||
) {
|
||||
val prefs = viewModel.prefs
|
||||
val prefs = vm.prefs
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@ -38,19 +38,19 @@ fun SettingsScreen(
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
if (viewModel.showThemePicker) {
|
||||
if (vm.showThemePicker) {
|
||||
ThemePicker(
|
||||
onDismissRequest = viewModel::dismissThemePicker,
|
||||
onConfirm = viewModel::setTheme
|
||||
onDismissRequest = vm::dismissThemePicker,
|
||||
onConfirm = vm::setTheme
|
||||
)
|
||||
}
|
||||
GroupHeader(stringResource(R.string.appearance))
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { viewModel.showThemePicker() },
|
||||
modifier = Modifier.clickable { vm.showThemePicker() },
|
||||
headlineText = { Text(stringResource(R.string.theme)) },
|
||||
leadingContent = { Icon(Icons.Default.Style, contentDescription = null) },
|
||||
trailingContent = {
|
||||
FilledTonalButton(onClick = { viewModel.showThemePicker() }) {
|
||||
FilledTonalButton(onClick = { vm.showThemePicker() }) {
|
||||
Text(text = prefs.theme.displayName)
|
||||
}
|
||||
}
|
||||
@ -85,7 +85,7 @@ fun SettingsScreen(
|
||||
}
|
||||
)
|
||||
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.screen_contributors_title, Icons.Default.Group, onClickContributors)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package app.revanced.manager.ui.screen.subscreens
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
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.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.AppIcon
|
||||
@ -31,17 +28,12 @@ fun AppSelectorSubscreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
vm: AppSelectorViewModel = getViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val filtered = mutableStateOf(vm.filteredApps.isNotEmpty())
|
||||
|
||||
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
it?.let { uri ->
|
||||
vm.setSelectedAppPackageFromFile(uri)
|
||||
navigator.pop()
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
Toast.makeText(context, "Couldn't load APK file.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@ -63,7 +55,7 @@ fun AppSelectorSubscreen(
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
if (filtered.value) {
|
||||
if (vm.filteredApps.isNotEmpty()) {
|
||||
LazyColumn(modifier = Modifier.padding(paddingValues)) {
|
||||
items(count = vm.filteredApps.size) { int ->
|
||||
val app = vm.filteredApps[int]
|
||||
|
@ -13,132 +13,122 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.PatchCard
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
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 app.revanced.manager.ui.component.PatchCard
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PatchesSelectorSubscreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
psvm: PatchesSelectorViewModel = getViewModel(),
|
||||
patcherUtils: PatcherUtils = get()
|
||||
vm: PatchesSelectorViewModel = getViewModel(),
|
||||
) {
|
||||
val patchesState by patcherUtils.patches
|
||||
val patches = psvm.getFilteredPatches()
|
||||
val patches = vm.filteredPatches
|
||||
var query by mutableStateOf("")
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MediumTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.card_patches_header),
|
||||
style = MaterialTheme.typography.headlineLarge
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = navigator::pop) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
psvm.selectAllPatches(patches, !psvm.anyPatchSelected())
|
||||
}) {
|
||||
if (!psvm.anyPatchSelected()) Icon(
|
||||
Icons.Default.SelectAll,
|
||||
contentDescription = null
|
||||
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
||||
}
|
||||
}
|
||||
)
|
||||
LaunchedEffect(null) {
|
||||
launch(Dispatchers.Default) {
|
||||
vm.filterPatches()
|
||||
}
|
||||
) { paddingValues ->
|
||||
}
|
||||
Scaffold(topBar = {
|
||||
MediumTopAppBar(title = {
|
||||
Text(
|
||||
text = stringResource(R.string.card_patches_header),
|
||||
style = MaterialTheme.typography.headlineLarge
|
||||
)
|
||||
}, navigationIcon = {
|
||||
IconButton(onClick = navigator::pop) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack, contentDescription = null
|
||||
)
|
||||
}
|
||||
}, actions = {
|
||||
IconButton(onClick = {
|
||||
vm.selectAllPatches(patches, vm.selectedPatches.isEmpty())
|
||||
}) {
|
||||
if (vm.selectedPatches.isEmpty()) Icon(
|
||||
Icons.Default.SelectAll, contentDescription = null
|
||||
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
||||
}
|
||||
})
|
||||
}) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
when (patchesState) {
|
||||
is Resource.Success -> {
|
||||
if (patches.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp, 4.dp),
|
||||
if (!vm.loading) {
|
||||
if (patches.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp, 4.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
value = query,
|
||||
onValueChange = { newValue ->
|
||||
query = newValue
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Search, stringResource(R.string.search))
|
||||
},
|
||||
trailingIcon = {
|
||||
if (query.isNotEmpty()) {
|
||||
IconButton(onClick = {
|
||||
query = ""
|
||||
}) {
|
||||
Icon(Icons.Default.Clear, stringResource(R.string.clear))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyColumn(Modifier.padding(0.dp, 2.dp)) {
|
||||
|
||||
if (query.isEmpty() || query.isBlank()) {
|
||||
items(count = patches.size) {
|
||||
val patch = patches[it]
|
||||
val name = patch.patch.patchName
|
||||
PatchCard(patch, psvm.isPatchSelected(name)) {
|
||||
psvm.selectPatch(name, !psvm.isPatchSelected(name))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(count = patches.size) {
|
||||
val patch = patches[it]
|
||||
val name = patch.patch.patchName
|
||||
if (name.contains(query.lowercase())) {
|
||||
PatchCard(patch, psvm.isPatchSelected(name)) {
|
||||
psvm.selectPatch(name, !psvm.isPatchSelected(name))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
value = query,
|
||||
onValueChange = { newValue ->
|
||||
query = newValue
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Search, "Search")
|
||||
},
|
||||
trailingIcon = {
|
||||
if (query.isNotEmpty()) {
|
||||
IconButton(onClick = {
|
||||
query = ""
|
||||
}) {
|
||||
Icon(Icons.Default.Clear, "Clear")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
Modifier.fillMaxSize(),
|
||||
Arrangement.Center,
|
||||
Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(R.string.no_compatible_patches))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyColumn(Modifier.padding(0.dp, 2.dp)) {
|
||||
|
||||
if (query.isEmpty() || query.isBlank()) {
|
||||
items(count = patches.size) {
|
||||
val patch = patches[it]
|
||||
val name = patch.patch.patchName
|
||||
PatchCard(patch, vm.isPatchSelected(name)) {
|
||||
vm.selectPatch(name, !vm.isPatchSelected(name))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(count = patches.size) {
|
||||
val patch = patches[it]
|
||||
val name = patch.patch.patchName
|
||||
if (name.contains(query.lowercase())) {
|
||||
PatchCard(patch, vm.isPatchSelected(name)) {
|
||||
vm.selectPatch(name, !vm.isPatchSelected(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
Modifier.fillMaxSize(),
|
||||
Arrangement.Center,
|
||||
Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(R.string.no_compatible_patches))
|
||||
}
|
||||
}
|
||||
else -> LoadingIndicator(null)
|
||||
}
|
||||
} else LoadingIndicator(null)
|
||||
}
|
||||
}
|
||||
}
|
@ -20,19 +20,17 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.component.SourceItem
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.viewmodel.SourceSelectorViewModel
|
||||
import com.xinto.taxi.BackstackNavigator
|
||||
import org.koin.androidx.compose.get
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SourceSelectorSubscreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
patcherUtils: PatcherUtils = get()
|
||||
viewModel: SourceSelectorViewModel = getViewModel()
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
state = rememberTopAppBarState(),
|
||||
@ -42,17 +40,7 @@ fun SourceSelectorSubscreen(
|
||||
|
||||
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
it?.let { uri ->
|
||||
val patchesFile = context.cacheDir.resolve("patches.jar")
|
||||
Files.copy(
|
||||
context.contentResolver.openInputStream(uri),
|
||||
patchesFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
patchesFile.absolutePath.also {
|
||||
patcherUtils.patchBundleFile = it
|
||||
patcherUtils.loadPatchBundle(it)
|
||||
}
|
||||
|
||||
viewModel.loadBundle(uri)
|
||||
navigator.pop()
|
||||
return@rememberLauncherForActivityResult
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
@ -23,8 +24,9 @@ class AppSelectorViewModel(
|
||||
val app: Application, patcherUtils: PatcherUtils
|
||||
) : ViewModel() {
|
||||
|
||||
val filteredApps = mutableListOf<ApplicationInfo>()
|
||||
val filteredApps = mutableStateListOf<ApplicationInfo>()
|
||||
val patches = patcherUtils.patches
|
||||
private val filteredPatches = patcherUtils.filteredPatches
|
||||
private val selectedAppPackage = patcherUtils.selectedAppPackage
|
||||
private val selectedPatches = patcherUtils.selectedPatches
|
||||
|
||||
@ -32,7 +34,7 @@ class AppSelectorViewModel(
|
||||
viewModelScope.launch { filterApps() }
|
||||
}
|
||||
|
||||
private suspend fun filterApps() = withContext(Dispatchers.Main) {
|
||||
private suspend fun filterApps() = withContext(Dispatchers.Default) {
|
||||
try {
|
||||
val (patches) = patches.value as Resource.Success
|
||||
patches.forEach patch@{ patch ->
|
||||
@ -59,13 +61,16 @@ class AppSelectorViewModel(
|
||||
return app.packageManager.getApplicationLabel(info).toString()
|
||||
}
|
||||
|
||||
fun loadIcon(info: ApplicationInfo): Drawable? {
|
||||
return info.loadIcon(app.packageManager)
|
||||
fun loadIcon(info: ApplicationInfo): Drawable {
|
||||
return app.packageManager.getApplicationIcon(info)
|
||||
}
|
||||
|
||||
fun setSelectedAppPackage(appId: ApplicationInfo) {
|
||||
selectedAppPackage.value.ifPresent { s ->
|
||||
if (s != appId) selectedPatches.clear()
|
||||
if (s != appId) {
|
||||
selectedPatches.clear()
|
||||
filteredPatches.clear()
|
||||
}
|
||||
}
|
||||
selectedAppPackage.value = Optional.of(appId)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import app.revanced.manager.patcher.PatcherUtils
|
||||
import app.revanced.manager.ui.Resource
|
||||
@ -13,16 +16,14 @@ import kotlinx.parcelize.Parcelize
|
||||
class PatchesSelectorViewModel(
|
||||
private val patcherUtils: PatcherUtils
|
||||
) : ViewModel() {
|
||||
private val selectedPatches = patcherUtils.selectedPatches
|
||||
val filteredPatches = patcherUtils.filteredPatches
|
||||
val selectedPatches = patcherUtils.selectedPatches
|
||||
var loading by mutableStateOf(true)
|
||||
|
||||
fun isPatchSelected(patchId: String): Boolean {
|
||||
return selectedPatches.contains(patchId)
|
||||
}
|
||||
|
||||
fun anyPatchSelected(): Boolean {
|
||||
return !selectedPatches.isEmpty()
|
||||
}
|
||||
|
||||
fun selectPatch(patchId: String, state: Boolean) {
|
||||
if (state) selectedPatches.add(patchId)
|
||||
else selectedPatches.remove(patchId)
|
||||
@ -36,23 +37,26 @@ class PatchesSelectorViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilteredPatches(): List<PatchClass> {
|
||||
return buildList {
|
||||
val selected = patcherUtils.getSelectedPackageInfo() ?: return@buildList
|
||||
val (patches) = patcherUtils.patches.value as? Resource.Success ?: return@buildList
|
||||
patches.forEach patch@{ patch ->
|
||||
var unsupported = false
|
||||
patch.compatiblePackages?.forEach { pkg ->
|
||||
// if we detect unsupported once, don't overwrite it
|
||||
if (pkg.name == selected.packageName) {
|
||||
if (!unsupported)
|
||||
unsupported =
|
||||
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
|
||||
add(PatchClass(patch, unsupported))
|
||||
}
|
||||
fun filterPatches() {
|
||||
loading = true
|
||||
val selected = patcherUtils.getSelectedPackageInfo() ?: return
|
||||
val (patches) = patcherUtils.patches.value as? Resource.Success ?: return
|
||||
if (filteredPatches.isNotEmpty()) {
|
||||
loading = false; return
|
||||
}
|
||||
patches.forEach patch@{ patch ->
|
||||
var unsupported = false
|
||||
patch.compatiblePackages?.forEach { pkg ->
|
||||
// if we detect unsupported once, don't overwrite it
|
||||
if (pkg.name == selected.packageName) {
|
||||
if (!unsupported)
|
||||
unsupported =
|
||||
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
|
||||
filteredPatches.add(PatchClass(patch, unsupported))
|
||||
}
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user