manager: Add status tracking to ensure that brush-write operations are performed correctly
This commit is contained in:
@@ -110,6 +110,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
var tempText: String
|
var tempText: String
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
val logContent = rememberSaveable { StringBuilder() }
|
||||||
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
var showFloatAction by rememberSaveable { mutableStateOf(false) }
|
||||||
|
// 添加状态跟踪是否已经完成刷写
|
||||||
|
var hasFlashCompleted by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -132,13 +134,19 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
totalModules = flashIt.uris.size,
|
totalModules = flashIt.uris.size,
|
||||||
currentModule = 1
|
currentModule = 1
|
||||||
)
|
)
|
||||||
|
hasFlashCompleted = false
|
||||||
|
} else if (flashIt !is FlashIt.FlashModules) {
|
||||||
|
hasFlashCompleted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
// 只有在未完成刷写时才执行刷写操作
|
||||||
if (text.isNotEmpty()) {
|
LaunchedEffect(flashIt, hasFlashCompleted) {
|
||||||
|
// 如果已经完成刷写或者已有文本内容,则不再执行
|
||||||
|
if (hasFlashCompleted || text.isNotEmpty()) {
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
setFlashingStatus(FlashingStatus.FLASHING)
|
setFlashingStatus(FlashingStatus.FLASHING)
|
||||||
|
|
||||||
@@ -157,7 +165,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flashIt(context, flashIt, onFinish = { showReboot, code ->
|
flashIt(flashIt, onFinish = { showReboot, code ->
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
text += "$errorCodeString $code.\n$checkLogString\n"
|
text += "$errorCodeString $code.\n$checkLogString\n"
|
||||||
setFlashingStatus(FlashingStatus.FAILED)
|
setFlashingStatus(FlashingStatus.FAILED)
|
||||||
@@ -176,6 +184,8 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
showFloatAction = true
|
showFloatAction = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasFlashCompleted = true
|
||||||
|
|
||||||
if (flashIt is FlashIt.FlashModules && flashIt.currentIndex < flashIt.uris.size - 1) {
|
if (flashIt is FlashIt.FlashModules && flashIt.currentIndex < flashIt.uris.size - 1) {
|
||||||
val nextFlashIt = flashIt.copy(
|
val nextFlashIt = flashIt.copy(
|
||||||
currentIndex = flashIt.currentIndex + 1
|
currentIndex = flashIt.currentIndex + 1
|
||||||
@@ -222,8 +232,6 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
TopBar(
|
TopBar(
|
||||||
currentFlashingStatus.value,
|
currentFlashingStatus.value,
|
||||||
currentStatus,
|
currentStatus,
|
||||||
navigator = navigator,
|
|
||||||
flashIt = flashIt,
|
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onSave = {
|
onSave = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -434,8 +442,6 @@ fun ModuleInstallProgressBar(
|
|||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
status: FlashingStatus,
|
status: FlashingStatus,
|
||||||
moduleStatus: ModuleInstallStatus = ModuleInstallStatus(),
|
moduleStatus: ModuleInstallStatus = ModuleInstallStatus(),
|
||||||
navigator: DestinationsNavigator,
|
|
||||||
flashIt: FlashIt,
|
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSave: () -> Unit = {},
|
onSave: () -> Unit = {},
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
@@ -531,7 +537,6 @@ sealed class FlashIt : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun flashIt(
|
fun flashIt(
|
||||||
context: android.content.Context,
|
|
||||||
flashIt: FlashIt,
|
flashIt: FlashIt,
|
||||||
onFinish: (Boolean, Int) -> Unit,
|
onFinish: (Boolean, Int) -> Unit,
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
|
|||||||
@@ -8,12 +8,20 @@ import android.util.Log
|
|||||||
import android.widget.Toast
|
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.animation.*
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.*
|
import androidx.compose.material.icons.automirrored.outlined.*
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
@@ -24,6 +32,8 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.*
|
import androidx.compose.ui.platform.*
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -66,7 +76,6 @@ import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
|
||||||
import com.sukisu.ultra.ui.webui.WebUIXActivity
|
import com.sukisu.ultra.ui.webui.WebUIXActivity
|
||||||
import com.dergoogler.mmrl.platform.Platform
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
@@ -74,6 +83,13 @@ import com.dergoogler.mmrl.platform.model.ModuleConfig
|
|||||||
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
|
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
|
||||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
|
|
||||||
|
// 菜单项数据类
|
||||||
|
data class ModuleBottomSheetMenuItem(
|
||||||
|
val icon: ImageVector,
|
||||||
|
val titleRes: Int,
|
||||||
|
val onClick: () -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/5/31.
|
* @date 2025/5/31.
|
||||||
@@ -89,6 +105,12 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
val confirmDialog = rememberConfirmDialog()
|
val confirmDialog = rememberConfirmDialog()
|
||||||
var lastClickTime by remember { mutableStateOf(0L) }
|
var lastClickTime by remember { mutableStateOf(0L) }
|
||||||
|
|
||||||
|
// BottomSheet状态
|
||||||
|
val bottomSheetState = rememberModalBottomSheetState(
|
||||||
|
skipPartiallyExpanded = true
|
||||||
|
)
|
||||||
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val selectZipLauncher = rememberLauncherForActivityResult(
|
val selectZipLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
) {
|
) {
|
||||||
@@ -201,6 +223,34 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
) { viewModel.fetchModuleList() }
|
) { viewModel.fetchModuleList() }
|
||||||
|
|
||||||
|
// BottomSheet菜单项
|
||||||
|
val bottomSheetMenuItems = remember {
|
||||||
|
listOf(
|
||||||
|
ModuleBottomSheetMenuItem(
|
||||||
|
icon = Icons.Outlined.Save,
|
||||||
|
titleRes = R.string.backup_modules,
|
||||||
|
onClick = {
|
||||||
|
backupLauncher.launch(ModuleModify.createBackupIntent())
|
||||||
|
scope.launch {
|
||||||
|
bottomSheetState.hide()
|
||||||
|
showBottomSheet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ModuleBottomSheetMenuItem(
|
||||||
|
icon = Icons.Outlined.RestoreFromTrash,
|
||||||
|
titleRes = R.string.restore_modules,
|
||||||
|
onClick = {
|
||||||
|
restoreLauncher.launch(ModuleModify.createRestoreIntent())
|
||||||
|
scope.launch {
|
||||||
|
bottomSheetState.hide()
|
||||||
|
showBottomSheet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
SearchAppBar(
|
SearchAppBar(
|
||||||
@@ -209,87 +259,13 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
onSearchTextChange = { viewModel.search = it },
|
onSearchTextChange = { viewModel.search = it },
|
||||||
onClearClick = { viewModel.search = "" },
|
onClearClick = { viewModel.search = "" },
|
||||||
dropdownContent = {
|
dropdownContent = {
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showDropdown = true },
|
onClick = { showBottomSheet = true },
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.MoreVert,
|
imageVector = Icons.Filled.MoreVert,
|
||||||
contentDescription = stringResource(id = R.string.settings),
|
contentDescription = stringResource(id = R.string.settings),
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = showDropdown,
|
|
||||||
onDismissRequest = { showDropdown = false }
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.module_sort_action_first)) },
|
|
||||||
trailingIcon = {
|
|
||||||
Checkbox(
|
|
||||||
checked = viewModel.sortActionFirst,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
viewModel.sortActionFirst = !viewModel.sortActionFirst
|
|
||||||
prefs.edit {
|
|
||||||
putBoolean(
|
|
||||||
"module_sort_action_first",
|
|
||||||
viewModel.sortActionFirst
|
|
||||||
)
|
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
viewModel.fetchModuleList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.module_sort_enabled_first)) },
|
|
||||||
trailingIcon = {
|
|
||||||
Checkbox(
|
|
||||||
checked = viewModel.sortEnabledFirst,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
|
|
||||||
prefs.edit {
|
|
||||||
putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
|
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
viewModel.fetchModuleList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
HorizontalDivider(thickness = Dp.Hairline, modifier = Modifier.padding(vertical = 4.dp))
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.backup_modules)) },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Save,
|
|
||||||
contentDescription = stringResource(R.string.backup),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
showDropdown = false
|
|
||||||
backupLauncher.launch(ModuleModify.createBackupIntent())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(stringResource(R.string.restore_modules)) },
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.RestoreFromTrash,
|
|
||||||
contentDescription = stringResource(R.string.restore),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
showDropdown = false
|
|
||||||
restoreLauncher.launch(ModuleModify.createRestoreIntent())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
@@ -425,6 +401,202 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BottomSheet
|
||||||
|
if (showBottomSheet) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = {
|
||||||
|
showBottomSheet = false
|
||||||
|
},
|
||||||
|
sheetState = bottomSheetState,
|
||||||
|
dragHandle = {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(vertical = 11.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f),
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier.size(
|
||||||
|
width = 32.dp,
|
||||||
|
height = 4.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
ModuleBottomSheetContent(
|
||||||
|
menuItems = bottomSheetMenuItems,
|
||||||
|
viewModel = viewModel,
|
||||||
|
prefs = prefs,
|
||||||
|
scope = scope,
|
||||||
|
bottomSheetState = bottomSheetState,
|
||||||
|
onDismiss = { showBottomSheet = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun ModuleBottomSheetContent(
|
||||||
|
menuItems: List<ModuleBottomSheetMenuItem>,
|
||||||
|
viewModel: ModuleViewModel,
|
||||||
|
prefs: android.content.SharedPreferences,
|
||||||
|
scope: kotlinx.coroutines.CoroutineScope,
|
||||||
|
bottomSheetState: SheetState,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 24.dp)
|
||||||
|
) {
|
||||||
|
// 标题
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.menu_options),
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 菜单选项网格
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(4),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
items(menuItems) { menuItem ->
|
||||||
|
ModuleBottomSheetMenuItemView(
|
||||||
|
menuItem = menuItem
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序选项
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sort_options),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 排序选项
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
// 优先显示有操作的模块
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.module_sort_action_first),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = viewModel.sortActionFirst,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
viewModel.sortActionFirst = checked
|
||||||
|
prefs.edit {
|
||||||
|
putBoolean("module_sort_action_first", checked)
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
viewModel.fetchModuleList()
|
||||||
|
bottomSheetState.hide()
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先显示已启用的模块
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.module_sort_enabled_first),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = viewModel.sortEnabledFirst,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
viewModel.sortEnabledFirst = checked
|
||||||
|
prefs.edit {
|
||||||
|
putBoolean("module_sort_enabled_first", checked)
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
viewModel.fetchModuleList()
|
||||||
|
bottomSheetState.hide()
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) {
|
||||||
|
// 添加交互状态
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 0.95f else 1.0f,
|
||||||
|
animationSpec = spring(
|
||||||
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
|
stiffness = Spring.StiffnessHigh
|
||||||
|
),
|
||||||
|
label = "menuItemScale"
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.scale(scale)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null
|
||||||
|
) { menuItem.onClick() }
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.size(48.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = menuItem.icon,
|
||||||
|
contentDescription = stringResource(menuItem.titleRes),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(menuItem.titleRes),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
maxLines = 2
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
package com.sukisu.ultra.ui.util
|
package com.sukisu.ultra.ui.util
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
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.material3.SnackbarDuration
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -25,18 +22,78 @@ import java.util.Date
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
object ModuleModify {
|
object ModuleModify {
|
||||||
suspend fun showRestoreConfirmation(context: Context): Boolean {
|
@Composable
|
||||||
val result = CompletableDeferred<Boolean>()
|
fun RestoreConfirmationDialog(
|
||||||
withContext(Dispatchers.Main) {
|
showDialog: Boolean,
|
||||||
AlertDialog.Builder(context)
|
onConfirm: () -> Unit,
|
||||||
.setTitle(context.getString(R.string.restore_confirm_title))
|
onDismiss: () -> Unit
|
||||||
.setMessage(context.getString(R.string.restore_confirm_message))
|
) {
|
||||||
.setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) }
|
val context = LocalContext.current
|
||||||
.setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) }
|
|
||||||
.setOnCancelListener { result.complete(false) }
|
if (showDialog) {
|
||||||
.show()
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = context.getString(R.string.restore_confirm_title),
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = context.getString(R.string.restore_confirm_message),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text(context.getString(R.string.confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(context.getString(R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AllowlistRestoreConfirmationDialog(
|
||||||
|
showDialog: Boolean,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
if (showDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = context.getString(R.string.allowlist_restore_confirm_title),
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = context.getString(R.string.allowlist_restore_confirm_message),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text(context.getString(R.string.confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(context.getString(R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return result.await()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
|
suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
|
||||||
@@ -82,8 +139,19 @@ object ModuleModify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun restoreModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
|
suspend fun restoreModules(
|
||||||
val userConfirmed = showRestoreConfirmation(context)
|
context: Context,
|
||||||
|
snackBarHost: SnackbarHostState,
|
||||||
|
uri: Uri,
|
||||||
|
showConfirmDialog: (Boolean) -> Unit,
|
||||||
|
confirmResult: CompletableDeferred<Boolean>
|
||||||
|
) {
|
||||||
|
// 显示确认对话框
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showConfirmDialog(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val userConfirmed = confirmResult.await()
|
||||||
if (!userConfirmed) return
|
if (!userConfirmed) return
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@@ -132,20 +200,6 @@ object ModuleModify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun showAllowlistRestoreConfirmation(context: Context): Boolean {
|
|
||||||
val result = CompletableDeferred<Boolean>()
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle(context.getString(R.string.allowlist_restore_confirm_title))
|
|
||||||
.setMessage(context.getString(R.string.allowlist_restore_confirm_message))
|
|
||||||
.setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) }
|
|
||||||
.setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) }
|
|
||||||
.setOnCancelListener { result.complete(false) }
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
return result.await()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun backupAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
|
suspend fun backupAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@@ -182,8 +236,19 @@ object ModuleModify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun restoreAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
|
suspend fun restoreAllowlist(
|
||||||
val userConfirmed = showAllowlistRestoreConfirmation(context)
|
context: Context,
|
||||||
|
snackBarHost: SnackbarHostState,
|
||||||
|
uri: Uri,
|
||||||
|
showConfirmDialog: (Boolean) -> Unit,
|
||||||
|
confirmResult: CompletableDeferred<Boolean>
|
||||||
|
) {
|
||||||
|
// 显示确认对话框
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showConfirmDialog(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val userConfirmed = confirmResult.await()
|
||||||
if (!userConfirmed) return
|
if (!userConfirmed) return
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@@ -246,13 +311,42 @@ object ModuleModify {
|
|||||||
context: Context,
|
context: Context,
|
||||||
snackBarHost: SnackbarHostState,
|
snackBarHost: SnackbarHostState,
|
||||||
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
|
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
|
||||||
) = rememberLauncherForActivityResult(
|
): androidx.activity.result.ActivityResultLauncher<Intent> {
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
var showRestoreDialog by remember { mutableStateOf(false) }
|
||||||
) { result ->
|
var restoreConfirmResult by remember { mutableStateOf<CompletableDeferred<Boolean>?>(null) }
|
||||||
if (result.resultCode == android.app.Activity.RESULT_OK) {
|
var pendingUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
result.data?.data?.let { uri ->
|
|
||||||
scope.launch {
|
// 显示恢复确认对话框
|
||||||
restoreModules(context, snackBarHost, uri)
|
RestoreConfirmationDialog(
|
||||||
|
showDialog = showRestoreDialog,
|
||||||
|
onConfirm = {
|
||||||
|
showRestoreDialog = false
|
||||||
|
restoreConfirmResult?.complete(true)
|
||||||
|
},
|
||||||
|
onDismiss = {
|
||||||
|
showRestoreDialog = false
|
||||||
|
restoreConfirmResult?.complete(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == android.app.Activity.RESULT_OK) {
|
||||||
|
result.data?.data?.let { uri ->
|
||||||
|
pendingUri = uri
|
||||||
|
scope.launch {
|
||||||
|
val confirmResult = CompletableDeferred<Boolean>()
|
||||||
|
restoreConfirmResult = confirmResult
|
||||||
|
|
||||||
|
restoreModules(
|
||||||
|
context = context,
|
||||||
|
snackBarHost = snackBarHost,
|
||||||
|
uri = uri,
|
||||||
|
showConfirmDialog = { show -> showRestoreDialog = show },
|
||||||
|
confirmResult = confirmResult
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,13 +374,42 @@ object ModuleModify {
|
|||||||
context: Context,
|
context: Context,
|
||||||
snackBarHost: SnackbarHostState,
|
snackBarHost: SnackbarHostState,
|
||||||
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
|
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
|
||||||
) = rememberLauncherForActivityResult(
|
): androidx.activity.result.ActivityResultLauncher<Intent> {
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
var showAllowlistRestoreDialog by remember { mutableStateOf(false) }
|
||||||
) { result ->
|
var allowlistRestoreConfirmResult by remember { mutableStateOf<CompletableDeferred<Boolean>?>(null) }
|
||||||
if (result.resultCode == android.app.Activity.RESULT_OK) {
|
var pendingUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
result.data?.data?.let { uri ->
|
|
||||||
scope.launch {
|
// 显示允许列表恢复确认对话框
|
||||||
restoreAllowlist(context, snackBarHost, uri)
|
AllowlistRestoreConfirmationDialog(
|
||||||
|
showDialog = showAllowlistRestoreDialog,
|
||||||
|
onConfirm = {
|
||||||
|
showAllowlistRestoreDialog = false
|
||||||
|
allowlistRestoreConfirmResult?.complete(true)
|
||||||
|
},
|
||||||
|
onDismiss = {
|
||||||
|
showAllowlistRestoreDialog = false
|
||||||
|
allowlistRestoreConfirmResult?.complete(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == android.app.Activity.RESULT_OK) {
|
||||||
|
result.data?.data?.let { uri ->
|
||||||
|
pendingUri = uri
|
||||||
|
scope.launch {
|
||||||
|
val confirmResult = CompletableDeferred<Boolean>()
|
||||||
|
allowlistRestoreConfirmResult = confirmResult
|
||||||
|
|
||||||
|
restoreAllowlist(
|
||||||
|
context = context,
|
||||||
|
snackBarHost = snackBarHost,
|
||||||
|
uri = uri,
|
||||||
|
showConfirmDialog = { show -> showAllowlistRestoreDialog = show },
|
||||||
|
confirmResult = confirmResult
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user