Settings facelift

This commit is contained in:
MM20 2025-06-03 00:12:07 +02:00
parent fcc111b048
commit b6d533466b
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
29 changed files with 1138 additions and 1026 deletions

View File

@ -10,7 +10,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
fun CheckboxPreference(
title: String,
icon: ImageVector? = null,
iconPadding: Boolean = true,
iconPadding: Boolean = icon != null,
summary: String? = null,
value: Boolean,
onValueChanged: (Boolean) -> Unit,

View File

@ -0,0 +1,54 @@
package de.mm20.launcher2.ui.component.preferences
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
@Composable
fun GuardedPreference(
locked: Boolean,
onUnlock: (() -> Unit)? = null,
description: String,
icon: ImageVector = Icons.Rounded.Lock,
unlockLabel: String = stringResource(R.string.grant_permission),
preference: @Composable () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.surface)
) {
if (locked) {
Banner(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
icon = icon,
text = description,
primaryAction = if (onUnlock != null) {
{
Button(onClick = onUnlock) {
Text(text = unlockLabel)
}
}
} else null
)
}
preference()
}
}

View File

@ -17,7 +17,7 @@ import androidx.compose.ui.window.Dialog
fun <T> ListPreference(
title: String,
icon: ImageVector? = null,
iconPadding: Boolean = true,
iconPadding: Boolean = icon != null,
items: List<ListPreferenceItem<T>>,
value: T,
summary: String? = items.firstOrNull { value == it.value }?.label,

View File

@ -1,15 +1,21 @@
package de.mm20.launcher2.ui.component.preferences
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
@ -20,22 +26,28 @@ fun Preference(
icon: @Composable (() -> Unit)? = null,
onClick: () -> Unit = {},
controls: @Composable (() -> Unit)? = null,
enabled: Boolean = true
enabled: Boolean = true,
containerColor: Color = MaterialTheme.colorScheme.surface,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.clickable(enabled = enabled, onClick = onClick)
.padding(horizontal = 16.dp)
.background(containerColor)
.padding(
start = if (icon != null) 8.dp else 16.dp,
end = 16.dp,
)
.alpha(if (enabled) 1f else 0.38f),
) {
if (icon != null) {
Box(
modifier = Modifier
.width(56.dp)
.padding(start = 4.dp),
contentAlignment = Alignment.CenterStart
.padding(end = 8.dp),
contentAlignment = Alignment.Center
) {
icon()
}
@ -45,14 +57,16 @@ fun Preference(
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 16.dp)
.padding(vertical = 16.dp),
) {
ProvideTextStyle(value = MaterialTheme.typography.titleMedium) {
title()
}
if (summary != null) {
Spacer(modifier = Modifier.height(2.dp))
ProvideTextStyle(value = MaterialTheme.typography.bodyMedium) {
CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.bodyMedium,
LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant,
) {
summary()
}
}
@ -75,7 +89,8 @@ fun Preference(
summary: String? = null,
onClick: () -> Unit = {},
controls: @Composable (() -> Unit)? = null,
enabled: Boolean = true
enabled: Boolean = true,
containerColor: Color = MaterialTheme.colorScheme.surface,
) {
Preference(
title = {
@ -89,7 +104,8 @@ fun Preference(
icon = if (iconPadding) icon else null,
onClick = onClick,
controls = controls,
enabled = enabled
enabled = enabled,
containerColor = containerColor,
)
}
@ -98,11 +114,12 @@ fun Preference(
fun Preference(
title: String,
icon: ImageVector? = null,
iconPadding: Boolean = true,
iconPadding: Boolean = icon != null,
summary: String? = null,
onClick: () -> Unit = {},
controls: @Composable (() -> Unit)? = null,
enabled: Boolean = true
enabled: Boolean = true,
containerColor: Color = MaterialTheme.colorScheme.surface,
) {
Preference(
title,
@ -114,6 +131,6 @@ fun Preference(
tint = MaterialTheme.colorScheme.primary,
)
}
}, iconPadding, summary, onClick, controls, enabled
}, iconPadding, summary, onClick, controls, enabled, containerColor,
)
}

View File

@ -6,6 +6,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@ -15,25 +16,27 @@ fun PreferenceCategory(
iconPadding: Boolean = true,
content: @Composable ColumnScope.() -> Unit
) {
Column {
Column(
modifier = Modifier.fillMaxWidth(),
) {
if (title != null) {
Row(
modifier = Modifier
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
.padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 8.dp)
) {
Text(
modifier = Modifier.padding(start = if (iconPadding) 56.dp else 0.dp),
text = title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary
color = MaterialTheme.colorScheme.secondary
)
}
}
Column(
modifier = Modifier
.clip(MaterialTheme.shapes.medium),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
content()
Box(
modifier = Modifier.fillMaxWidth().height(0.5.dp).background(
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
)
)
}
}
}

View File

@ -1,12 +1,13 @@
package de.mm20.launcher2.ui.component.preferences
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.compose.LocalActivity
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -16,14 +17,15 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.HelpOutline
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -31,18 +33,18 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import androidx.core.net.toUri
import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.R
@Composable
@ -102,7 +104,9 @@ fun PreferenceScreen(
}
}
val activity = LocalContext.current as? AppCompatActivity
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val activity = LocalActivity.current
Scaffold(
floatingActionButton = {
AnimatedVisibility(
@ -116,13 +120,20 @@ fun PreferenceScreen(
topBar = {
CenterAlignedTopAppBar(
title = title,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
),
navigationIcon = {
IconButton(onClick = {
if (navController?.navigateUp() != true) {
activity?.onBackPressed()
}
}) {
Icon(imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = "Back"
)
}
},
actions = {
@ -135,25 +146,32 @@ fun PreferenceScreen(
.setSecondaryToolbarColor(colorScheme.secondaryContainer.toArgb())
.build()
)
.build().launchUrl(context, Uri.parse(helpUrl))
.build().launchUrl(context, helpUrl.toUri())
}) {
Icon(
imageVector = Icons.Rounded.HelpOutline,
contentDescription = "Help"
contentDescription = stringResource(R.string.help)
)
}
}
topBarActions()
}
},
scrollBehavior = scrollBehavior,
)
}) {
},
containerColor = MaterialTheme.colorScheme.surfaceContainer
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.padding(it),
state = lazyColumnState,
content = content,
verticalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(
12.dp
)
)
}

View File

@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.component.preferences
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -20,10 +21,11 @@ fun PreferenceWithSwitch(
onClick: () -> Unit = {},
switchValue: Boolean,
onSwitchChanged: (Boolean) -> Unit,
iconPadding: Boolean = true
iconPadding: Boolean = icon != null
) {
Row(
verticalAlignment = (Alignment.CenterVertically)
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.extraSmall)
) {
Box(
modifier = Modifier.weight(1f)

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.component.preferences
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Slider
import androidx.compose.material3.Icon
@ -10,6 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import java.text.DecimalFormat
import kotlin.math.floor
@ -20,6 +22,7 @@ import kotlin.math.roundToInt
fun SliderPreference(
title: String,
icon: ImageVector? = null,
iconPadding: Boolean = icon != null,
value: Float,
min: Float = 0f,
max: Float = 1f,
@ -33,14 +36,19 @@ fun SliderPreference(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
.background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.extraSmall)
.padding(
start = if (icon != null || iconPadding) 8.dp else 16.dp,
end = 16.dp,
)
.alpha(if (enabled) 1f else 0.38f),
) {
if (icon != null || iconPadding) {
Box(
modifier = Modifier
.width(54.dp)
.padding(start = 4.dp),
contentAlignment = Alignment.CenterStart
.width(56.dp)
.padding(end = 8.dp),
contentAlignment = Alignment.Center
) {
if (icon != null) {
Icon(
@ -50,8 +58,10 @@ fun SliderPreference(
)
}
}
}
Column(
modifier = Modifier.weight(1f)
.padding(vertical = 16.dp)
) {
Text(
modifier = Modifier.padding(start = 2.dp),
@ -71,7 +81,7 @@ fun SliderPreference(
steps = step?.let { ((max - min) / it).toInt() - 1 } ?: 0,
onValueChangeFinished = {
onValueChanged(sliderValue)
}
},
)
if (label != null) {
label(sliderValue)
@ -84,7 +94,8 @@ fun SliderPreference(
Text(
modifier = Modifier.width(56.dp).padding(start = 24.dp),
text = format.format(sliderValue),
style = MaterialTheme.typography.titleSmall
style = MaterialTheme.typography.titleSmall,
textAlign = TextAlign.Center,
)
}
}

View File

@ -1,18 +1,21 @@
package de.mm20.launcher2.ui.component.preferences
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@Composable
fun SwitchPreference(
title: String,
icon: ImageVector? = null,
iconPadding: Boolean = true,
iconPadding: Boolean = icon != null,
summary: String? = null,
value: Boolean,
onValueChanged: (Boolean) -> Unit,
enabled: Boolean = true
enabled: Boolean = true,
containerColor: Color = MaterialTheme.colorScheme.surface,
) {
Preference(
title = title,
@ -27,6 +30,7 @@ fun SwitchPreference(
Switch(
enabled = enabled, checked = value, onCheckedChange = onValueChanged,
)
}
},
containerColor = containerColor,
)
}

View File

@ -11,6 +11,7 @@ import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.CompositionLocalProvider
@ -132,7 +133,9 @@ class SettingsActivity : BaseActivity() {
)
)
}
OverlayHost {
OverlayHost(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceContainer)
) {
NavHost(
modifier = Modifier.fillMaxSize(),
navController = navController,

View File

@ -21,6 +21,7 @@ fun BuildInfoSettingsScreen() {
val buildFeatures by viewModel.buildFeatures.collectAsState(emptyMap())
PreferenceScreen(title = stringResource(R.string.preference_screen_buildinfo)) {
item {
PreferenceCategory {
Preference(title = "Build type", summary = BuildConfig.BUILD_TYPE)
var buildSignature by remember { mutableStateOf<String?>(null) }
LaunchedEffect(null) {
@ -46,6 +47,7 @@ fun BuildInfoSettingsScreen() {
}
Preference(title = "Signature hash", summary = buildSignature)
}
}
item {
PreferenceCategory(title = "Features") {
for (feature in buildFeatures) {

View File

@ -6,8 +6,6 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ErrorOutline
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -22,8 +20,8 @@ import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
@ -47,15 +45,13 @@ fun CalendarSearchSettingsScreen() {
PreferenceScreen(title = stringResource(R.string.preference_search_calendar)) {
item {
PreferenceCategory {
AnimatedVisibility(hasCalendarPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search_settings),
onClick = {
GuardedPreference(
locked = hasCalendarPermission == false,
onUnlock = {
viewModel.requestCalendarPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(R.string.missing_permission_calendar_search_settings),
) {
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_local_calendar_summary),
@ -68,16 +64,15 @@ fun CalendarSearchSettingsScreen() {
navController?.navigate("settings/search/calendar/local")
}
)
}
if (isTasksAppInstalled) {
AnimatedVisibility(hasTasksPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_tasks_search_settings),
onClick = {
GuardedPreference(
locked = hasTasksPermission == false,
onUnlock = {
viewModel.requestTasksPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(R.string.missing_permission_tasks_search_settings),
) {
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_tasks),
summary = stringResource(R.string.preference_search_tasks_summary),
@ -91,27 +86,25 @@ fun CalendarSearchSettingsScreen() {
}
)
}
}
for (plugin in plugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.SetupRequired,
onUnlock = {
try {
state.setupActivity.sendWithBackgroundPermission(context)
(state as PluginState.SetupRequired).setupActivity.sendWithBackgroundPermission(
context
)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(id = R.string.plugin_action_setup))
}
}
)
}
},
description = (state as? PluginState.SetupRequired)?.message
?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
unlockLabel = stringResource(id = R.string.plugin_action_setup),
) {
PreferenceWithSwitch(
title = plugin.plugin.label,
enabled = state is PluginState.Ready,
@ -131,3 +124,4 @@ fun CalendarSearchSettingsScreen() {
}
}
}
}

View File

@ -1,6 +1,8 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
@ -10,10 +12,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.DarkMode
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SegmentedButton
@ -23,6 +22,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@ -35,12 +35,13 @@ fun ColorSchemePreferenceCategory(
preview: @Composable RowScope.() -> Unit
) {
Column(
modifier = Modifier.padding(top = 16.dp)
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
.padding(start = 16.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
@ -66,21 +67,22 @@ fun ColorSchemePreferenceCategory(
}
}
}
MaterialTheme(
colorScheme = previewColorScheme
) {
Card(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
)
.clip(MaterialTheme.shapes.medium),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
MaterialTheme(
colorScheme = previewColorScheme
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceContainerLowest, MaterialTheme.shapes.extraSmall)
.horizontalScroll(rememberScrollState())
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
@ -88,10 +90,8 @@ fun ColorSchemePreferenceCategory(
preview()
}
}
}
colorPreferences()
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
}

View File

@ -66,6 +66,7 @@ import de.mm20.launcher2.themes.merge
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
@ -142,17 +143,9 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
if (previewDarkTheme) DefaultDarkColorScheme else DefaultLightColorScheme
item {
Column(
modifier = Modifier
PreferenceCategory(
title = stringResource(R.string.preference_custom_colors_corepalette),
) {
Text(
stringResource(R.string.preference_custom_colors_corepalette),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
CorePaletteColorPreference(
title = "Primary",
value = theme?.corePalette?.primary,
@ -267,7 +260,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
}
},
)
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
item {
@ -681,7 +673,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceDim,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface",

View File

@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
@ -28,6 +29,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
@ -56,10 +58,12 @@ fun CorePaletteColorPreference(
Row(
modifier = modifier.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.clickable(
onClick = { showDialog = true },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ColorSwatch(

View File

@ -80,10 +80,12 @@ fun ThemeColorPreference(
Row(
modifier = modifier.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.clickable(
onClick = { showDialog = true },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ColorSwatch(

View File

@ -26,9 +26,9 @@ import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@Composable
@ -38,7 +38,10 @@ fun ContactsSettingsScreen() {
val hasContactsPermission by viewModel.hasContactsPermission.collectAsStateWithLifecycle(null)
val hasCallPermission by viewModel.hasCallPermission.collectAsStateWithLifecycle(null)
val plugins by viewModel.availablePlugins.collectAsStateWithLifecycle(emptyList(), minActiveState = Lifecycle.State.RESUMED)
val plugins by viewModel.availablePlugins.collectAsStateWithLifecycle(
emptyList(),
minActiveState = Lifecycle.State.RESUMED
)
val enabledProviders by viewModel.enabledProviders.collectAsState(emptySet())
val callOnTap by viewModel.callOnTap.collectAsStateWithLifecycle(null)
@ -56,6 +59,13 @@ fun ContactsSettingsScreen() {
modifier = Modifier.padding(16.dp)
)
}
GuardedPreference(
locked = hasContactsPermission == false,
onUnlock = {
viewModel.requestContactsPermission(context as AppCompatActivity)
},
description = stringResource(R.string.missing_permission_contact_search_settings),
) {
SwitchPreference(
title = stringResource(R.string.preference_search_contacts),
summary = stringResource(R.string.preference_search_contacts_summary),
@ -65,27 +75,25 @@ fun ContactsSettingsScreen() {
viewModel.setProviderEnabled("local", it)
}
)
}
for (plugin in plugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.SetupRequired,
onUnlock = {
try {
state.setupActivity.sendWithBackgroundPermission(context)
(state as PluginState.SetupRequired).setupActivity.sendWithBackgroundPermission(
context
)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(id = R.string.plugin_action_setup))
}
}
)
}
},
description = (state as? PluginState.SetupRequired)?.message
?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
unlockLabel = stringResource(id = R.string.plugin_action_setup),
) {
SwitchPreference(
title = plugin.plugin.label,
enabled = state is PluginState.Ready,
@ -99,16 +107,17 @@ fun ContactsSettingsScreen() {
)
}
}
}
}
item {
PreferenceCategory {
AnimatedVisibility(hasCallPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_call_contacts_settings),
onClick = {
GuardedPreference(
locked = hasCallPermission == false,
onUnlock = {
viewModel.requestCallPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(R.string.missing_permission_call_contacts_settings),
) {
SwitchPreference(
title = stringResource(R.string.preference_contacts_call_on_tap),
summary = stringResource(R.string.preference_contacts_call_on_tap_summary),
@ -122,5 +131,6 @@ fun ContactsSettingsScreen() {
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.settings.filesearch
import android.app.PendingIntent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@ -31,7 +30,7 @@ import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@ -67,16 +66,15 @@ fun FileSearchSettingsScreen() {
PreferenceCategory {
val localFiles by viewModel.localFiles.collectAsState()
val hasFilePermission by viewModel.hasFilePermission.collectAsState()
AnimatedVisibility(hasFilePermission == false) {
MissingPermissionBanner(
text = stringResource(
if (isAtLeastApiLevel(29)) R.string.missing_permission_file_search_settings_android10 else R.string.missing_permission_file_search_settings
), onClick = {
GuardedPreference(
locked = hasFilePermission == false,
onUnlock = {
viewModel.requestFilePermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(
if (isAtLeastApiLevel(29)) R.string.missing_permission_file_search_settings_android10 else R.string.missing_permission_file_search_settings
),
) {
SwitchPreference(
title = stringResource(R.string.preference_search_localfiles),
summary = stringResource(R.string.preference_search_localfiles_summary),
@ -86,28 +84,19 @@ fun FileSearchSettingsScreen() {
},
enabled = hasFilePermission == true
)
}
val nextcloud by viewModel.nextcloud.collectAsState()
val nextcloudAccount by viewModel.nextcloudAccount
AnimatedVisibility(nextcloudAccount == null) {
Banner(
text = stringResource(R.string.no_account_nextcloud),
icon = Icons.Rounded.AccountBox,
primaryAction = {
TextButton(onClick = {
viewModel.login(
context as AppCompatActivity,
AccountType.Nextcloud
)
}) {
Text(
stringResource(R.string.connect_account),
)
}
GuardedPreference(
locked = nextcloudAccount == null,
onUnlock = {
viewModel.login(context as AppCompatActivity, AccountType.Nextcloud)
},
modifier = Modifier.padding(16.dp)
)
}
icon = Icons.Rounded.AccountBox,
description = stringResource(R.string.no_account_nextcloud),
unlockLabel = stringResource(R.string.connect_account),
) {
SwitchPreference(
title = stringResource(R.string.preference_search_nextcloud),
summary = nextcloudAccount?.let {
@ -119,28 +108,19 @@ fun FileSearchSettingsScreen() {
},
enabled = nextcloudAccount != null
)
}
val owncloud by viewModel.owncloud.collectAsState()
val owncloudAccount by viewModel.owncloudAccount
AnimatedVisibility(owncloudAccount == null) {
Banner(
text = stringResource(R.string.no_account_owncloud),
icon = Icons.Rounded.AccountBox,
primaryAction = {
TextButton(onClick = {
viewModel.login(
context as AppCompatActivity,
AccountType.Owncloud
)
}) {
Text(
stringResource(R.string.connect_account),
)
}
GuardedPreference(
locked = owncloudAccount == null,
onUnlock = {
viewModel.login(context as AppCompatActivity, AccountType.Owncloud)
},
modifier = Modifier.padding(16.dp)
)
}
icon = Icons.Rounded.AccountBox,
description = stringResource(R.string.no_account_owncloud),
unlockLabel = stringResource(R.string.connect_account),
) {
SwitchPreference(
title = stringResource(R.string.preference_search_owncloud),
summary = owncloudAccount?.let {
@ -152,27 +132,26 @@ fun FileSearchSettingsScreen() {
},
enabled = owncloudAccount != null
)
}
for (plugin in plugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message ?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.SetupRequired,
onUnlock = {
try {
state.setupActivity.sendWithBackgroundPermission(context)
(state as PluginState.SetupRequired).setupActivity.sendWithBackgroundPermission(
context
)
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(id = R.string.plugin_action_setup))
}
}
)
}
},
description = (state as? PluginState.SetupRequired)?.message
?: stringResource(id = R.string.plugin_state_setup_required),
icon = Icons.Rounded.ErrorOutline,
unlockLabel = stringResource(id = R.string.plugin_action_setup),
) {
SwitchPreference(
title = plugin.plugin.label,
enabled = enabledPlugins != null && state is PluginState.Ready,
@ -189,3 +168,4 @@ fun FileSearchSettingsScreen() {
}
}
}
}

View File

@ -41,6 +41,7 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.SearchablePicker
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.ListPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
@ -72,17 +73,16 @@ fun GestureSettingsScreen() {
val swipeDown by viewModel.swipeDown.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeDown)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
val swipeDownApp by viewModel.swipeDownApp.collectAsState(null)
val swipeDownAppIcon by remember(swipeDownApp?.key) {
viewModel.getIcon(swipeDownApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeDown),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onUnlock = { viewModel.requestPermission(context as AppCompatActivity) },
) {
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_down),
icon = Icons.Rounded.SwipeDownAlt,
@ -93,19 +93,18 @@ fun GestureSettingsScreen() {
appIcon = swipeDownAppIcon,
onAppChanged = { viewModel.setSwipeDownApp(it) }
)
}
val swipeLeft by viewModel.swipeLeft.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeLeft)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
val swipeLeftApp by viewModel.swipeLeftApp.collectAsState(null)
val swipeLeftAppIcon by remember(swipeLeftApp?.key) {
viewModel.getIcon(swipeLeftApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeLeft),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onUnlock = { viewModel.requestPermission(context as AppCompatActivity) },
) {
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_left),
icon = Icons.Rounded.SwipeLeftAlt,
@ -116,19 +115,18 @@ fun GestureSettingsScreen() {
appIcon = swipeLeftAppIcon,
onAppChanged = { viewModel.setSwipeLeftApp(it) }
)
}
val swipeRight by viewModel.swipeRight.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeRight)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
val swipeRightApp by viewModel.swipeRightApp.collectAsState(null)
val swipeRightAppIcon by remember(swipeRightApp?.key) {
viewModel.getIcon(swipeRightApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeRight),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onUnlock = { viewModel.requestPermission(context as AppCompatActivity) },
) {
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_right),
icon = Icons.Rounded.SwipeRightAlt,
@ -139,19 +137,18 @@ fun GestureSettingsScreen() {
appIcon = swipeRightAppIcon,
onAppChanged = { viewModel.setSwipeRightApp(it) }
)
}
val swipeUp by viewModel.swipeUp.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeUp)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
val swipeUpApp by viewModel.swipeUpApp.collectAsState(null)
val swipeUpAppIcon by remember(swipeUpApp?.key) {
viewModel.getIcon(swipeUpApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(swipeUp),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onUnlock = { viewModel.requestPermission(context as AppCompatActivity) },
) {
GesturePreference(
title = stringResource(R.string.preference_gesture_swipe_up),
icon = Icons.Rounded.SwipeUpAlt,
@ -162,19 +159,18 @@ fun GestureSettingsScreen() {
appIcon = swipeUpAppIcon,
onAppChanged = { viewModel.setSwipeUpApp(it) }
)
}
val doubleTap by viewModel.doubleTap.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(doubleTap)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
val doubleTapApp by viewModel.doubleTapApp.collectAsState(null)
val doubleTapAppIcon by remember(doubleTapApp?.key) {
viewModel.getIcon(doubleTapApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(doubleTap),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onUnlock = { viewModel.requestPermission(context as AppCompatActivity) },
) {
GesturePreference(
title = stringResource(R.string.preference_gesture_double_tap),
icon = Icons.Rounded.Adjust,
@ -185,19 +181,18 @@ fun GestureSettingsScreen() {
appIcon = doubleTapAppIcon,
onAppChanged = { viewModel.setDoubleTapApp(it) }
)
}
val longPress by viewModel.longPress.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(longPress)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
)
}
val longPressApp by viewModel.longPressApp.collectAsState(null)
val longPressAppIcon by remember(longPressApp?.key) {
viewModel.getIcon(longPressApp, appIconSize.toInt())
}.collectAsState(null)
GuardedPreference(
locked = hasPermission == false && requiresAccessibilityService(longPress),
description = stringResource(R.string.missing_permission_accessibility_gesture_settings),
onUnlock = { viewModel.requestPermission(context as AppCompatActivity) },
) {
GesturePreference(
title = stringResource(R.string.preference_gesture_long_press),
icon = Icons.Rounded.Circle,
@ -212,6 +207,7 @@ fun GestureSettingsScreen() {
}
}
}
}
fun requiresAccessibilityService(action: GestureAction?): Boolean {
return when (action) {

View File

@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.settings.icons
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@ -144,16 +143,13 @@ fun IconsSettingsScreen() {
item {
PreferenceCategory(stringResource(R.string.preference_category_icons)) {
if (previewIcons.value.isNotEmpty()) {
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceContainer,
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
) {
Row(
modifier = Modifier.padding(vertical = 24.dp, horizontal = 8.dp)
modifier = Modifier
.background(
MaterialTheme.colorScheme.surfaceContainerLowest,
MaterialTheme.shapes.extraSmall
)
.padding(vertical = 24.dp, horizontal = 8.dp)
) {
for (icon in previewIcons.value) {
Box(
@ -165,7 +161,6 @@ fun IconsSettingsScreen() {
}
}
}
}
IconShapePreference(
title = stringResource(R.string.preference_icon_shape),
summary = getShapeName(iconShape),
@ -445,7 +440,8 @@ fun IconShapePreference(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier.clip(getShape(it))
modifier = Modifier
.clip(getShape(it))
.size(48.dp)
.background(MaterialTheme.colorScheme.primary)
.clickable {

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.settings.nextcloud
import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@ -14,7 +15,6 @@ import androidx.compose.material.icons.automirrored.rounded.Login
import androidx.compose.material.icons.automirrored.rounded.Logout
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -24,7 +24,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
@ -33,6 +32,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.locals.LocalNavController
@ -60,10 +60,12 @@ fun NextcloudSettingsScreen() {
item {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.secondaryContainer)
.background(
MaterialTheme.colorScheme.secondaryContainer,
MaterialTheme.shapes.medium
)
.fillParentMaxWidth()
.padding(vertical = 64.dp)
,
.padding(vertical = 64.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
@ -72,7 +74,11 @@ fun NextcloudSettingsScreen() {
modifier = Modifier
.size(72.dp)
.background(MaterialTheme.colorScheme.secondary, CircleShape)
.border(2.dp, MaterialTheme.colorScheme.onSecondaryContainer, CircleShape),
.border(
2.dp,
MaterialTheme.colorScheme.onSecondaryContainer,
CircleShape
),
) {
Text(
text = nextcloudUser!!.userName.split(" ")
@ -80,15 +86,17 @@ fun NextcloudSettingsScreen() {
.joinToString("").let {
if (it.length >= 2) it.first().toString() + it.last().toString()
else it.first().toString()
}
,
},
color = MaterialTheme.colorScheme.onSecondary,
style = MaterialTheme.typography.headlineMedium,
)
}
Text(
modifier = Modifier.padding(top = 24.dp),
text = stringResource(R.string.preference_signin_user, nextcloudUser!!.userName),
text = stringResource(
R.string.preference_signin_user,
nextcloudUser!!.userName
),
color = MaterialTheme.colorScheme.onSecondaryContainer,
style = MaterialTheme.typography.bodyLarge,
)
@ -104,19 +112,21 @@ fun NextcloudSettingsScreen() {
modifier = Modifier
.padding(end = ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize),
contentDescription = null)
contentDescription = null
)
Text(text = stringResource(R.string.preference_signout))
}
}
}
item {
HorizontalDivider()
}
item {
PreferenceCategory {
SwitchPreference(
title = stringResource(R.string.plugin_type_filesearch),
summary = stringResource(R.string.preference_search_cloud_summary, nextcloudUser!!.userName),
summary = stringResource(
R.string.preference_search_cloud_summary,
nextcloudUser!!.userName
),
value = searchFiles == true,
onValueChanged = {
viewModel.setSearchFiles(it)
@ -124,9 +134,11 @@ fun NextcloudSettingsScreen() {
iconPadding = false,
)
}
}
} else {
item {
val activity = LocalContext.current as AppCompatActivity
val activity = LocalActivity.current as AppCompatActivity
PreferenceCategory {
Preference(
title = stringResource(R.string.preference_nextcloud_signin),
summary = stringResource(R.string.preference_nextcloud_signin_summary),
@ -136,8 +148,6 @@ fun NextcloudSettingsScreen() {
}
)
}
item {
HorizontalDivider()
}
}
}

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.settings.owncloud
import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@ -33,6 +34,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.locals.LocalNavController
@ -60,7 +62,7 @@ fun OwncloudSettingsScreen() {
item {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.secondaryContainer)
.background(MaterialTheme.colorScheme.secondaryContainer, MaterialTheme.shapes.medium)
.fillParentMaxWidth()
.padding(vertical = 64.dp)
,
@ -109,14 +111,15 @@ fun OwncloudSettingsScreen() {
}
}
}
item {
HorizontalDivider()
}
item {
PreferenceCategory {
SwitchPreference(
title = stringResource(R.string.plugin_type_filesearch),
summary = stringResource(R.string.preference_search_cloud_summary, owncloudUser!!.userName),
summary = stringResource(
R.string.preference_search_cloud_summary,
owncloudUser!!.userName
),
value = searchFiles == true,
onValueChanged = {
viewModel.setSearchFiles(it)
@ -124,9 +127,11 @@ fun OwncloudSettingsScreen() {
iconPadding = false,
)
}
}
} else {
item {
val activity = LocalContext.current as AppCompatActivity
val activity = LocalActivity.current as AppCompatActivity
PreferenceCategory {
Preference(
title = stringResource(R.string.preference_owncloud_signin),
summary = stringResource(R.string.preference_owncloud_signin_summary),
@ -136,8 +141,6 @@ fun OwncloudSettingsScreen() {
}
)
}
item {
HorizontalDivider()
}
}
}

View File

@ -7,7 +7,7 @@ import androidx.activity.compose.LocalActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@ -34,8 +34,8 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -59,8 +59,8 @@ import de.mm20.launcher2.ktx.sendWithBackgroundPermission
import de.mm20.launcher2.plugin.PluginState
import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
@ -142,6 +142,10 @@ fun PluginSettingsScreen(pluginId: String) {
topBar = {
TopAppBar(
title = {},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
),
navigationIcon = {
IconButton(onClick = {
if (navController?.navigateUp() != true) {
@ -186,7 +190,8 @@ fun PluginSettingsScreen(pluginId: String) {
}
}
)
}
},
containerColor = MaterialTheme.colorScheme.surfaceContainer,
) {
Column(
modifier = Modifier
@ -194,10 +199,14 @@ fun PluginSettingsScreen(pluginId: String) {
.padding(it)
) {
Surface(
modifier = Modifier.fillMaxWidth()
.padding(bottom = 16.dp)
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
color = MaterialTheme.colorScheme.surfaceContainer,
) {
Column(
modifier = Modifier.padding(bottom = 16.dp)
) {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp, horizontal = 12.dp)
@ -253,18 +262,11 @@ fun PluginSettingsScreen(pluginId: String) {
}
}
val surfaceColor by animateColorAsState(
if (pluginPackage?.enabled == true) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.surfaceContainer
}
)
Surface(
modifier = Modifier
.fillMaxWidth(),
color = surfaceColor,
Column(
modifier = Modifier.padding(horizontal = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
PreferenceCategory {
SwitchPreference(
enabled = pluginPackage != null && hasPermission != null,
iconPadding = false,
@ -281,7 +283,8 @@ fun PluginSettingsScreen(pluginId: String) {
}
)
}
}
},
containerColor = MaterialTheme.colorScheme.secondaryContainer
)
}
AnimatedVisibility(pluginPackage?.enabled == true && hasPermission == true) {
@ -295,14 +298,24 @@ fun PluginSettingsScreen(pluginId: String) {
) {
for (plugin in filePlugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(R.string.plugin_state_setup_required),
icon = Icons.Rounded.Info,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.Error || state is PluginState.SetupRequired,
icon = if (state is PluginState.Error) Icons.Rounded.Error else Icons.Rounded.Info,
description = when (state) {
is PluginState.Error -> {
stringResource(R.string.plugin_state_error)
}
is PluginState.SetupRequired -> {
state.message
?: stringResource(R.string.plugin_state_setup_required)
}
else -> ""
},
onUnlock = if (state is PluginState.SetupRequired) {
{
try {
state.setupActivity.sendWithBackgroundPermission(
context
@ -310,19 +323,9 @@ fun PluginSettingsScreen(pluginId: String) {
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(R.string.plugin_action_setup))
}
}
)
} else if (state is PluginState.Error) {
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.plugin_state_error),
icon = Icons.Rounded.Error,
color = MaterialTheme.colorScheme.errorContainer,
)
}
} else null
) {
SwitchPreference(
title = plugin.plugin.label,
enabled = enabledFileSearchPlugins != null && state is PluginState.Ready,
@ -339,6 +342,8 @@ fun PluginSettingsScreen(pluginId: String) {
iconPadding = false,
)
}
}
}
}
if (contactPlugins.isNotEmpty()) {
@ -348,14 +353,24 @@ fun PluginSettingsScreen(pluginId: String) {
) {
for (plugin in contactPlugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(R.string.plugin_state_setup_required),
icon = Icons.Rounded.Info,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.Error || state is PluginState.SetupRequired,
icon = if (state is PluginState.Error) Icons.Rounded.Error else Icons.Rounded.Info,
description = when (state) {
is PluginState.Error -> {
stringResource(R.string.plugin_state_error)
}
is PluginState.SetupRequired -> {
state.message
?: stringResource(R.string.plugin_state_setup_required)
}
else -> ""
},
onUnlock = if (state is PluginState.SetupRequired) {
{
try {
state.setupActivity.sendWithBackgroundPermission(
context
@ -363,19 +378,9 @@ fun PluginSettingsScreen(pluginId: String) {
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(R.string.plugin_action_setup))
}
}
)
} else if (state is PluginState.Error) {
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.plugin_state_error),
icon = Icons.Rounded.Error,
color = MaterialTheme.colorScheme.errorContainer,
)
}
} else null
) {
SwitchPreference(
title = plugin.plugin.label,
enabled = enabledContactPlugins != null && state is PluginState.Ready,
@ -394,6 +399,7 @@ fun PluginSettingsScreen(pluginId: String) {
}
}
}
}
if (locationPlugins.isNotEmpty()) {
PreferenceCategory(
stringResource(R.string.plugin_type_locationsearch),
@ -401,14 +407,24 @@ fun PluginSettingsScreen(pluginId: String) {
) {
for (plugin in locationPlugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(R.string.plugin_state_setup_required),
icon = Icons.Rounded.Info,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.Error || state is PluginState.SetupRequired,
icon = if (state is PluginState.Error) Icons.Rounded.Error else Icons.Rounded.Info,
description = when (state) {
is PluginState.Error -> {
stringResource(R.string.plugin_state_error)
}
is PluginState.SetupRequired -> {
state.message
?: stringResource(R.string.plugin_state_setup_required)
}
else -> ""
},
onUnlock = if (state is PluginState.SetupRequired) {
{
try {
state.setupActivity.sendWithBackgroundPermission(
context
@ -416,19 +432,9 @@ fun PluginSettingsScreen(pluginId: String) {
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(R.string.plugin_action_setup))
}
}
)
} else if (state is PluginState.Error) {
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.plugin_state_error),
icon = Icons.Rounded.Error,
color = MaterialTheme.colorScheme.errorContainer,
)
}
} else null
) {
SwitchPreference(
title = plugin.plugin.label,
enabled = enabledLocationSearchPlugins != null && state is PluginState.Ready,
@ -447,6 +453,7 @@ fun PluginSettingsScreen(pluginId: String) {
}
}
}
}
if (calendarPlugins.isNotEmpty()) {
PreferenceCategory(
stringResource(R.string.plugin_type_calendar),
@ -457,14 +464,39 @@ fun PluginSettingsScreen(pluginId: String) {
)
for (plugin in calendarPlugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(R.string.plugin_state_setup_required),
icon = Icons.Rounded.Info,
primaryAction = {
TextButton(onClick = {
val calendarLists by remember(plugin.state, plugin.plugin) {
viewModel.getCalendarLists(plugin.plugin)
}.collectAsStateWithLifecycle(
null,
minActiveState = Lifecycle.State.RESUMED
)
val selectedCalendars =
remember(excludedCalendars, calendarLists) {
calendarLists?.size?.minus(excludedCalendars.count {
it.startsWith(
plugin.plugin.authority
)
})
}
GuardedPreference(
locked = state is PluginState.Error || state is PluginState.SetupRequired,
icon = if (state is PluginState.Error) Icons.Rounded.Error else Icons.Rounded.Info,
description = when (state) {
is PluginState.Error -> {
stringResource(R.string.plugin_state_error)
}
is PluginState.SetupRequired -> {
state.message
?: stringResource(R.string.plugin_state_setup_required)
}
else -> ""
},
onUnlock = if (state is PluginState.SetupRequired) {
{
try {
state.setupActivity.sendWithBackgroundPermission(
context
@ -472,33 +504,9 @@ fun PluginSettingsScreen(pluginId: String) {
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(R.string.plugin_action_setup))
}
}
)
} else if (state is PluginState.Error) {
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.plugin_state_error),
icon = Icons.Rounded.Error,
color = MaterialTheme.colorScheme.errorContainer,
)
}
val calendarLists by remember(plugin.state, plugin.plugin) {
viewModel.getCalendarLists(plugin.plugin)
}.collectAsStateWithLifecycle(
null,
minActiveState = Lifecycle.State.RESUMED
)
var showDialog by remember { mutableStateOf(false) }
val selectedCalendars = remember(excludedCalendars, calendarLists) {
calendarLists?.size?.minus(excludedCalendars.count {
it.startsWith(
plugin.plugin.authority
)
})
}
} else null
) {
PreferenceWithSwitch(
title = plugin.plugin.label,
enabled = enabledCalendarSearchPlugins != null && state is PluginState.Ready,
@ -509,10 +517,11 @@ fun PluginSettingsScreen(pluginId: String) {
selectedCalendars,
selectedCalendars
)
}
else (state as? PluginState.Ready)?.text
} else (state as? PluginState.Ready)?.text
?: plugin.plugin.description,
switchValue = enabledCalendarSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
switchValue = enabledCalendarSearchPlugins?.contains(
plugin.plugin.authority
) == true && state is PluginState.Ready,
onSwitchChanged = {
viewModel.setCalendarSearchPluginEnabled(
plugin.plugin.authority,
@ -521,40 +530,9 @@ fun PluginSettingsScreen(pluginId: String) {
},
iconPadding = false,
onClick = {
showDialog = true
navController?.navigate("settings/search/calendar/${plugin.plugin.authority}")
}
)
if (showDialog && calendarLists?.isNotEmpty() == true) {
ModalBottomSheet(
onDismissRequest = {
showDialog = false
},
) {
LazyColumn {
items(calendarLists!!) {
CheckboxPreference(
title = it.name,
summary = it.owner,
iconPadding = false,
value = it.id !in excludedCalendars,
onValueChanged = { value ->
viewModel.setCalendarExcluded(it.id, !value)
},
checkboxColors = CheckboxDefaults.colors(
checkedColor = if (it.color == 0) MaterialTheme.colorScheme.primary
else Color(
it.color.atTone(if (LocalDarkTheme.current) 80 else 40)
),
checkmarkColor = if (it.color == 0) MaterialTheme.colorScheme.onPrimary
else Color(
it.color.atTone(if (LocalDarkTheme.current) 20 else 100)
)
)
)
}
}
}
}
}
}
@ -566,14 +544,24 @@ fun PluginSettingsScreen(pluginId: String) {
) {
for (plugin in weatherPlugins) {
val state = plugin.state
if (state is PluginState.SetupRequired) {
Banner(
modifier = Modifier.padding(16.dp),
text = state.message
?: stringResource(R.string.plugin_state_setup_required),
icon = Icons.Rounded.Info,
primaryAction = {
TextButton(onClick = {
GuardedPreference(
locked = state is PluginState.Error || state is PluginState.SetupRequired,
icon = if (state is PluginState.Error) Icons.Rounded.Error else Icons.Rounded.Info,
description = when (state) {
is PluginState.Error -> {
stringResource(R.string.plugin_state_error)
}
is PluginState.SetupRequired -> {
state.message
?: stringResource(R.string.plugin_state_setup_required)
}
else -> ""
},
onUnlock = if (state is PluginState.SetupRequired) {
{
try {
state.setupActivity.sendWithBackgroundPermission(
context
@ -581,19 +569,9 @@ fun PluginSettingsScreen(pluginId: String) {
} catch (e: PendingIntent.CanceledException) {
CrashReporter.logException(e)
}
}) {
Text(stringResource(R.string.plugin_action_setup))
}
}
)
} else if (state is PluginState.Error) {
Banner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.plugin_state_error),
icon = Icons.Rounded.Error,
color = MaterialTheme.colorScheme.errorContainer,
)
}
} else null
) {
Preference(
title = plugin.plugin.label,
enabled = state is PluginState.Ready && weatherProviderId != plugin.plugin.authority,
@ -608,6 +586,7 @@ fun PluginSettingsScreen(pluginId: String) {
}
)
}
}
Preference(
title = stringResource(R.string.widget_config_weather_integration_settings),
icon = Icons.AutoMirrored.Rounded.OpenInNew,
@ -622,3 +601,4 @@ fun PluginSettingsScreen(pluginId: String) {
}
}
}
}

View File

@ -31,7 +31,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.Wikipedia
@ -40,6 +39,7 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.SmallMessage
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.ListPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
@ -65,7 +65,9 @@ fun SearchSettingsScreen() {
val hasContactPlugins by remember { derivedStateOf { plugins?.any { it.plugin.type == PluginType.ContactSearch } } }
val isTasksAppInstalled by viewModel.isTasksAppInstalled.collectAsStateWithLifecycle()
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(null)
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(
null
)
val hasContactsPermission by viewModel.hasContactsPermission.collectAsStateWithLifecycle(null)
val hasCalendarPermission by viewModel.hasCalendarPermission.collectAsStateWithLifecycle(null)
val hasLocationPermission by viewModel.hasLocationPermission.collectAsStateWithLifecycle(null)
@ -121,15 +123,13 @@ fun SearchSettingsScreen() {
},
)
} else {
AnimatedVisibility(hasContactsPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_contact_search_settings),
onClick = {
GuardedPreference(
locked = hasContactsPermission == false,
onUnlock = {
viewModel.requestContactsPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(R.string.missing_permission_contact_search_settings),
) {
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_contacts),
summary = stringResource(R.string.preference_search_contacts_summary),
@ -144,6 +144,7 @@ fun SearchSettingsScreen() {
enabled = hasContactsPermission == true
)
}
}
if (hasCalendarPlugins != false || isTasksAppInstalled != false) {
Preference(
@ -155,15 +156,14 @@ fun SearchSettingsScreen() {
},
)
} else {
AnimatedVisibility(hasCalendarPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search_settings),
onClick = {
GuardedPreference(
locked = hasCalendarPermission == false,
onUnlock = {
viewModel.requestCalendarPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(R.string.missing_permission_calendar_search_settings),
) {
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_calendar_summary),
@ -178,18 +178,17 @@ fun SearchSettingsScreen() {
}
)
}
AnimatedVisibility(hasAppShortcutsPermission == false) {
MissingPermissionBanner(
text = stringResource(
R.string.missing_permission_appshortcuts_search_settings,
stringResource(R.string.app_name)
),
onClick = {
}
GuardedPreference(
locked = hasAppShortcutsPermission == false,
onUnlock = {
viewModel.requestAppShortcutsPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(
R.string.missing_permission_appshortcuts_search_settings,
stringResource(R.string.app_name),
),
) {
SwitchPreference(
title = stringResource(R.string.preference_search_appshortcuts),
summary = stringResource(R.string.preference_search_appshortcuts_summary),
@ -200,6 +199,7 @@ fun SearchSettingsScreen() {
},
enabled = hasAppShortcutsPermission == true
)
}
SwitchPreference(
title = stringResource(R.string.preference_search_calculator),
@ -246,19 +246,13 @@ fun SearchSettingsScreen() {
viewModel.setWebsites(it)
}
)
AnimatedVisibility(hasLocationPermission == false) {
MissingPermissionBanner(
text = stringResource(
R.string.missing_permission_location_search,
),
onClick = {
GuardedPreference(
locked = hasLocationPermission == false,
onUnlock = {
viewModel.requestLocationPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
description = stringResource(R.string.missing_permission_location_search),
) {
if (hasLocationPlugins != false) {
Preference(
title = stringResource(R.string.preference_search_locations),
@ -284,6 +278,7 @@ fun SearchSettingsScreen() {
enabled = hasLocationPermission == true,
)
}
}
Preference(
title = stringResource(R.string.preference_screen_search_actions),
@ -327,6 +322,7 @@ fun SearchSettingsScreen() {
)
SwitchPreference(
title = stringResource(R.string.preference_filter_bar),
iconPadding = true,
summary = stringResource(R.string.preference_filter_bar_summary),
value = filterBar == true,
onValueChanged = {
@ -336,6 +332,7 @@ fun SearchSettingsScreen() {
AnimatedVisibility(filterBar == true) {
Preference(
title = stringResource(R.string.preference_customize_filter_bar),
iconPadding = true,
summary = stringResource(R.string.preference_customize_filter_bar_summary),
onClick = {
navController?.navigate("settings/search/filterbar")
@ -357,6 +354,7 @@ fun SearchSettingsScreen() {
)
SwitchPreference(
title = stringResource(R.string.preference_search_bar_launch_on_enter),
iconPadding = true,
summary = stringResource(R.string.preference_search_bar_launch_on_enter_summary),
value = launchOnEnter == true,
onValueChanged = {

View File

@ -29,8 +29,6 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButton
@ -53,6 +51,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -222,6 +221,8 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
}
item {
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
@ -246,6 +247,8 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
}
item {
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
@ -279,7 +282,9 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
PreferenceCategory {
}
item {
PreferenceCategory(title = "Large") {
ShapePreview(
previewShapes = previewShapes,
) {
@ -299,6 +304,8 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
}
item {
PreferenceCategory {
ShapePreference(
title = "Large increased",
@ -312,6 +319,8 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
}
item {
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
@ -344,6 +353,8 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
}
item {
PreferenceCategory {
ShapePreference(
title = "Extra large increased",
@ -357,6 +368,8 @@ fun ShapeSchemeSettingsScreen(themeId: UUID) {
}
)
}
}
item {
PreferenceCategory {
ShapePreference(
title = "Extra extra large",
@ -398,11 +411,12 @@ fun ShapePreference(
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.surface)
.clickable(
onClick = { showDialog = true },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
@ -706,28 +720,20 @@ private fun ShapePreview(
previewShapes: Shapes,
content: @Composable () -> Unit,
) {
MaterialTheme(
shapes = previewShapes
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.surfaceContainerLowest)
.horizontalScroll(rememberScrollState())
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
MaterialTheme(
shapes = previewShapes
) {
content()
}
}
}
}

View File

@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.settings.tasks
import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
@ -9,6 +10,7 @@ import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.TaskAlt
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -19,7 +21,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.GuardedPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
@ -41,13 +43,16 @@ fun TasksIntegrationSettingsScreen() {
) {
if (isTasksInstalled == false) {
item {
PreferenceCategory {
Banner(
text = stringResource(
R.string.preference_tasks_integration_description,
stringResource(R.string.app_name)
),
icon = Icons.Rounded.Info,
modifier = Modifier.padding(16.dp),
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
primaryAction = {
Button(onClick = {
viewModel.downloadTasksApp(activity as AppCompatActivity)
@ -58,25 +63,29 @@ fun TasksIntegrationSettingsScreen() {
)
}
}
}
if (isTasksInstalled == true) {
item {
PreferenceCategory {
if (hasTasksPermission == false) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_tasks_integration),
onClick = {
viewModel.requestTasksPermission(activity as AppCompatActivity)
}
)
}
if (hasTasksPermission == true) {
Banner(
text = stringResource(R.string.preference_tasks_integration_ready),
icon = Icons.Rounded.CheckCircle,
modifier = Modifier.padding(16.dp),
modifier = Modifier
.background(
MaterialTheme.colorScheme.surface,
MaterialTheme.shapes.extraSmall
)
.padding(16.dp),
)
}
GuardedPreference(
locked = hasTasksPermission == false,
onUnlock = {
viewModel.requestTasksPermission(activity as AppCompatActivity)
},
description = stringResource(R.string.missing_permission_tasks_integration),
) {
PreferenceWithSwitch(
icon = Icons.Rounded.TaskAlt,
title = stringResource(R.string.preference_search_tasks),
@ -90,6 +99,7 @@ fun TasksIntegrationSettingsScreen() {
navController?.navigate("settings/search/calendar/tasks.org")
}
)
}
Preference(
title = stringResource(R.string.preference_launch_tasks_app),
icon = Icons.AutoMirrored.Rounded.OpenInNew,

View File

@ -45,6 +45,8 @@ fun UnitConverterSettingsScreen() {
}
)
}
}
item {
PreferenceCategory {
Preference(
title = stringResource(R.string.preference_search_supportedunits),

View File

@ -10,19 +10,34 @@ fun makeTypography(
val baseTypography = Typography()
return Typography(
displayLarge = baseTypography.displayLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Medium),
displayLargeEmphasized = baseTypography.displayLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
displayMedium = baseTypography.displayMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Medium),
displayMediumEmphasized = baseTypography.displayMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
displaySmall = baseTypography.displaySmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Medium),
displaySmallEmphasized = baseTypography.displaySmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
headlineLarge = baseTypography.headlineLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
headlineLargeEmphasized = baseTypography.headlineLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
headlineMedium = baseTypography.headlineMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
headlineMediumEmphasized = baseTypography.headlineMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
headlineSmall = baseTypography.headlineSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
headlineSmallEmphasized = baseTypography.headlineSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
titleLarge = baseTypography.titleLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
titleMedium = baseTypography.titleMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
titleLargeEmphasized = baseTypography.titleLargeEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
titleMedium = baseTypography.titleMedium.copy(fontFamily = headlineFamily),
titleMediumEmphasized = baseTypography.titleMediumEmphasized.copy(fontFamily = headlineFamily),
titleSmall = baseTypography.titleSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
titleSmallEmphasized = baseTypography.titleSmallEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Bold),
bodyLarge = baseTypography.bodyLarge.copy(fontFamily = bodyFamily),
bodyLargeEmphasized = baseTypography.bodyLargeEmphasized.copy(fontFamily = bodyFamily, fontWeight = FontWeight.Medium),
bodyMedium = baseTypography.bodyMedium.copy(fontFamily = bodyFamily),
bodyMediumEmphasized = baseTypography.bodyMediumEmphasized.copy(fontFamily = bodyFamily, fontWeight = FontWeight.Medium),
bodySmall = baseTypography.bodySmall.copy(fontFamily = bodyFamily),
labelLarge = baseTypography.labelLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
labelMedium = baseTypography.labelMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
labelSmall = baseTypography.labelSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold)
bodySmallEmphasized = baseTypography.bodySmallEmphasized.copy(fontFamily = bodyFamily, fontWeight = FontWeight.Medium),
labelLarge = baseTypography.labelLarge.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Medium),
labelLargeEmphasized = baseTypography.labelLargeEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
labelMedium = baseTypography.labelMedium.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Medium),
labelMediumEmphasized = baseTypography.labelMediumEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
labelSmall = baseTypography.labelSmall.copy(fontFamily = headlineFamily, fontWeight = FontWeight.Medium),
labelSmallEmphasized = baseTypography.labelSmallEmphasized.copy(fontFamily = headlineFamily, fontWeight = FontWeight.SemiBold),
)
}

View File

@ -11,6 +11,7 @@ val Outfit = FontFamily(
Font(R.font.outfit_200, weight = FontWeight.ExtraLight, style = FontStyle.Normal),
Font(R.font.outfit_300, weight = FontWeight.Light, style = FontStyle.Normal),
Font(R.font.outfit_400, weight = FontWeight.Normal, style = FontStyle.Normal),
Font(R.font.outfit_500, weight = FontWeight.Medium, style = FontStyle.Normal),
Font(R.font.outfit_600, weight = FontWeight.SemiBold, style = FontStyle.Normal),
Font(R.font.outfit_700, weight = FontWeight.Bold, style = FontStyle.Normal),
Font(R.font.outfit_800, weight = FontWeight.ExtraBold, style = FontStyle.Normal),