feat: add patches selector bottom sheet (#1360)

This commit is contained in:
Ax333l 2023-10-13 18:11:40 +02:00 committed by GitHub
parent 608bac6854
commit 5762859906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 224 additions and 128 deletions

View File

@ -14,17 +14,17 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FilterChip
@ -32,8 +32,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -79,9 +81,86 @@ fun PatchesSelectorScreen(
) {
val pagerState = rememberPagerState()
val composableScope = rememberCoroutineScope()
var search: String? by rememberSaveable {
mutableStateOf(null)
}
var showBottomSheet by rememberSaveable { mutableStateOf(false) }
val bundles by vm.bundlesFlow.collectAsStateWithLifecycle(initialValue = emptyList())
if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = {
showBottomSheet = false
}
) {
Column(
modifier = Modifier.padding(horizontal = 24.dp)
) {
Text(
text = stringResource(R.string.patches_selector_sheet_filter_title),
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = stringResource(R.string.patches_selector_sheet_filter_compat_title),
style = MaterialTheme.typography.titleMedium
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(5.dp)
) {
FilterChip(
selected = vm.filter and SHOW_SUPPORTED != 0,
onClick = { vm.toggleFlag(SHOW_SUPPORTED) },
label = { Text(stringResource(R.string.supported)) }
)
FilterChip(
selected = vm.filter and SHOW_UNIVERSAL != 0,
onClick = { vm.toggleFlag(SHOW_UNIVERSAL) },
label = { Text(stringResource(R.string.universal)) },
)
FilterChip(
selected = vm.filter and SHOW_UNSUPPORTED != 0,
onClick = { vm.toggleFlag(SHOW_UNSUPPORTED) },
label = { Text(stringResource(R.string.unsupported)) },
)
}
}
Divider()
ListItem(
modifier = Modifier
.fillMaxWidth()
.clickable(
enabled = vm.hasPreviousSelection,
onClick = vm::switchBaseSelectionMode
),
leadingContent = {
Checkbox(
checked = vm.baseSelectionMode == BaseSelectionMode.PREVIOUS,
onCheckedChange = {
vm.switchBaseSelectionMode()
},
enabled = vm.hasPreviousSelection
)
},
headlineContent = {
Text(
"Use previous selection",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
}
)
}
}
if (vm.compatibleVersions.isNotEmpty())
UnsupportedDialog(
appVersion = vm.input.selectedApp.version,
@ -106,6 +185,113 @@ fun PatchesSelectorScreen(
)
}
val allowExperimental by vm.allowExperimental.getAsState()
fun LazyListScope.patchList(
uid: Int,
patches: List<PatchInfo>,
filterFlag: Int,
supported: Boolean,
header: (@Composable () -> Unit)? = null
) {
if (patches.isNotEmpty() && (vm.filter and filterFlag) != 0 || vm.filter == 0) {
header?.let {
item {
it()
}
}
items(
items = patches,
key = { it.name }
) { patch ->
PatchItem(
patch = patch,
onOptionsDialog = {
vm.optionsDialog = uid to patch
},
selected = supported && vm.isSelected(
uid,
patch
),
onToggle = {
if (vm.selectionWarningEnabled) {
vm.pendingSelectionAction = {
vm.togglePatch(uid, patch)
}
} else {
vm.togglePatch(uid, patch)
}
},
supported = supported
)
}
}
}
search?.let { query ->
SearchBar(
query = query,
onQueryChange = { new ->
search = new
},
onSearch = {},
active = true,
onActiveChange = { new ->
if (new) return@SearchBar
search = null
},
placeholder = {
Text(stringResource(R.string.search_patches))
},
leadingIcon = {
IconButton(onClick = { search = null }) {
Icon(
Icons.Default.ArrowBack,
stringResource(R.string.back)
)
}
}
) {
val bundle = bundles[pagerState.currentPage]
LazyColumn(modifier = Modifier.fillMaxSize()) {
fun List<PatchInfo>.searched() = filter {
it.name.contains(query, true)
}
patchList(
uid = bundle.uid,
patches = bundle.supported.searched(),
filterFlag = SHOW_SUPPORTED,
supported = true
)
patchList(
uid = bundle.uid,
patches = bundle.universal.searched(),
filterFlag = SHOW_UNIVERSAL,
supported = true
) {
ListHeader(
title = stringResource(R.string.universal_patches),
)
}
if (!allowExperimental) return@LazyColumn
patchList(
uid = bundle.uid,
patches = bundle.unsupported.searched(),
filterFlag = SHOW_UNSUPPORTED,
supported = true
) {
ListHeader(
title = stringResource(R.string.unsupported_patches),
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
)
}
}
}
}
Scaffold(
topBar = {
AppTopBar(
@ -115,34 +301,14 @@ fun PatchesSelectorScreen(
IconButton(onClick = vm::reset) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
}
var dropdownActive by rememberSaveable {
mutableStateOf(false)
IconButton(onClick = { showBottomSheet = true }) {
Icon(Icons.Outlined.FilterList, stringResource(R.string.more))
}
// This part should probably be changed
IconButton(onClick = { dropdownActive = true }) {
Icon(Icons.Outlined.MoreVert, stringResource(R.string.more))
DropdownMenu(
expanded = dropdownActive,
onDismissRequest = { dropdownActive = false }
) {
DropdownMenuItem(
text = {
val id =
if (vm.baseSelectionMode == BaseSelectionMode.DEFAULT)
R.string.menu_opt_selection_mode_previous else R.string.menu_opt_selection_mode_default
Text(stringResource(id))
},
onClick = {
dropdownActive = false
vm.switchBaseSelectionMode()
},
enabled = vm.hasPreviousSelection
)
IconButton(
onClick = {
search = ""
}
}
IconButton(onClick = { }) {
) {
Icon(Icons.Outlined.Search, stringResource(R.string.search))
}
}
@ -198,107 +364,35 @@ fun PatchesSelectorScreen(
pageContent = { index ->
val bundle = bundles[index]
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp, vertical = 2.dp),
horizontalArrangement = Arrangement.spacedBy(5.dp)
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
patchList(
uid = bundle.uid,
patches = bundle.supported,
filterFlag = SHOW_SUPPORTED,
supported = true
)
patchList(
uid = bundle.uid,
patches = bundle.universal,
filterFlag = SHOW_UNIVERSAL,
supported = true
) {
FilterChip(
selected = vm.filter and SHOW_SUPPORTED != 0 && bundle.supported.isNotEmpty(),
onClick = { vm.toggleFlag(SHOW_SUPPORTED) },
label = { Text(stringResource(R.string.supported)) },
enabled = bundle.supported.isNotEmpty()
)
FilterChip(
selected = vm.filter and SHOW_UNIVERSAL != 0 && bundle.universal.isNotEmpty(),
onClick = { vm.toggleFlag(SHOW_UNIVERSAL) },
label = { Text(stringResource(R.string.universal)) },
enabled = bundle.universal.isNotEmpty()
)
FilterChip(
selected = vm.filter and SHOW_UNSUPPORTED != 0 && bundle.unsupported.isNotEmpty(),
onClick = { vm.toggleFlag(SHOW_UNSUPPORTED) },
label = { Text(stringResource(R.string.unsupported)) },
enabled = bundle.unsupported.isNotEmpty()
ListHeader(
title = stringResource(R.string.universal_patches),
)
}
val allowExperimental by vm.allowExperimental.getAsState()
LazyColumn(
modifier = Modifier.fillMaxSize()
patchList(
uid = bundle.uid,
patches = bundle.unsupported,
filterFlag = SHOW_UNSUPPORTED,
supported = allowExperimental
) {
fun LazyListScope.patchList(
patches: List<PatchInfo>,
filterFlag: Int,
supported: Boolean,
header: (@Composable () -> Unit)? = null
) {
if (patches.isNotEmpty() && (vm.filter and filterFlag) != 0 || vm.filter == 0) {
header?.let {
item {
it()
}
}
items(
items = patches,
key = { it.name }
) { patch ->
PatchItem(
patch = patch,
onOptionsDialog = {
vm.optionsDialog = bundle.uid to patch
},
selected = supported && vm.isSelected(
bundle.uid,
patch
),
onToggle = {
if (vm.selectionWarningEnabled) {
vm.pendingSelectionAction = {
vm.togglePatch(bundle.uid, patch)
}
} else {
vm.togglePatch(bundle.uid, patch)
}
},
supported = supported
)
}
}
}
patchList(
patches = bundle.supported,
filterFlag = SHOW_SUPPORTED,
supported = true
ListHeader(
title = stringResource(R.string.unsupported_patches),
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
)
patchList(
patches = bundle.universal,
filterFlag = SHOW_UNIVERSAL,
supported = true
) {
ListHeader(
title = stringResource(R.string.universal_patches),
onHelpClick = { }
)
}
patchList(
patches = bundle.unsupported,
filterFlag = SHOW_UNSUPPORTED,
supported = allowExperimental
) {
ListHeader(
title = stringResource(R.string.unsupported_patches),
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
)
}
}
}
}
@ -400,7 +494,7 @@ fun PatchItem(
leadingContent = {
Checkbox(
checked = selected,
onCheckedChange = null,
onCheckedChange = { onToggle() },
enabled = supported
)
},

View File

@ -143,8 +143,6 @@
<string name="unsupported_app">Unsupported app</string>
<string name="unsupported_patches">Unsupported patches</string>
<string name="universal_patches">Universal patches</string>
<string name="menu_opt_selection_mode_default">Use default selection</string>
<string name="menu_opt_selection_mode_previous">Use previous selection</string>
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
<string name="selection_warning_title">Stop using defaults?</string>
<string name="selection_warning_description">You may encounter issues when not using the default patch selection and options.</string>
@ -152,6 +150,7 @@
<string name="supported">Supported</string>
<string name="universal">Universal</string>
<string name="unsupported">Unsupported</string>
<string name="search_patches">Patch name</string>
<string name="app_not_supported">Some of the patches do not support this app version (%1$s). The patches only support the following version(s): %2$s.</string>
<string name="continue_with_version">Continue with this version?</string>
<string name="version_not_supported">Not all patches support this version (%s). Do you want to continue anyway?</string>
@ -188,6 +187,9 @@
<string name="downloadable_versions">Downloadable versions</string>
<string name="already_patched">Already patched</string>
<string name="patches_selector_sheet_filter_title">Filter</string>
<string name="patches_selector_sheet_filter_compat_title">Compatibility</string>
<string name="string_option_icon_description">Edit</string>
<string name="string_option_menu_description">More options</string>
<string name="string_option_placeholder">Value</string>