compose: migrate preferences screens
Settings, UI, Installation, Network and Updates preference panes are now native Compose. Replaces the old PreferenceFragment XML hierarchy with explicit ListItem / Switch / dialog rows.
This commit is contained in:
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.preferences
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
|
import com.aurora.store.compose.navigation.Destination
|
||||||
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
import com.aurora.store.data.model.PermissionType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(onNavigateTo: (Destination) -> Unit) {
|
||||||
|
ScreenContent(onNavigateTo = onNavigateTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(onNavigateTo: (Destination) -> Unit = {}) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.title_settings)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
onNavigateTo(
|
||||||
|
Destination.PermissionRationale(PermissionType.entries.toSet())
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_list_check),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.onboarding_title_permissions))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
onNavigateTo(Destination.InstallationPreference)
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_installation),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.title_installation)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { onNavigateTo(Destination.UIPreference) },
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_ui),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_ui_title)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { onNavigateTo(Destination.NetworkPreference) },
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_network),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_network_title)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { onNavigateTo(Destination.UpdatesPreference) },
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_updates),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.title_updates)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewWrapper(ThemePreviewProvider::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SettingsScreenPreview() {
|
||||||
|
ScreenContent()
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.preferences
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLocale
|
||||||
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.aurora.extensions.isTAndAbove
|
||||||
|
import com.aurora.extensions.setAppTheme
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
import com.aurora.store.compose.ui.preferences.network.SingleChoiceDialog
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_THEME_STYLE
|
||||||
|
import com.aurora.store.util.save
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UIPreferenceScreen() {
|
||||||
|
ScreenContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val themeEntries = stringArrayResource(R.array.pref_theme_style)
|
||||||
|
var themeStyle by remember {
|
||||||
|
mutableIntStateOf(Preferences.getInteger(context, PREFERENCE_THEME_STYLE))
|
||||||
|
}
|
||||||
|
val tabEntries = stringArrayResource(R.array.pref_default_tab)
|
||||||
|
var selectedTab by remember {
|
||||||
|
mutableIntStateOf(Preferences.getInteger(context, PREFERENCE_DEFAULT_SELECTED_TAB))
|
||||||
|
}
|
||||||
|
var forYou by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_FOR_YOU, true))
|
||||||
|
}
|
||||||
|
var showThemeDialog by remember { mutableStateOf(false) }
|
||||||
|
var showTabDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showThemeDialog) {
|
||||||
|
SingleChoiceDialog(
|
||||||
|
title = stringResource(R.string.pref_ui_theme),
|
||||||
|
options = themeEntries.toList(),
|
||||||
|
selected = themeStyle,
|
||||||
|
onSelect = { index ->
|
||||||
|
themeStyle = index
|
||||||
|
context.save(PREFERENCE_THEME_STYLE, index)
|
||||||
|
setAppTheme(index)
|
||||||
|
showThemeDialog = false
|
||||||
|
},
|
||||||
|
onDismiss = { showThemeDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showTabDialog) {
|
||||||
|
SingleChoiceDialog(
|
||||||
|
title = stringResource(R.string.pref_ui_layout_tab),
|
||||||
|
options = tabEntries.toList(),
|
||||||
|
selected = selectedTab,
|
||||||
|
onSelect = { index ->
|
||||||
|
selectedTab = index
|
||||||
|
context.save(PREFERENCE_DEFAULT_SELECTED_TAB, index)
|
||||||
|
showTabDialog = false
|
||||||
|
},
|
||||||
|
onDismiss = { showTabDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.pref_ui_title)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
if (isTAndAbove) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
context.startActivity(
|
||||||
|
Intent(Settings.ACTION_APP_LOCALE_SETTINGS).apply {
|
||||||
|
data = ("package:" + context.packageName).toUri()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.app_language)) },
|
||||||
|
supportingContent = { Text(LocalLocale.current.platformLocale.displayName) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(headlineContent = { Text(stringResource(R.string.pref_ui_theme)) })
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showThemeDialog = true },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_ui_theme)) },
|
||||||
|
supportingContent = { Text(themeEntries.getOrElse(themeStyle) { "" }) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(headlineContent = { Text(stringResource(R.string.pref_ui_layout)) })
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showTabDialog = true },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_ui_layout_tab)) },
|
||||||
|
supportingContent = { Text(tabEntries.getOrElse(selectedTab) { "" }) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(headlineContent = { Text(stringResource(R.string.pref_common_extra)) })
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
forYou = !forYou
|
||||||
|
context.save(PREFERENCE_FOR_YOU, forYou)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_ui_no_for_you)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.pref_ui_no_for_you_desc)) },
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = forYou,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
forYou = checked
|
||||||
|
context.save(PREFERENCE_FOR_YOU, checked)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewWrapper(ThemePreviewProvider::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun UIPreferenceScreenPreview() {
|
||||||
|
ScreenContent()
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.preferences.installation
|
||||||
|
|
||||||
|
import android.app.admin.DevicePolicyManager
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
|
import com.aurora.store.compose.navigation.Destination
|
||||||
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE
|
||||||
|
import com.aurora.store.util.save
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InstallationPreferenceScreen(onNavigateTo: (Destination) -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val devicePolicyManager = context.getSystemService<DevicePolicyManager>()
|
||||||
|
val isDeviceOwner = devicePolicyManager?.isDeviceOwnerApp(context.packageName) ?: false
|
||||||
|
|
||||||
|
ScreenContent(
|
||||||
|
onNavigateTo = onNavigateTo,
|
||||||
|
isDeviceOwner = isDeviceOwner,
|
||||||
|
onClearDeviceOwner = {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
devicePolicyManager?.clearDeviceOwnerApp(context.packageName)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(
|
||||||
|
onNavigateTo: (Destination) -> Unit = {},
|
||||||
|
isDeviceOwner: Boolean = false,
|
||||||
|
onClearDeviceOwner: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var autoDelete by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_AUTO_DELETE, true))
|
||||||
|
}
|
||||||
|
var showClearOwnerDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showClearOwnerDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showClearOwnerDialog = false },
|
||||||
|
title = { Text(stringResource(R.string.pref_clear_device_owner_title)) },
|
||||||
|
text = { Text(stringResource(R.string.pref_clear_device_owner_desc)) },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
showClearOwnerDialog = false
|
||||||
|
onClearDeviceOwner()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showClearOwnerDialog = false }) {
|
||||||
|
Text(stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.title_installation)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { onNavigateTo(Destination.Installer) },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_install_mode_title)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.pref_install_mode_summary)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_common_extra)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
autoDelete = !autoDelete
|
||||||
|
context.save(PREFERENCE_AUTO_DELETE, autoDelete)
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_install_delete_title)) },
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.pref_install_delete_summary))
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = autoDelete,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
autoDelete = checked
|
||||||
|
context.save(PREFERENCE_AUTO_DELETE, checked)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isDeviceOwner) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showClearOwnerDialog = true },
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.pref_clear_device_owner_title))
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.pref_clear_device_owner_summary))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewWrapper(ThemePreviewProvider::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun InstallationPreferenceScreenPreview() {
|
||||||
|
ScreenContent()
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ import com.aurora.store.data.model.InstallerInfo
|
|||||||
import com.aurora.store.viewmodel.preferences.InstallerViewModel
|
import com.aurora.store.viewmodel.preferences.InstallerViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InstallerScreen(onNavigateUp: () -> Unit, viewModel: InstallerViewModel = hiltViewModel()) {
|
fun InstallerScreen(viewModel: InstallerViewModel = hiltViewModel()) {
|
||||||
val currentInstallerId by viewModel.currentInstaller.collectAsStateWithLifecycle()
|
val currentInstallerId by viewModel.currentInstaller.collectAsStateWithLifecycle()
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
@@ -46,7 +46,6 @@ fun InstallerScreen(onNavigateUp: () -> Unit, viewModel: InstallerViewModel = hi
|
|||||||
}
|
}
|
||||||
|
|
||||||
ScreenContent(
|
ScreenContent(
|
||||||
onNavigateUp = onNavigateUp,
|
|
||||||
snackBarHostState = snackBarHostState,
|
snackBarHostState = snackBarHostState,
|
||||||
currentInstaller = Installer.entries[currentInstallerId],
|
currentInstaller = Installer.entries[currentInstallerId],
|
||||||
availableInstallers = AppInstaller.getAvailableInstallersInfo(LocalContext.current),
|
availableInstallers = AppInstaller.getAvailableInstallersInfo(LocalContext.current),
|
||||||
@@ -56,7 +55,6 @@ fun InstallerScreen(onNavigateUp: () -> Unit, viewModel: InstallerViewModel = hi
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ScreenContent(
|
private fun ScreenContent(
|
||||||
onNavigateUp: () -> Unit = {},
|
|
||||||
snackBarHostState: SnackbarHostState = SnackbarHostState(),
|
snackBarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
currentInstaller: Installer = Installer.SESSION,
|
currentInstaller: Installer = Installer.SESSION,
|
||||||
availableInstallers: List<InstallerInfo> = emptyList(),
|
availableInstallers: List<InstallerInfo> = emptyList(),
|
||||||
@@ -70,8 +68,7 @@ private fun ScreenContent(
|
|||||||
},
|
},
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = stringResource(R.string.pref_install_mode_title),
|
title = stringResource(R.string.pref_install_mode_title)
|
||||||
onNavigateUp = onNavigateUp
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.preferences.network
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
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.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
|
import com.aurora.store.compose.navigation.Destination
|
||||||
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
import com.aurora.store.compose.ui.commons.ForceRestartDialog
|
||||||
|
import com.aurora.store.util.CommonUtil
|
||||||
|
import com.aurora.store.util.PackageUtil
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_MICROG_AUTH
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_PROXY_INFO
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_PROXY_URL
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_VENDING_VERSION
|
||||||
|
import com.aurora.store.util.remove
|
||||||
|
import com.aurora.store.util.save
|
||||||
|
import com.aurora.store.viewmodel.preferences.ProxyURLViewModel
|
||||||
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NetworkPreferenceScreen(
|
||||||
|
onNavigateTo: (Destination) -> Unit,
|
||||||
|
viewModel: ProxyURLViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val hasMicroG = PackageUtil.hasSupportedMicroGVariant(context)
|
||||||
|
|
||||||
|
ScreenContent(
|
||||||
|
onNavigateTo = onNavigateTo,
|
||||||
|
hasMicroG = hasMicroG,
|
||||||
|
onSaveProxyUrl = { url ->
|
||||||
|
val proxyInfo = CommonUtil.parseProxyUrl(url)
|
||||||
|
if (proxyInfo != null) {
|
||||||
|
context.save(PREFERENCE_PROXY_URL, url)
|
||||||
|
context.save(PREFERENCE_PROXY_INFO, viewModel.json.encodeToString(proxyInfo))
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDeleteProxyUrl = {
|
||||||
|
Preferences.remove(context, PREFERENCE_PROXY_URL)
|
||||||
|
Preferences.remove(context, PREFERENCE_PROXY_INFO)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(
|
||||||
|
onNavigateTo: (Destination) -> Unit = {},
|
||||||
|
hasMicroG: Boolean = false,
|
||||||
|
onSaveProxyUrl: (String) -> Boolean = { false },
|
||||||
|
onDeleteProxyUrl: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val vendingEntries = stringArrayResource(R.array.pref_vending_version)
|
||||||
|
var vendingVersion by remember {
|
||||||
|
mutableIntStateOf(Preferences.getInteger(context, PREFERENCE_VENDING_VERSION))
|
||||||
|
}
|
||||||
|
var microGAuth by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_MICROG_AUTH, true))
|
||||||
|
}
|
||||||
|
var showProxyDialog by remember { mutableStateOf(false) }
|
||||||
|
var showVendingDialog by remember { mutableStateOf(false) }
|
||||||
|
var showForceRestartDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showForceRestartDialog) {
|
||||||
|
ForceRestartDialog(
|
||||||
|
onConfirm = { ProcessPhoenix.triggerRebirth(context) },
|
||||||
|
onDismiss = { showForceRestartDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showProxyDialog) {
|
||||||
|
ProxyURLDialog(
|
||||||
|
currentUrl = Preferences.getString(context, PREFERENCE_PROXY_URL),
|
||||||
|
onSave = { url ->
|
||||||
|
showProxyDialog = false
|
||||||
|
if (onSaveProxyUrl(url)) {
|
||||||
|
showForceRestartDialog = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
showProxyDialog = false
|
||||||
|
onDeleteProxyUrl()
|
||||||
|
showForceRestartDialog = true
|
||||||
|
},
|
||||||
|
onDismiss = { showProxyDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showVendingDialog) {
|
||||||
|
SingleChoiceDialog(
|
||||||
|
title = stringResource(R.string.pref_vending_version_title),
|
||||||
|
options = vendingEntries.toList(),
|
||||||
|
selected = vendingVersion,
|
||||||
|
onSelect = { index ->
|
||||||
|
vendingVersion = index
|
||||||
|
context.save(PREFERENCE_VENDING_VERSION, index)
|
||||||
|
showVendingDialog = false
|
||||||
|
},
|
||||||
|
onDismiss = { showVendingDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.pref_network_title)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { onNavigateTo(Destination.Dispenser) },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_dispenser_title)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.pref_dispenser_summary)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showProxyDialog = true },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_network_proxy_url)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.pref_network_proxy_desc)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(headlineContent = { Text(stringResource(R.string.pref_common_extra)) })
|
||||||
|
}
|
||||||
|
if (hasMicroG) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
microGAuth = !microGAuth
|
||||||
|
context.save(PREFERENCE_MICROG_AUTH, microGAuth)
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.pref_network_microg_login_title))
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.pref_network_microg_login_desc))
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = microGAuth,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
microGAuth = checked
|
||||||
|
context.save(PREFERENCE_MICROG_AUTH, checked)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showVendingDialog = true },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_vending_version_title)) },
|
||||||
|
supportingContent = { Text(vendingEntries.getOrElse(vendingVersion) { "" }) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ProxyURLDialog(
|
||||||
|
currentUrl: String,
|
||||||
|
onSave: (String) -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
var url by remember { mutableStateOf(currentUrl) }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(stringResource(R.string.pref_network_proxy_url)) },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.pref_network_proxy_url_message),
|
||||||
|
modifier = Modifier.padding(bottom = dimensionResource(R.dimen.padding_small))
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = url,
|
||||||
|
onValueChange = { url = it },
|
||||||
|
label = { Text(stringResource(R.string.pref_network_proxy_url_hint)) },
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
enabled = url.isNotBlank(),
|
||||||
|
onClick = { onSave(url.trim()) }
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.set))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Row {
|
||||||
|
if (currentUrl.isNotBlank()) {
|
||||||
|
TextButton(onClick = onDelete) {
|
||||||
|
Text(stringResource(R.string.disable))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(dimensionResource(R.dimen.margin_small)))
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun SingleChoiceDialog(
|
||||||
|
title: String,
|
||||||
|
options: List<String>,
|
||||||
|
selected: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(title) },
|
||||||
|
text = {
|
||||||
|
Column(modifier = Modifier.selectableGroup()) {
|
||||||
|
options.forEachIndexed { index, option ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.selectable(
|
||||||
|
selected = index == selected,
|
||||||
|
onClick = { onSelect(index) },
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
.padding(vertical = dimensionResource(R.dimen.padding_small)),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = index == selected,
|
||||||
|
onClick = null
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(dimensionResource(R.dimen.margin_small)))
|
||||||
|
Text(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewWrapper(ThemePreviewProvider::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun NetworkPreferenceScreenPreview() {
|
||||||
|
ScreenContent(hasMicroG = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.preferences.updates
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
||||||
|
import com.aurora.extensions.isTAndAbove
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composable.TopAppBar
|
||||||
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
|
import com.aurora.store.compose.ui.preferences.network.SingleChoiceDialog
|
||||||
|
import com.aurora.store.data.model.UpdateMode
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_BATTERY
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_IDLE
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS_METERED
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||||
|
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||||
|
import com.aurora.store.util.save
|
||||||
|
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UpdatesPreferenceScreen(viewModel: UpdatesViewModel = hiltViewModel()) {
|
||||||
|
ScreenContent(
|
||||||
|
onCancelAutomatedCheck = { viewModel.updateHelper.cancelAutomatedCheck() },
|
||||||
|
onScheduleAutomatedCheck = { viewModel.updateHelper.scheduleAutomatedCheck() },
|
||||||
|
onUpdateAutomatedCheck = { viewModel.updateHelper.updateAutomatedCheck() },
|
||||||
|
onCheckUpdatesNow = { viewModel.updateHelper.checkUpdatesNow() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(
|
||||||
|
onCancelAutomatedCheck: () -> Unit = {},
|
||||||
|
onScheduleAutomatedCheck: () -> Unit = {},
|
||||||
|
onUpdateAutomatedCheck: () -> Unit = {},
|
||||||
|
onCheckUpdatesNow: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val autoEntries = stringArrayResource(R.array.pref_updates_auto)
|
||||||
|
|
||||||
|
var autoMode by remember {
|
||||||
|
mutableIntStateOf(Preferences.getInteger(context, PREFERENCE_UPDATES_AUTO, 2))
|
||||||
|
}
|
||||||
|
val autoEnabled = autoMode != UpdateMode.DISABLED.ordinal
|
||||||
|
|
||||||
|
var checkInterval by remember {
|
||||||
|
mutableFloatStateOf(
|
||||||
|
Preferences.getInteger(context, PREFERENCE_UPDATES_CHECK_INTERVAL, 3).toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var filterFDroid by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_FILTER_FDROID, true))
|
||||||
|
}
|
||||||
|
var filterAuroraOnly by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_FILTER_AURORA_ONLY))
|
||||||
|
}
|
||||||
|
var updatesExtended by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCE_UPDATES_EXTENDED))
|
||||||
|
}
|
||||||
|
var showAutoDialog by remember { mutableStateOf(false) }
|
||||||
|
var showRestrictionsDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val notifPermissionLauncher = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { granted ->
|
||||||
|
if (granted) {
|
||||||
|
context.save(PREFERENCE_UPDATES_AUTO, UpdateMode.CHECK_AND_NOTIFY.ordinal)
|
||||||
|
autoMode = UpdateMode.CHECK_AND_NOTIFY.ordinal
|
||||||
|
onScheduleAutomatedCheck()
|
||||||
|
} else {
|
||||||
|
autoMode = Preferences.getInteger(context, PREFERENCE_UPDATES_AUTO, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val batteryOptLauncher = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) {
|
||||||
|
if (context.isIgnoringBatteryOptimizations()) {
|
||||||
|
context.save(PREFERENCE_UPDATES_AUTO, UpdateMode.CHECK_AND_INSTALL.ordinal)
|
||||||
|
autoMode = UpdateMode.CHECK_AND_INSTALL.ordinal
|
||||||
|
onScheduleAutomatedCheck()
|
||||||
|
} else {
|
||||||
|
autoMode = Preferences.getInteger(context, PREFERENCE_UPDATES_AUTO, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showAutoDialog) {
|
||||||
|
SingleChoiceDialog(
|
||||||
|
title = stringResource(R.string.pref_updates_auto),
|
||||||
|
options = autoEntries.toList(),
|
||||||
|
selected = autoMode,
|
||||||
|
onSelect = { index ->
|
||||||
|
showAutoDialog = false
|
||||||
|
when (UpdateMode.entries[index]) {
|
||||||
|
UpdateMode.DISABLED -> {
|
||||||
|
context.save(PREFERENCE_UPDATES_AUTO, 0)
|
||||||
|
autoMode = 0
|
||||||
|
onCancelAutomatedCheck()
|
||||||
|
}
|
||||||
|
UpdateMode.CHECK_AND_NOTIFY -> {
|
||||||
|
if (!isTAndAbove ||
|
||||||
|
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) ==
|
||||||
|
android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
context.save(PREFERENCE_UPDATES_AUTO, 1)
|
||||||
|
autoMode = 1
|
||||||
|
onScheduleAutomatedCheck()
|
||||||
|
} else {
|
||||||
|
notifPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateMode.CHECK_AND_INSTALL -> {
|
||||||
|
if (context.isIgnoringBatteryOptimizations()) {
|
||||||
|
context.save(PREFERENCE_UPDATES_AUTO, 2)
|
||||||
|
autoMode = 2
|
||||||
|
onScheduleAutomatedCheck()
|
||||||
|
} else {
|
||||||
|
batteryOptLauncher.launch(
|
||||||
|
Intent(
|
||||||
|
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||||
|
"package:${context.packageName}".toUri()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismiss = { showAutoDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showRestrictionsDialog) {
|
||||||
|
UpdatesRestrictionsDialog(
|
||||||
|
onUpdateAutomatedCheck = onUpdateAutomatedCheck,
|
||||||
|
onDismiss = { showRestrictionsDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = stringResource(R.string.title_updates)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showAutoDialog = true },
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_updates_auto)) },
|
||||||
|
supportingContent = { Text(autoEntries.getOrElse(autoMode) { "" }) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (autoEnabled) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.pref_updates_check_frequency))
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Column {
|
||||||
|
Text(stringResource(R.string.pref_updates_check_frequency_desc))
|
||||||
|
Spacer(Modifier.height(dimensionResource(R.dimen.margin_small)))
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Slider(
|
||||||
|
value = checkInterval,
|
||||||
|
onValueChange = { checkInterval = it },
|
||||||
|
onValueChangeFinished = {
|
||||||
|
context.save(
|
||||||
|
PREFERENCE_UPDATES_CHECK_INTERVAL,
|
||||||
|
checkInterval.roundToInt()
|
||||||
|
)
|
||||||
|
onUpdateAutomatedCheck()
|
||||||
|
},
|
||||||
|
valueRange = 1f..24f,
|
||||||
|
steps = 22,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(dimensionResource(R.dimen.margin_small)))
|
||||||
|
Text(checkInterval.roundToInt().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { showRestrictionsDialog = true },
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.pref_updates_restrictions_title))
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.pref_updates_restrictions_desc))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(headlineContent = {
|
||||||
|
Text(stringResource(R.string.pref_updates_app_source))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
filterFDroid = !filterFDroid
|
||||||
|
context.save(PREFERENCE_FILTER_FDROID, filterFDroid)
|
||||||
|
onCheckUpdatesNow()
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_filter_fdroid_title)) },
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.pref_filter_fdroid_summary))
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = filterFDroid,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
filterFDroid = checked
|
||||||
|
context.save(PREFERENCE_FILTER_FDROID, checked)
|
||||||
|
onCheckUpdatesNow()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
filterAuroraOnly = !filterAuroraOnly
|
||||||
|
context.save(PREFERENCE_FILTER_AURORA_ONLY, filterAuroraOnly)
|
||||||
|
if (filterAuroraOnly) filterFDroid = false
|
||||||
|
onCheckUpdatesNow()
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_aurora_only)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.pref_aurora_only_desc)) },
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = filterAuroraOnly,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
filterAuroraOnly = checked
|
||||||
|
context.save(PREFERENCE_FILTER_AURORA_ONLY, checked)
|
||||||
|
if (checked) filterFDroid = false
|
||||||
|
onCheckUpdatesNow()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item { HorizontalDivider() }
|
||||||
|
item {
|
||||||
|
ListItem(headlineContent = { Text(stringResource(R.string.pref_common_advanced)) })
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
updatesExtended = !updatesExtended
|
||||||
|
context.save(PREFERENCE_UPDATES_EXTENDED, updatesExtended)
|
||||||
|
onCheckUpdatesNow()
|
||||||
|
},
|
||||||
|
headlineContent = { Text(stringResource(R.string.pref_updates_incompatible)) },
|
||||||
|
supportingContent = {
|
||||||
|
Text(stringResource(R.string.pref_updates_incompatible_desc))
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = updatesExtended,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
updatesExtended = checked
|
||||||
|
context.save(PREFERENCE_UPDATES_EXTENDED, checked)
|
||||||
|
onCheckUpdatesNow()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun UpdatesRestrictionsDialog(onUpdateAutomatedCheck: () -> Unit, onDismiss: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var metered by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_METERED, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var idle by remember {
|
||||||
|
mutableStateOf(Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_IDLE, true))
|
||||||
|
}
|
||||||
|
var battery by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
Preferences.getBoolean(context, PREFERENCES_UPDATES_RESTRICTIONS_BATTERY, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose { onUpdateAutomatedCheck() }
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(stringResource(R.string.pref_updates_restrictions_title)) },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(stringResource(R.string.pref_updates_restrictions_desc))
|
||||||
|
Spacer(Modifier.height(dimensionResource(R.dimen.margin_small)))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
metered = !metered
|
||||||
|
Preferences.putBoolean(
|
||||||
|
context,
|
||||||
|
PREFERENCES_UPDATES_RESTRICTIONS_METERED,
|
||||||
|
metered
|
||||||
|
)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = metered,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
metered = checked
|
||||||
|
Preferences.putBoolean(
|
||||||
|
context,
|
||||||
|
PREFERENCES_UPDATES_RESTRICTIONS_METERED,
|
||||||
|
checked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.pref_updates_restrictions_metered))
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
idle = !idle
|
||||||
|
Preferences.putBoolean(
|
||||||
|
context,
|
||||||
|
PREFERENCES_UPDATES_RESTRICTIONS_IDLE,
|
||||||
|
idle
|
||||||
|
)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = idle,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
idle = checked
|
||||||
|
Preferences.putBoolean(
|
||||||
|
context,
|
||||||
|
PREFERENCES_UPDATES_RESTRICTIONS_IDLE,
|
||||||
|
checked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.pref_updates_restrictions_idle))
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
battery = !battery
|
||||||
|
Preferences.putBoolean(
|
||||||
|
context,
|
||||||
|
PREFERENCES_UPDATES_RESTRICTIONS_BATTERY,
|
||||||
|
battery
|
||||||
|
)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = battery,
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
battery = checked
|
||||||
|
Preferences.putBoolean(
|
||||||
|
context,
|
||||||
|
PREFERENCES_UPDATES_RESTRICTIONS_BATTERY,
|
||||||
|
checked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.pref_updates_restrictions_battery))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewWrapper(ThemePreviewProvider::class)
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun UpdatesPreferenceScreenPreview() {
|
||||||
|
ScreenContent()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user