From 8e96e2b2dbbef9142e487a87b6072fc324df4acf Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 30 Jan 2022 13:32:59 +0100 Subject: [PATCH] Migrate card settings --- .../fragment/PreferencesCardFragment.kt | 4 - .../de/mm20/launcher2/preferences/Defaults.kt | 6 + .../preferences/LauncherPreferences.kt | 2 - preferences/src/main/proto/settings.proto | 7 ++ .../launcher2/ui/component/LauncherCard.kt | 10 +- .../component/preferences/SliderPreference.kt | 115 ++++++++++++++++++ .../launcher2/ui/launcher/search/SearchBar.kt | 4 +- .../ui/legacy/component/SearchBar.kt | 35 ++++-- .../launcher2/ui/legacy/view/InnerCardView.kt | 35 +++++- .../ui/legacy/view/LauncherCardView.kt | 84 +++++++++---- .../launcher2/ui/legacy/view/SwipeCardView.kt | 39 ++++-- .../launcher2/ui/locals/CompositionLocals.kt | 5 +- .../launcher2/ui/settings/SettingsActivity.kt | 32 ++++- .../appearance/AppearanceSettingsScreen.kt | 9 ++ .../ui/settings/cards/CardsSettingsScreen.kt | 83 +++++++++++++ .../settings/cards/CardsSettingsScreenVM.kt | 50 ++++++++ 16 files changed, 466 insertions(+), 54 deletions(-) create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/component/preferences/SliderPreference.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt diff --git a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesCardFragment.kt b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesCardFragment.kt index 29abfcdb..f6ebbf67 100644 --- a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesCardFragment.kt +++ b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesCardFragment.kt @@ -24,13 +24,11 @@ class PreferencesCardFragment : Fragment(R.layout.fragment_card_settings) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val previewCard = view.findViewById(R.id.previewCard) - previewCard.strokeOpacity = 0xFF val prefFragment = PreferenesCardInnerFragment() prefFragment.onPreferencesReady = { findPreference("card_radius")?.let { - it.summary = preferences.cardRadius.toString() it.setOnPreferenceChangeListener { pref, newValue -> val value = newValue as Int previewCard.radius = value * dp @@ -39,7 +37,6 @@ class PreferencesCardFragment : Fragment(R.layout.fragment_card_settings) { } } findPreference("card_opacity")?.let { - it.summary = preferences.cardOpacity.toString() it.setOnPreferenceChangeListener { pref, newValue -> val value = newValue as Int previewCard.backgroundOpacity = value @@ -49,7 +46,6 @@ class PreferencesCardFragment : Fragment(R.layout.fragment_card_settings) { } } findPreference("card_stroke_width")?.let { - it.summary = preferences.cardRadius.toString() it.setOnPreferenceChangeListener { pref, newValue -> val value = newValue as Int previewCard.strokeWidth = (value * dp).roundToInt() diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt index cf0dc946..ad1fd256 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt @@ -119,5 +119,11 @@ fun createFactorySettings(context: Context): Settings { .setLightNavBar(false) .setLightStatusBar(false) ) + .setCards( + Settings.CardSettings.newBuilder() + .setBorderWidth(0) + .setRadius(8) + .setOpacity(1f) + ) .build() } \ No newline at end of file diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt index 2299b936..5c55628f 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherPreferences.kt @@ -49,8 +49,6 @@ class LauncherPreferences(val context: Application, version: Int = 3) { var appStartAnim by EnumPreference("app_start_anim", default = AppStartAnimation.M) - var cardOpacity by IntPreference("card_opacity", default = 0xFF) - var cardStrokeWidth by IntPreference("card_stroke_width", default = 0) var cardRadius by IntPreference("card_radius", default = 8) var easterEggEnabled by BooleanPreference("easter_egg", default = false) diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto index 38f7d5db..c339f6e7 100644 --- a/preferences/src/main/proto/settings.proto +++ b/preferences/src/main/proto/settings.proto @@ -164,4 +164,11 @@ message Settings { bool lightNavBar = 2; } SystemBarsSettings system_bars = 23; + + message CardSettings { + float opacity = 1; + uint32 radius = 2; + uint32 border_width = 3; + } + CardSettings cards = 24; } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt index ff323e8d..33b93111 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt @@ -1,5 +1,6 @@ package de.mm20.launcher2.ui.component +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -7,20 +8,23 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import de.mm20.launcher2.ui.locals.LocalCardStyle @Composable fun LauncherCard( modifier: Modifier = Modifier, elevation: Dp = 2.dp, - backgroundOpacity: Float = 1f, + backgroundOpacity: Float = LocalCardStyle.current.opacity, content: @Composable () -> Unit = {} ) { Surface( modifier = modifier, - shape = RoundedCornerShape(8.dp), + shape = RoundedCornerShape(LocalCardStyle.current.radius.dp), + border = LocalCardStyle.current.borderWidth.takeIf { it > 0 } + ?.let { BorderStroke(it.dp, MaterialTheme.colorScheme.surface) }, content = content, color = MaterialTheme.colorScheme.surface.copy(alpha = backgroundOpacity.coerceIn(0f, 1f)), - shadowElevation = elevation, + shadowElevation = if (backgroundOpacity == 1f) elevation else 0.dp, tonalElevation = elevation ) } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/SliderPreference.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/SliderPreference.kt new file mode 100644 index 00000000..7f4c135d --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/SliderPreference.kt @@ -0,0 +1,115 @@ +package de.mm20.launcher2.ui.component.preferences + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Slider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import de.mm20.launcher2.ktx.ceilToInt +import java.text.DecimalFormat +import kotlin.math.floor +import kotlin.math.log +import kotlin.math.pow +import kotlin.math.roundToInt + +@Composable +fun SliderPreference( + title: String, + icon: ImageVector? = null, + value: Float, + min: Float = 0f, + max: Float = 1f, + step: Float? = null, + onValueChanged: (Float) -> Unit, + enabled: Boolean = true +) { + var sliderValue by remember(value) { mutableStateOf(value) } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp) + .alpha(if (enabled) 1f else 0.38f), + ) { + Box( + modifier = Modifier + .width(48.dp) + .padding(start = 4.dp), + contentAlignment = Alignment.CenterStart + ) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } + } + Column( + modifier = Modifier.weight(1f) + ) { + Text( + modifier = Modifier.padding(start = 8.dp), + text = title, + style = MaterialTheme.typography.titleMedium + ) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Slider( + modifier = Modifier.weight(1f), + value = sliderValue, + onValueChange = { + sliderValue = it + }, + valueRange = min..max, + steps = step?.let { ((max - min) / it).toInt() - 1 } ?: 0, + onValueChangeFinished = { + onValueChanged(sliderValue) + } + ) + val decimalPlaces = -log(step ?: 0.01f, 10f) + val format = remember { DecimalFormat().apply { + maximumFractionDigits = floor(decimalPlaces).toInt() + minimumFractionDigits = 0 + } } + Text( + modifier = Modifier.width(56.dp).padding(start = 24.dp), + text = format.format(sliderValue), + style = MaterialTheme.typography.titleSmall + ) + } + } + } +} + +@Composable +fun SliderPreference( + title: String, + icon: ImageVector? = null, + value: Int, + min: Int = 0, + max: Int = 100, + step: Int = 1, + onValueChanged: (Int) -> Unit, + enabled: Boolean = true +) { + SliderPreference( + title = title, + icon = icon, + value = value.toFloat(), + enabled = enabled, + min = min.toFloat(), + max = max.toFloat(), + step = step.toFloat(), + onValueChanged = { + onValueChanged(it.roundToInt()) + } + ) +} diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt index 89c29369..c7f6024b 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt @@ -49,6 +49,7 @@ import de.mm20.launcher2.search.data.Websearch import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.LauncherCard import de.mm20.launcher2.ui.launcher.LauncherActivityVM +import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.settings.SettingsActivity import kotlinx.coroutines.flow.map import org.koin.androidx.compose.inject @@ -176,10 +177,11 @@ fun SearchBar( durationMillis = 200, delayMillis = 200 ) - else -> tween(durationMillis = 500) + else -> tween(durationMillis = 200) } }) { when { + it == SearchBarLevel.Active -> LocalCardStyle.current.opacity style != SearchBarSettings.SearchBarStyle.Transparent -> 1f it == SearchBarLevel.Resting -> 0f else -> 1f diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt index eeadf3f8..9ddf6182 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt @@ -6,21 +6,32 @@ import android.util.Log import android.view.MotionEvent import android.widget.FrameLayout import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.MutableLiveData +import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.LegacyLauncherTheme import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.launcher.search.SearchBar import de.mm20.launcher2.ui.launcher.search.SearchBarLevel +import de.mm20.launcher2.ui.locals.LocalCardStyle +import de.mm20.launcher2.ui.locals.LocalNavController +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class SearchBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.materialCardViewStyle -) : FrameLayout(context, attrs, defStyleAttr) { +) : FrameLayout(context, attrs, defStyleAttr), KoinComponent { var level: SearchBarLevel = SearchBarLevel.Resting set(value) { @@ -28,6 +39,7 @@ class SearchBar @JvmOverloads constructor( field = value } + private val dataStore: LauncherDataStore by inject() private val levelState = MutableLiveData(level) var onFocus: (() -> Unit)? = null @@ -36,12 +48,21 @@ class SearchBar @JvmOverloads constructor( val view = ComposeView(context) view.setContent { val level by levelState.observeAsState(SearchBarLevel.Resting) - LegacyLauncherTheme { - Box(contentAlignment = Alignment.TopCenter) { - SearchBar( - level, - onFocus = { onFocus?.invoke() } - ) + val cardStyle by remember { + dataStore.data.map { it.cards }.distinctUntilChanged() + }.collectAsState( + Settings.CardSettings.getDefaultInstance() + ) + CompositionLocalProvider( + LocalCardStyle provides cardStyle + ) { + LegacyLauncherTheme { + Box(contentAlignment = Alignment.TopCenter) { + SearchBar( + level, + onFocus = { onFocus?.invoke() } + ) + } } } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt index a3a02652..89ed14d3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt @@ -3,20 +3,51 @@ package de.mm20.launcher2.ui.legacy.view import android.content.Context import android.util.AttributeSet import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.card.MaterialCardView import de.mm20.launcher2.ktx.dp +import de.mm20.launcher2.ktx.lifecycleOwner +import de.mm20.launcher2.ktx.lifecycleScope +import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.ui.R +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject open class InnerCardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.materialCardViewStyle -) : MaterialCardView(context, attrs, defStyleAttr) { +) : MaterialCardView(context, attrs, defStyleAttr), KoinComponent { init { - radius = LauncherPreferences.instance.cardRadius * dp + radius = LauncherCardView.currentCardStyle.radius * dp strokeColor = ContextCompat.getColor(context, R.color.color_divider) strokeWidth = (1 * dp).toInt() cardElevation = 2 * dp outlineProvider = null } + + private val dataStore: LauncherDataStore by inject() + private var job: Job? = null + override fun onAttachedToWindow() { + super.onAttachedToWindow() + job?.cancel() + job = lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + dataStore.data.map { it.cards.radius }.distinctUntilChanged().collectLatest { + radius = it * dp + } + } + } + } + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + job?.cancel() + } + } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt index a7794a84..724d3345 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt @@ -3,10 +3,22 @@ package de.mm20.launcher2.ui.legacy.view import android.content.Context import android.content.res.ColorStateList import android.util.AttributeSet +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.card.MaterialCardView import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.preferences.LauncherPreferences +import de.mm20.launcher2.ktx.lifecycleOwner +import de.mm20.launcher2.ktx.lifecycleScope +import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.R +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import kotlin.math.roundToInt /** @@ -15,15 +27,12 @@ import kotlin.math.roundToInt * (2) Elevation overlay color */ open class LauncherCardView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = R.attr.materialCardViewStyle -) : MaterialCardView(context, attrs, defStyleAttr) { + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.materialCardViewStyle +) : MaterialCardView(context, attrs, defStyleAttr), KoinComponent { - private val isDarkTheme = resources.getBoolean(R.bool.is_dark_theme) - - - var backgroundOpacity: Int = LauncherPreferences.instance.cardOpacity + var backgroundOpacity: Int = (currentCardStyle.opacity * 255).roundToInt() set(value) { setCardBackgroundColor(cardBackgroundColor.defaultColor.let { ColorStateList.valueOf((it and 0xFFFFFF) or (value shl 24)) @@ -31,7 +40,7 @@ open class LauncherCardView @JvmOverloads constructor( field = value } - var strokeOpacity: Int = if (LauncherPreferences.instance.cardStrokeWidth > 0) 0xFF else 0 + private var strokeOpacity: Int = if (currentCardStyle.borderWidth > 0) 0xFF else 0 set(value) { setStrokeColor(strokeColorStateList?.defaultColor?.let { ColorStateList.valueOf((it and 0xFFFFFF) or (value shl 24)) @@ -39,23 +48,24 @@ open class LauncherCardView @JvmOverloads constructor( field = value } + private var overrideBackgroundOpacity = false + init { - /*val cardColor = when (LauncherPreferences.instance.cardBackground) { - CardBackground.DEFAULT-> context.getColor(R.color.cardview_background) - CardBackground.BLACK -> context.getColor(R.color.cardview_background_black) - } - setCardBackgroundColor(cardColor)*/ strokeColor = cardBackgroundColor.defaultColor - strokeWidth = (LauncherPreferences.instance.cardStrokeWidth * dp).roundToInt() - radius = LauncherPreferences.instance.cardRadius * dp + strokeWidth = (currentCardStyle.borderWidth * dp).roundToInt() + radius = currentCardStyle.radius * dp context.theme.obtainStyledAttributes( - attrs, - R.styleable.LauncherCardView, - 0, 0).apply { + attrs, + R.styleable.LauncherCardView, + 0, 0 + ).apply { try { - backgroundOpacity = getInt(R.styleable.LauncherCardView_backgroundOpacity, LauncherPreferences.instance.cardOpacity) + if (hasValue(R.styleable.LauncherCardView_backgroundOpacity)) { + overrideBackgroundOpacity = true + backgroundOpacity = getInt(R.styleable.LauncherCardView_backgroundOpacity, 255) + } } finally { recycle() } @@ -64,4 +74,36 @@ open class LauncherCardView @JvmOverloads constructor( elevation = if (backgroundOpacity == 255) elevation else 0f cardElevation = elevation } + + private val dataStore: LauncherDataStore by inject() + + private var job: Job? = null + override fun onAttachedToWindow() { + super.onAttachedToWindow() + job?.cancel() + job = lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + dataStore.data.map { it.cards }.distinctUntilChanged().collectLatest { + currentCardStyle = it + if (!overrideBackgroundOpacity) { + backgroundOpacity = (it.opacity * 255).roundToInt() + strokeOpacity = if (backgroundOpacity == 0) 0 else 0xFF + elevation = if (backgroundOpacity == 255) elevation else 0f + cardElevation = elevation + } + strokeWidth = (it.borderWidth * dp).roundToInt() + radius = it.radius * dp + } + } + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + job?.cancel() + } + + companion object { + internal var currentCardStyle = Settings.CardSettings.getDefaultInstance() + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt index 4873a216..0f7610b0 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt @@ -13,23 +13,28 @@ import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity import androidx.core.animation.doOnEnd import androidx.core.content.ContextCompat -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.asLiveData -import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.* import com.google.android.material.card.MaterialCardView import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.preferences.LauncherPreferences +import de.mm20.launcher2.ktx.lifecycleOwner +import de.mm20.launcher2.ktx.lifecycleScope +import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.transition.ChangingLayoutTransition import de.mm20.launcher2.ui.R +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject import kotlin.math.abs class SwipeCardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : MaterialCardView(context, attrs, defStyleAttr) { +) : MaterialCardView(context, attrs, defStyleAttr), KoinComponent { private val backdrop = FrameLayout(context) private val icon = ImageView(context) @@ -44,10 +49,9 @@ class SwipeCardView @JvmOverloads constructor( icon.setColorFilter(iconColor) super.addView(content) content.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - content.radius = radius content.transitionName = "SwipeCardView/content" - radius = LauncherPreferences.instance.cardRadius * dp - //content.setCardBackgroundColor(cardBackgroundColor) + radius = LauncherCardView.currentCardStyle.radius * dp + content.radius = radius super.setCardBackgroundColor( ContextCompat.getColor( context, @@ -362,6 +366,25 @@ class SwipeCardView @JvmOverloads constructor( } } + private val dataStore: LauncherDataStore by inject() + private var job: Job? = null + override fun onAttachedToWindow() { + super.onAttachedToWindow() + job?.cancel() + job = lifecycleScope.launch { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + dataStore.data.map { it.cards.radius }.distinctUntilChanged().collectLatest { + radius = it * dp + } + } + } + } + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + job?.cancel() + } + + open class SwipeAction( @DrawableRes var icon: Int, var color: Int, diff --git a/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt b/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt index 53d427c2..080c6d3e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt @@ -4,6 +4,7 @@ import android.appwidget.AppWidgetHost import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.geometry.Size import androidx.navigation.NavController +import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.theme.WallpaperColors import de.mm20.launcher2.ui.theme.colors.ColorPalette import de.mm20.launcher2.ui.theme.colors.DefaultColorPalette @@ -16,4 +17,6 @@ val LocalWallpaperColors = compositionLocalOf { null } val LocalColorScheme = compositionLocalOf { DefaultColorPalette() } -val LocalNavController = compositionLocalOf { null } \ No newline at end of file +val LocalNavController = compositionLocalOf { null } + +val LocalCardStyle = compositionLocalOf { Settings.CardSettings.getDefaultInstance() } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index de6344fa..539ccd2d 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -7,6 +7,8 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.navigation.navArgument import com.google.accompanist.navigation.animation.AnimatedNavHost @@ -14,37 +16,54 @@ import com.google.accompanist.navigation.animation.composable import com.google.accompanist.navigation.animation.rememberAnimatedNavController import de.mm20.launcher2.licenses.AppLicense import de.mm20.launcher2.licenses.OpenSourceLicenses +import de.mm20.launcher2.preferences.LauncherDataStore +import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.LegacyLauncherTheme import de.mm20.launcher2.ui.base.BaseActivity +import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen +import de.mm20.launcher2.ui.settings.accounts.AccountsSettingsScreen import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen 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 import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen +import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen +import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen import de.mm20.launcher2.ui.settings.license.LicenseScreen import de.mm20.launcher2.ui.settings.main.MainSettingsScreen import de.mm20.launcher2.ui.settings.musicwidget.MusicWidgetSettingsScreen import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen -import de.mm20.launcher2.ui.settings.accounts.AccountsSettingsScreen -import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen -import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen -import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen import de.mm20.launcher2.ui.settings.weatherwidget.WeatherWidgetSettingsScreen import de.mm20.launcher2.ui.settings.websearch.WebSearchSettingsScreen import de.mm20.launcher2.ui.settings.widgets.WidgetsSettingsScreen import de.mm20.launcher2.ui.settings.wikipedia.WikipediaSettingsScreen +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import org.koin.android.ext.android.inject class SettingsActivity : BaseActivity() { + private val dataStore: LauncherDataStore by inject() + @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val navController = rememberAnimatedNavController() - CompositionLocalProvider(LocalNavController provides navController) { + val cardStyle by remember { + dataStore.data.map { it.cards }.distinctUntilChanged() + }.collectAsState( + Settings.CardSettings.getDefaultInstance() + ) + CompositionLocalProvider( + LocalNavController provides navController, + LocalCardStyle provides cardStyle + ) { LegacyLauncherTheme { AnimatedNavHost( navController = navController, @@ -60,6 +79,9 @@ class SettingsActivity : BaseActivity() { composable("settings/appearance") { AppearanceSettingsScreen() } + composable("settings/appearance/cards") { + CardsSettingsScreen() + } composable("settings/search") { SearchSettingsScreen() } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt index 8efce922..4d1d1cac 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt @@ -37,6 +37,7 @@ import de.mm20.launcher2.ui.component.preferences.* import de.mm20.launcher2.ui.launcher.search.SearchBar import de.mm20.launcher2.ui.launcher.search.SearchBarLevel import de.mm20.launcher2.ui.legacy.view.LauncherIconView +import de.mm20.launcher2.ui.locals.LocalNavController import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -44,6 +45,7 @@ import kotlinx.coroutines.isActive fun AppearanceSettingsScreen() { val viewModel: AppearanceSettingsScreenVM = viewModel() val context = LocalContext.current + val navController = LocalNavController.current PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) { item { PreferenceCategory { @@ -74,6 +76,13 @@ fun AppearanceSettingsScreen() { viewModel.setColorScheme(newValue) } ) + Preference( + title = stringResource(R.string.preference_cards), + summary = stringResource(R.string.preference_cards_summary), + onClick = { + navController?.navigate("settings/appearance/cards") + } + ) } PreferenceCategory(title = stringResource(R.string.preference_category_grid)) { val columnCount by viewModel.columnCount.observeAsState() diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt new file mode 100644 index 00000000..dfe889fa --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt @@ -0,0 +1,83 @@ +package de.mm20.launcher2.ui.settings.cards + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.BorderStyle +import androidx.compose.material.icons.rounded.LineWeight +import androidx.compose.material.icons.rounded.Opacity +import androidx.compose.material.icons.rounded.RoundedCorner +import androidx.compose.material3.MaterialTheme +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.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.LauncherCard +import de.mm20.launcher2.ui.component.preferences.PreferenceCategory +import de.mm20.launcher2.ui.component.preferences.PreferenceScreen +import de.mm20.launcher2.ui.component.preferences.SliderPreference + +@Composable +fun CardsSettingsScreen() { + val viewModel: CardsSettingsScreenVM = viewModel() + PreferenceScreen(title = stringResource(R.string.preference_cards)) { + item { + Box(modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.secondary)) { + LauncherCard( + modifier = Modifier + .height(200.dp) + .fillMaxWidth() + .padding(16.dp) + ) + } + } + item { + PreferenceCategory { + val opacity by viewModel.opacity.observeAsState(0f) + SliderPreference( + title = stringResource(R.string.preference_cards_opacity), + icon = Icons.Rounded.Opacity, + value = opacity, + min = 0f, + max = 1f, + onValueChanged = { + viewModel.setOpacity(it) + } + ) + val radius by viewModel.radius.observeAsState(0) + SliderPreference( + title = stringResource(R.string.preference_cards_corner_radius), + icon = Icons.Rounded.RoundedCorner, + value = radius, + min = 0, + max = 24, + step = 1, + onValueChanged = { + viewModel.setRadius(it) + } + ) + val borderWidth by viewModel.borderWidth.observeAsState(0) + SliderPreference( + title = stringResource(R.string.preference_cards_stroke_width), + icon = Icons.Rounded.LineWeight, + value = borderWidth, + min = 0, + max = 8, + step = 1, + onValueChanged = { + viewModel.setBorderWidth(it) + } + ) + } + } + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt new file mode 100644 index 00000000..41573b71 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt @@ -0,0 +1,50 @@ +package de.mm20.launcher2.ui.settings.cards + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +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 CardsSettingsScreenVM: ViewModel(), KoinComponent { + private val dataStore: LauncherDataStore by inject() + + val opacity = dataStore.data.map { it.cards.opacity }.asLiveData() + fun setOpacity(opacity: Float) { + viewModelScope.launch { + dataStore.updateData { + it.toBuilder() + .setCards(it.cards.toBuilder() + .setOpacity(opacity) + ).build() + } + } + } + + val radius = dataStore.data.map { it.cards.radius }.asLiveData() + fun setRadius(radius: Int) { + viewModelScope.launch { + dataStore.updateData { + it.toBuilder() + .setCards(it.cards.toBuilder() + .setRadius(radius) + ).build() + } + } + } + + val borderWidth = dataStore.data.map { it.cards.borderWidth }.asLiveData() + fun setBorderWidth(borderWidth: Int) { + viewModelScope.launch { + dataStore.updateData { + it.toBuilder() + .setCards(it.cards.toBuilder() + .setBorderWidth(borderWidth) + ).build() + } + } + } +} \ No newline at end of file