Refactor icons
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import android.graphics.*
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -25,15 +22,26 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.toRect
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import de.mm20.launcher2.badges.Badge
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.icons.*
|
||||
import de.mm20.launcher2.ktx.drawWithColorFilter
|
||||
import de.mm20.launcher2.preferences.Settings.IconSettings.IconShape
|
||||
import de.mm20.launcher2.ui.base.LocalTime
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import palettes.TonalPalette
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -48,6 +56,25 @@ fun ShapedLauncherIcon(
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
shape: Shape = LocalIconShape.current
|
||||
) {
|
||||
|
||||
val time = LocalTime.current
|
||||
|
||||
var currentIcon by remember(icon) {
|
||||
mutableStateOf(
|
||||
when (icon) {
|
||||
is DynamicLauncherIcon -> null
|
||||
is StaticLauncherIcon -> icon
|
||||
null -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (icon is DynamicLauncherIcon) {
|
||||
LaunchedEffect(time) {
|
||||
currentIcon = icon.getIcon(time)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
@@ -56,7 +83,7 @@ fun ShapedLauncherIcon(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
clip = true
|
||||
clip = currentIcon?.backgroundLayer !is TransparentLayer
|
||||
this.shape = shape
|
||||
}
|
||||
.combinedClickable(
|
||||
@@ -68,45 +95,19 @@ fun ShapedLauncherIcon(
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (icon != null) {
|
||||
|
||||
val fgScale = icon.foregroundScale
|
||||
val bgScale = icon.backgroundScale
|
||||
|
||||
val themedFgColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
val themedBgColor = MaterialTheme.colorScheme.primaryContainer
|
||||
|
||||
val fg = remember(icon, icon.isThemeable, themedFgColor) {
|
||||
icon.foreground.also {
|
||||
if (icon.isThemeable) it.setTint(themedFgColor.toArgb())
|
||||
}
|
||||
}
|
||||
val bg = remember(icon, icon.isThemeable, themedBgColor) {
|
||||
icon.background?.also {
|
||||
if (icon.isThemeable) it.setTint(themedBgColor.toArgb())
|
||||
}
|
||||
}
|
||||
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
drawIntoCanvas {
|
||||
val paddingFg = (size * (1 - fgScale) * 0.5f).toPx()
|
||||
val paddingBg = (size * (1 - bgScale) * 0.5f).toPx()
|
||||
bg?.setBounds(
|
||||
paddingBg.toInt(),
|
||||
paddingBg.toInt(),
|
||||
(this.size.width - paddingBg).toInt(),
|
||||
(this.size.height - paddingBg).toInt()
|
||||
)
|
||||
bg?.draw(it.nativeCanvas)
|
||||
fg.setBounds(
|
||||
paddingFg.toInt(),
|
||||
paddingFg.toInt(),
|
||||
(this.size.width - paddingFg).toInt(),
|
||||
(this.size.height - paddingFg).toInt()
|
||||
)
|
||||
fg.draw(it.nativeCanvas)
|
||||
}
|
||||
}
|
||||
currentIcon?.let {
|
||||
IconLayer(
|
||||
it.backgroundLayer,
|
||||
size,
|
||||
colorTone = if (!LocalDarkTheme.current) 30 else 90,
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
)
|
||||
IconLayer(
|
||||
it.foregroundLayer,
|
||||
size,
|
||||
colorTone = if (!LocalDarkTheme.current) 90 else 10,
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
if (badge != null) {
|
||||
@@ -138,12 +139,18 @@ fun ShapedLauncherIcon(
|
||||
val number = badge.number
|
||||
if (badgeIconRes != null) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize().padding(size / 48),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(size / 48),
|
||||
painter = painterResource(badgeIconRes),
|
||||
contentDescription = null
|
||||
)
|
||||
} else if (badgeIcon != null) {
|
||||
Canvas(modifier = Modifier.fillMaxSize().padding(size / 48)) {
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(size / 48)
|
||||
) {
|
||||
badgeIcon.setBounds(
|
||||
0,
|
||||
0,
|
||||
@@ -171,6 +178,172 @@ fun ShapedLauncherIcon(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconLayer(
|
||||
layer: LauncherIconLayer,
|
||||
size: Dp,
|
||||
colorTone: Int,
|
||||
defaultTintColor: Color
|
||||
) {
|
||||
when (layer) {
|
||||
is ClockLayer -> {
|
||||
ClockLayer(layer.sublayers, scale = layer.scale, tintColor = null)
|
||||
}
|
||||
is TintedClockLayer -> {
|
||||
ClockLayer(
|
||||
layer.sublayers,
|
||||
scale = layer.scale,
|
||||
tintColor = if (layer.color == 0) defaultTintColor
|
||||
else Color(getTone(layer.color, colorTone))
|
||||
)
|
||||
|
||||
}
|
||||
is ColorLayer -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
if (layer.color == 0) {
|
||||
defaultTintColor
|
||||
} else {
|
||||
Color(getTone(layer.color, colorTone))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
is StaticIconLayer -> {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
withTransform({
|
||||
this.scale(layer.scale)
|
||||
}) {
|
||||
drawIntoCanvas {
|
||||
layer.icon.bounds = this.size.toRect().toAndroidRect()
|
||||
layer.icon.draw(it.nativeCanvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TextLayer -> {
|
||||
Text(
|
||||
text = layer.text,
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontSize = 20.sp * (size / 48.dp)
|
||||
),
|
||||
color = if (layer.color == 0) {
|
||||
defaultTintColor
|
||||
} else {
|
||||
Color(getTone(layer.color, colorTone))
|
||||
},
|
||||
)
|
||||
}
|
||||
is TintedIconLayer -> {
|
||||
val color =
|
||||
if (layer.color == 0) defaultTintColor.toArgb()
|
||||
else getTone(layer.color, colorTone)
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
withTransform({
|
||||
this.scale(layer.scale)
|
||||
}) {
|
||||
drawIntoCanvas {
|
||||
layer.icon.bounds = this.size.toRect().toAndroidRect()
|
||||
layer.icon.drawWithColorFilter(
|
||||
it.nativeCanvas,
|
||||
PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TransparentLayer -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTone(argb: Int, tone: Int): Int {
|
||||
return TonalPalette
|
||||
.fromInt(argb)
|
||||
.tone(tone)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClockLayer(
|
||||
sublayers: List<ClockSublayer>,
|
||||
scale: Float,
|
||||
tintColor: Color?,
|
||||
) {
|
||||
val time = remember {
|
||||
Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault())
|
||||
}
|
||||
val transition = rememberInfiniteTransition()
|
||||
|
||||
val minute by transition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 60f,
|
||||
animationSpec = InfiniteRepeatableSpec(
|
||||
animation = tween(durationMillis = 60 * 60 * 1000, easing = LinearEasing),
|
||||
initialStartOffset = StartOffset(
|
||||
offsetMillis = time.minute * 60 * 1000 + time.second * 1000,
|
||||
offsetType = StartOffsetType.FastForward
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val hour by transition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 12f,
|
||||
animationSpec = InfiniteRepeatableSpec(
|
||||
animation = tween(durationMillis = 12 * 60 * 60 * 1000, easing = LinearEasing),
|
||||
initialStartOffset = StartOffset(
|
||||
offsetMillis = (time.hour % 12) * 60 * 60 * 1000 + time.minute * 60 * 1000 + time.second * 1000,
|
||||
offsetType = StartOffsetType.FastForward
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
val second by transition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 60f,
|
||||
animationSpec = InfiniteRepeatableSpec(
|
||||
animation = tween(durationMillis = 60000, easing = LinearEasing),
|
||||
initialStartOffset = StartOffset(
|
||||
offsetMillis = time.second * 1000,
|
||||
offsetType = StartOffsetType.FastForward
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
val colorFilter = tintColor?.let {
|
||||
PorterDuffColorFilter(tintColor.toArgb(), PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
withTransform({
|
||||
this.scale(scale)
|
||||
}) {
|
||||
for (sublayer in sublayers) {
|
||||
withTransform({
|
||||
when (sublayer.role) {
|
||||
ClockSublayerRole.Hour -> {
|
||||
rotate(hour / 12f * 360f)
|
||||
}
|
||||
ClockSublayerRole.Minute -> {
|
||||
rotate(minute / 60f * 360f)
|
||||
}
|
||||
ClockSublayerRole.Second -> {
|
||||
rotate(second / 60f * 360f)
|
||||
}
|
||||
ClockSublayerRole.Static -> {}
|
||||
}
|
||||
}) {
|
||||
drawIntoCanvas {
|
||||
sublayer.drawable.bounds = this.size.toRect().toAndroidRect()
|
||||
sublayer.drawable.drawWithColorFilter(it.nativeCanvas, colorFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val LocalIconShape = compositionLocalOf<Shape> { CircleShape }
|
||||
|
||||
fun getShape(iconShape: IconShape): Shape {
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
package de.mm20.launcher2.ui.icons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
import de.mm20.launcher2.search.data.File
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.*
|
||||
|
||||
data class PlaceholderIcon(
|
||||
val color: Color,
|
||||
val icon: ImageVector
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Searchable.getPlaceholderIcon(): PlaceholderIcon {
|
||||
return when (this) {
|
||||
is Application -> getPlaceholderIcon()
|
||||
is File -> getPlaceholderIcon()
|
||||
else -> PlaceholderIcon(
|
||||
Color.LightGray,
|
||||
Icons.Rounded.Circle
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Application.getPlaceholderIcon(): PlaceholderIcon {
|
||||
return PlaceholderIcon(
|
||||
colorResource(id = R.color.android_green),
|
||||
Icons.Rounded.Android
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun File.getPlaceholderIcon(): PlaceholderIcon {
|
||||
return when {
|
||||
isDirectory -> PlaceholderIcon(
|
||||
colorResource(id = R.color.lightblue),
|
||||
Icons.Rounded.Folder
|
||||
)
|
||||
mimeType.startsWith("image/") -> PlaceholderIcon(
|
||||
colorResource(id = R.color.teal),
|
||||
Icons.Rounded.Image
|
||||
)
|
||||
mimeType.startsWith("audio/") -> PlaceholderIcon(
|
||||
colorResource(id = R.color.orange),
|
||||
Icons.Rounded.Audiotrack
|
||||
)
|
||||
mimeType.startsWith("video/") -> PlaceholderIcon(
|
||||
colorResource(id = R.color.purple),
|
||||
Icons.Rounded.Movie
|
||||
)
|
||||
/*
|
||||
else -> when (mimeType) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"application/vnd.google-apps.drawing" -> R.drawable.ic_file_picture to R.color.teal
|
||||
}*/
|
||||
else -> when (mimeType) {
|
||||
"application/pdf" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.red),
|
||||
Icons.Rounded.Pdf
|
||||
)
|
||||
"application/zip",
|
||||
"application/x-gtar",
|
||||
"application/x-tar",
|
||||
"application/java-archive",
|
||||
"application/x-7z-compressed",
|
||||
"application/x-compressed-tar",
|
||||
"application/x-zip-compressed",
|
||||
"application/x-gzip",
|
||||
"application/x-bzip2" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.brown),
|
||||
Icons.Rounded.Archive
|
||||
)
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/msword",
|
||||
"text/plain",
|
||||
"application/x-iwork-pages-sffpages",
|
||||
"application/vnd.apple.pages",
|
||||
"application/vnd.google-apps.document" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.blue),
|
||||
Icons.Rounded.Notes
|
||||
)
|
||||
"application/vnd.oasis.opendocument.spreadsheet",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-excel",
|
||||
"application/x-iwork-numbers-sffnumbers",
|
||||
"application/vnd.apple.numbers",
|
||||
"application/vnd.google-apps.spreadsheet" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.lightgreen),
|
||||
Icons.Rounded.BorderAll
|
||||
)
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/x-iwork-keynote-sffkey",
|
||||
"application/vnd.apple.keynote",
|
||||
"application/vnd.google-apps.presentation" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.amber),
|
||||
Icons.Rounded.Slideshow
|
||||
)
|
||||
"application/vnd.android.package-archive" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.android_green),
|
||||
Icons.Rounded.Android
|
||||
)
|
||||
"text/x-asm",
|
||||
"text/x-c",
|
||||
"text/x-java-source",
|
||||
"text/x-script.phyton",
|
||||
"text/x-pascal",
|
||||
"text/x-script.perl",
|
||||
"text/javascript",
|
||||
"application/json" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.pink),
|
||||
Icons.Rounded.Code
|
||||
)
|
||||
"text/xml",
|
||||
"text/html" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.deeporange),
|
||||
Icons.Rounded.Code
|
||||
)
|
||||
"application/vnd.google-apps.form" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.deeppurple),
|
||||
Icons.Rounded.ViewList
|
||||
)
|
||||
"application/epub+zip" -> PlaceholderIcon(
|
||||
colorResource(id = R.color.blue),
|
||||
Icons.Rounded.Book
|
||||
)
|
||||
else -> PlaceholderIcon(
|
||||
colorResource(id = R.color.bluegrey),
|
||||
Icons.Rounded.InsertDriveFile
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.android.launcher3.GestureNavContract
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import de.mm20.launcher2.icons.DynamicIconController
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.base.BaseActivity
|
||||
@@ -45,7 +44,6 @@ import de.mm20.launcher2.ui.launcher.transitions.LocalHomeTransitionManager
|
||||
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import de.mm20.launcher2.ui.theme.LauncherTheme
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
class LauncherActivity : BaseActivity() {
|
||||
@@ -174,10 +172,6 @@ class LauncherActivity : BaseActivity() {
|
||||
editFavoritesDialog = null
|
||||
}
|
||||
}
|
||||
|
||||
val dynamicIconController: DynamicIconController by inject()
|
||||
|
||||
lifecycle.addObserver(dynamicIconController)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
|
||||
@@ -1,31 +1,10 @@
|
||||
package de.mm20.launcher2.ui.legacy.view
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.toRectF
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.IconSettings.IconShape
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.helper.BitmapHolder
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import java.lang.Math.pow
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.hypot
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class LauncherIconView : View, KoinComponent {
|
||||
constructor(context: Context) : super(context)
|
||||
@@ -36,499 +15,10 @@ class LauncherIconView : View, KoinComponent {
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
var shape: IconShape
|
||||
set(value) {
|
||||
if (value == IconShape.PlatformDefault) {
|
||||
platformShape = getSystemShape()
|
||||
transformMatrix = Matrix()
|
||||
platformShapeBounds = RectF()
|
||||
field = value
|
||||
} else {
|
||||
platformShape = null
|
||||
transformMatrix = null
|
||||
platformShapeBounds = null
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
private var platformShape: Path? = null
|
||||
private var transformMatrix: Matrix? = null
|
||||
private var platformShapeBounds: RectF? = null
|
||||
|
||||
|
||||
private fun getSystemShape(): Path {
|
||||
return AdaptiveIconDrawable(null, null).iconMask
|
||||
}
|
||||
|
||||
var icon: LauncherIcon? = null
|
||||
set(value) {
|
||||
field = value
|
||||
foregroundScale = value?.foregroundScale ?: 1f
|
||||
backgroundScale = value?.backgroundScale ?: 1f
|
||||
value?.registerCallback(iconObserver)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private val iconObserver: (LauncherIcon) -> Unit = {
|
||||
foregroundScale = it.foregroundScale
|
||||
backgroundScale = it.backgroundScale
|
||||
// Implicit invalidate
|
||||
}
|
||||
|
||||
var foregroundScale = 1f
|
||||
set(value) {
|
||||
field = value
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
var backgroundScale = 1f
|
||||
set(value) {
|
||||
field = value
|
||||
postInvalidate()
|
||||
}
|
||||
|
||||
init {
|
||||
shape = IconShape.Circle
|
||||
setLayerType(LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
|
||||
|
||||
override fun setElevation(elevation: Float) {
|
||||
super.setElevation(elevation)
|
||||
shadowPaint = updateShadowPaint()
|
||||
badgeShadowPaint = updateBadgeShadowPaing()
|
||||
}
|
||||
|
||||
override fun setTranslationZ(translationZ: Float) {
|
||||
super.setTranslationZ(translationZ)
|
||||
shadowPaint = updateShadowPaint()
|
||||
badgeShadowPaint = updateBadgeShadowPaing()
|
||||
}
|
||||
|
||||
private var shadowPaint: Paint = updateShadowPaint()
|
||||
|
||||
private var badgeShadowPaint = updateBadgeShadowPaing()
|
||||
|
||||
private fun updateShadowPaint(): Paint {
|
||||
return Paint().apply {
|
||||
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
|
||||
color = Color.TRANSPARENT
|
||||
isAntiAlias = true
|
||||
setShadowLayer(0.5f * z, 0f, 0.5f * z, 0x40000000)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBadgeShadowPaing(): Paint {
|
||||
return Paint().apply {
|
||||
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
|
||||
color = Color.TRANSPARENT
|
||||
isAntiAlias = true
|
||||
setShadowLayer(0.5f * z, 0f, 0.5f * z, 0x40000000)
|
||||
}
|
||||
}
|
||||
|
||||
private val drawRect = Rect()
|
||||
private val bmpDrawRect = Rect()
|
||||
|
||||
private val maskPaint = Paint().apply {
|
||||
style = Paint.Style.FILL
|
||||
color = 0xFF000000.toInt()
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val bitmapPaint = Paint().apply {
|
||||
color = 0xFF000000.toInt()
|
||||
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
|
||||
isAntiAlias = true
|
||||
isFilterBitmap = true
|
||||
}
|
||||
|
||||
private val badgeTextPaint = Paint().apply {
|
||||
color = ContextCompat.getColor(context, R.color.badge_text)
|
||||
isAntiAlias = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
private val badgeProgressPaint = Paint().apply {
|
||||
color = 0x30000000
|
||||
isAntiAlias = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
private var path: Path = Path()
|
||||
|
||||
private val badgeRect = RectF()
|
||||
private val textBounds = Rect()
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
val fg = icon?.foreground ?: return
|
||||
val bg = icon?.background
|
||||
canvas.getClipBounds(drawRect)
|
||||
drawRect.left += paddingLeft
|
||||
drawRect.top += paddingTop
|
||||
drawRect.right -= paddingRight
|
||||
drawRect.bottom -= paddingBottom
|
||||
val (bmp, c) = BitmapHolder.getBitmapAndCanvas((drawRect.width() * 1.8).toInt())
|
||||
c.getClipBounds(bmpDrawRect)
|
||||
fg.bounds = bmpDrawRect
|
||||
if (bg != null) {
|
||||
bg.bounds = bmpDrawRect
|
||||
when (shape) {
|
||||
IconShape.PlatformDefault -> {
|
||||
path.rewind()
|
||||
val matrix = transformMatrix!!
|
||||
val bounds = platformShapeBounds!!
|
||||
val shape = platformShape!!
|
||||
shape.computeBounds(bounds, true)
|
||||
matrix.setRectToRect(
|
||||
bounds,
|
||||
badgeRect.also { drawRect.toRectF(it) },
|
||||
Matrix.ScaleToFit.CENTER
|
||||
)
|
||||
path.rewind()
|
||||
shape.transform(matrix, path)
|
||||
canvas.drawPath(path, maskPaint)
|
||||
}
|
||||
IconShape.Circle -> {
|
||||
canvas.drawOval(
|
||||
drawRect.left.toFloat(),
|
||||
drawRect.top.toFloat(),
|
||||
drawRect.right.toFloat(),
|
||||
drawRect.bottom.toFloat(),
|
||||
maskPaint
|
||||
)
|
||||
}
|
||||
IconShape.Square -> {
|
||||
canvas.drawRect(drawRect, maskPaint)
|
||||
}
|
||||
IconShape.RoundedSquare -> {
|
||||
canvas.drawRoundRect(
|
||||
drawRect.left.toFloat(),
|
||||
drawRect.top.toFloat(),
|
||||
drawRect.right.toFloat(),
|
||||
drawRect.bottom.toFloat(),
|
||||
width * 0.125f,
|
||||
height * 0.125f,
|
||||
maskPaint
|
||||
)
|
||||
}
|
||||
IconShape.Triangle -> {
|
||||
path.rewind()
|
||||
var cx = drawRect.left.toFloat()
|
||||
var cy = drawRect.top + drawRect.height().toFloat() * 0.86f
|
||||
val r = drawRect.width()
|
||||
path.moveTo(cx, cy)
|
||||
path.arcTo(cx - r, cy - r, cx + r, cy + r, 300f, 60f, true)
|
||||
canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 300f, 60f, true, maskPaint)
|
||||
cx = drawRect.right.toFloat()
|
||||
cy = drawRect.top + drawRect.height().toFloat() * 0.86f
|
||||
path.lineTo(cx, cy)
|
||||
path.arcTo(cx - r, cy - r, cx + r, cy + r, 180f, 60f, true)
|
||||
canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 180f, 60f, true, maskPaint)
|
||||
cx = drawRect.left + drawRect.width() * 0.5f
|
||||
cy = drawRect.top.toFloat()
|
||||
path.lineTo(cx, cy)
|
||||
path.close()
|
||||
path.arcTo(cx - r, cy - r, cx + r, cy + r, 60f, 60f, true)
|
||||
canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 60f, 60f, true, maskPaint)
|
||||
|
||||
}
|
||||
IconShape.Squircle -> {
|
||||
path.rewind()
|
||||
val radius = drawRect.width() / 2
|
||||
val radiusToPow = pow(radius.toDouble(), 3.0)
|
||||
path.moveTo(-radius.toFloat(), 0f)
|
||||
for (x in -radius..radius)
|
||||
path.lineTo(
|
||||
x.toFloat(),
|
||||
Math.cbrt(radiusToPow - Math.abs(x * x * x)).toFloat()
|
||||
)
|
||||
for (x in radius downTo -radius)
|
||||
path.lineTo(
|
||||
x.toFloat(),
|
||||
(-Math.cbrt(radiusToPow - Math.abs(x * x * x))).toFloat()
|
||||
)
|
||||
path.close()
|
||||
canvas.save()
|
||||
canvas.translate(width / 2f, height / 2f)
|
||||
canvas.drawPath(path, maskPaint)
|
||||
canvas.restore()
|
||||
}
|
||||
IconShape.Hexagon -> {
|
||||
path.rewind()
|
||||
path.moveTo(
|
||||
drawRect.left + drawRect.width() * 0.25f,
|
||||
drawRect.top + drawRect.height() * 0.933f
|
||||
)
|
||||
path.lineTo(
|
||||
drawRect.left + drawRect.width() * 0.75f,
|
||||
drawRect.top + drawRect.height() * 0.933f
|
||||
)
|
||||
path.lineTo(
|
||||
drawRect.left + drawRect.width() * 1.0f,
|
||||
drawRect.top + drawRect.height() * 0.5f
|
||||
)
|
||||
path.lineTo(
|
||||
drawRect.left + drawRect.width() * 0.75f,
|
||||
drawRect.top + drawRect.height() * 0.067f
|
||||
)
|
||||
path.lineTo(
|
||||
drawRect.left + drawRect.width() * 0.25f,
|
||||
drawRect.top + drawRect.height() * 0.067f
|
||||
)
|
||||
path.lineTo(drawRect.left.toFloat(), drawRect.top + drawRect.height() * 0.5f)
|
||||
path.close()
|
||||
canvas.drawPath(path, maskPaint)
|
||||
}
|
||||
IconShape.EasterEgg -> {
|
||||
path.rewind()
|
||||
path.moveTo(
|
||||
0.49999999f * drawRect.width() + drawRect.left,
|
||||
1f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.lineTo(
|
||||
0.42749999f * drawRect.width() + drawRect.left,
|
||||
0.9339999999999999f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.cubicTo(
|
||||
0.16999998f * drawRect.width() + drawRect.left,
|
||||
0.7005004f * drawRect.height() + drawRect.top,
|
||||
0f + drawRect.left,
|
||||
0.5460004f * drawRect.height() + drawRect.top,
|
||||
0f + drawRect.left,
|
||||
0.3575003f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.cubicTo(
|
||||
0f + drawRect.left,
|
||||
0.2030004f * drawRect.height() + drawRect.top,
|
||||
0.12100002f * drawRect.width() + drawRect.left,
|
||||
0.0825004f * drawRect.height() + drawRect.top,
|
||||
0.275f * drawRect.width() + drawRect.left,
|
||||
0.0825004f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.cubicTo(
|
||||
0.362f * drawRect.width() + drawRect.left,
|
||||
0.0825004f * drawRect.height() + drawRect.top,
|
||||
0.4455f * drawRect.width() + drawRect.left,
|
||||
0.123f * drawRect.height() + drawRect.top,
|
||||
0.5f * drawRect.width() + drawRect.left,
|
||||
0.1865003f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.cubicTo(
|
||||
0.55449999f * drawRect.width() + drawRect.left,
|
||||
0.123f * drawRect.height() + drawRect.top,
|
||||
0.638f * drawRect.width() + drawRect.left,
|
||||
0.0825f * drawRect.height() + drawRect.top,
|
||||
0.725f * drawRect.width() + drawRect.left,
|
||||
0.0825f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.cubicTo(
|
||||
0.87900006f * drawRect.width() + drawRect.left,
|
||||
0.0825004f * drawRect.height() + drawRect.top,
|
||||
1f * drawRect.width() + drawRect.left,
|
||||
0.2030004f * drawRect.height() + drawRect.top,
|
||||
1f * drawRect.width() + drawRect.left,
|
||||
0.3575003f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.cubicTo(
|
||||
1f * drawRect.width() + drawRect.left,
|
||||
0.5460004f * drawRect.height() + drawRect.top,
|
||||
0.82999999f * drawRect.width() + drawRect.left,
|
||||
0.7005004f * drawRect.height() + drawRect.top,
|
||||
0.57250001f * drawRect.width() + drawRect.left,
|
||||
0.9340004f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.close()
|
||||
canvas.drawPath(path, maskPaint)
|
||||
}
|
||||
IconShape.Pentagon -> {
|
||||
path.rewind()
|
||||
path.moveTo(
|
||||
0.49997027f * drawRect.width() + drawRect.left,
|
||||
0.0060308f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.lineTo(
|
||||
0.99994053f * drawRect.width() + drawRect.left,
|
||||
0.36928048f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.lineTo(
|
||||
0.80896887f * drawRect.width() + drawRect.left,
|
||||
0.95703078f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.lineTo(
|
||||
0.19097162f * drawRect.width() + drawRect.left,
|
||||
0.95703076f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.lineTo(
|
||||
drawRect.left.toFloat(),
|
||||
0.36928045f * drawRect.height() + drawRect.top
|
||||
)
|
||||
path.close()
|
||||
canvas.drawPath(path, maskPaint)
|
||||
}
|
||||
}
|
||||
c.save()
|
||||
c.scale(
|
||||
backgroundScale,
|
||||
backgroundScale,
|
||||
bmpDrawRect.centerX().toFloat(),
|
||||
bmpDrawRect.centerY().toFloat()
|
||||
)
|
||||
bg.draw(c)
|
||||
c.restore()
|
||||
}
|
||||
c.save()
|
||||
c.scale(
|
||||
foregroundScale,
|
||||
foregroundScale,
|
||||
bmpDrawRect.centerX().toFloat(),
|
||||
bmpDrawRect.centerY().toFloat()
|
||||
)
|
||||
fg.draw(c)
|
||||
c.restore()
|
||||
if (bg != null) {
|
||||
canvas.drawBitmap(bmp, bmpDrawRect, drawRect, bitmapPaint)
|
||||
} else {
|
||||
canvas.drawBitmap(bmp, bmpDrawRect, drawRect, maskPaint)
|
||||
}
|
||||
if (bg != null) {
|
||||
when (shape) {
|
||||
IconShape.Circle -> {
|
||||
canvas.drawOval(
|
||||
drawRect.left.toFloat(),
|
||||
drawRect.top.toFloat(),
|
||||
drawRect.right.toFloat(),
|
||||
drawRect.bottom.toFloat(),
|
||||
shadowPaint
|
||||
)
|
||||
}
|
||||
IconShape.Square -> {
|
||||
canvas.drawRect(drawRect, shadowPaint)
|
||||
}
|
||||
IconShape.RoundedSquare -> {
|
||||
canvas.drawRoundRect(
|
||||
drawRect.left.toFloat(),
|
||||
drawRect.top.toFloat(),
|
||||
drawRect.right.toFloat(),
|
||||
drawRect.bottom.toFloat(),
|
||||
width * 0.125f,
|
||||
height * 0.125f,
|
||||
shadowPaint
|
||||
)
|
||||
}
|
||||
IconShape.Triangle, IconShape.Hexagon, IconShape.EasterEgg, IconShape.Pentagon, IconShape.PlatformDefault -> {
|
||||
canvas.drawPath(path, shadowPaint)
|
||||
}
|
||||
IconShape.Squircle -> {
|
||||
canvas.save()
|
||||
canvas.translate(width / 2f, height / 2f)
|
||||
canvas.drawPath(path, shadowPaint)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var longClicked = false
|
||||
private val longClickRunnable = Runnable {
|
||||
longClicked = true
|
||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
performLongClick()
|
||||
}
|
||||
|
||||
private var downX = 0f
|
||||
private var downY = 0f
|
||||
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (!hasOnClickListeners()) return false
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
animateTouchDown()
|
||||
downX = ev.rawX
|
||||
downY = ev.rawY
|
||||
longClicked = false
|
||||
handler?.postDelayed(
|
||||
longClickRunnable,
|
||||
ViewConfiguration.getLongPressTimeout().toLong()
|
||||
)
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (abs(hypot(downX - ev.rawX, downY - ev.rawY)) > width * 0.25f) {
|
||||
handler?.removeCallbacks(longClickRunnable)
|
||||
animateTouchUp()
|
||||
return false
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
animateTouchUp()
|
||||
if (ev.x > 0 && ev.x < width && ev.y > 0 && ev.y < height && !longClicked) {
|
||||
performClick()
|
||||
}
|
||||
handler?.removeCallbacks(longClickRunnable)
|
||||
return false
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_OUTSIDE -> {
|
||||
animateTouchUp()
|
||||
handler?.removeCallbacks(longClickRunnable)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun animateTouchUp() {
|
||||
AnimatorSet().also {
|
||||
it.playTogether(
|
||||
ViewPropertyObjectAnimator.animate(this).translationZ(0f).get(),
|
||||
ObjectAnimator.ofFloat(this, "foregroundScale", icon?.foregroundScale ?: 1f),
|
||||
ObjectAnimator.ofFloat(this, "backgroundScale", icon?.backgroundScale ?: 1f)
|
||||
)
|
||||
it.duration = 300
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateTouchDown() {
|
||||
AnimatorSet().also {
|
||||
it.playTogether(
|
||||
ViewPropertyObjectAnimator.animate(this).translationZ(2 * dp).get(),
|
||||
ObjectAnimator.ofFloat(
|
||||
this, "foregroundScale", (icon?.foregroundScale
|
||||
?: 1f) * 0.8f
|
||||
),
|
||||
ObjectAnimator.ofFloat(
|
||||
this, "backgroundScale", (icon?.backgroundScale
|
||||
?: 1f) * 1.2f
|
||||
)
|
||||
)
|
||||
it.duration = 250
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object: KoinComponent {
|
||||
|
||||
var currentShape: IconShape = IconShape.PlatformDefault
|
||||
|
||||
fun getDefaultShape(): Flow<IconShape> = channelFlow {
|
||||
send(currentShape)
|
||||
val dataStore: LauncherDataStore = get()
|
||||
dataStore.data.map { it.icons.shape }.distinctUntilChanged().collectLatest { shape ->
|
||||
dataStore.data.map { it.easterEgg }.distinctUntilChanged().collectLatest { ee ->
|
||||
if (ee) {
|
||||
currentShape = IconShape.EasterEgg
|
||||
send(IconShape.EasterEgg)
|
||||
} else {
|
||||
currentShape = shape
|
||||
send(shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-102
@@ -1,17 +1,12 @@
|
||||
package de.mm20.launcher2.ui.settings.appearance
|
||||
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@@ -24,6 +19,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.airbnb.lottie.LottieProperty
|
||||
import com.airbnb.lottie.compose.*
|
||||
@@ -31,7 +27,9 @@ import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.HorizontalPagerIndicator
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.icons.ColorLayer
|
||||
import de.mm20.launcher2.icons.StaticIconLayer
|
||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||
import de.mm20.launcher2.preferences.Settings.*
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
|
||||
@@ -58,8 +56,8 @@ fun AppearanceSettingsScreen() {
|
||||
title = stringResource(id = R.string.preference_layout),
|
||||
summary = stringResource(id = R.string.preference_layout_summary),
|
||||
value = layout, onValueChanged = {
|
||||
viewModel.setLayout(it)
|
||||
})
|
||||
viewModel.setLayout(it)
|
||||
})
|
||||
val theme by viewModel.theme.observeAsState()
|
||||
ListPreference(
|
||||
title = stringResource(id = R.string.preference_theme),
|
||||
@@ -147,6 +145,15 @@ fun AppearanceSettingsScreen() {
|
||||
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),
|
||||
@@ -176,17 +183,6 @@ fun AppearanceSettingsScreen() {
|
||||
if (it != null) viewModel.setIconPack(it)
|
||||
}
|
||||
)
|
||||
|
||||
val legacyIconBackground by viewModel.legacyIconBackground.observeAsState()
|
||||
LegacyIconBackgroundPreference(
|
||||
title = stringResource(R.string.preference_legacy_icon_bg),
|
||||
summary = stringResource(R.string.preference_legacy_icon_bg_summary),
|
||||
value = legacyIconBackground,
|
||||
onValueChanged = {
|
||||
viewModel.setLegacyIconBackground(it)
|
||||
},
|
||||
iconShape = iconShape
|
||||
)
|
||||
}
|
||||
PreferenceCategory(stringResource(R.string.preference_category_searchbar)) {
|
||||
val searchBarStyle by viewModel.searchBarStyle.observeAsState()
|
||||
@@ -370,13 +366,15 @@ fun IconShapePreference(
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
size = 48.dp,
|
||||
icon = LauncherIcon(
|
||||
foreground = AppCompatResources.getDrawable(
|
||||
LocalContext.current,
|
||||
R.mipmap.ic_launcher_foreground
|
||||
)!!,
|
||||
foregroundScale = 1.5f,
|
||||
background = ColorDrawable(LocalContext.current.getColor(R.color.ic_launcher_background))
|
||||
icon = StaticLauncherIcon(
|
||||
foregroundLayer = StaticIconLayer(
|
||||
icon = ContextCompat.getDrawable(
|
||||
LocalContext.current,
|
||||
R.mipmap.ic_launcher_foreground
|
||||
)!!,
|
||||
scale = 1.5f,
|
||||
),
|
||||
ColorLayer(LocalContext.current.getColor(R.color.ic_launcher_background))
|
||||
),
|
||||
onClick = {
|
||||
onValueChanged(it)
|
||||
@@ -399,82 +397,6 @@ fun IconShapePreference(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LegacyIconBackgroundPreference(
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
value: IconSettings.LegacyIconBackground?,
|
||||
onValueChanged: (IconSettings.LegacyIconBackground) -> Unit,
|
||||
iconShape: IconSettings.IconShape
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
Preference(title = title, summary = summary, onClick = { showDialog = true })
|
||||
|
||||
if (showDialog && value != null) {
|
||||
val colors = remember {
|
||||
IconSettings.LegacyIconBackground.values()
|
||||
.filter { it != IconSettings.LegacyIconBackground.UNRECOGNIZED }
|
||||
}
|
||||
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(colors) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
size = 48.dp,
|
||||
icon = LauncherIcon(
|
||||
foreground = AppCompatResources.getDrawable(
|
||||
LocalContext.current,
|
||||
R.mipmap.ic_launcher_foreground
|
||||
)!!,
|
||||
background = null,
|
||||
autoGenerateBackgroundMode = when (it) {
|
||||
IconSettings.LegacyIconBackground.Dynamic -> LauncherIcon.BACKGROUND_DYNAMIC
|
||||
IconSettings.LegacyIconBackground.None -> LauncherIcon.BACKGROUND_NONE
|
||||
else -> LauncherIcon.BACKGROUND_WHITE
|
||||
}
|
||||
),
|
||||
onClick = {
|
||||
onValueChanged(it)
|
||||
showDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun LayoutPreference(
|
||||
|
||||
+3
-4
@@ -9,7 +9,6 @@ import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
|
||||
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
|
||||
import de.mm20.launcher2.preferences.Settings.SearchBarSettings
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -108,14 +107,14 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
val legacyIconBackground = dataStore.data.map { it.icons.legacyIconBg }.asLiveData()
|
||||
fun setLegacyIconBackground(legacyIconBackground: LegacyIconBackground) {
|
||||
val adaptifyLegacyIcons = dataStore.data.map { it.icons.adaptify }.asLiveData()
|
||||
fun setAdaptifyLegacyIcons(adaptify: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setIcons(
|
||||
it.icons.toBuilder()
|
||||
.setLegacyIconBg(legacyIconBackground)
|
||||
.setAdaptify(adaptify)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user