mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-04-30 06:24: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(::PatchesSelectorViewModel)
|
||||||
viewModelOf(::PatchingScreenViewModel)
|
viewModelOf(::PatchingScreenViewModel)
|
||||||
viewModelOf(::ContributorsViewModel)
|
viewModelOf(::ContributorsViewModel)
|
||||||
|
viewModelOf(::PatcherScreenViewModel)
|
||||||
|
viewModelOf(::SourceSelectorViewModel)
|
||||||
}
|
}
|
@ -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")
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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