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.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons 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.filled.Build
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.HelpOutline 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.Restore
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.Divider
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
@ -32,8 +32,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -79,9 +81,86 @@ fun PatchesSelectorScreen(
) { ) {
val pagerState = rememberPagerState() val pagerState = rememberPagerState()
val composableScope = rememberCoroutineScope() val composableScope = rememberCoroutineScope()
var search: String? by rememberSaveable {
mutableStateOf(null)
}
var showBottomSheet by rememberSaveable { mutableStateOf(false) }
val bundles by vm.bundlesFlow.collectAsStateWithLifecycle(initialValue = emptyList()) 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()) if (vm.compatibleVersions.isNotEmpty())
UnsupportedDialog( UnsupportedDialog(
appVersion = vm.input.selectedApp.version, 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( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
@ -115,34 +301,14 @@ fun PatchesSelectorScreen(
IconButton(onClick = vm::reset) { IconButton(onClick = vm::reset) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset)) Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
} }
IconButton(onClick = { showBottomSheet = true }) {
var dropdownActive by rememberSaveable { Icon(Icons.Outlined.FilterList, stringResource(R.string.more))
mutableStateOf(false)
} }
// This part should probably be changed IconButton(
IconButton(onClick = { dropdownActive = true }) { onClick = {
Icon(Icons.Outlined.MoreVert, stringResource(R.string.more)) search = ""
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 = { }) {
Icon(Icons.Outlined.Search, stringResource(R.string.search)) Icon(Icons.Outlined.Search, stringResource(R.string.search))
} }
} }
@ -198,107 +364,35 @@ fun PatchesSelectorScreen(
pageContent = { index -> pageContent = { index ->
val bundle = bundles[index] val bundle = bundles[index]
Column { LazyColumn(
modifier = Modifier.fillMaxSize()
Row( ) {
modifier = Modifier patchList(
.fillMaxWidth() uid = bundle.uid,
.padding(horizontal = 10.dp, vertical = 2.dp), patches = bundle.supported,
horizontalArrangement = Arrangement.spacedBy(5.dp) filterFlag = SHOW_SUPPORTED,
supported = true
)
patchList(
uid = bundle.uid,
patches = bundle.universal,
filterFlag = SHOW_UNIVERSAL,
supported = true
) { ) {
FilterChip( ListHeader(
selected = vm.filter and SHOW_SUPPORTED != 0 && bundle.supported.isNotEmpty(), title = stringResource(R.string.universal_patches),
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()
) )
} }
patchList(
val allowExperimental by vm.allowExperimental.getAsState() uid = bundle.uid,
patches = bundle.unsupported,
LazyColumn( filterFlag = SHOW_UNSUPPORTED,
modifier = Modifier.fillMaxSize() supported = allowExperimental
) { ) {
fun LazyListScope.patchList( ListHeader(
patches: List<PatchInfo>, title = stringResource(R.string.unsupported_patches),
filterFlag: Int, onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
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
) )
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 = { leadingContent = {
Checkbox( Checkbox(
checked = selected, checked = selected,
onCheckedChange = null, onCheckedChange = { onToggle() },
enabled = supported enabled = supported
) )
}, },

View File

@ -143,8 +143,6 @@
<string name="unsupported_app">Unsupported app</string> <string name="unsupported_app">Unsupported app</string>
<string name="unsupported_patches">Unsupported patches</string> <string name="unsupported_patches">Unsupported patches</string>
<string name="universal_patches">Universal 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="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_title">Stop using defaults?</string>
<string name="selection_warning_description">You may encounter issues when not using the default patch selection and options.</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="supported">Supported</string>
<string name="universal">Universal</string> <string name="universal">Universal</string>
<string name="unsupported">Unsupported</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="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="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> <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="downloadable_versions">Downloadable versions</string>
<string name="already_patched">Already patched</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_icon_description">Edit</string>
<string name="string_option_menu_description">More options</string> <string name="string_option_menu_description">More options</string>
<string name="string_option_placeholder">Value</string> <string name="string_option_placeholder">Value</string>