Restructure settings

This commit is contained in:
MM20
2023-04-13 00:53:19 +02:00
parent 971b015d9a
commit dfe1c83c80
42 changed files with 1550 additions and 1484 deletions
@@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.launcher.sheets
import WidgetsService
import android.app.Application
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.content.pm.PackageManager
@@ -10,6 +11,7 @@ import androidx.compose.material.icons.rounded.Star
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
@@ -32,11 +34,9 @@ import org.koin.core.component.get
class WidgetPickerSheetVM(
private val widgetsService: WidgetsService,
private val context: Context,
private val packageManager: PackageManager,
) : ViewModel() {
private val packageManager = context.packageManager
val searchQuery = MutableStateFlow("")
private val enabledWidgets = widgetsService.getWidgets()
@@ -23,10 +23,9 @@ import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
import de.mm20.launcher2.ui.settings.accounts.AccountsSettingsScreen
import de.mm20.launcher2.ui.settings.integrations.IntegrationsSettingsScreen
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen
import de.mm20.launcher2.ui.settings.badges.BadgeSettingsScreen
import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
import de.mm20.launcher2.ui.settings.calendarwidget.CalendarWidgetSettingsScreen
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
@@ -41,17 +40,17 @@ import de.mm20.launcher2.ui.settings.favorites.FavoritesSettingsScreen
import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen
import de.mm20.launcher2.ui.settings.gestures.GestureSettingsScreen
import de.mm20.launcher2.ui.settings.hiddenitems.HiddenItemsSettingsScreen
import de.mm20.launcher2.ui.settings.layout.LayoutSettingsScreen
import de.mm20.launcher2.ui.settings.homescreen.HomescreenSettingsScreen
import de.mm20.launcher2.ui.settings.icons.IconsSettingsScreen
import de.mm20.launcher2.ui.settings.license.LicenseScreen
import de.mm20.launcher2.ui.settings.log.LogScreen
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
import de.mm20.launcher2.ui.settings.musicwidget.MusicWidgetSettingsScreen
import de.mm20.launcher2.ui.settings.media.MediaIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
import de.mm20.launcher2.ui.settings.searchactions.SearchActionsSettingsScreen
import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen
import de.mm20.launcher2.ui.settings.weatherwidget.WeatherWidgetSettingsScreen
import de.mm20.launcher2.ui.settings.widgets.WidgetsSettingsScreen
import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.wikipedia.WikipediaSettingsScreen
import de.mm20.launcher2.ui.theme.LauncherTheme
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
@@ -101,8 +100,11 @@ class SettingsActivity : BaseActivity() {
composable("settings/appearance") {
AppearanceSettingsScreen()
}
composable("settings/appearance/layout") {
LayoutSettingsScreen()
composable("settings/homescreen") {
HomescreenSettingsScreen()
}
composable("settings/icons") {
IconsSettingsScreen()
}
composable("settings/appearance/colorscheme") {
ColorSchemeSettingsScreen()
@@ -137,29 +139,23 @@ class SettingsActivity : BaseActivity() {
composable("settings/search/tags") {
TagsSettingsScreen()
}
composable("settings/widgets") {
WidgetsSettingsScreen()
composable("settings/integrations/weather") {
WeatherIntegrationSettingsScreen()
}
composable("settings/widgets/weather") {
WeatherWidgetSettingsScreen()
}
composable("settings/widgets/music") {
MusicWidgetSettingsScreen()
composable("settings/integrations/media") {
MediaIntegrationSettingsScreen()
}
composable("settings/widgets/calendar") {
CalendarWidgetSettingsScreen()
}
composable("settings/widgets/clock") {
composable("settings/homescreen/clock") {
ClockWidgetSettingsScreen()
}
composable("settings/favorites") {
FavoritesSettingsScreen()
}
composable("settings/badges") {
BadgeSettingsScreen()
}
composable("settings/accounts") {
AccountsSettingsScreen()
composable("settings/integrations") {
IntegrationsSettingsScreen()
}
composable("settings/about") {
AboutSettingsScreen()
@@ -86,13 +86,6 @@ fun AppearanceSettingsScreen() {
PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) {
item {
PreferenceCategory {
Preference(
title = stringResource(id = R.string.preference_layout),
summary = stringResource(id = R.string.preference_layout_summary),
onClick = {
navController?.navigate("settings/appearance/layout")
}
)
val theme by viewModel.theme.observeAsState()
ListPreference(
title = stringResource(id = R.string.preference_theme),
@@ -147,466 +140,6 @@ fun AppearanceSettingsScreen() {
}
)
}
PreferenceCategory(title = stringResource(R.string.preference_category_grid)) {
val iconSize by viewModel.iconSize.observeAsState(48)
SliderPreference(
title = stringResource(R.string.preference_grid_icon_size),
value = iconSize,
step = 8,
min = 32,
max = 64,
onValueChanged = {
viewModel.setIconSize(it)
}
)
val showLabels by viewModel.showLabels.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_grid_labels),
summary = stringResource(R.string.preference_grid_labels_summary),
value = showLabels == true,
onValueChanged = {
viewModel.setShowLabels(it)
}
)
val columnCount by viewModel.columnCount.observeAsState(5)
SliderPreference(
title = stringResource(R.string.preference_grid_column_count),
value = columnCount,
min = 3,
max = 8,
onValueChanged = {
viewModel.setColumnCount(it)
}
)
}
PreferenceCategory(stringResource(id = R.string.preference_category_wallpaper)) {
Preference(
title = stringResource(R.string.wallpaper),
summary = stringResource(R.string.preference_wallpaper_summary),
onClick = {
viewModel.openWallpaperChooser(context as AppCompatActivity)
}
)
val dimWallpaper by viewModel.dimWallpaper.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_dim_wallpaper),
summary = stringResource(R.string.preference_dim_wallpaper_summary),
value = dimWallpaper == true,
onValueChanged = {
viewModel.setDimWallpaper(it)
}
)
val isBlurSupported = remember { viewModel.isBlurAvailable(context) }
if (isBlurSupported) {
val blurWallpaper by viewModel.blurWallpaper.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_blur_wallpaper),
summary = stringResource(
if (isBlurSupported) R.string.preference_blur_wallpaper_summary
else R.string.preference_blur_wallpaper_unsupported
),
value = blurWallpaper == true && isBlurSupported,
onValueChanged = {
viewModel.setBlurWallpaper(it)
},
enabled = isBlurSupported
)
}
}
PreferenceCategory(stringResource(R.string.preference_category_icons)) {
val iconShape by viewModel.iconShape.observeAsState(IconSettings.IconShape.PlatformDefault)
IconShapePreference(
title = stringResource(R.string.preference_icon_shape),
summary = getShapeName(iconShape),
value = iconShape,
onValueChanged = {
viewModel.setIconShape(it)
}
)
val adaptifyLegacyIcons by viewModel.adaptifyLegacyIcons.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_enforce_icon_shape),
summary = stringResource(R.string.preference_enforce_icon_shape_summary),
value = adaptifyLegacyIcons == true,
onValueChanged = {
viewModel.setAdaptifyLegacyIcons(it)
}
)
val themedIcons by viewModel.themedIcons.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_themed_icons),
summary = stringResource(R.string.preference_themed_icons_summary),
value = themedIcons == true,
onValueChanged = {
viewModel.setThemedIcons(it)
}
)
val forceThemedIcons by viewModel.forceThemedIcons.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_force_themed_icons),
summary = stringResource(R.string.preference_force_themed_icons_summary),
value = forceThemedIcons == true,
enabled = themedIcons == true,
onValueChanged = {
viewModel.setForceThemedIcons(it)
}
)
val iconPackPackage by viewModel.iconPack.observeAsState()
val iconPackThemed by viewModel.iconPackThemed.collectAsState(true)
val installedIconPacks by viewModel.installedIconPacks.collectAsState(emptyList())
val iconPack by remember {
derivedStateOf { installedIconPacks.firstOrNull { it.packageName == iconPackPackage } }
}
val items = installedIconPacks.map {
it.name to it
}
Row(
verticalAlignment = (Alignment.CenterVertically)
) {
Box(
modifier = Modifier.weight(1f)
) {
ListPreference(
title = stringResource(R.string.preference_icon_pack),
items = items,
summary = if (items.size <= 1) {
stringResource(R.string.preference_icon_pack_summary_empty)
} else {
iconPack?.name ?: "System"
},
enabled = installedIconPacks.size > 1,
value = iconPack,
onValueChanged = {
if (it != null) viewModel.setIconPack(it.packageName)
},
itemLabel = {
Column(
verticalArrangement = Arrangement.Center,
) {
Text(
text = it.label,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (it.value?.themed == true) {
Surface(
shape = MaterialTheme.shapes.extraSmall,
color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.padding(top = 4.dp)
) {
Row(
modifier = Modifier.padding(horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier
.size(20.dp)
.padding(end = 4.dp),
imageVector = Icons.Rounded.FormatPaint,
contentDescription = null,
)
Text(
text = stringResource(R.string.icon_pack_dynamic_colors),
style = MaterialTheme.typography.labelSmall
)
}
}
}
}
}
)
}
if (iconPack?.themed == true) {
Box(
modifier = Modifier
.height(36.dp)
.width(1.dp)
.alpha(0.38f)
.background(LocalContentColor.current)
)
Box(
modifier = Modifier
.padding(12.dp)
) {
PlainTooltipBox(tooltip = { Text(stringResource(R.string.icon_pack_dynamic_colors)) }) {
FilledIconToggleButton(
modifier = Modifier.tooltipAnchor(),
checked = iconPackThemed,
onCheckedChange = {
viewModel.setIconPackThemed(it)
}) {
Icon(
Icons.Rounded.FormatPaint,
stringResource(R.string.icon_pack_dynamic_colors)
)
}
}
}
}
}
}
PreferenceCategory(stringResource(R.string.preference_category_searchbar)) {
val searchBarStyle by viewModel.searchBarStyle.observeAsState()
SearchBarStylePreference(
title = stringResource(R.string.preference_search_bar_style),
summary = stringResource(R.string.preference_search_bar_style_summary),
value = searchBarStyle,
onValueChanged = {
viewModel.setSearchBarStyle(it)
}
)
AnimatedVisibility(searchBarStyle == SearchBarStyle.Transparent) {
val searchBarColor by viewModel.searchBarColor.observeAsState()
ListPreference(
title = stringResource(R.string.preference_search_bar_color),
value = searchBarColor,
items = listOf(
stringResource(R.string.preference_system_bar_icons_auto) to SearchBarColors.Auto,
stringResource(R.string.preference_system_bar_icons_light) to SearchBarColors.Light,
stringResource(R.string.preference_system_bar_icons_dark) to SearchBarColors.Dark,
),
onValueChanged = {
if (it != null) viewModel.setSearchBarColor(it)
}
)
}
}
PreferenceCategory(stringResource(R.string.preference_category_system_bars)) {
val lightStatusBar by viewModel.statusBarIcons.observeAsState()
ListPreference(
title = stringResource(R.string.preference_status_bar_icons),
value = lightStatusBar,
items = listOf(
stringResource(R.string.preference_system_bar_icons_auto) to SystemBarColors.Auto,
stringResource(R.string.preference_system_bar_icons_light) to SystemBarColors.Light,
stringResource(R.string.preference_system_bar_icons_dark) to SystemBarColors.Dark,
),
onValueChanged = {
if (it != null) viewModel.setLightStatusBar(it)
}
)
val lightNavBar by viewModel.navBarIcons.observeAsState()
ListPreference(
title = stringResource(R.string.preference_nav_bar_icons),
value = lightNavBar,
items = listOf(
stringResource(R.string.preference_system_bar_icons_auto) to SystemBarColors.Auto,
stringResource(R.string.preference_system_bar_icons_light) to SystemBarColors.Light,
stringResource(R.string.preference_system_bar_icons_dark) to SystemBarColors.Dark,
),
onValueChanged = {
if (it != null) viewModel.setLightNavBar(it)
}
)
val hideStatusBar by viewModel.hideStatusBar.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_hide_status_bar),
value = hideStatusBar == true,
onValueChanged = {
viewModel.setHideStatusBar(it)
}
)
val hideNavBar by viewModel.hideNavBar.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_hide_nav_bar),
value = hideNavBar == true,
onValueChanged = {
viewModel.setHideNavBar(it)
}
)
}
}
}
}
@Composable
fun SearchBarStylePreference(
title: String,
summary: String? = null,
value: SearchBarSettings.SearchBarStyle?,
onValueChanged: (SearchBarSettings.SearchBarStyle) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) {
val styles = remember {
SearchBarSettings.SearchBarStyle.values()
.filter { it != SearchBarSettings.SearchBarStyle.UNRECOGNIZED }
}
val pagerState = rememberPagerState(styles.indexOf(value))
var level by remember { mutableStateOf(SearchBarLevel.Resting) }
var previewSearchValue by remember { mutableStateOf("") }
LaunchedEffect(null) {
while (isActive) {
delay(2000)
level = SearchBarLevel.Active
delay(1000)
previewSearchValue = "A"
delay(100)
previewSearchValue = "AB"
delay(100)
previewSearchValue = "ABC"
delay(800)
level = SearchBarLevel.Raised
delay(2000)
level = SearchBarLevel.Resting
previewSearchValue = ""
}
}
AlertDialog(
onDismissRequest = { showDialog = false },
confirmButton = {
TextButton(onClick = {
showDialog = false
onValueChanged(styles[pagerState.currentPage])
}) {
Text(
text = stringResource(android.R.string.ok),
)
}
},
dismissButton = {
TextButton(onClick = { showDialog = false }) {
Text(
text = stringResource(android.R.string.cancel),
)
}
},
text = {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
HorizontalPager(
count = styles.size,
state = pagerState,
modifier = Modifier
.height(150.dp)
.padding(bottom = 16.dp)
.background(MaterialTheme.colorScheme.secondary)
) {
SearchBar(
modifier = Modifier.padding(8.dp),
level = level,
style = styles[it],
value = previewSearchValue,
onValueChange = {}
)
}
HorizontalPagerIndicator(pagerState = pagerState)
}
}
)
}
}
@Composable
fun IconShapePreference(
title: String,
summary: String? = null,
value: IconSettings.IconShape?,
onValueChanged: (IconSettings.IconShape) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) {
val shapes = remember {
IconSettings.IconShape.values()
.filter { it != IconSettings.IconShape.UNRECOGNIZED && it != IconSettings.IconShape.EasterEgg }
}
Dialog(onDismissRequest = { showDialog = false }) {
Surface(
tonalElevation = 16.dp,
shadowElevation = 16.dp,
shape = MaterialTheme.shapes.extraLarge,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
)
)
LazyVerticalGrid(
columns = GridCells.Adaptive(96.dp),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp)
) {
items(shapes) {
Column(
modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val context = LocalContext.current
ShapedLauncherIcon(
size = 48.dp,
icon = {
StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = ContextCompat.getDrawable(
context,
R.mipmap.ic_launcher_foreground
)!!,
scale = 1.5f,
),
backgroundLayer = StaticIconLayer(
icon = ColorDrawable(
context.getColor(R.color.ic_launcher_background)
)
)
)
},
onClick = {
onValueChanged(it)
showDialog = false
},
shape = getShape(it)
)
Text(
getShapeName(it) ?: "",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}
}
}
}
}
@Composable
private fun getShapeName(shape: IconSettings.IconShape?): String? {
return stringResource(
when (shape) {
IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle
IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon
IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square
IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle
IconSettings.IconShape.Square -> R.string.preference_icon_shape_square
IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon
IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform
IconSettings.IconShape.Circle -> R.string.preference_icon_shape_circle
IconSettings.IconShape.Teardrop -> R.string.preference_icon_shape_teardrop
IconSettings.IconShape.Pebble -> R.string.preference_icon_shape_pebble
else -> return null
}
)
}
@@ -62,248 +62,4 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
}
}
}
val columnCount = dataStore.data.map { it.grid.columnCount }.asLiveData()
fun setColumnCount(columnCount: Int) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setGrid(it.grid.toBuilder().setColumnCount(columnCount))
.build()
}
}
}
val iconSize = dataStore.data.map { it.grid.iconSize }.asLiveData()
fun setIconSize(iconSize: Int) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setGrid(it.grid.toBuilder().setIconSize(iconSize))
.build()
}
}
}
val showLabels = dataStore.data.map { it.grid.showLabels }.asLiveData()
fun setShowLabels(showLabels: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setGrid(it.grid.toBuilder().setShowLabels(showLabels))
.build()
}
}
}
val dimWallpaper = dataStore.data.map { it.appearance.dimWallpaper }.asLiveData()
fun setDimWallpaper(dimWallpaper: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setAppearance(it.appearance.toBuilder().setDimWallpaper(dimWallpaper))
.build()
}
}
}
fun isBlurAvailable(context: Context): Boolean {
if (!isAtLeastApiLevel(31)) return false
return context.getSystemService<WindowManager>()?.isCrossWindowBlurEnabled == true
}
val blurWallpaper = dataStore.data.map { it.appearance.blurWallpaper }.asLiveData()
fun setBlurWallpaper(blurWallpaper: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setAppearance(it.appearance.toBuilder().setBlurWallpaper(blurWallpaper))
.build()
}
}
}
fun openWallpaperChooser(context: AppCompatActivity) {
context.startActivity(Intent.createChooser(Intent(Intent.ACTION_SET_WALLPAPER), null))
}
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }.asLiveData()
fun setSearchBarStyle(searchBarStyle: SearchBarSettings.SearchBarStyle) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSearchBar(
it.searchBar.toBuilder()
.setSearchBarStyle(searchBarStyle)
)
.build()
}
}
}
val iconShape = dataStore.data.map { it.icons.shape }.asLiveData()
fun setIconShape(iconShape: Settings.IconSettings.IconShape) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setShape(iconShape)
)
.build()
}
}
}
val adaptifyLegacyIcons = dataStore.data.map { it.icons.adaptify }.asLiveData()
fun setAdaptifyLegacyIcons(adaptify: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setAdaptify(adaptify)
)
.build()
}
}
}
val themedIcons = dataStore.data.map { it.icons.themedIcons }.asLiveData()
fun setThemedIcons(themedIcons: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setThemedIcons(themedIcons)
)
.build()
}
}
}
val forceThemedIcons = dataStore.data.map { it.icons.forceThemed }.asLiveData()
fun setForceThemedIcons(forceThemedIcons: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setForceThemed(forceThemedIcons)
)
.build()
}
}
}
val installedIconPacks: Flow<List<IconPack>> = iconService.getInstalledIconPacks().map {
listOf(
IconPack(
name = "System",
packageName = "",
version = "",
)
) + it
}
val iconPackThemed = dataStore.data.map { it.icons.iconPackThemed }
fun setIconPackThemed(iconPackThemed: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons
.toBuilder()
.setIconPackThemed(iconPackThemed)
)
.build()
}
}
}
val iconPack = dataStore.data.map { it.icons.iconPack }.asLiveData()
fun setIconPack(iconPack: String) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setIconPack(iconPack)
)
.build()
}
}
}
val searchBarColor = dataStore.data.map { it.searchBar.color }.asLiveData()
fun setSearchBarColor(color: SearchBarColors) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSearchBar(
it.searchBar.toBuilder()
.setColor(color)
)
.build()
}
}
}
val statusBarIcons = dataStore.data.map { it.systemBars.statusBarColor }.asLiveData()
fun setLightStatusBar(statusBarColor: SystemBarsSettings.SystemBarColors) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setStatusBarColor(statusBarColor)
)
.build()
}
}
}
val navBarIcons = dataStore.data.map { it.systemBars.navBarColor }.asLiveData()
fun setLightNavBar(navBarColors: SystemBarsSettings.SystemBarColors) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setNavBarColor(navBarColors)
)
.build()
}
}
}
val hideStatusBar = dataStore.data.map { it.systemBars.hideStatusBar }.asLiveData()
fun setHideStatusBar(hideStatusBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setHideStatusBar(hideStatusBar)
)
.build()
}
}
}
val hideNavBar = dataStore.data.map { it.systemBars.hideNavBar }.asLiveData()
fun setHideNavBar(hideNavBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setHideNavBar(hideNavBar)
)
.build()
}
}
}
}
@@ -1,77 +0,0 @@
package de.mm20.launcher2.ui.settings.badges
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
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.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@Composable
fun BadgeSettingsScreen() {
val viewModel: BadgeSettingsScreenVM = viewModel()
val context = LocalContext.current
PreferenceScreen(title = stringResource(R.string.preference_screen_badges)) {
item {
PreferenceCategory {
val notifications by viewModel.notifications.observeAsState()
val hasNotificationsPermission by viewModel.hasNotificationsPermission.observeAsState()
AnimatedVisibility(hasNotificationsPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_notification_badges),
onClick = {
viewModel.requestNotificationsPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
SwitchPreference(
title = stringResource(R.string.preference_notification_badges),
summary = stringResource(R.string.preference_notification_badges_summary),
enabled = hasNotificationsPermission != false,
value = notifications == true && hasNotificationsPermission == true,
onValueChanged = {
viewModel.setNotifications(it)
}
)
val cloudFiles by viewModel.cloudFiles.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_cloud_badges),
summary = stringResource(R.string.preference_cloud_badges_summary),
value = cloudFiles == true,
onValueChanged = {
viewModel.setCloudFiles(it)
}
)
val suspendedApps by viewModel.suspendedApps.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_suspended_badges),
summary = stringResource(R.string.preference_suspended_badges_summary),
value = suspendedApps == true,
onValueChanged = {
viewModel.setSuspendedApps(it)
}
)
val shortcuts by viewModel.shortcuts.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_shortcut_badges),
summary = stringResource(R.string.preference_shortcut_badges_summary),
value = shortcuts == true,
onValueChanged = {
viewModel.setShortcuts(it)
}
)
}
}
}
}
@@ -1,81 +0,0 @@
package de.mm20.launcher2.ui.settings.badges
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class BadgeSettingsScreenVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val permissionsManager: PermissionsManager by inject()
val hasNotificationsPermission = permissionsManager.hasPermission(PermissionGroup.Notifications).asLiveData()
val notifications = dataStore.data.map { it.badges.notifications }.asLiveData()
fun setNotifications(notifications: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setNotifications(notifications)
)
.build()
}
}
}
fun requestNotificationsPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Notifications)
}
val cloudFiles = dataStore.data.map { it.badges.cloudFiles }.asLiveData()
fun setCloudFiles(cloudFiles: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setCloudFiles(cloudFiles)
)
.build()
}
}
}
val shortcuts = dataStore.data.map { it.badges.shortcuts }.asLiveData()
fun setShortcuts(shortcuts: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setShortcuts(shortcuts)
)
.build()
}
}
}
val suspendedApps = dataStore.data.map { it.badges.suspendedApps }.asLiveData()
fun setSuspendedApps(suspendedApps: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setSuspendedApps(suspendedApps)
)
.build()
}
}
}
}
@@ -24,6 +24,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.isAtLeastApiLevel
@@ -35,7 +36,6 @@ 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.ListPreference
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.ktx.toPixels
@@ -44,8 +44,8 @@ import de.mm20.launcher2.ui.ktx.toPixels
fun GestureSettingsScreen() {
val viewModel: GestureSettingsScreenVM = viewModel()
val layout by viewModel.layout.observeAsState()
val hasPermission by viewModel.hasPermission.observeAsState()
val layout by viewModel.baseLayout.collectAsStateWithLifecycle(null)
val hasPermission by viewModel.hasPermission.collectAsStateWithLifecycle(null)
val options = buildList {
add(stringResource(R.string.gesture_action_none) to GestureAction.None)
@@ -60,10 +60,26 @@ fun GestureSettingsScreen() {
val context = LocalContext.current
PreferenceScreen(title = stringResource(R.string.preference_screen_gestures)) {
item {
PreferenceCategory {
val baseLayout by viewModel.baseLayout.collectAsStateWithLifecycle(null)
ListPreference(title = stringResource(R.string.preference_layout_open_search),
items = listOf(
stringResource(R.string.open_search_pull_down) to Layout.PullDown,
stringResource(R.string.open_search_swipe_left) to Layout.Pager,
stringResource(R.string.open_search_swipe_right) to Layout.PagerReversed,
),
value = baseLayout,
onValueChanged = {
if (it != null) viewModel.setBaseLayout(it)
},
)
}
}
item {
val appIconSize = 32.dp.toPixels()
PreferenceCategory {
val doubleTap by viewModel.doubleTap.observeAsState()
val doubleTap by viewModel.doubleTap.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(doubleTap)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
@@ -86,7 +102,7 @@ fun GestureSettingsScreen() {
onAppChanged = { viewModel.setDoubleTapApp(it) }
)
val longPress by viewModel.longPress.observeAsState()
val longPress by viewModel.longPress.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(longPress)) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
@@ -109,7 +125,7 @@ fun GestureSettingsScreen() {
onAppChanged = { viewModel.setLongPressApp(it) }
)
val swipeDown by viewModel.swipeDown.observeAsState()
val swipeDown by viewModel.swipeDown.collectAsStateWithLifecycle(null)
val swipeDownIsSearch = layout == Layout.PullDown
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeDown) && !swipeDownIsSearch) {
MissingPermissionBanner(
@@ -133,7 +149,7 @@ fun GestureSettingsScreen() {
onAppChanged = { viewModel.setSwipeDownApp(it) }
)
val swipeLeft by viewModel.swipeLeft.observeAsState()
val swipeLeft by viewModel.swipeLeft.collectAsStateWithLifecycle(null)
val swipeLeftIsSearch = layout == Layout.Pager
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeLeft) && !swipeLeftIsSearch) {
MissingPermissionBanner(
@@ -157,7 +173,7 @@ fun GestureSettingsScreen() {
onAppChanged = { viewModel.setSwipeLeftApp(it) }
)
val swipeRight by viewModel.swipeRight.observeAsState()
val swipeRight by viewModel.swipeRight.collectAsStateWithLifecycle(null)
val swipeRightIsSearch = layout == Layout.PagerReversed
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeRight) && !swipeRightIsSearch) {
MissingPermissionBanner(
@@ -10,6 +10,7 @@ import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.flow.Flow
@@ -27,15 +28,25 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
private val searchableRepository: SearchableRepository by inject()
private val iconService: IconService by inject()
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility).asLiveData()
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility)
val layout = dataStore.data.map { it.layout.baseLayout }.asLiveData()
val baseLayout = dataStore.data.map { it.layout.baseLayout }
val swipeDown = dataStore.data.map { it.gestures.swipeDown }.asLiveData()
val swipeLeft = dataStore.data.map { it.gestures.swipeLeft }.asLiveData()
val swipeRight = dataStore.data.map { it.gestures.swipeRight }.asLiveData()
val doubleTap = dataStore.data.map { it.gestures.doubleTap }.asLiveData()
val longPress = dataStore.data.map { it.gestures.longPress }.asLiveData()
fun setBaseLayout(baseLayout: Settings.LayoutSettings.Layout) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setBaseLayout(baseLayout))
.build()
}
}
}
val swipeDown = dataStore.data.map { it.gestures.swipeDown }
val swipeLeft = dataStore.data.map { it.gestures.swipeLeft }
val swipeRight = dataStore.data.map { it.gestures.swipeRight }
val doubleTap = dataStore.data.map { it.gestures.doubleTap }
val longPress = dataStore.data.map { it.gestures.longPress }
fun setSwipeDown(action: GestureAction) {
viewModelScope.launch {
@@ -0,0 +1,303 @@
package de.mm20.launcher2.ui.settings.homescreen
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.pager.HorizontalPagerIndicator
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.SearchBar
import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.component.preferences.ListPreference
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
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
@Composable
fun HomescreenSettingsScreen() {
val viewModel: HomescreenSettingsScreenVM =
viewModel(factory = HomescreenSettingsScreenVM.Factory)
val navController = LocalNavController.current
val context = LocalContext.current
val fixedRotation by viewModel.fixedRotation.collectAsStateWithLifecycle(null)
val editButton by viewModel.widgetEditButton.collectAsStateWithLifecycle(null)
val searchBarStyle by viewModel.searchBarStyle.collectAsStateWithLifecycle(null)
val searchBarColor by viewModel.searchBarColor.collectAsStateWithLifecycle(null)
val bottomSearchBar by viewModel.bottomSearchBar.collectAsStateWithLifecycle(null)
val fixedSearchBar by viewModel.fixedSearchBar.collectAsStateWithLifecycle(null)
val lightStatusBar by viewModel.statusBarIcons.collectAsStateWithLifecycle(null)
val dimWallpaper by viewModel.dimWallpaper.collectAsStateWithLifecycle(false)
val blurWallpaper by viewModel.blurWallpaper.collectAsStateWithLifecycle(false)
val lightNavBar by viewModel.navBarIcons.collectAsStateWithLifecycle(null)
val hideStatusBar by viewModel.hideStatusBar.collectAsStateWithLifecycle(null)
val hideNavBar by viewModel.hideNavBar.collectAsStateWithLifecycle(null)
PreferenceScreen(title = stringResource(id = R.string.preference_screen_homescreen)) {
item {
PreferenceCategory {
SwitchPreference(
title = stringResource(R.string.preference_layout_fixed_rotation),
summary = stringResource(R.string.preference_layout_fixed_rotation_summary),
value = fixedRotation == true,
onValueChanged = {
viewModel.setFixedRotation(it)
},
)
}
}
item {
PreferenceCategory(
title = stringResource(id = R.string.preference_category_widgets)
) {
Preference(
title = stringResource(R.string.preference_screen_clockwidget),
summary = stringResource(R.string.preference_screen_clockwidget_summary),
onClick = {
navController?.navigate("settings/homescreen/clock")
}
)
SwitchPreference(
title = stringResource(id = R.string.preference_edit_button),
summary = stringResource(id = R.string.preference_widgets_edit_button_summary),
value = editButton == true,
onValueChanged = {
viewModel.setWidgetEditButton(it)
})
}
}
item {
PreferenceCategory(stringResource(R.string.preference_category_searchbar)) {
SearchBarStylePreference(
title = stringResource(R.string.preference_search_bar_style),
summary = stringResource(R.string.preference_search_bar_style_summary),
value = searchBarStyle,
onValueChanged = {
viewModel.setSearchBarStyle(it)
}
)
AnimatedVisibility(searchBarStyle == Settings.SearchBarSettings.SearchBarStyle.Transparent) {
ListPreference(
title = stringResource(R.string.preference_search_bar_color),
value = searchBarColor,
items = listOf(
stringResource(R.string.preference_system_bar_icons_auto) to Settings.SearchBarSettings.SearchBarColors.Auto,
stringResource(R.string.preference_system_bar_icons_light) to Settings.SearchBarSettings.SearchBarColors.Light,
stringResource(R.string.preference_system_bar_icons_dark) to Settings.SearchBarSettings.SearchBarColors.Dark,
),
onValueChanged = {
if (it != null) viewModel.setSearchBarColor(it)
}
)
}
ListPreference(
title = stringResource(R.string.preference_layout_search_bar_position),
items = listOf(
stringResource(R.string.search_bar_position_top) to false,
stringResource(R.string.search_bar_position_bottom) to true,
),
value = bottomSearchBar,
onValueChanged = {
if (it != null) viewModel.setBottomSearchBar(it)
},
)
SwitchPreference(
title = stringResource(R.string.preference_layout_fixed_search_bar),
summary = stringResource(R.string.preference_layout_fixed_search_bar_summary),
value = fixedSearchBar == true,
onValueChanged = {
viewModel.setFixedSearchBar(it)
},
)
}
}
item {
PreferenceCategory(stringResource(id = R.string.preference_category_wallpaper)) {
Preference(
title = stringResource(R.string.wallpaper),
summary = stringResource(R.string.preference_wallpaper_summary),
onClick = {
viewModel.openWallpaperChooser(context as AppCompatActivity)
}
)
SwitchPreference(
title = stringResource(R.string.preference_dim_wallpaper),
summary = stringResource(R.string.preference_dim_wallpaper_summary),
value = dimWallpaper,
onValueChanged = {
viewModel.setDimWallpaper(it)
}
)
val isBlurSupported = remember { viewModel.isBlurAvailable(context) }
SwitchPreference(
title = stringResource(R.string.preference_blur_wallpaper),
summary = stringResource(
if (isBlurSupported) R.string.preference_blur_wallpaper_summary
else R.string.preference_blur_wallpaper_unsupported
),
value = blurWallpaper && isBlurSupported,
onValueChanged = {
viewModel.setBlurWallpaper(it)
},
enabled = isBlurSupported
)
}
}
item {
PreferenceCategory(stringResource(R.string.preference_category_system_bars)) {
ListPreference(
title = stringResource(R.string.preference_status_bar_icons),
value = lightStatusBar,
items = listOf(
stringResource(R.string.preference_system_bar_icons_auto) to Settings.SystemBarsSettings.SystemBarColors.Auto,
stringResource(R.string.preference_system_bar_icons_light) to Settings.SystemBarsSettings.SystemBarColors.Light,
stringResource(R.string.preference_system_bar_icons_dark) to Settings.SystemBarsSettings.SystemBarColors.Dark,
),
onValueChanged = {
if (it != null) viewModel.setLightStatusBar(it)
}
)
ListPreference(
title = stringResource(R.string.preference_nav_bar_icons),
value = lightNavBar,
items = listOf(
stringResource(R.string.preference_system_bar_icons_auto) to Settings.SystemBarsSettings.SystemBarColors.Auto,
stringResource(R.string.preference_system_bar_icons_light) to Settings.SystemBarsSettings.SystemBarColors.Light,
stringResource(R.string.preference_system_bar_icons_dark) to Settings.SystemBarsSettings.SystemBarColors.Dark,
),
onValueChanged = {
if (it != null) viewModel.setLightNavBar(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_hide_status_bar),
value = hideStatusBar == true,
onValueChanged = {
viewModel.setHideStatusBar(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_hide_nav_bar),
value = hideNavBar == true,
onValueChanged = {
viewModel.setHideNavBar(it)
}
)
}
}
}
}
@Composable
fun SearchBarStylePreference(
title: String,
summary: String? = null,
value: Settings.SearchBarSettings.SearchBarStyle?,
onValueChanged: (Settings.SearchBarSettings.SearchBarStyle) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) {
val styles = remember {
Settings.SearchBarSettings.SearchBarStyle.values()
.filter { it != Settings.SearchBarSettings.SearchBarStyle.UNRECOGNIZED }
}
val pagerState = rememberPagerState(styles.indexOf(value))
var level by remember { mutableStateOf(SearchBarLevel.Resting) }
var previewSearchValue by remember { mutableStateOf("") }
LaunchedEffect(null) {
while (isActive) {
delay(2000)
level = SearchBarLevel.Active
delay(1000)
previewSearchValue = "A"
delay(100)
previewSearchValue = "AB"
delay(100)
previewSearchValue = "ABC"
delay(800)
level = SearchBarLevel.Raised
delay(2000)
level = SearchBarLevel.Resting
previewSearchValue = ""
}
}
AlertDialog(
onDismissRequest = { showDialog = false },
confirmButton = {
TextButton(onClick = {
showDialog = false
onValueChanged(styles[pagerState.currentPage])
}) {
Text(
text = stringResource(android.R.string.ok),
)
}
},
dismissButton = {
TextButton(onClick = { showDialog = false }) {
Text(
text = stringResource(android.R.string.cancel),
)
}
},
text = {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
HorizontalPager(
pageCount = styles.size,
state = pagerState,
modifier = Modifier
.height(150.dp)
.padding(bottom = 16.dp)
.background(MaterialTheme.colorScheme.secondary)
) {
SearchBar(
modifier = Modifier.padding(8.dp),
level = level,
style = styles[it],
value = previewSearchValue,
onValueChange = {}
)
}
HorizontalPagerIndicator(pagerState = pagerState, pageCount = styles.size)
}
}
)
}
}
@@ -0,0 +1,204 @@
package de.mm20.launcher2.ui.settings.homescreen
import android.content.Context
import android.content.Intent
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class HomescreenSettingsScreenVM(
private val dataStore: LauncherDataStore,
) : ViewModel() {
val dimWallpaper = dataStore.data.map { it.appearance.dimWallpaper }
fun setDimWallpaper(dimWallpaper: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setAppearance(
it.appearance.toBuilder()
.setDimWallpaper(dimWallpaper)
).build()
}
}
}
val blurWallpaper = dataStore.data.map { it.appearance.blurWallpaper }
fun setBlurWallpaper(blurWallpaper: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setAppearance(
it.appearance.toBuilder()
.setBlurWallpaper(blurWallpaper)
).build()
}
}
}
fun openWallpaperChooser(context: AppCompatActivity) {
context.startActivity(Intent.createChooser(Intent(Intent.ACTION_SET_WALLPAPER), null))
}
fun isBlurAvailable(context: Context): Boolean {
if (!isAtLeastApiLevel(31)) return false
return context.getSystemService<WindowManager>()?.isCrossWindowBlurEnabled == true
}
val statusBarIcons = dataStore.data.map { it.systemBars.statusBarColor }
fun setLightStatusBar(statusBarColor: Settings.SystemBarsSettings.SystemBarColors) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setStatusBarColor(statusBarColor)
)
.build()
}
}
}
val navBarIcons = dataStore.data.map { it.systemBars.navBarColor }
fun setLightNavBar(navBarColors: Settings.SystemBarsSettings.SystemBarColors) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setNavBarColor(navBarColors)
)
.build()
}
}
}
val hideStatusBar = dataStore.data.map { it.systemBars.hideStatusBar }
fun setHideStatusBar(hideStatusBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setHideStatusBar(hideStatusBar)
)
.build()
}
}
}
val hideNavBar = dataStore.data.map { it.systemBars.hideNavBar }
fun setHideNavBar(hideNavBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSystemBars(
it.systemBars.toBuilder()
.setHideNavBar(hideNavBar)
)
.build()
}
}
}
val searchBarColor = dataStore.data.map { it.searchBar.color }
fun setSearchBarColor(color: Settings.SearchBarSettings.SearchBarColors) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSearchBar(
it.searchBar.toBuilder()
.setColor(color)
)
.build()
}
}
}
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }
fun setSearchBarStyle(searchBarStyle: Settings.SearchBarSettings.SearchBarStyle) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setSearchBar(
it.searchBar.toBuilder()
.setSearchBarStyle(searchBarStyle)
)
.build()
}
}
}
val fixedSearchBar = dataStore.data.map { it.layout.fixedSearchBar }
fun setFixedSearchBar(fixedSearchBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setFixedSearchBar(fixedSearchBar))
.build()
}
}
}
val bottomSearchBar = dataStore.data.map { it.layout.bottomSearchBar }
fun setBottomSearchBar(bottomSearchBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setBottomSearchBar(bottomSearchBar))
.build()
}
}
}
val fixedRotation = dataStore.data.map { it.layout.fixedRotation }
fun setFixedRotation(fixedRotation: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setFixedRotation(fixedRotation))
.build()
}
}
}
val widgetEditButton = dataStore.data.map { it.widgets.editButton }
fun setWidgetEditButton(editButton: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setWidgets(
it.widgets.toBuilder()
.setEditButton(editButton)
)
.build()
}
}
}
companion object : KoinComponent {
val Factory = viewModelFactory {
initializer {
HomescreenSettingsScreenVM(
dataStore = get()
)
}
}
}
}
@@ -0,0 +1,406 @@
package de.mm20.launcher2.ui.settings.icons
import android.graphics.drawable.ColorDrawable
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.FormatPaint
import androidx.compose.material3.FilledIconToggleButton
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltipBox
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.StaticIconLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
import de.mm20.launcher2.ui.component.getShape
import de.mm20.launcher2.ui.component.preferences.ListPreference
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.SliderPreference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
import de.mm20.launcher2.ui.component.preferences.label
import de.mm20.launcher2.ui.component.preferences.value
@Composable
fun IconsSettingsScreen() {
val viewModel: IconsSettingsScreenVM = viewModel(factory = IconsSettingsScreenVM.Factory)
val context = LocalContext.current
val iconSize by viewModel.iconSize.collectAsStateWithLifecycle(48)
val showLabels by viewModel.showLabels.collectAsStateWithLifecycle(null)
val columnCount by viewModel.columnCount.collectAsStateWithLifecycle(5)
val iconShape by viewModel.iconShape.collectAsStateWithLifecycle(Settings.IconSettings.IconShape.PlatformDefault)
val adaptifyLegacyIcons by viewModel.adaptifyLegacyIcons.collectAsStateWithLifecycle(null)
val themedIcons by viewModel.themedIcons.collectAsStateWithLifecycle(null)
val iconPackPackage by viewModel.iconPack.collectAsStateWithLifecycle(null)
val iconPackThemed by viewModel.iconPackThemed.collectAsState(true)
val installedIconPacks by viewModel.installedIconPacks.collectAsState(emptyList())
val forceThemedIcons by viewModel.forceThemedIcons.collectAsStateWithLifecycle(null)
val hasNotificationsPermission by viewModel.hasNotificationsPermission.collectAsStateWithLifecycle(null)
val notificationBadges by viewModel.notificationBadges.collectAsStateWithLifecycle(null)
val cloudFileBadges by viewModel.cloudFileBadges.collectAsStateWithLifecycle(null)
val suspendedAppBadges by viewModel.suspendedAppBadges.collectAsStateWithLifecycle(null)
val shortcutBadges by viewModel.shortcutBadges.collectAsStateWithLifecycle(null)
PreferenceScreen(title = stringResource(id = R.string.preference_screen_icons)) {
item {
PreferenceCategory(title = stringResource(R.string.preference_category_grid)) {
SliderPreference(
title = stringResource(R.string.preference_grid_icon_size),
value = iconSize,
step = 8,
min = 32,
max = 64,
onValueChanged = {
viewModel.setIconSize(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_grid_labels),
summary = stringResource(R.string.preference_grid_labels_summary),
value = showLabels == true,
onValueChanged = {
viewModel.setShowLabels(it)
}
)
SliderPreference(
title = stringResource(R.string.preference_grid_column_count),
value = columnCount,
min = 3,
max = 8,
onValueChanged = {
viewModel.setColumnCount(it)
}
)
}
}
item {
PreferenceCategory(stringResource(R.string.preference_category_icons)) {
IconShapePreference(
title = stringResource(R.string.preference_icon_shape),
summary = getShapeName(iconShape),
value = iconShape,
onValueChanged = {
viewModel.setIconShape(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_enforce_icon_shape),
summary = stringResource(R.string.preference_enforce_icon_shape_summary),
value = adaptifyLegacyIcons == true,
onValueChanged = {
viewModel.setAdaptifyLegacyIcons(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_themed_icons),
summary = stringResource(R.string.preference_themed_icons_summary),
value = themedIcons == true,
onValueChanged = {
viewModel.setThemedIcons(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_force_themed_icons),
summary = stringResource(R.string.preference_force_themed_icons_summary),
value = forceThemedIcons == true,
enabled = themedIcons == true,
onValueChanged = {
viewModel.setForceThemedIcons(it)
}
)
val iconPack by remember {
derivedStateOf { installedIconPacks.firstOrNull { it.packageName == iconPackPackage } }
}
val items = installedIconPacks.map {
it.name to it
}
Row(
verticalAlignment = (Alignment.CenterVertically)
) {
Box(
modifier = Modifier.weight(1f)
) {
ListPreference(
title = stringResource(R.string.preference_icon_pack),
items = items,
summary = if (items.size <= 1) {
stringResource(R.string.preference_icon_pack_summary_empty)
} else {
iconPack?.name ?: "System"
},
enabled = installedIconPacks.size > 1,
value = iconPack,
onValueChanged = {
if (it != null) viewModel.setIconPack(it.packageName)
},
itemLabel = {
Column(
verticalArrangement = Arrangement.Center,
) {
Text(
text = it.label,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (it.value?.themed == true) {
Surface(
shape = MaterialTheme.shapes.extraSmall,
color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.padding(top = 4.dp)
) {
Row(
modifier = Modifier.padding(horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier
.size(20.dp)
.padding(end = 4.dp),
imageVector = Icons.Rounded.FormatPaint,
contentDescription = null,
)
Text(
text = stringResource(R.string.icon_pack_dynamic_colors),
style = MaterialTheme.typography.labelSmall
)
}
}
}
}
}
)
}
if (iconPack?.themed == true) {
Box(
modifier = Modifier
.height(36.dp)
.width(1.dp)
.alpha(0.38f)
.background(LocalContentColor.current)
)
Box(
modifier = Modifier
.padding(12.dp)
) {
PlainTooltipBox(tooltip = { Text(stringResource(R.string.icon_pack_dynamic_colors)) }) {
FilledIconToggleButton(
modifier = Modifier.tooltipAnchor(),
checked = iconPackThemed,
onCheckedChange = {
viewModel.setIconPackThemed(it)
}) {
Icon(
Icons.Rounded.FormatPaint,
stringResource(R.string.icon_pack_dynamic_colors)
)
}
}
}
}
}
}
}
item {
PreferenceCategory(
title = stringResource(R.string.preference_category_badges),
) {
AnimatedVisibility(hasNotificationsPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_notification_badges),
onClick = {
viewModel.requestNotificationsPermission(context as AppCompatActivity)
},
modifier = Modifier.padding(16.dp)
)
}
SwitchPreference(
title = stringResource(R.string.preference_notification_badges),
summary = stringResource(R.string.preference_notification_badges_summary),
enabled = hasNotificationsPermission != false,
value = notificationBadges == true && hasNotificationsPermission == true,
onValueChanged = {
viewModel.setNotifications(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_cloud_badges),
summary = stringResource(R.string.preference_cloud_badges_summary),
value = cloudFileBadges == true,
onValueChanged = {
viewModel.setCloudFiles(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_suspended_badges),
summary = stringResource(R.string.preference_suspended_badges_summary),
value = suspendedAppBadges == true,
onValueChanged = {
viewModel.setSuspendedApps(it)
}
)
SwitchPreference(
title = stringResource(R.string.preference_shortcut_badges),
summary = stringResource(R.string.preference_shortcut_badges_summary),
value = shortcutBadges == true,
onValueChanged = {
viewModel.setShortcuts(it)
}
)
}
}
}
}
@Composable
fun IconShapePreference(
title: String,
summary: String? = null,
value: Settings.IconSettings.IconShape?,
onValueChanged: (Settings.IconSettings.IconShape) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) {
val shapes = remember {
Settings.IconSettings.IconShape.values()
.filter { it != Settings.IconSettings.IconShape.UNRECOGNIZED && it != Settings.IconSettings.IconShape.EasterEgg }
}
Dialog(onDismissRequest = { showDialog = false }) {
Surface(
tonalElevation = 16.dp,
shadowElevation = 16.dp,
shape = MaterialTheme.shapes.extraLarge,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
)
)
LazyVerticalGrid(
columns = GridCells.Adaptive(96.dp),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp)
) {
items(shapes) {
Column(
modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val context = LocalContext.current
ShapedLauncherIcon(
size = 48.dp,
icon = {
StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = ContextCompat.getDrawable(
context,
R.mipmap.ic_launcher_foreground
)!!,
scale = 1.5f,
),
backgroundLayer = StaticIconLayer(
icon = ColorDrawable(
context.getColor(R.color.ic_launcher_background)
)
)
)
},
onClick = {
onValueChanged(it)
showDialog = false
},
shape = getShape(it)
)
Text(
getShapeName(it) ?: "",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}
}
}
}
}
@Composable
private fun getShapeName(shape: Settings.IconSettings.IconShape?): String? {
return stringResource(
when (shape) {
Settings.IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle
Settings.IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon
Settings.IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square
Settings.IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle
Settings.IconSettings.IconShape.Square -> R.string.preference_icon_shape_square
Settings.IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon
Settings.IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform
Settings.IconSettings.IconShape.Circle -> R.string.preference_icon_shape_circle
Settings.IconSettings.IconShape.Teardrop -> R.string.preference_icon_shape_teardrop
Settings.IconSettings.IconShape.Pebble -> R.string.preference_icon_shape_pebble
else -> return null
}
)
}
@@ -0,0 +1,229 @@
package de.mm20.launcher2.ui.settings.icons
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import de.mm20.launcher2.icons.IconPack
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class IconsSettingsScreenVM(
private val dataStore: LauncherDataStore,
private val iconService: IconService,
private val permissionsManager: PermissionsManager,
) : ViewModel() {
val columnCount = dataStore.data.map { it.grid.columnCount }
fun setColumnCount(columnCount: Int) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setGrid(it.grid.toBuilder().setColumnCount(columnCount))
.build()
}
}
}
val iconSize = dataStore.data.map { it.grid.iconSize }
fun setIconSize(iconSize: Int) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setGrid(it.grid.toBuilder().setIconSize(iconSize))
.build()
}
}
}
val showLabels = dataStore.data.map { it.grid.showLabels }
fun setShowLabels(showLabels: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setGrid(it.grid.toBuilder().setShowLabels(showLabels))
.build()
}
}
}
val iconShape = dataStore.data.map { it.icons.shape }
fun setIconShape(iconShape: Settings.IconSettings.IconShape) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setShape(iconShape)
)
.build()
}
}
}
val adaptifyLegacyIcons = dataStore.data.map { it.icons.adaptify }
fun setAdaptifyLegacyIcons(adaptify: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setAdaptify(adaptify)
)
.build()
}
}
}
val themedIcons = dataStore.data.map { it.icons.themedIcons }
fun setThemedIcons(themedIcons: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setThemedIcons(themedIcons)
)
.build()
}
}
}
val forceThemedIcons = dataStore.data.map { it.icons.forceThemed }
fun setForceThemedIcons(forceThemedIcons: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setForceThemed(forceThemedIcons)
)
.build()
}
}
}
val installedIconPacks: Flow<List<IconPack>> = iconService.getInstalledIconPacks().map {
listOf(
IconPack(
name = "System",
packageName = "",
version = "",
)
) + it
}
val iconPackThemed = dataStore.data.map { it.icons.iconPackThemed }
fun setIconPackThemed(iconPackThemed: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons
.toBuilder()
.setIconPackThemed(iconPackThemed)
)
.build()
}
}
}
val iconPack = dataStore.data.map { it.icons.iconPack }
fun setIconPack(iconPack: String) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setIconPack(iconPack)
)
.build()
}
}
}
val hasNotificationsPermission = permissionsManager.hasPermission(PermissionGroup.Notifications)
val notificationBadges = dataStore.data.map { it.badges.notifications }
fun setNotifications(notifications: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setNotifications(notifications)
)
.build()
}
}
}
fun requestNotificationsPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Notifications)
}
val cloudFileBadges = dataStore.data.map { it.badges.cloudFiles }
fun setCloudFiles(cloudFiles: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setCloudFiles(cloudFiles)
)
.build()
}
}
}
val shortcutBadges = dataStore.data.map { it.badges.shortcuts }
fun setShortcuts(shortcuts: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setShortcuts(shortcuts)
)
.build()
}
}
}
val suspendedAppBadges = dataStore.data.map { it.badges.suspendedApps }
fun setSuspendedApps(suspendedApps: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setBadges(
it.badges.toBuilder()
.setSuspendedApps(suspendedApps)
)
.build()
}
}
}
companion object : KoinComponent {
val Factory = viewModelFactory {
initializer {
IconsSettingsScreenVM(
dataStore = get(),
iconService = get(),
permissionsManager = get(),
)
}
}
}
}
@@ -1,17 +1,24 @@
package de.mm20.launcher2.ui.settings.accounts
package de.mm20.launcher2.ui.settings.integrations
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.PlayCircleOutline
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -31,12 +38,14 @@ 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.locals.LocalNavController
@Composable
fun AccountsSettingsScreen() {
val viewModel: AccountsSettingsScreenVM = viewModel()
fun IntegrationsSettingsScreen() {
val viewModel: IntegrationsSettingsScreenVM = viewModel()
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val navController = LocalNavController.current
LaunchedEffect(null) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
@@ -44,76 +53,91 @@ fun AccountsSettingsScreen() {
}
}
val loading by viewModel.loading.observeAsState(true)
val owncloudUser by viewModel.owncloudUser
val nextcloudUser by viewModel.nextcloudUser
val msUser by viewModel.msUser
val googleUser by viewModel.googleUser
PreferenceScreen(title = stringResource(R.string.preference_screen_services)) {
val loading by viewModel.loading
PreferenceScreen(title = stringResource(R.string.preference_screen_integrations)) {
if (loading) {
item {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
return@PreferenceScreen
}
item {
PreferenceCategory(title = stringResource(R.string.preference_category_services_nextcloud)) {
val account by viewModel.nextcloudUser.observeAsState()
Preference(
title = stringResource(R.string.preference_weather_integration),
icon = Icons.Rounded.LightMode,
onClick = {
navController?.navigate("settings/integrations/weather")
}
)
Preference(
title = stringResource(R.string.preference_media_integration),
icon = Icons.Rounded.PlayCircleOutline,
onClick = {
navController?.navigate("settings/integrations/media")
}
)
}
item {
PreferenceCategory(
title = stringResource(id = R.string.preference_category_accounts)
) {
Preference(
title = if (account != null) {
stringResource(R.string.preference_signin_logout)
title = if (nextcloudUser != null) {
stringResource(R.string.preference_nextcloud)
} else {
stringResource(R.string.preference_nextcloud_signin)
},
summary = account?.let {
summary = nextcloudUser?.let {
stringResource(R.string.preference_signin_user, it.userName)
} ?: stringResource(R.string.preference_nextcloud_signin_summary),
onClick = {
if (account != null) {
if (nextcloudUser != null) {
viewModel.signOut(AccountType.Nextcloud)
} else {
viewModel.signIn(context as AppCompatActivity, AccountType.Nextcloud)
}
}
},
enabled = !loading,
)
}
}
item {
PreferenceCategory(title = stringResource(R.string.preference_category_services_owncloud)) {
val account by viewModel.owncloudUser.observeAsState()
Preference(
title = if (account != null) {
stringResource(R.string.preference_signin_logout)
title = if (owncloudUser != null) {
stringResource(R.string.preference_owncloud)
} else {
stringResource(R.string.preference_owncloud_signin)
},
summary = account?.let {
summary = owncloudUser?.let {
stringResource(R.string.preference_signin_user, it.userName)
} ?: stringResource(R.string.preference_owncloud_signin_summary),
onClick = {
if (account != null) {
if (owncloudUser != null) {
viewModel.signOut(AccountType.Owncloud)
} else {
viewModel.signIn(context as AppCompatActivity, AccountType.Owncloud)
}
}
},
enabled = !loading,
)
}
}
if (viewModel.isMicrosoftAvailable) {
item {
PreferenceCategory(title = stringResource(R.string.preference_category_services_microsoft)) {
val account by viewModel.msUser.observeAsState()
if (viewModel.isMicrosoftAvailable) {
Preference(
title = if (account != null) {
stringResource(R.string.preference_signin_logout)
title = if (msUser != null) {
stringResource(R.string.preference_microsoft)
} else {
stringResource(R.string.preference_ms_signin)
},
summary = account?.let {
summary = msUser?.let {
stringResource(R.string.preference_signin_user, it.userName)
} ?: stringResource(R.string.preference_ms_signin_summary),
onClick = {
if (account != null) {
if (msUser != null) {
viewModel.signOut(AccountType.Microsoft)
} else {
viewModel.signIn(
@@ -121,39 +145,32 @@ fun AccountsSettingsScreen() {
AccountType.Microsoft
)
}
}
},
enabled = !loading,
)
}
}
}
if (viewModel.isGoogleAvailable) {
item {
PreferenceCategory(title = stringResource(R.string.preference_category_services_google)) {
val account by viewModel.googleUser.observeAsState()
if (account == null) {
Box(modifier = Modifier
.padding(start = 56.dp)
.padding(16.dp)) {
GoogleSigninButton(
onClick = {
viewModel.signIn(
context as AppCompatActivity,
AccountType.Google
)
}
)
}
} else {
Preference(
title = stringResource(R.string.preference_signin_logout),
summary = account?.userName?.let {
stringResource(R.string.preference_signin_user, it)
},
onClick = {
if (viewModel.isGoogleAvailable) {
Preference(
title = if (googleUser != null) {
stringResource(R.string.preference_google)
} else {
stringResource(R.string.preference_google_signin)
},
summary = googleUser?.let {
stringResource(R.string.preference_signin_user, it.userName)
} ?: stringResource(R.string.preference_google_signin_summary),
onClick = {
if (googleUser != null) {
viewModel.signOut(AccountType.Google)
} else {
viewModel.signIn(
context as AppCompatActivity,
AccountType.Google
)
}
)
}
},
enabled = !loading,
)
}
}
}
@@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.settings.accounts
package de.mm20.launcher2.ui.settings.integrations
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -11,18 +12,18 @@ import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class AccountsSettingsScreenVM : ViewModel(), KoinComponent {
class IntegrationsSettingsScreenVM : ViewModel(), KoinComponent {
private val accountsRepository: AccountsRepository by inject()
val isGoogleAvailable = accountsRepository.isSupported(AccountType.Google)
val isMicrosoftAvailable = accountsRepository.isSupported(AccountType.Microsoft)
val googleUser = MutableLiveData<Account?>(null)
val msUser= MutableLiveData<Account?>(null)
val nextcloudUser = MutableLiveData<Account?>(null)
val owncloudUser = MutableLiveData<Account?>(null)
val googleUser = mutableStateOf<Account?>(null)
val msUser= mutableStateOf<Account?>(null)
val nextcloudUser = mutableStateOf<Account?>(null)
val owncloudUser = mutableStateOf<Account?>(null)
val loading = MutableLiveData(true)
val loading = mutableStateOf(true)
fun onResume() {
viewModelScope.launch {
@@ -1,79 +0,0 @@
package de.mm20.launcher2.ui.settings.layout
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.Settings.LayoutSettings.Layout
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.ListPreference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@Composable
fun LayoutSettingsScreen() {
val viewModel: LayoutSettingsScreenVM = viewModel()
PreferenceScreen(
title = stringResource(id = R.string.preference_layout)
) {
item {
PreferenceCategory {
val baseLayout by viewModel.baseLayout.observeAsState()
ListPreference(title = stringResource(R.string.preference_layout_open_search),
items = listOf(
stringResource(R.string.open_search_pull_down) to Layout.PullDown,
stringResource(R.string.open_search_swipe_left) to Layout.Pager,
stringResource(R.string.open_search_swipe_right) to Layout.PagerReversed,
),
value = baseLayout,
onValueChanged = {
if (it != null) viewModel.setBaseLayout(it)
},
)
val bottomSearchBar by viewModel.bottomSearchBar.observeAsState()
ListPreference(
title = stringResource(R.string.preference_layout_search_bar_position),
items = listOf(
stringResource(R.string.search_bar_position_top) to false,
stringResource(R.string.search_bar_position_bottom) to true,
),
value = bottomSearchBar,
onValueChanged = {
if (it != null) viewModel.setBottomSearchBar(it)
},
)
val fixedSearchBar by viewModel.fixedSearchBar.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_layout_fixed_search_bar),
summary = stringResource(R.string.preference_layout_fixed_search_bar_summary),
value = fixedSearchBar == true,
onValueChanged = {
viewModel.setFixedSearchBar(it)
},
)
val reverseSearchResults by viewModel.reverseSearchResults.observeAsState()
ListPreference(title = stringResource(R.string.preference_layout_search_results),
items = listOf(
stringResource(R.string.search_results_order_top_down) to false,
stringResource(R.string.search_results_order_bottom_up) to true,
),
value = reverseSearchResults,
onValueChanged = {
if (it != null) viewModel.setReverseSearchResults(it)
},
)
val fixedRotation by viewModel.fixedRotation.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_layout_fixed_rotation),
summary = stringResource(R.string.preference_layout_fixed_rotation_summary),
value = fixedRotation == true,
onValueChanged = {
viewModel.setFixedRotation(it)
},
)
}
}
}
}
@@ -1,71 +0,0 @@
package de.mm20.launcher2.ui.settings.layout
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings.LayoutSettings.Layout
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class LayoutSettingsScreenVM: ViewModel(), KoinComponent {
private val dataStore : LauncherDataStore by inject()
val baseLayout = dataStore.data.map { it.layout.baseLayout }.asLiveData()
fun setBaseLayout(baseLayout: Layout) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setBaseLayout(baseLayout))
.build()
}
}
}
val bottomSearchBar = dataStore.data.map { it.layout.bottomSearchBar }.asLiveData()
fun setBottomSearchBar(bottomSearchBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setBottomSearchBar(bottomSearchBar))
.build()
}
}
}
val reverseSearchResults = dataStore.data.map { it.layout.reverseSearchResults }.asLiveData()
fun setReverseSearchResults(reverseSearchResults: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setReverseSearchResults(reverseSearchResults))
.build()
}
}
}
val fixedSearchBar = dataStore.data.map { it.layout.fixedSearchBar }.asLiveData()
fun setFixedSearchBar(fixedSearchBar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setFixedSearchBar(fixedSearchBar))
.build()
}
}
}
val fixedRotation = dataStore.data.map { it.layout.fixedRotation }.asLiveData()
fun setFixedRotation(fixedRotation: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setFixedRotation(fixedRotation))
.build()
}
}
}
}
@@ -1,21 +1,21 @@
package de.mm20.launcher2.ui.settings.main
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AccountBox
import androidx.compose.material.icons.rounded.Apps
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.Gesture
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.Power
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.SettingsBackupRestore
import androidx.compose.material.icons.rounded.Widgets
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
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.icons.NotificationBadge
import de.mm20.launcher2.ui.locals.LocalNavController
@Composable
@@ -34,6 +34,22 @@ fun MainSettingsScreen() {
navController?.navigate("settings/appearance")
}
)
Preference(
icon = Icons.Rounded.Home,
title = stringResource(id = R.string.preference_screen_homescreen),
summary = stringResource(id = R.string.preference_screen_homescreen_summary),
onClick = {
navController?.navigate("settings/homescreen")
}
)
Preference(
icon = Icons.Rounded.Apps,
title = stringResource(id = R.string.preference_screen_icons),
summary = stringResource(id = R.string.preference_screen_icons_summary),
onClick = {
navController?.navigate("settings/icons")
}
)
Preference(
icon = Icons.Rounded.Search,
title = stringResource(id = R.string.preference_screen_search),
@@ -42,14 +58,6 @@ fun MainSettingsScreen() {
navController?.navigate("settings/search")
}
)
Preference(
icon = Icons.Rounded.Widgets,
title = stringResource(id = R.string.preference_screen_widgets),
summary = stringResource(id = R.string.preference_screen_widgets_summary),
onClick = {
navController?.navigate("settings/widgets")
}
)
Preference(
icon = Icons.Rounded.Gesture,
title = stringResource(id = R.string.preference_screen_gestures),
@@ -59,19 +67,11 @@ fun MainSettingsScreen() {
}
)
Preference(
icon = Icons.Rounded.NotificationBadge,
title = stringResource(id = R.string.preference_screen_badges),
summary = stringResource(id = R.string.preference_screen_badges_summary),
icon = Icons.Rounded.Power,
title = stringResource(id = R.string.preference_screen_integrations),
summary = stringResource(id = R.string.preference_screen_integrations_summary),
onClick = {
navController?.navigate("settings/badges")
}
)
Preference(
icon = Icons.Rounded.AccountBox,
title = stringResource(id = R.string.preference_screen_services),
summary = stringResource(id = R.string.preference_screen_services_summary),
onClick = {
navController?.navigate("settings/accounts")
navController?.navigate("settings/integrations")
}
)
Preference(
@@ -1,4 +1,4 @@
package de.mm20.launcher2.ui.settings.musicwidget
package de.mm20.launcher2.ui.settings.media
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
@@ -20,9 +20,9 @@ import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@Composable
fun MusicWidgetSettingsScreen() {
fun MediaIntegrationSettingsScreen() {
val context = LocalContext.current
val viewModel: MusicWidgetSettingsScreenVM = viewModel()
val viewModel: MediaIntegrationSettingsScreenVM = viewModel()
val hasPermission by viewModel.hasPermission.observeAsState()
PreferenceScreen(
stringResource(R.string.preference_screen_musicwidget),
@@ -1,4 +1,4 @@
package de.mm20.launcher2.ui.settings.musicwidget
package de.mm20.launcher2.ui.settings.media
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
@@ -13,7 +13,7 @@ import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class MusicWidgetSettingsScreenVM : ViewModel(), KoinComponent {
class MediaIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
private val permissionsManager: PermissionsManager by inject()
private val musicService: MusicService by inject()
private val dataStore: LauncherDataStore by inject()
@@ -12,6 +12,7 @@ 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.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.ui.R
@@ -31,7 +32,7 @@ fun SearchSettingsScreen() {
PreferenceScreen(title = stringResource(R.string.preference_screen_search)) {
item {
PreferenceCategory {
val favorites by viewModel.favorites.observeAsState()
val favorites by viewModel.favorites.collectAsStateWithLifecycle(null)
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_favorites),
summary = stringResource(R.string.preference_search_favorites_summary),
@@ -54,7 +55,7 @@ fun SearchSettingsScreen() {
}
)
val hasContactsPermission by viewModel.hasContactsPermission.observeAsState()
val hasContactsPermission by viewModel.hasContactsPermission.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasContactsPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_contact_search_settings),
@@ -64,7 +65,7 @@ fun SearchSettingsScreen() {
modifier = Modifier.padding(16.dp)
)
}
val contacts by viewModel.contacts.observeAsState()
val contacts by viewModel.contacts.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_contacts),
summary = stringResource(R.string.preference_search_contacts_summary),
@@ -76,7 +77,7 @@ fun SearchSettingsScreen() {
enabled = hasContactsPermission == true
)
val hasCalendarPermission by viewModel.hasCalendarPermission.observeAsState()
val hasCalendarPermission by viewModel.hasCalendarPermission.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasCalendarPermission == false) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_calendar_search_settings),
@@ -86,7 +87,7 @@ fun SearchSettingsScreen() {
modifier = Modifier.padding(16.dp)
)
}
val calendar by viewModel.calendar.observeAsState()
val calendar by viewModel.calendar.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_calendar_summary),
@@ -98,7 +99,7 @@ fun SearchSettingsScreen() {
enabled = hasCalendarPermission == true
)
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.observeAsState()
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(null)
AnimatedVisibility(hasAppShortcutsPermission == false) {
MissingPermissionBanner(
text = stringResource(
@@ -111,7 +112,7 @@ fun SearchSettingsScreen() {
modifier = Modifier.padding(16.dp)
)
}
val appShortcuts by viewModel.appShortcuts.observeAsState()
val appShortcuts by viewModel.appShortcuts.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_appshortcuts),
summary = stringResource(R.string.preference_search_appshortcuts_summary),
@@ -123,7 +124,7 @@ fun SearchSettingsScreen() {
enabled = hasAppShortcutsPermission == true
)
val calculator by viewModel.calculator.observeAsState()
val calculator by viewModel.calculator.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_calculator),
summary = stringResource(R.string.preference_search_calculator_summary),
@@ -134,7 +135,7 @@ fun SearchSettingsScreen() {
}
)
val unitConverter by viewModel.unitConverter.observeAsState()
val unitConverter by viewModel.unitConverter.collectAsStateWithLifecycle(null)
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_unitconverter),
summary = stringResource(R.string.preference_search_unitconverter_summary),
@@ -148,7 +149,7 @@ fun SearchSettingsScreen() {
}
)
val wikipedia by viewModel.wikipedia.observeAsState()
val wikipedia by viewModel.wikipedia.collectAsStateWithLifecycle(null)
PreferenceWithSwitch(
title = stringResource(R.string.preference_search_wikipedia),
summary = stringResource(R.string.preference_search_wikipedia_summary),
@@ -162,7 +163,7 @@ fun SearchSettingsScreen() {
}
)
val websites by viewModel.websites.observeAsState()
val websites by viewModel.websites.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_websites),
summary = stringResource(R.string.preference_search_websites_summary),
@@ -183,43 +184,6 @@ fun SearchSettingsScreen() {
)
}
}
item {
PreferenceCategory {
val autoFocus by viewModel.autoFocus.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_search_bar_auto_focus),
summary = stringResource(R.string.preference_search_bar_auto_focus_summary),
icon = Icons.Rounded.Keyboard,
value = autoFocus == true,
onValueChanged = {
viewModel.setAutoFocus(it)
}
)
val launchOnEnter by viewModel.launchOnEnter.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_search_bar_launch_on_enter),
summary = stringResource(R.string.preference_search_bar_launch_on_enter_summary),
icon = Icons.Rounded.ArrowRightAlt,
value = launchOnEnter == true,
onValueChanged = {
viewModel.setLaunchOnEnter(it)
}
)
val searchResultOrdering by viewModel.searchResultOrdering.observeAsState()
ListPreference(
title = stringResource(R.string.preference_search_result_ordering),
items = listOf(
stringResource(R.string.preference_search_result_ordering_alphabetic) to Settings.SearchResultOrderingSettings.Ordering.Alphabetic,
stringResource(R.string.preference_search_result_ordering_weighted) to Settings.SearchResultOrderingSettings.Ordering.Weighted
),
value = searchResultOrdering,
onValueChanged = {
if (it != null) viewModel.setSearchResultOrdering(it)
},
icon = Icons.Rounded.Sort
)
}
}
item {
PreferenceCategory {
Preference(
@@ -240,5 +204,57 @@ fun SearchSettingsScreen() {
)
}
}
item {
PreferenceCategory {
val autoFocus by viewModel.autoFocus.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_bar_auto_focus),
summary = stringResource(R.string.preference_search_bar_auto_focus_summary),
icon = Icons.Rounded.Keyboard,
value = autoFocus == true,
onValueChanged = {
viewModel.setAutoFocus(it)
}
)
val launchOnEnter by viewModel.launchOnEnter.collectAsStateWithLifecycle(null)
SwitchPreference(
title = stringResource(R.string.preference_search_bar_launch_on_enter),
summary = stringResource(R.string.preference_search_bar_launch_on_enter_summary),
value = launchOnEnter == true,
onValueChanged = {
viewModel.setLaunchOnEnter(it)
}
)
}
}
item {
PreferenceCategory {
val searchResultOrdering by viewModel.searchResultOrdering.collectAsStateWithLifecycle(null)
ListPreference(
title = stringResource(R.string.preference_search_result_ordering),
items = listOf(
stringResource(R.string.preference_search_result_ordering_alphabetic) to Settings.SearchResultOrderingSettings.Ordering.Alphabetic,
stringResource(R.string.preference_search_result_ordering_weighted) to Settings.SearchResultOrderingSettings.Ordering.Weighted
),
value = searchResultOrdering,
onValueChanged = {
if (it != null) viewModel.setSearchResultOrdering(it)
},
icon = Icons.Rounded.Sort
)
val reverseSearchResults by viewModel.reverseSearchResults.collectAsStateWithLifecycle(null)
ListPreference(title = stringResource(R.string.preference_layout_search_results),
items = listOf(
stringResource(R.string.search_results_order_top_down) to false,
stringResource(R.string.search_results_order_bottom_up) to true,
),
value = reverseSearchResults,
onValueChanged = {
if (it != null) viewModel.setReverseSearchResults(it)
},
)
}
}
}
}
@@ -18,7 +18,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val permissionsManager: PermissionsManager by inject()
val favorites = dataStore.data.map { it.favorites.enabled }.asLiveData()
val favorites = dataStore.data.map { it.favorites.enabled }
fun setFavorites(favorites: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -30,8 +30,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
val hasContactsPermission = permissionsManager.hasPermission(PermissionGroup.Contacts).asLiveData()
val contacts = dataStore.data.map { it.contactsSearch.enabled }.asLiveData()
val hasContactsPermission = permissionsManager.hasPermission(PermissionGroup.Contacts)
val contacts = dataStore.data.map { it.contactsSearch.enabled }
fun setContacts(contacts: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -46,8 +46,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
permissionsManager.requestPermission(activity, PermissionGroup.Contacts)
}
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar).asLiveData()
val calendar = dataStore.data.map { it.calendarSearch.enabled }.asLiveData()
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
val calendar = dataStore.data.map { it.calendarSearch.enabled }
fun setCalendar(calendar: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -62,7 +62,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
}
val calculator = dataStore.data.map { it.calculatorSearch.enabled }.asLiveData()
val calculator = dataStore.data.map { it.calculatorSearch.enabled }
fun setCalculator(calculator: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -73,7 +73,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val unitConverter = dataStore.data.map { it.unitConverterSearch.enabled }.asLiveData()
val unitConverter = dataStore.data.map { it.unitConverterSearch.enabled }
fun setUnitConverter(unitConverter: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -84,7 +84,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val wikipedia = dataStore.data.map { it.wikipediaSearch.enabled }.asLiveData()
val wikipedia = dataStore.data.map { it.wikipediaSearch.enabled }
fun setWikipedia(wikipedia: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -95,7 +95,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val websites = dataStore.data.map { it.websiteSearch.enabled }.asLiveData()
val websites = dataStore.data.map { it.websiteSearch.enabled }
fun setWebsites(websites: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -106,7 +106,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val webSearch = dataStore.data.map { it.webSearch.enabled }.asLiveData()
val webSearch = dataStore.data.map { it.webSearch.enabled }
fun setWebSearch(webSearch: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -117,7 +117,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val autoFocus = dataStore.data.map { it.searchBar.autoFocus }.asLiveData()
val autoFocus = dataStore.data.map { it.searchBar.autoFocus }
fun setAutoFocus(autoFocus: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -128,7 +128,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val launchOnEnter = dataStore.data.map { it.searchBar.launchOnEnter }.asLiveData()
val launchOnEnter = dataStore.data.map { it.searchBar.launchOnEnter }
fun setLaunchOnEnter(launchOnEnter: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -139,8 +139,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val hasAppShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts).asLiveData()
val appShortcuts = dataStore.data.map { it.appShortcutSearch.enabled }.asLiveData()
val hasAppShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts)
val appShortcuts = dataStore.data.map { it.appShortcutSearch.enabled }
fun setAppShortcuts(appShortcuts: Boolean) {
viewModelScope.launch {
dataStore.updateData {
@@ -151,7 +151,7 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val searchResultOrdering = dataStore.data.map { it.resultOrdering.ordering }.asLiveData()
val searchResultOrdering = dataStore.data.map { it.resultOrdering.ordering }
fun setSearchResultOrdering(searchResultOrdering: Settings.SearchResultOrderingSettings.Ordering) {
viewModelScope.launch {
dataStore.updateData {
@@ -162,6 +162,18 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val reverseSearchResults = dataStore.data.map { it.layout.reverseSearchResults }
fun setReverseSearchResults(reverseSearchResults: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setLayout(it.layout.toBuilder().setReverseSearchResults(reverseSearchResults))
.build()
}
}
}
fun requestAppShortcutsPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.AppShortcuts)
}
@@ -1,4 +1,4 @@
package de.mm20.launcher2.ui.settings.weatherwidget
package de.mm20.launcher2.ui.settings.weather
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
@@ -19,8 +19,8 @@ import de.mm20.launcher2.ui.component.preferences.*
import de.mm20.launcher2.weather.WeatherLocation
@Composable
fun WeatherWidgetSettingsScreen() {
val viewModel: WeatherWidgetSettingsScreenVM = viewModel()
fun WeatherIntegrationSettingsScreen() {
val viewModel: WeatherIntegrationSettingsScreenVM = viewModel()
val context = LocalContext.current
PreferenceScreen(
@@ -1,4 +1,4 @@
package de.mm20.launcher2.ui.settings.weatherwidget
package de.mm20.launcher2.ui.settings.weather
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
@@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class WeatherWidgetSettingsScreenVM : ViewModel(), KoinComponent {
class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
private val repository: WeatherRepository by inject()
private val dataStore: LauncherDataStore by inject()
private val permissionsManager: PermissionsManager by inject()
@@ -75,7 +75,7 @@ class WeatherWidgetSettingsScreenVM : ViewModel(), KoinComponent {
if (autoLoc) lastLoc
else loc
}.collectLatest {
this@WeatherWidgetSettingsScreenVM.location.postValue(it)
this@WeatherIntegrationSettingsScreenVM.location.postValue(it)
}
}
}
@@ -1,40 +0,0 @@
package de.mm20.launcher2.ui.settings.widgets
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.widgets.CalendarWidget
import de.mm20.launcher2.widgets.FavoritesWidget
import de.mm20.launcher2.widgets.MusicWidget
import de.mm20.launcher2.widgets.WeatherWidget
import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class WidgetSettingsScreenVM : ViewModel(), KoinComponent {
private val widgetRepository: WidgetRepository by inject()
private val dataStore: LauncherDataStore by inject()
val calendarWidget = widgetRepository.exists(CalendarWidget.Type).asLiveData()
val musicWidget = widgetRepository.exists(MusicWidget.Type).asLiveData()
val weatherWidget = widgetRepository.exists(WeatherWidget.Type).asLiveData()
val favoritesWidget = widgetRepository.exists(FavoritesWidget.Type).asLiveData()
val editButton = dataStore.data.map { it.widgets.editButton }.asLiveData()
fun setEditButton(editButton: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setWidgets(
it.widgets.toBuilder()
.setEditButton(editButton)
)
.build()
}
}
}
}
@@ -1,95 +0,0 @@
package de.mm20.launcher2.ui.settings.widgets
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Audiotrack
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.Schedule
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.Today
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.res.stringResource
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
@Composable
fun WidgetsSettingsScreen() {
val navController = LocalNavController.current
val viewModel: WidgetSettingsScreenVM = viewModel()
PreferenceScreen(title = stringResource(R.string.preference_screen_widgets)) {
item {
PreferenceCategory {
Preference(
title = stringResource(R.string.preference_screen_clockwidget),
icon = Icons.Rounded.Schedule,
onClick = {
navController?.navigate("settings/widgets/clock")
}
)
val weatherWidget by viewModel.weatherWidget.observeAsState()
if (weatherWidget == true) {
Preference(
title = stringResource(R.string.preference_screen_weatherwidget),
icon = Icons.Rounded.LightMode,
onClick = {
navController?.navigate("settings/widgets/weather")
}
)
}
val musicWidget by viewModel.musicWidget.observeAsState()
if (musicWidget == true) {
Preference(
title = stringResource(R.string.preference_screen_musicwidget),
icon = Icons.Rounded.Audiotrack,
onClick = {
navController?.navigate("settings/widgets/music")
}
)
}
val calendarWidget by viewModel.calendarWidget.observeAsState()
if (calendarWidget == true) {
Preference(
title = stringResource(R.string.preference_screen_calendarwidget),
icon = Icons.Rounded.Today,
onClick = {
navController?.navigate("settings/widgets/calendar")
}
)
}
val favoritesWidget by viewModel.favoritesWidget.observeAsState()
if (favoritesWidget == true) {
Preference(
title = stringResource(R.string.favorites),
icon = Icons.Rounded.Star,
onClick = {
navController?.navigate("settings/favorites")
}
)
}
}
}
item {
PreferenceCategory {
val editButton by viewModel.editButton.observeAsState()
SwitchPreference(
title = stringResource(id = R.string.preference_edit_button),
summary = stringResource(id = R.string.preference_widgets_edit_button_summary),
value = editButton == true,
onValueChanged = {
viewModel.setEditButton(it)
})
}
}
}
}