Anywhere dropdown menu
This commit is contained in:
parent
8498da904c
commit
75e004e4c7
|
|
@ -0,0 +1,64 @@
|
|||
package org.lsposed.lspatch.ui.component
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun AnywhereDropdown(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
surface: @Composable () -> Unit,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
val indication = LocalIndication.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val state by interactionSource.interactions.collectAsState(null)
|
||||
var offset by remember { mutableStateOf(Offset.Zero) }
|
||||
val dpOffset = with(LocalDensity.current) {
|
||||
DpOffset(offset.x.toDp(), offset.y.toDp())
|
||||
}
|
||||
|
||||
LaunchedEffect(state) {
|
||||
if (state is PressInteraction.Release) {
|
||||
val i = state as PressInteraction.Release
|
||||
offset = i.press.pressPosition
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.combinedClickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = indication,
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
) {
|
||||
surface()
|
||||
Box {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
offset = dpOffset,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@ package org.lsposed.lspatch.ui.component
|
|||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowForwardIos
|
||||
|
|
@ -19,15 +17,13 @@ import androidx.compose.ui.unit.dp
|
|||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import org.lsposed.lspatch.ui.theme.LSPTheme
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppItem(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: Drawable,
|
||||
label: String,
|
||||
packageName: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
checked: Boolean? = null,
|
||||
rightIcon: (@Composable () -> Unit)? = null,
|
||||
additionalContent: (@Composable ColumnScope.() -> Unit)? = null,
|
||||
|
|
@ -37,10 +33,6 @@ fun AppItem(
|
|||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -68,7 +60,7 @@ fun AppItem(
|
|||
if (checked != null) {
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = { onClick() },
|
||||
onCheckedChange = null,
|
||||
modifier = Modifier.padding(start = 20.dp)
|
||||
)
|
||||
}
|
||||
|
|
@ -90,7 +82,6 @@ private fun AppItemPreview() {
|
|||
icon = shape,
|
||||
label = "Sample App",
|
||||
packageName = "org.lsposed.sample",
|
||||
onClick = {},
|
||||
rightIcon = { Icon(Icons.Filled.ArrowForwardIos, null) }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.lsposed.lspatch.ui.component.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
|
@ -17,14 +18,13 @@ fun SettingsCheckBox(
|
|||
modifier: Modifier = Modifier,
|
||||
checked: Boolean,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector? = null,
|
||||
title: String,
|
||||
desc: String? = null,
|
||||
extraContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||
) {
|
||||
SettingsSlot(modifier, enabled, onClick, icon, title, desc, extraContent) {
|
||||
Checkbox(checked = checked, onCheckedChange = { onClick() })
|
||||
SettingsSlot(modifier, enabled, icon, title, desc, extraContent) {
|
||||
Checkbox(checked = checked, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,14 +35,14 @@ private fun SettingsCheckBoxPreview() {
|
|||
var checked2 by remember { mutableStateOf(false) }
|
||||
Column {
|
||||
SettingsCheckBox(
|
||||
modifier = Modifier.clickable { checked1 = !checked1 },
|
||||
checked = checked1,
|
||||
onClick = { checked1 = !checked1 },
|
||||
title = "Title",
|
||||
desc = "Description"
|
||||
)
|
||||
SettingsCheckBox(
|
||||
modifier = Modifier.clickable { checked2 = !checked2 },
|
||||
checked = checked2,
|
||||
onClick = { checked2 = !checked2 },
|
||||
icon = Icons.Outlined.Api,
|
||||
title = "Title",
|
||||
desc = "Description"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package org.lsposed.lspatch.ui.component.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -16,22 +15,16 @@ import androidx.compose.ui.unit.dp
|
|||
fun SettingsSlot(
|
||||
modifier: Modifier,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector? = null,
|
||||
title: String,
|
||||
desc: String?,
|
||||
extraContent: (@Composable ColumnScope.() -> Unit)? = null,
|
||||
action: (@Composable RowScope.() -> Unit)?,
|
||||
) {
|
||||
val enabledModifier =
|
||||
if (enabled) Modifier
|
||||
.alpha(1f)
|
||||
.clickable(onClick = onClick)
|
||||
else Modifier.alpha(0.5f)
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.then(enabledModifier)
|
||||
.alpha(if (enabled) 1f else 0.5f)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
|
|
@ -71,9 +64,8 @@ fun SettingsSlot(
|
|||
fun SettingsItem(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector? = null,
|
||||
title: String,
|
||||
desc: String? = null,
|
||||
extraContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||
) = SettingsSlot(modifier, enabled, onClick, icon, title, desc, extraContent, null)
|
||||
) = SettingsSlot(modifier, enabled, icon, title, desc, extraContent, null)
|
||||
|
|
|
|||
|
|
@ -16,14 +16,13 @@ fun SettingsSwitch(
|
|||
modifier: Modifier = Modifier,
|
||||
checked: Boolean,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector? = null,
|
||||
title: String,
|
||||
desc: String? = null,
|
||||
extraContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||
) {
|
||||
SettingsSlot(modifier.clickable(onClick = onClick), enabled, onClick, icon, title, desc, extraContent) {
|
||||
Switch(checked = checked, onCheckedChange = { onClick() })
|
||||
SettingsSlot(modifier, enabled, icon, title, desc, extraContent) {
|
||||
Switch(checked = checked, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,14 +33,14 @@ private fun SettingsCheckBoxPreview() {
|
|||
var checked2 by remember { mutableStateOf(false) }
|
||||
Column {
|
||||
SettingsSwitch(
|
||||
modifier = Modifier.clickable { checked1 = !checked1 },
|
||||
checked = checked1,
|
||||
onClick = { checked1 = !checked1 },
|
||||
title = "Title",
|
||||
desc = "Description"
|
||||
)
|
||||
SettingsSwitch(
|
||||
modifier = Modifier.clickable { checked2 = !checked2 },
|
||||
checked = checked2,
|
||||
onClick = { checked2 = !checked2 },
|
||||
icon = Icons.Outlined.Api,
|
||||
title = "Title",
|
||||
desc = "Description"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.animation.animateContentSize
|
|||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
|
|
@ -39,6 +40,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.ui.component.AnywhereDropdown
|
||||
import org.lsposed.lspatch.ui.component.SelectionColumn
|
||||
import org.lsposed.lspatch.ui.component.ShimmerAnimation
|
||||
import org.lsposed.lspatch.ui.component.settings.SettingsCheckBox
|
||||
|
|
@ -219,28 +221,36 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
|||
)
|
||||
}
|
||||
SettingsCheckBox(
|
||||
modifier = Modifier.padding(top = 6.dp),
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp)
|
||||
.clickable { viewModel.debuggable = !viewModel.debuggable },
|
||||
checked = viewModel.debuggable,
|
||||
onClick = { viewModel.debuggable = !viewModel.debuggable },
|
||||
icon = Icons.Outlined.BugReport,
|
||||
title = stringResource(R.string.patch_debuggable)
|
||||
)
|
||||
SettingsCheckBox(
|
||||
modifier = Modifier.clickable { viewModel.overrideVersionCode = !viewModel.overrideVersionCode },
|
||||
checked = viewModel.overrideVersionCode,
|
||||
onClick = { viewModel.overrideVersionCode = !viewModel.overrideVersionCode },
|
||||
icon = Icons.Outlined.Layers,
|
||||
title = stringResource(R.string.patch_override_version_code),
|
||||
desc = stringResource(R.string.patch_override_version_code_desc)
|
||||
)
|
||||
Box {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var signExpanded by remember { mutableStateOf(false) }
|
||||
AnywhereDropdown(
|
||||
expanded = signExpanded,
|
||||
onDismissRequest = { signExpanded = false },
|
||||
onClick = { signExpanded = true },
|
||||
surface = {
|
||||
SettingsItem(
|
||||
onClick = { expanded = true },
|
||||
icon = Icons.Outlined.Edit,
|
||||
title = stringResource(R.string.patch_sign),
|
||||
desc = viewModel.sign.mapIndexedNotNull { index, on -> if (on) "V" + (index + 1) else null }.joinToString(" + ").ifEmpty { "None" }
|
||||
desc = viewModel.sign
|
||||
.mapIndexedNotNull { index, on -> if (on) "V" + (index + 1) else null }
|
||||
.joinToString(" + ")
|
||||
.ifEmpty { "None" }
|
||||
)
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
}
|
||||
) {
|
||||
repeat(2) { index ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
|
|
@ -253,16 +263,19 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Box {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var bypassExpanded by remember { mutableStateOf(false) }
|
||||
AnywhereDropdown(
|
||||
expanded = bypassExpanded,
|
||||
onDismissRequest = { bypassExpanded = false },
|
||||
onClick = { bypassExpanded = true },
|
||||
surface = {
|
||||
SettingsItem(
|
||||
onClick = { expanded = true },
|
||||
icon = Icons.Outlined.RemoveModerator,
|
||||
title = stringResource(R.string.patch_sigbypass),
|
||||
desc = sigBypassLvStr(viewModel.sigBypassLevel)
|
||||
)
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
}
|
||||
) {
|
||||
repeat(3) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
|
|
@ -273,13 +286,12 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
|||
},
|
||||
onClick = {
|
||||
viewModel.sigBypassLevel = it
|
||||
expanded = false
|
||||
bypassExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.activity.compose.BackHandler
|
|||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -124,11 +125,12 @@ private fun SingleSelect(onSelect: (AppInfo) -> Unit) {
|
|||
key = { it.app.packageName }
|
||||
) {
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
modifier = Modifier
|
||||
.animateItemPlacement(spring(stiffness = Spring.StiffnessLow))
|
||||
.clickable { onSelect(it) },
|
||||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
onClick = { onSelect(it) }
|
||||
packageName = it.app.packageName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -145,14 +147,15 @@ private fun MultiSelect() {
|
|||
) {
|
||||
val checked = viewModel.multiSelected.contains(it)
|
||||
AppItem(
|
||||
modifier = Modifier.animateItemPlacement(spring(stiffness = Spring.StiffnessLow)),
|
||||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
onClick = {
|
||||
modifier = Modifier
|
||||
.animateItemPlacement(spring(stiffness = Spring.StiffnessLow))
|
||||
.clickable {
|
||||
if (checked) viewModel.multiSelected.remove(it)
|
||||
else viewModel.multiSelected.add(it)
|
||||
},
|
||||
icon = LSPPackageManager.getIcon(it),
|
||||
label = it.label,
|
||||
packageName = it.app.packageName,
|
||||
checked = checked
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package org.lsposed.lspatch.ui.page
|
|||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.launch
|
|||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.config.Configs
|
||||
import org.lsposed.lspatch.config.MyKeyStore
|
||||
import org.lsposed.lspatch.ui.component.AnywhereDropdown
|
||||
import org.lsposed.lspatch.ui.component.CenterTopBar
|
||||
import org.lsposed.lspatch.ui.component.settings.SettingsItem
|
||||
import org.lsposed.lspatch.ui.component.settings.SettingsSwitch
|
||||
|
|
@ -56,33 +57,36 @@ fun SettingsScreen() {
|
|||
private fun KeyStore() {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var dropdownExpanded by remember { mutableStateOf(false) }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Box {
|
||||
AnywhereDropdown(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
onClick = { expanded = true },
|
||||
surface = {
|
||||
SettingsItem(
|
||||
onClick = { dropdownExpanded = !dropdownExpanded },
|
||||
icon = Icons.Outlined.Ballot,
|
||||
title = stringResource(R.string.settings_keystore),
|
||||
desc = stringResource(if (MyKeyStore.useDefault) R.string.settings_keystore_default else R.string.settings_keystore_custom)
|
||||
)
|
||||
DropdownMenu(expanded = dropdownExpanded, onDismissRequest = { dropdownExpanded = false }) {
|
||||
}
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.settings_keystore_default)) },
|
||||
onClick = {
|
||||
scope.launch { MyKeyStore.reset() }
|
||||
dropdownExpanded = false
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.settings_keystore_custom)) },
|
||||
onClick = {
|
||||
dropdownExpanded = false
|
||||
expanded = false
|
||||
showDialog = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
var wrongKeystore by rememberSaveable { mutableStateOf(false) }
|
||||
|
|
@ -106,7 +110,7 @@ private fun KeyStore() {
|
|||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { dropdownExpanded = false; showDialog = false },
|
||||
onDismissRequest = { expanded = false; showDialog = false },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
content = { Text(stringResource(android.R.string.ok)) },
|
||||
|
|
@ -144,14 +148,14 @@ private fun KeyStore() {
|
|||
}
|
||||
|
||||
scope.launch { MyKeyStore.setCustom(password, alias, aliasPassword) }
|
||||
dropdownExpanded = false
|
||||
expanded = false
|
||||
showDialog = false
|
||||
})
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
content = { Text(stringResource(android.R.string.cancel)) },
|
||||
onClick = { dropdownExpanded = false; showDialog = false }
|
||||
onClick = { expanded = false; showDialog = false }
|
||||
)
|
||||
},
|
||||
title = {
|
||||
|
|
@ -228,8 +232,8 @@ private fun KeyStore() {
|
|||
@Composable
|
||||
private fun DetailPatchLogs() {
|
||||
SettingsSwitch(
|
||||
modifier = Modifier.clickable { Configs.detailPatchLogs = !Configs.detailPatchLogs },
|
||||
checked = Configs.detailPatchLogs,
|
||||
onClick = { Configs.detailPatchLogs = !Configs.detailPatchLogs },
|
||||
title = stringResource(R.string.settings_detail_patch_logs)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import org.lsposed.lspatch.config.Configs
|
|||
import org.lsposed.lspatch.database.entity.Module
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.share.LSPConfig
|
||||
import org.lsposed.lspatch.ui.component.AnywhereDropdown
|
||||
import org.lsposed.lspatch.ui.component.AppItem
|
||||
import org.lsposed.lspatch.ui.component.LoadingDialog
|
||||
import org.lsposed.lspatch.ui.page.SelectAppsResult
|
||||
|
|
@ -49,6 +50,7 @@ import org.lsposed.lspatch.ui.page.destinations.NewPatchScreenDestination
|
|||
import org.lsposed.lspatch.ui.page.destinations.SelectAppsScreenDestination
|
||||
import org.lsposed.lspatch.ui.util.LocalSnackbarHost
|
||||
import org.lsposed.lspatch.ui.viewmodel.manage.AppManageViewModel
|
||||
import org.lsposed.lspatch.ui.viewstate.ProcessingState
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
import java.io.IOException
|
||||
|
|
@ -93,13 +95,16 @@ fun AppManageBody(
|
|||
}
|
||||
}
|
||||
|
||||
if (viewModel.processingUpdate) LoadingDialog()
|
||||
viewModel.updateLoaderResult?.let {
|
||||
when (viewModel.updateLoaderState) {
|
||||
is ProcessingState.Idle -> Unit
|
||||
is ProcessingState.Processing -> LoadingDialog()
|
||||
is ProcessingState.Done -> {
|
||||
val it = viewModel.updateLoaderState as ProcessingState.Done
|
||||
val updateSuccessfully = stringResource(R.string.manage_update_loader_successfully)
|
||||
val updateFailed = stringResource(R.string.manage_update_loader_failed)
|
||||
val copyError = stringResource(R.string.copy_error)
|
||||
LaunchedEffect(Unit) {
|
||||
it.onSuccess {
|
||||
it.result.onSuccess {
|
||||
snackbarHost.showSnackbar(updateSuccessfully)
|
||||
}.onFailure {
|
||||
val result = snackbarHost.showSnackbar(updateFailed, copyError)
|
||||
|
|
@ -111,6 +116,20 @@ fun AppManageBody(
|
|||
viewModel.dispatch(AppManageViewModel.ViewAction.ClearUpdateLoaderResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
when (viewModel.optimizeState) {
|
||||
is ProcessingState.Idle -> Unit
|
||||
is ProcessingState.Processing -> LoadingDialog()
|
||||
is ProcessingState.Done -> {
|
||||
val it = viewModel.optimizeState as ProcessingState.Done
|
||||
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
|
||||
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
||||
LaunchedEffect(Unit) {
|
||||
snackbarHost.showSnackbar(if (it.result) optimizeSucceed else optimizeFailed)
|
||||
viewModel.dispatch(AppManageViewModel.ViewAction.ClearOptimizeResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(Modifier.fillMaxHeight()) {
|
||||
items(
|
||||
|
|
@ -118,13 +137,15 @@ fun AppManageBody(
|
|||
key = { it.first.app.packageName }
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
AnywhereDropdown(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
onClick = { expanded = true },
|
||||
surface = {
|
||||
AppItem(
|
||||
icon = LSPPackageManager.getIcon(it.first),
|
||||
label = it.first.label,
|
||||
packageName = it.first.app.packageName,
|
||||
onClick = { expanded = true },
|
||||
onLongClick = { expanded = true },
|
||||
additionalContent = {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
|
|
@ -141,7 +162,8 @@ fun AppManageBody(
|
|||
)
|
||||
}
|
||||
)
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
}
|
||||
) {
|
||||
val shizukuUnavailable = stringResource(R.string.shizuku_unavailable)
|
||||
if (it.second.lspConfig.VERSION_CODE < LSPConfig.instance.VERSION_CODE || BuildConfig.DEBUG) {
|
||||
DropdownMenuItem(
|
||||
|
|
@ -174,8 +196,6 @@ fun AppManageBody(
|
|||
}
|
||||
)
|
||||
}
|
||||
val optimizeSucceed = stringResource(R.string.manage_optimize_successfully)
|
||||
val optimizeFailed = stringResource(R.string.manage_optimize_failed)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.manage_optimize)) },
|
||||
onClick = {
|
||||
|
|
@ -184,8 +204,7 @@ fun AppManageBody(
|
|||
if (!ShizukuApi.isPermissionGranted) {
|
||||
snackbarHost.showSnackbar(shizukuUnavailable)
|
||||
} else {
|
||||
val result = ShizukuApi.performDexOptMode(it.first.app.packageName)
|
||||
snackbarHost.showSnackbar(if (result) optimizeSucceed else optimizeFailed)
|
||||
viewModel.dispatch(AppManageViewModel.ViewAction.PerformOptimize(it.first))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -213,7 +232,6 @@ fun AppManageBody(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.text.font.FontFamily
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import org.lsposed.lspatch.R
|
||||
import org.lsposed.lspatch.ui.component.AnywhereDropdown
|
||||
import org.lsposed.lspatch.ui.component.AppItem
|
||||
import org.lsposed.lspatch.ui.viewmodel.manage.ModuleManageViewModel
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
|
|
@ -44,13 +45,16 @@ fun ModuleManageBody() {
|
|||
key = { it.first.app.packageName }
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
AnywhereDropdown(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
onClick = { /* TODO: Start module */ },
|
||||
onLongClick = { expanded = true },
|
||||
surface = {
|
||||
AppItem(
|
||||
icon = LSPPackageManager.getIcon(it.first),
|
||||
label = it.first.label,
|
||||
packageName = it.first.app.packageName,
|
||||
onClick = { /* TODO: startAndSendModuleBinder */ },
|
||||
onLongClick = { expanded = true },
|
||||
additionalContent = {
|
||||
Text(
|
||||
text = it.second.description,
|
||||
|
|
@ -69,6 +73,9 @@ fun ModuleManageBody() {
|
|||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
// TODO: Implement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,15 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.lspatch.Patcher
|
||||
import org.lsposed.lspatch.lspApp
|
||||
import org.lsposed.lspatch.share.Constants
|
||||
import org.lsposed.lspatch.share.PatchConfig
|
||||
import org.lsposed.lspatch.ui.viewstate.ProcessingState
|
||||
import org.lsposed.lspatch.util.LSPPackageManager
|
||||
import org.lsposed.lspatch.util.LSPPackageManager.AppInfo
|
||||
import org.lsposed.lspatch.util.ShizukuApi
|
||||
import org.lsposed.patch.util.Logger
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.zip.ZipFile
|
||||
|
|
@ -32,6 +33,8 @@ class AppManageViewModel : ViewModel() {
|
|||
sealed class ViewAction {
|
||||
data class UpdateLoader(val appInfo: AppInfo, val config: PatchConfig) : ViewAction()
|
||||
object ClearUpdateLoaderResult : ViewAction()
|
||||
data class PerformOptimize(val appInfo: AppInfo) : ViewAction()
|
||||
object ClearOptimizeResult : ViewAction()
|
||||
}
|
||||
|
||||
val appList: List<Pair<AppInfo, PatchConfig>> by derivedStateOf {
|
||||
|
|
@ -46,9 +49,10 @@ class AppManageViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
var processingUpdate by mutableStateOf(false)
|
||||
var updateLoaderState: ProcessingState<Result<Unit>> by mutableStateOf(ProcessingState.Idle)
|
||||
private set
|
||||
var updateLoaderResult: Result<Unit>? by mutableStateOf(null)
|
||||
|
||||
var optimizeState: ProcessingState<Boolean> by mutableStateOf(ProcessingState.Idle)
|
||||
private set
|
||||
|
||||
private val logger = object : Logger() {
|
||||
|
|
@ -65,17 +69,20 @@ class AppManageViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun dispatch(action: ViewAction) {
|
||||
suspend fun dispatch(action: ViewAction) {
|
||||
withContext(viewModelScope.coroutineContext) {
|
||||
when (action) {
|
||||
is ViewAction.UpdateLoader -> updateLoader(action.appInfo, action.config)
|
||||
is ViewAction.ClearUpdateLoaderResult -> updateLoaderResult = null
|
||||
is ViewAction.ClearUpdateLoaderResult -> updateLoaderState = ProcessingState.Idle
|
||||
is ViewAction.PerformOptimize -> performOptimize(action.appInfo)
|
||||
is ViewAction.ClearOptimizeResult -> optimizeState = ProcessingState.Idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLoader(appInfo: AppInfo, config: PatchConfig) {
|
||||
private suspend fun updateLoader(appInfo: AppInfo, config: PatchConfig) {
|
||||
Log.i(TAG, "Update loader for ${appInfo.app.packageName}")
|
||||
viewModelScope.launch {
|
||||
processingUpdate = true
|
||||
updateLoaderState = ProcessingState.Processing
|
||||
val result = runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
LSPPackageManager.cleanTmpApkDir()
|
||||
|
|
@ -101,8 +108,15 @@ class AppManageViewModel : ViewModel() {
|
|||
if (status != PackageInstaller.STATUS_SUCCESS) throw RuntimeException(message)
|
||||
}
|
||||
}
|
||||
processingUpdate = false
|
||||
updateLoaderResult = result
|
||||
updateLoaderState = ProcessingState.Done(result)
|
||||
}
|
||||
|
||||
private suspend fun performOptimize(appInfo: AppInfo) {
|
||||
Log.i(TAG, "Perform optimize for ${appInfo.app.packageName}")
|
||||
optimizeState = ProcessingState.Processing
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
ShizukuApi.performDexOptMode(appInfo.app.packageName)
|
||||
}
|
||||
optimizeState = ProcessingState.Done(result)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package org.lsposed.lspatch.ui.viewstate
|
||||
|
||||
sealed class ProcessingState<out T> {
|
||||
object Idle : ProcessingState<Nothing>()
|
||||
object Processing : ProcessingState<Nothing>()
|
||||
data class Done<T>(val result: T) : ProcessingState<T>()
|
||||
}
|
||||
Loading…
Reference in New Issue