Settings facelift
This commit is contained in:
parent
fcc111b048
commit
b6d533466b
@ -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,
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
content()
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).background(
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||
)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -89,7 +91,7 @@ fun PreferenceScreen(
|
||||
val touchSlop = LocalViewConfiguration.current.touchSlop
|
||||
var fabVisible by remember { mutableStateOf(true) }
|
||||
val nestedScrollConnection = remember {
|
||||
object: NestedScrollConnection {
|
||||
object : NestedScrollConnection {
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,25 +36,32 @@ 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),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(54.dp)
|
||||
.padding(start = 4.dp),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
if (icon != null || iconPadding) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(56.dp)
|
||||
.padding(end = 8.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -21,30 +21,32 @@ fun BuildInfoSettingsScreen() {
|
||||
val buildFeatures by viewModel.buildFeatures.collectAsState(emptyMap())
|
||||
PreferenceScreen(title = stringResource(R.string.preference_screen_buildinfo)) {
|
||||
item {
|
||||
Preference(title = "Build type", summary = BuildConfig.BUILD_TYPE)
|
||||
var buildSignature by remember { mutableStateOf<String?>(null) }
|
||||
LaunchedEffect(null) {
|
||||
val signature = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val pi = context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES
|
||||
)
|
||||
pi.signingInfo?.apkContentsSigners?.firstOrNull()
|
||||
} else {
|
||||
val pi = context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_SIGNATURES
|
||||
)
|
||||
pi.signatures?.firstOrNull()
|
||||
PreferenceCategory {
|
||||
Preference(title = "Build type", summary = BuildConfig.BUILD_TYPE)
|
||||
var buildSignature by remember { mutableStateOf<String?>(null) }
|
||||
LaunchedEffect(null) {
|
||||
val signature = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val pi = context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES
|
||||
)
|
||||
pi.signingInfo?.apkContentsSigners?.firstOrNull()
|
||||
} else {
|
||||
val pi = context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.GET_SIGNATURES
|
||||
)
|
||||
pi.signatures?.firstOrNull()
|
||||
}
|
||||
val signatureHash = if (signature != null) {
|
||||
val digest = MessageDigest.getInstance("SHA")
|
||||
digest.update(signature.toByteArray())
|
||||
Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
|
||||
} else "null"
|
||||
buildSignature = signatureHash
|
||||
}
|
||||
val signatureHash = if (signature != null) {
|
||||
val digest = MessageDigest.getInstance("SHA")
|
||||
digest.update(signature.toByteArray())
|
||||
Base64.encodeToString(digest.digest(), Base64.NO_WRAP)
|
||||
} else "null"
|
||||
buildSignature = signatureHash
|
||||
Preference(title = "Signature hash", summary = buildSignature)
|
||||
}
|
||||
Preference(title = "Signature hash", summary = buildSignature)
|
||||
}
|
||||
item {
|
||||
PreferenceCategory(title = "Features") {
|
||||
|
||||
@ -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,85 +45,81 @@ 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 = {
|
||||
viewModel.requestCalendarPermission(context as AppCompatActivity)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_calendar),
|
||||
summary = stringResource(R.string.preference_search_local_calendar_summary),
|
||||
switchValue = enabledProviders.contains("local") && hasCalendarPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setProviderEnabled("local", it)
|
||||
GuardedPreference(
|
||||
locked = hasCalendarPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestCalendarPermission(context as AppCompatActivity)
|
||||
},
|
||||
enabled = hasCalendarPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/local")
|
||||
}
|
||||
)
|
||||
if (isTasksAppInstalled) {
|
||||
AnimatedVisibility(hasTasksPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(R.string.missing_permission_tasks_search_settings),
|
||||
onClick = {
|
||||
viewModel.requestTasksPermission(context as AppCompatActivity)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
description = stringResource(R.string.missing_permission_calendar_search_settings),
|
||||
) {
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_tasks),
|
||||
summary = stringResource(R.string.preference_search_tasks_summary),
|
||||
switchValue = enabledProviders.contains("tasks.org") && hasTasksPermission == true,
|
||||
title = stringResource(R.string.preference_search_calendar),
|
||||
summary = stringResource(R.string.preference_search_local_calendar_summary),
|
||||
switchValue = enabledProviders.contains("local") && hasCalendarPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setProviderEnabled("tasks.org", it)
|
||||
viewModel.setProviderEnabled("local", it)
|
||||
},
|
||||
enabled = hasTasksPermission == true,
|
||||
enabled = hasCalendarPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/tasks.org")
|
||||
navController?.navigate("settings/search/calendar/local")
|
||||
}
|
||||
)
|
||||
}
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(context)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.plugin_action_setup))
|
||||
}
|
||||
if (isTasksAppInstalled) {
|
||||
GuardedPreference(
|
||||
locked = hasTasksPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestTasksPermission(context as AppCompatActivity)
|
||||
},
|
||||
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),
|
||||
switchValue = enabledProviders.contains("tasks.org") && hasTasksPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setProviderEnabled("tasks.org", it)
|
||||
},
|
||||
enabled = hasTasksPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/tasks.org")
|
||||
}
|
||||
)
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready,
|
||||
summary = (state as? PluginState.SetupRequired)?.message
|
||||
?: (state as? PluginState.Ready)?.text
|
||||
?: plugin.plugin.description,
|
||||
switchValue = enabledProviders.contains(plugin.plugin.authority) && state is PluginState.Ready,
|
||||
onSwitchChanged = {
|
||||
viewModel.setProviderEnabled(plugin.plugin.authority, it)
|
||||
}
|
||||
for (plugin in plugins) {
|
||||
val state = plugin.state
|
||||
GuardedPreference(
|
||||
locked = state is PluginState.SetupRequired,
|
||||
onUnlock = {
|
||||
try {
|
||||
(state as PluginState.SetupRequired).setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/${plugin.plugin.authority}")
|
||||
}
|
||||
)
|
||||
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,
|
||||
summary = (state as? PluginState.SetupRequired)?.message
|
||||
?: (state as? PluginState.Ready)?.text
|
||||
?: plugin.plugin.description,
|
||||
switchValue = enabledProviders.contains(plugin.plugin.authority) && state is PluginState.Ready,
|
||||
onSwitchChanged = {
|
||||
viewModel.setProviderEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/${plugin.plugin.authority}")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
colorPreferences()
|
||||
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
|
||||
}
|
||||
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,69 +59,76 @@ fun ContactsSettingsScreen() {
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_contacts),
|
||||
summary = stringResource(R.string.preference_search_contacts_summary),
|
||||
icon = Icons.Rounded.Person,
|
||||
value = enabledProviders.contains("local"),
|
||||
onValueChanged = {
|
||||
viewModel.setProviderEnabled("local", it)
|
||||
}
|
||||
)
|
||||
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),
|
||||
icon = Icons.Rounded.Person,
|
||||
value = enabledProviders.contains("local"),
|
||||
onValueChanged = {
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(context)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.plugin_action_setup))
|
||||
}
|
||||
GuardedPreference(
|
||||
locked = state is PluginState.SetupRequired,
|
||||
onUnlock = {
|
||||
try {
|
||||
(state as PluginState.SetupRequired).setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
},
|
||||
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,
|
||||
summary = (state as? PluginState.SetupRequired)?.message
|
||||
?: (state as? PluginState.Ready)?.text
|
||||
?: plugin.plugin.description,
|
||||
value = enabledProviders.contains(plugin.plugin.authority) && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setProviderEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready,
|
||||
summary = (state as? PluginState.SetupRequired)?.message
|
||||
?: (state as? PluginState.Ready)?.text
|
||||
?: plugin.plugin.description,
|
||||
value = enabledProviders.contains(plugin.plugin.authority) && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setProviderEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
PreferenceCategory {
|
||||
AnimatedVisibility(hasCallPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(R.string.missing_permission_call_contacts_settings),
|
||||
onClick = {
|
||||
viewModel.requestCallPermission(context as AppCompatActivity)
|
||||
GuardedPreference(
|
||||
locked = hasCallPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestCallPermission(context as AppCompatActivity)
|
||||
},
|
||||
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),
|
||||
icon = Icons.Rounded.Call,
|
||||
value = callOnTap == true && hasCallPermission == true,
|
||||
onValueChanged = {
|
||||
viewModel.setCallOnTap(it)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
enabled = hasCallPermission == true
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_contacts_call_on_tap),
|
||||
summary = stringResource(R.string.preference_contacts_call_on_tap_summary),
|
||||
icon = Icons.Rounded.Call,
|
||||
value = callOnTap == true && hasCallPermission == true,
|
||||
onValueChanged = {
|
||||
viewModel.setCallOnTap(it)
|
||||
},
|
||||
enabled = hasCallPermission == true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,123 +66,104 @@ 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 = {
|
||||
viewModel.requestFilePermission(context as AppCompatActivity)
|
||||
GuardedPreference(
|
||||
locked = hasFilePermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestFilePermission(context as AppCompatActivity)
|
||||
},
|
||||
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),
|
||||
value = localFiles == true && hasFilePermission == true,
|
||||
onValueChanged = {
|
||||
viewModel.setLocalFiles(it)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
enabled = hasFilePermission == true
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_localfiles),
|
||||
summary = stringResource(R.string.preference_search_localfiles_summary),
|
||||
value = localFiles == true && hasFilePermission == true,
|
||||
onValueChanged = {
|
||||
viewModel.setLocalFiles(it)
|
||||
},
|
||||
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)
|
||||
},
|
||||
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 {
|
||||
stringResource(R.string.preference_search_cloud_summary, it.userName)
|
||||
} ?: stringResource(R.string.preference_summary_not_logged_in),
|
||||
value = nextcloud == true && nextcloudAccount != null,
|
||||
onValueChanged = {
|
||||
viewModel.setNextcloud(it)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
enabled = nextcloudAccount != null
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_nextcloud),
|
||||
summary = nextcloudAccount?.let {
|
||||
stringResource(R.string.preference_search_cloud_summary, it.userName)
|
||||
} ?: stringResource(R.string.preference_summary_not_logged_in),
|
||||
value = nextcloud == true && nextcloudAccount != null,
|
||||
onValueChanged = {
|
||||
viewModel.setNextcloud(it)
|
||||
},
|
||||
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)
|
||||
},
|
||||
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 {
|
||||
stringResource(R.string.preference_search_cloud_summary, it.userName)
|
||||
} ?: stringResource(R.string.preference_summary_not_logged_in),
|
||||
value = owncloud == true && owncloudAccount != null,
|
||||
onValueChanged = {
|
||||
viewModel.setOwncloud(it)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
enabled = owncloudAccount != null
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_owncloud),
|
||||
summary = owncloudAccount?.let {
|
||||
stringResource(R.string.preference_search_cloud_summary, it.userName)
|
||||
} ?: stringResource(R.string.preference_summary_not_logged_in),
|
||||
value = owncloud == true && owncloudAccount != null,
|
||||
onValueChanged = {
|
||||
viewModel.setOwncloud(it)
|
||||
},
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(context)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.plugin_action_setup))
|
||||
}
|
||||
GuardedPreference(
|
||||
locked = state is PluginState.SetupRequired,
|
||||
onUnlock = {
|
||||
try {
|
||||
(state as PluginState.SetupRequired).setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
},
|
||||
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,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setPluginEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setPluginEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,142 +73,137 @@ 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)
|
||||
GesturePreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_down),
|
||||
icon = Icons.Rounded.SwipeDownAlt,
|
||||
value = swipeDown,
|
||||
onValueChanged = { viewModel.setSwipeDown(it) },
|
||||
options = options,
|
||||
app = swipeDownApp,
|
||||
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) }
|
||||
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,
|
||||
value = swipeDown,
|
||||
onValueChanged = { viewModel.setSwipeDown(it) },
|
||||
options = options,
|
||||
app = swipeDownApp,
|
||||
appIcon = swipeDownAppIcon,
|
||||
onAppChanged = { viewModel.setSwipeDownApp(it) }
|
||||
)
|
||||
}
|
||||
|
||||
val swipeLeft by viewModel.swipeLeft.collectAsStateWithLifecycle(null)
|
||||
val swipeLeftApp by viewModel.swipeLeftApp.collectAsState(null)
|
||||
val swipeLeftAppIcon by remember(swipeLeftApp?.key) {
|
||||
viewModel.getIcon(swipeLeftApp, appIconSize.toInt())
|
||||
}.collectAsState(null)
|
||||
GesturePreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_left),
|
||||
icon = Icons.Rounded.SwipeLeftAlt,
|
||||
value = swipeLeft,
|
||||
onValueChanged = { viewModel.setSwipeLeft(it) },
|
||||
options = options,
|
||||
app = swipeLeftApp,
|
||||
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) }
|
||||
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,
|
||||
value = swipeLeft,
|
||||
onValueChanged = { viewModel.setSwipeLeft(it) },
|
||||
options = options,
|
||||
app = swipeLeftApp,
|
||||
appIcon = swipeLeftAppIcon,
|
||||
onAppChanged = { viewModel.setSwipeLeftApp(it) }
|
||||
)
|
||||
}
|
||||
|
||||
val swipeRight by viewModel.swipeRight.collectAsStateWithLifecycle(null)
|
||||
val swipeRightApp by viewModel.swipeRightApp.collectAsState(null)
|
||||
val swipeRightAppIcon by remember(swipeRightApp?.key) {
|
||||
viewModel.getIcon(swipeRightApp, appIconSize.toInt())
|
||||
}.collectAsState(null)
|
||||
GesturePreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_right),
|
||||
icon = Icons.Rounded.SwipeRightAlt,
|
||||
value = swipeRight,
|
||||
onValueChanged = { viewModel.setSwipeRight(it) },
|
||||
options = options,
|
||||
app = swipeRightApp,
|
||||
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) }
|
||||
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,
|
||||
value = swipeRight,
|
||||
onValueChanged = { viewModel.setSwipeRight(it) },
|
||||
options = options,
|
||||
app = swipeRightApp,
|
||||
appIcon = swipeRightAppIcon,
|
||||
onAppChanged = { viewModel.setSwipeRightApp(it) }
|
||||
)
|
||||
}
|
||||
|
||||
val swipeUp by viewModel.swipeUp.collectAsStateWithLifecycle(null)
|
||||
val swipeUpApp by viewModel.swipeUpApp.collectAsState(null)
|
||||
val swipeUpAppIcon by remember(swipeUpApp?.key) {
|
||||
viewModel.getIcon(swipeUpApp, appIconSize.toInt())
|
||||
}.collectAsState(null)
|
||||
GesturePreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_up),
|
||||
icon = Icons.Rounded.SwipeUpAlt,
|
||||
value = swipeUp,
|
||||
onValueChanged = { viewModel.setSwipeUp(it) },
|
||||
options = options,
|
||||
app = swipeUpApp,
|
||||
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) }
|
||||
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,
|
||||
value = swipeUp,
|
||||
onValueChanged = { viewModel.setSwipeUp(it) },
|
||||
options = options,
|
||||
app = swipeUpApp,
|
||||
appIcon = swipeUpAppIcon,
|
||||
onAppChanged = { viewModel.setSwipeUpApp(it) }
|
||||
)
|
||||
}
|
||||
|
||||
val doubleTap by viewModel.doubleTap.collectAsStateWithLifecycle(null)
|
||||
val doubleTapApp by viewModel.doubleTapApp.collectAsState(null)
|
||||
val doubleTapAppIcon by remember(doubleTapApp?.key) {
|
||||
viewModel.getIcon(doubleTapApp, appIconSize.toInt())
|
||||
}.collectAsState(null)
|
||||
GesturePreference(
|
||||
title = stringResource(R.string.preference_gesture_double_tap),
|
||||
icon = Icons.Rounded.Adjust,
|
||||
value = doubleTap,
|
||||
onValueChanged = { viewModel.setDoubleTap(it) },
|
||||
options = options,
|
||||
app = doubleTapApp,
|
||||
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) }
|
||||
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,
|
||||
value = doubleTap,
|
||||
onValueChanged = { viewModel.setDoubleTap(it) },
|
||||
options = options,
|
||||
app = doubleTapApp,
|
||||
appIcon = doubleTapAppIcon,
|
||||
onAppChanged = { viewModel.setDoubleTapApp(it) }
|
||||
)
|
||||
}
|
||||
|
||||
val longPress by viewModel.longPress.collectAsStateWithLifecycle(null)
|
||||
val longPressApp by viewModel.longPressApp.collectAsState(null)
|
||||
val longPressAppIcon by remember(longPressApp?.key) {
|
||||
viewModel.getIcon(longPressApp, appIconSize.toInt())
|
||||
}.collectAsState(null)
|
||||
GesturePreference(
|
||||
title = stringResource(R.string.preference_gesture_long_press),
|
||||
icon = Icons.Rounded.Circle,
|
||||
value = longPress,
|
||||
onValueChanged = { viewModel.setLongPress(it) },
|
||||
options = options,
|
||||
app = longPressApp,
|
||||
appIcon = longPressAppIcon,
|
||||
onAppChanged = { viewModel.setLongPressApp(it) }
|
||||
)
|
||||
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,
|
||||
value = longPress,
|
||||
onValueChanged = { viewModel.setLongPress(it) },
|
||||
options = options,
|
||||
app = longPressApp,
|
||||
appIcon = longPressAppIcon,
|
||||
onAppChanged = { viewModel.setLongPressApp(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,24 +143,20 @@ fun IconsSettingsScreen() {
|
||||
item {
|
||||
PreferenceCategory(stringResource(R.string.preference_category_icons)) {
|
||||
if (previewIcons.value.isNotEmpty()) {
|
||||
Surface(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||
MaterialTheme.shapes.extraSmall
|
||||
)
|
||||
.padding(vertical = 24.dp, horizontal = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 24.dp, horizontal = 8.dp)
|
||||
) {
|
||||
for (icon in previewIcons.value) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ShapedLauncherIcon(size = grid.iconSize.dp, icon = { icon })
|
||||
}
|
||||
for (icon in previewIcons.value) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ShapedLauncherIcon(size = grid.iconSize.dp, icon = { icon })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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,40 +112,42 @@ 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 {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.plugin_type_filesearch),
|
||||
summary = stringResource(R.string.preference_search_cloud_summary, nextcloudUser!!.userName),
|
||||
value = searchFiles == true,
|
||||
onValueChanged = {
|
||||
viewModel.setSearchFiles(it)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
PreferenceCategory {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.plugin_type_filesearch),
|
||||
summary = stringResource(
|
||||
R.string.preference_search_cloud_summary,
|
||||
nextcloudUser!!.userName
|
||||
),
|
||||
value = searchFiles == true,
|
||||
onValueChanged = {
|
||||
viewModel.setSearchFiles(it)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
val activity = LocalContext.current as AppCompatActivity
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_nextcloud_signin),
|
||||
summary = stringResource(R.string.preference_nextcloud_signin_summary),
|
||||
icon = Icons.AutoMirrored.Rounded.Login,
|
||||
onClick = {
|
||||
viewModel.signIn(activity)
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
val activity = LocalActivity.current as AppCompatActivity
|
||||
PreferenceCategory {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_nextcloud_signin),
|
||||
summary = stringResource(R.string.preference_nextcloud_signin_summary),
|
||||
icon = Icons.AutoMirrored.Rounded.Login,
|
||||
onClick = {
|
||||
viewModel.signIn(activity)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,35 +111,36 @@ fun OwncloudSettingsScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.plugin_type_filesearch),
|
||||
summary = stringResource(R.string.preference_search_cloud_summary, owncloudUser!!.userName),
|
||||
value = searchFiles == true,
|
||||
onValueChanged = {
|
||||
viewModel.setSearchFiles(it)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
PreferenceCategory {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.plugin_type_filesearch),
|
||||
summary = stringResource(
|
||||
R.string.preference_search_cloud_summary,
|
||||
owncloudUser!!.userName
|
||||
),
|
||||
value = searchFiles == true,
|
||||
onValueChanged = {
|
||||
viewModel.setSearchFiles(it)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
val activity = LocalContext.current as AppCompatActivity
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_owncloud_signin),
|
||||
summary = stringResource(R.string.preference_owncloud_signin_summary),
|
||||
icon = Icons.AutoMirrored.Rounded.Login,
|
||||
onClick = {
|
||||
viewModel.signIn(activity)
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
HorizontalDivider()
|
||||
val activity = LocalActivity.current as AppCompatActivity
|
||||
PreferenceCategory {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_owncloud_signin),
|
||||
summary = stringResource(R.string.preference_owncloud_signin_summary),
|
||||
icon = Icons.AutoMirrored.Rounded.Login,
|
||||
onClick = {
|
||||
viewModel.signIn(activity)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
Column(
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 12.dp)
|
||||
@ -253,327 +262,60 @@ 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),
|
||||
) {
|
||||
SwitchPreference(
|
||||
enabled = pluginPackage != null && hasPermission != null,
|
||||
iconPadding = false,
|
||||
title = stringResource(R.string.preference_plugin_enable),
|
||||
value = pluginPackage?.enabled == true && hasPermission == true,
|
||||
onValueChanged = {
|
||||
if (hasPermission == true) {
|
||||
viewModel.setPluginEnabled(it)
|
||||
} else {
|
||||
requestPermissionStarter.launch(
|
||||
Intent().apply {
|
||||
`package` = pluginPackage?.packageName
|
||||
action = "de.mm20.launcher2.plugin.REQUEST_PERMISSION"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(pluginPackage?.enabled == true && hasPermission == true) {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
if (filePlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_filesearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} 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,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledFileSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledFileSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setFileSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contactPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_contacts),
|
||||
iconPadding = false,
|
||||
) {
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} 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,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledContactPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledContactPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setContactPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (locationPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_locationsearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} 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,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledLocationSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledLocationSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setLocationSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (calendarPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_calendar),
|
||||
iconPadding = false,
|
||||
) {
|
||||
val excludedCalendars by viewModel.excludedCalendars.collectAsState(
|
||||
emptySet()
|
||||
)
|
||||
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 = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
)
|
||||
} 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
|
||||
)
|
||||
})
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledCalendarSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.SetupRequired)?.message
|
||||
?: if (selectedCalendars != null && calendarLists != null) {
|
||||
pluralStringResource(
|
||||
R.plurals.calendar_search_enabled_lists,
|
||||
selectedCalendars,
|
||||
selectedCalendars
|
||||
)
|
||||
}
|
||||
else (state as? PluginState.Ready)?.text
|
||||
?: plugin.plugin.description,
|
||||
switchValue = enabledCalendarSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onSwitchChanged = {
|
||||
viewModel.setCalendarSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
onClick = {
|
||||
showDialog = true
|
||||
PreferenceCategory {
|
||||
SwitchPreference(
|
||||
enabled = pluginPackage != null && hasPermission != null,
|
||||
iconPadding = false,
|
||||
title = stringResource(R.string.preference_plugin_enable),
|
||||
value = pluginPackage?.enabled == true && hasPermission == true,
|
||||
onValueChanged = {
|
||||
if (hasPermission == true) {
|
||||
viewModel.setPluginEnabled(it)
|
||||
} else {
|
||||
requestPermissionStarter.launch(
|
||||
Intent().apply {
|
||||
`package` = pluginPackage?.packageName
|
||||
action = "de.mm20.launcher2.plugin.REQUEST_PERMISSION"
|
||||
}
|
||||
)
|
||||
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)
|
||||
},
|
||||
}
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(pluginPackage?.enabled == true && hasPermission == true) {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
if (filePlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_filesearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in filePlugins) {
|
||||
val state = plugin.state
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
||||
)
|
||||
is PluginState.SetupRequired -> {
|
||||
state.message
|
||||
?: stringResource(R.string.plugin_state_setup_required)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (weatherPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_weather),
|
||||
iconPadding = false,
|
||||
) {
|
||||
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 = {
|
||||
|
||||
else -> ""
|
||||
},
|
||||
onUnlock = if (state is PluginState.SetupRequired) {
|
||||
{
|
||||
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(
|
||||
context
|
||||
@ -581,40 +323,278 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledFileSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledFileSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setFileSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contactPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_contacts),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in contactPlugins) {
|
||||
val state = plugin.state
|
||||
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
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledContactPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledContactPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setContactPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (locationPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_locationsearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in locationPlugins) {
|
||||
val state = plugin.state
|
||||
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
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledLocationSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledLocationSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setLocationSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (calendarPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_calendar),
|
||||
iconPadding = false,
|
||||
) {
|
||||
val excludedCalendars by viewModel.excludedCalendars.collectAsState(
|
||||
emptySet()
|
||||
)
|
||||
for (plugin in calendarPlugins) {
|
||||
val state = plugin.state
|
||||
|
||||
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
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
} 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,
|
||||
)
|
||||
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
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
PreferenceWithSwitch(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledCalendarSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.SetupRequired)?.message
|
||||
?: if (selectedCalendars != null && calendarLists != null) {
|
||||
pluralStringResource(
|
||||
R.plurals.calendar_search_enabled_lists,
|
||||
selectedCalendars,
|
||||
selectedCalendars
|
||||
)
|
||||
} else (state as? PluginState.Ready)?.text
|
||||
?: plugin.plugin.description,
|
||||
switchValue = enabledCalendarSearchPlugins?.contains(
|
||||
plugin.plugin.authority
|
||||
) == true && state is PluginState.Ready,
|
||||
onSwitchChanged = {
|
||||
viewModel.setCalendarSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/${plugin.plugin.authority}")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (weatherPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_weather),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in weatherPlugins) {
|
||||
val state = plugin.state
|
||||
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
|
||||
)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
} else null
|
||||
) {
|
||||
Preference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready && weatherProviderId != plugin.plugin.authority,
|
||||
iconPadding = false,
|
||||
summary = if (weatherProviderId != plugin.plugin.authority) {
|
||||
stringResource(R.string.plugin_weather_provider_enable)
|
||||
} else {
|
||||
stringResource(R.string.plugin_weather_provider_enabled)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setWeatherProvider(plugin.plugin.authority)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Preference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready && weatherProviderId != plugin.plugin.authority,
|
||||
iconPadding = false,
|
||||
summary = if (weatherProviderId != plugin.plugin.authority) {
|
||||
stringResource(R.string.plugin_weather_provider_enable)
|
||||
} else {
|
||||
stringResource(R.string.plugin_weather_provider_enabled)
|
||||
},
|
||||
title = stringResource(R.string.widget_config_weather_integration_settings),
|
||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||
onClick = {
|
||||
viewModel.setWeatherProvider(plugin.plugin.authority)
|
||||
navController?.navigate("settings/integrations/weather")
|
||||
}
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = stringResource(R.string.widget_config_weather_integration_settings),
|
||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||
onClick = {
|
||||
navController?.navigate("settings/integrations/weather")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,28 +123,27 @@ fun SearchSettingsScreen() {
|
||||
},
|
||||
)
|
||||
} else {
|
||||
AnimatedVisibility(hasContactsPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(R.string.missing_permission_contact_search_settings),
|
||||
onClick = {
|
||||
viewModel.requestContactsPermission(context as AppCompatActivity)
|
||||
GuardedPreference(
|
||||
locked = hasContactsPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestContactsPermission(context as AppCompatActivity)
|
||||
},
|
||||
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),
|
||||
icon = Icons.Rounded.Person,
|
||||
switchValue = contacts == true && hasContactsPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setContacts(it)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/contacts")
|
||||
},
|
||||
enabled = hasContactsPermission == true
|
||||
)
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_contacts),
|
||||
summary = stringResource(R.string.preference_search_contacts_summary),
|
||||
icon = Icons.Rounded.Person,
|
||||
switchValue = contacts == true && hasContactsPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setContacts(it)
|
||||
},
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/contacts")
|
||||
},
|
||||
enabled = hasContactsPermission == true
|
||||
)
|
||||
}
|
||||
|
||||
if (hasCalendarPlugins != false || isTasksAppInstalled != false) {
|
||||
@ -155,51 +156,50 @@ fun SearchSettingsScreen() {
|
||||
},
|
||||
)
|
||||
} else {
|
||||
AnimatedVisibility(hasCalendarPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(R.string.missing_permission_calendar_search_settings),
|
||||
onClick = {
|
||||
viewModel.requestCalendarPermission(context as AppCompatActivity)
|
||||
|
||||
GuardedPreference(
|
||||
locked = hasCalendarPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestCalendarPermission(context as AppCompatActivity)
|
||||
},
|
||||
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),
|
||||
switchValue = calendar == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setCalendarSearch(it)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
icon = Icons.Rounded.Today,
|
||||
enabled = hasCalendarPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/local")
|
||||
}
|
||||
)
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_calendar),
|
||||
summary = stringResource(R.string.preference_search_calendar_summary),
|
||||
switchValue = calendar == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setCalendarSearch(it)
|
||||
},
|
||||
icon = Icons.Rounded.Today,
|
||||
enabled = hasCalendarPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/local")
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(hasAppShortcutsPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(
|
||||
R.string.missing_permission_appshortcuts_search_settings,
|
||||
stringResource(R.string.app_name)
|
||||
),
|
||||
onClick = {
|
||||
viewModel.requestAppShortcutsPermission(context as AppCompatActivity)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_appshortcuts),
|
||||
summary = stringResource(R.string.preference_search_appshortcuts_summary),
|
||||
icon = Icons.Rounded.AppShortcut,
|
||||
value = appShortcuts == true && hasAppShortcutsPermission == true,
|
||||
onValueChanged = {
|
||||
viewModel.setAppShortcuts(it)
|
||||
GuardedPreference(
|
||||
locked = hasAppShortcutsPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestAppShortcutsPermission(context as AppCompatActivity)
|
||||
},
|
||||
enabled = hasAppShortcutsPermission == true
|
||||
)
|
||||
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),
|
||||
icon = Icons.Rounded.AppShortcut,
|
||||
value = appShortcuts == true && hasAppShortcutsPermission == true,
|
||||
onValueChanged = {
|
||||
viewModel.setAppShortcuts(it)
|
||||
},
|
||||
enabled = hasAppShortcutsPermission == true
|
||||
)
|
||||
}
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_calculator),
|
||||
@ -246,43 +246,38 @@ fun SearchSettingsScreen() {
|
||||
viewModel.setWebsites(it)
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(hasLocationPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(
|
||||
R.string.missing_permission_location_search,
|
||||
),
|
||||
onClick = {
|
||||
viewModel.requestLocationPermission(context as AppCompatActivity)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (hasLocationPlugins != false) {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_search_locations),
|
||||
summary = stringResource(R.string.preference_search_locations_summary),
|
||||
icon = Icons.Rounded.Place,
|
||||
enabled = hasLocationPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/locations")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_locations),
|
||||
summary = stringResource(R.string.preference_search_locations_summary),
|
||||
icon = Icons.Rounded.Place,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/locations")
|
||||
},
|
||||
switchValue = places == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setPlacesSearch(it)
|
||||
},
|
||||
enabled = hasLocationPermission == true,
|
||||
)
|
||||
GuardedPreference(
|
||||
locked = hasLocationPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestLocationPermission(context as AppCompatActivity)
|
||||
},
|
||||
description = stringResource(R.string.missing_permission_location_search),
|
||||
) {
|
||||
if (hasLocationPlugins != false) {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_search_locations),
|
||||
summary = stringResource(R.string.preference_search_locations_summary),
|
||||
icon = Icons.Rounded.Place,
|
||||
enabled = hasLocationPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/locations")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_locations),
|
||||
summary = stringResource(R.string.preference_search_locations_summary),
|
||||
icon = Icons.Rounded.Place,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/locations")
|
||||
},
|
||||
switchValue = places == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setPlacesSearch(it)
|
||||
},
|
||||
enabled = hasLocationPermission == true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Preference(
|
||||
@ -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 = {
|
||||
|
||||
@ -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
|
||||
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)
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
)
|
||||
MaterialTheme(
|
||||
shapes = previewShapes
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,55 +43,63 @@ fun TasksIntegrationSettingsScreen() {
|
||||
) {
|
||||
if (isTasksInstalled == false) {
|
||||
item {
|
||||
Banner(
|
||||
text = stringResource(
|
||||
R.string.preference_tasks_integration_description,
|
||||
stringResource(R.string.app_name)
|
||||
),
|
||||
icon = Icons.Rounded.Info,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
primaryAction = {
|
||||
Button(onClick = {
|
||||
viewModel.downloadTasksApp(activity as AppCompatActivity)
|
||||
}) {
|
||||
Text(stringResource(R.string.action_install))
|
||||
PreferenceCategory {
|
||||
Banner(
|
||||
text = stringResource(
|
||||
R.string.preference_tasks_integration_description,
|
||||
stringResource(R.string.app_name)
|
||||
),
|
||||
icon = Icons.Rounded.Info,
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(16.dp),
|
||||
primaryAction = {
|
||||
Button(onClick = {
|
||||
viewModel.downloadTasksApp(activity as AppCompatActivity)
|
||||
}) {
|
||||
Text(stringResource(R.string.action_install))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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),
|
||||
)
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
icon = Icons.Rounded.TaskAlt,
|
||||
title = stringResource(R.string.preference_search_tasks),
|
||||
summary = stringResource(R.string.preference_search_tasks_summary),
|
||||
switchValue = isTasksSearchEnabled == true && hasTasksPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setTasksSearchEnabled(it)
|
||||
GuardedPreference(
|
||||
locked = hasTasksPermission == false,
|
||||
onUnlock = {
|
||||
viewModel.requestTasksPermission(activity as AppCompatActivity)
|
||||
},
|
||||
enabled = hasTasksPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/tasks.org")
|
||||
}
|
||||
)
|
||||
description = stringResource(R.string.missing_permission_tasks_integration),
|
||||
) {
|
||||
PreferenceWithSwitch(
|
||||
icon = Icons.Rounded.TaskAlt,
|
||||
title = stringResource(R.string.preference_search_tasks),
|
||||
summary = stringResource(R.string.preference_search_tasks_summary),
|
||||
switchValue = isTasksSearchEnabled == true && hasTasksPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setTasksSearchEnabled(it)
|
||||
},
|
||||
enabled = hasTasksPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/tasks.org")
|
||||
}
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_launch_tasks_app),
|
||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||
|
||||
@ -45,6 +45,8 @@ fun UnitConverterSettingsScreen() {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
PreferenceCategory {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_search_supportedunits),
|
||||
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user