Clean :ui module
This commit is contained in:
parent
bb4a7a1e04
commit
35132b4e4b
@ -1,143 +0,0 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
|
||||
import de.mm20.launcher2.ui.locals.LocalColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colors.toDarkColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colors.toLightColorScheme
|
||||
|
||||
val Poppins = FontFamily(
|
||||
Font(R.font.poppins100, FontWeight.Thin, FontStyle.Normal),
|
||||
Font(R.font.poppins100i, FontWeight.Thin, FontStyle.Italic),
|
||||
Font(R.font.poppins200, FontWeight.ExtraLight, FontStyle.Normal),
|
||||
Font(R.font.poppins200i, FontWeight.ExtraLight, FontStyle.Italic),
|
||||
Font(R.font.poppins300, FontWeight.Light, FontStyle.Normal),
|
||||
Font(R.font.poppins300i, FontWeight.Light, FontStyle.Italic),
|
||||
Font(R.font.poppins400, FontWeight.Normal, FontStyle.Normal),
|
||||
Font(R.font.poppins400i, FontWeight.Normal, FontStyle.Italic),
|
||||
Font(R.font.poppins500, FontWeight.Medium, FontStyle.Normal),
|
||||
Font(R.font.poppins500i, FontWeight.Medium, FontStyle.Italic),
|
||||
Font(R.font.poppins600, FontWeight.SemiBold, FontStyle.Normal),
|
||||
Font(R.font.poppins600i, FontWeight.SemiBold, FontStyle.Italic),
|
||||
Font(R.font.poppins700, FontWeight.Bold, FontStyle.Normal),
|
||||
Font(R.font.poppins700i, FontWeight.Bold, FontStyle.Italic),
|
||||
Font(R.font.poppins800, FontWeight.ExtraBold, FontStyle.Normal),
|
||||
Font(R.font.poppins800i, FontWeight.ExtraBold, FontStyle.Italic),
|
||||
Font(R.font.poppins900, FontWeight.Black, FontStyle.Normal),
|
||||
Font(R.font.poppins900i, FontWeight.Black, FontStyle.Italic),
|
||||
)
|
||||
|
||||
|
||||
val typography = Typography(
|
||||
displayLarge = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 57.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
displayMedium = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 45.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
displaySmall = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 36.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
headlineLarge = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
headlineMedium = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
headlineSmall = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
titleSmall = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodySmall = TextStyle(
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
labelLarge = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = Poppins,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LauncherTheme(content: @Composable () -> Unit) {
|
||||
|
||||
val theme = Theme.System
|
||||
|
||||
val darkTheme = theme == Theme.Dark || theme == Theme.System && isSystemInDarkTheme()
|
||||
|
||||
val colorScheme = LocalColorScheme.current
|
||||
|
||||
val colors = if (darkTheme) {
|
||||
colorScheme.toDarkColorScheme()
|
||||
} else {
|
||||
colorScheme.toLightColorScheme()
|
||||
}
|
||||
|
||||
androidx.compose.material.MaterialTheme(
|
||||
colors = if (darkTheme) darkColors() else lightColors()
|
||||
) {
|
||||
MaterialTheme(
|
||||
colorScheme = colors,
|
||||
typography = typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.locals.LocalColorScheme
|
||||
import de.mm20.launcher2.ui.theme.colors.ColorSwatch
|
||||
|
||||
@Composable
|
||||
fun ColorSchemeTest() {
|
||||
val colorScheme = LocalColorScheme.current
|
||||
|
||||
Card {
|
||||
Column {
|
||||
SwatchRow(swatch = colorScheme.neutral)
|
||||
SwatchRow(swatch = colorScheme.neutralVariant)
|
||||
SwatchRow(swatch = colorScheme.primary)
|
||||
SwatchRow(swatch = colorScheme.secondary)
|
||||
SwatchRow(swatch = colorScheme.tertiary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SwatchRow(swatch: ColorSwatch) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade100))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade99))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade95))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade90))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade80))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade70))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade60))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade50))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade40))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade30))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade20))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade10))
|
||||
Box(modifier = Modifier
|
||||
.height(24.dp).weight(1f)
|
||||
.background(swatch.shade0))
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.theme.divider
|
||||
|
||||
@Composable
|
||||
fun InformationText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
Card(
|
||||
elevation = 0.dp,
|
||||
border = BorderStroke(
|
||||
width = 1.dp,
|
||||
color = LocalContentColor.current.copy(alpha = ContentAlpha.divider)),
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = (if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier).padding(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun LauncherCard() {
|
||||
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.asComposePath
|
||||
import androidx.core.graphics.flatten
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
|
||||
val LocalLauncherIconShape = compositionLocalOf { LauncherIconShape.circle }
|
||||
|
||||
object LauncherIconShape {
|
||||
val circle: Shape = CircleShape
|
||||
val square: Shape = RectangleShape
|
||||
val roundedSquare: Shape = RoundedCornerShape(13)
|
||||
val hexagon: Shape = GenericShape { size, _ ->
|
||||
moveTo(size.width * 0.25f, size.height * 0.933f)
|
||||
lineTo(size.width * 0.75f, size.height * 0.933f)
|
||||
lineTo(size.width * 1.0f, size.height * 0.5f)
|
||||
lineTo(size.width * 0.75f, size.height * 0.067f)
|
||||
lineTo(size.width * 0.25f, size.height * 0.067f)
|
||||
lineTo(0f, size.height * 0.5f)
|
||||
close()
|
||||
}
|
||||
val platformDefault: Shape = run {
|
||||
val platformShape = getSystemShape()
|
||||
GenericShape { size, _ ->
|
||||
Log.d("MM20", "GenericShape {}")
|
||||
val matrix = Matrix()
|
||||
val bounds = RectF()
|
||||
platformShape.computeBounds(bounds, true)
|
||||
matrix.setRectToRect(bounds, RectF(0f, 0f, size.width, size.height), Matrix.ScaleToFit.CENTER)
|
||||
platformShape.transform(matrix)
|
||||
addPath(platformShape.asComposePath())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSystemShape(): Path {
|
||||
return AdaptiveIconDrawable(null, null).iconMask
|
||||
}
|
||||
}
|
||||
@ -1,90 +1,12 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import android.util.TypedValue
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import com.google.android.material.composethemeadapter3.Mdc3Theme
|
||||
import androidx.compose.material.MaterialTheme as Material2Theme
|
||||
|
||||
val legacyTypography = Typography(
|
||||
displayLarge = TextStyle(
|
||||
fontSize = 57.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
displayMedium = TextStyle(
|
||||
fontSize = 45.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
displaySmall = TextStyle(
|
||||
fontSize = 36.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
headlineLarge = TextStyle(
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
headlineMedium = TextStyle(
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
headlineSmall = TextStyle(
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
titleSmall = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
bodySmall = TextStyle(
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
labelLarge = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@Composable
|
||||
fun LegacyLauncherTheme(content: @Composable () -> Unit) {
|
||||
|
||||
fun MdcLauncherTheme(content: @Composable () -> Unit) {
|
||||
Mdc3Theme {
|
||||
MdcTheme(content = content)
|
||||
}
|
||||
|
||||
@ -1,204 +0,0 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.icons.PlaceholderIcon
|
||||
import de.mm20.launcher2.ui.icons.getPlaceholderIcon
|
||||
import de.mm20.launcher2.ui.ktx.conditional
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
@Composable
|
||||
fun ShapedLauncherIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 64.dp,
|
||||
item: Searchable,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val iconSize = size.toPixels().toInt()
|
||||
|
||||
var icon by remember {
|
||||
mutableStateOf<LauncherIcon?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(item) {
|
||||
icon = withContext(Dispatchers.IO) {
|
||||
item.loadIcon(context, iconSize, LegacyIconBackground.Dynamic)
|
||||
}
|
||||
}
|
||||
|
||||
val placeholderIcon = item.getPlaceholderIcon()
|
||||
|
||||
ShapedLauncherIcon(
|
||||
modifier = modifier,
|
||||
size = size,
|
||||
icon = icon,
|
||||
placeholder = placeholderIcon,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ShapedLauncherIcon(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 64.dp,
|
||||
icon: LauncherIcon?,
|
||||
placeholder: PlaceholderIcon,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null
|
||||
) {
|
||||
val iconShape = LocalLauncherIconShape.current
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
val fgScale by animateFloatAsState(if (isPressed) 0.75f else 1f)
|
||||
val bgScale by animateFloatAsState(if (isPressed) 1.25f else 1f)
|
||||
|
||||
Surface(
|
||||
shape = iconShape,
|
||||
shadowElevation = animateDpAsState(if (isPressed) 4.dp else 1.dp).value,
|
||||
modifier = modifier
|
||||
.requiredSize(size)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(size)
|
||||
.background(
|
||||
color = if (icon == null) {
|
||||
placeholder.color.copy(alpha = 0.4f).compositeOver(MaterialTheme.colorScheme.surface)
|
||||
} else {
|
||||
Color.Gray
|
||||
}
|
||||
)
|
||||
.conditional(
|
||||
onClick != null || onLongClick != null, Modifier.combinedClickable(
|
||||
onClick = {
|
||||
onClick?.invoke()
|
||||
},
|
||||
onLongClick = {
|
||||
onLongClick?.invoke()
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (icon == null) {
|
||||
Icon(
|
||||
imageVector = placeholder.icon, contentDescription = null,
|
||||
tint = placeholder.color,
|
||||
modifier = Modifier
|
||||
.scale(fgScale)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
} else {
|
||||
|
||||
val fg = icon.foreground
|
||||
val bg = icon.background
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
drawIntoCanvas {
|
||||
val actualSize = size.toPx() * icon.backgroundScale * bgScale
|
||||
val offset = (size.toPx() - actualSize) / 2
|
||||
bg?.setBounds(
|
||||
offset.toInt(),
|
||||
offset.toInt(),
|
||||
(offset + actualSize).toInt(),
|
||||
(offset + actualSize).toInt()
|
||||
)
|
||||
bg?.draw(it.nativeCanvas)
|
||||
}
|
||||
}
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
drawIntoCanvas {
|
||||
val actualSize = size.toPx() * icon.foregroundScale * fgScale
|
||||
val offset = (size.toPx() - actualSize) / 2
|
||||
fg.setBounds(
|
||||
offset.toInt(),
|
||||
offset.toInt(),
|
||||
(offset + actualSize).toInt(),
|
||||
(offset + actualSize).toInt()
|
||||
)
|
||||
fg.draw(it.nativeCanvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*private fun getSystemShape(): AndroidPath? {
|
||||
return if (isAtLeastApiLevel(Build.VERSION_CODES.O)) {
|
||||
AdaptiveIconDrawable(null, null).iconMask
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconShape(shape: LauncherIconShape): Shape {
|
||||
return when (shape) {
|
||||
LauncherIconShape.Circle -> CircleShape
|
||||
LauncherIconShape.Square -> RectangleShape
|
||||
LauncherIconShape.RoundedSquare -> RoundedCornerShape(13)
|
||||
LauncherIconShape.Hexagon -> GenericShape {
|
||||
moveTo(it.width * 0.25f, it.height * 0.933f)
|
||||
lineTo(it.width * 0.75f, it.height * 0.933f)
|
||||
lineTo(it.width * 1.0f, it.height * 0.5f)
|
||||
lineTo(it.width * 0.75f, it.height * 0.067f)
|
||||
lineTo(it.width * 0.25f, it.height * 0.067f)
|
||||
lineTo(0f, it.height * 0.5f)
|
||||
close()
|
||||
}
|
||||
LauncherIconShape.PlatformDefault -> {
|
||||
val platformShape = getSystemShape() ?: return CircleShape
|
||||
GenericShape {
|
||||
val matrix = AndroidMatrix()
|
||||
val bounds = RectF()
|
||||
platformShape.computeBounds(bounds, true)
|
||||
matrix.setRectToRect(bounds, RectF(0f, 0f, it.width, it.height), AndroidMatrix.ScaleToFit.CENTER)
|
||||
platformShape.transform(matrix)
|
||||
addPath(platformShape.asComposePath())
|
||||
}
|
||||
}
|
||||
else -> CircleShape
|
||||
}
|
||||
}*/
|
||||
@ -1,23 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun Chip(
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.border(1.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f))
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
|
||||
@Composable
|
||||
fun ChipGroup(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
mainAxisSpacing = 16.dp,
|
||||
crossAxisSpacing = 8.dp
|
||||
)
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import de.mm20.launcher2.ui.ktx.toHexString
|
||||
|
||||
@Composable
|
||||
fun ColorPicker(
|
||||
value: Color,
|
||||
onValueChanged: (Color) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val hsl = remember { floatArrayOf(0f, 0f, 0f) }
|
||||
|
||||
ColorUtils.colorToHSL(value.toArgb(), hsl)
|
||||
|
||||
val hue = hsl[0]
|
||||
val sat = hsl[1]
|
||||
val lig = hsl[2]
|
||||
|
||||
var hex by remember { mutableStateOf(value.toHexString()) }
|
||||
|
||||
LaunchedEffect(value) {
|
||||
hex = value.toHexString()
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(128.dp)
|
||||
.background(value)
|
||||
)
|
||||
SliderRow("H") {
|
||||
Slider(
|
||||
value = hue,
|
||||
valueRange = 0f..360f,
|
||||
onValueChange = {
|
||||
val newValue = Color(ColorUtils.HSLToColor(floatArrayOf(it, sat, lig)))
|
||||
onValueChanged(newValue)
|
||||
}
|
||||
)
|
||||
}
|
||||
SliderRow("S") {
|
||||
Slider(
|
||||
value = sat,
|
||||
valueRange = 0f..1f,
|
||||
onValueChange = {
|
||||
val newValue = Color(ColorUtils.HSLToColor(floatArrayOf(hue, it, lig)))
|
||||
onValueChanged(newValue)
|
||||
}
|
||||
)
|
||||
}
|
||||
SliderRow("L") {
|
||||
Slider(
|
||||
value = lig,
|
||||
valueRange = 0f..1f,
|
||||
onValueChange = {
|
||||
val newValue = Color(ColorUtils.HSLToColor(floatArrayOf(hue, sat, it)))
|
||||
onValueChanged(newValue)
|
||||
}
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp, start = 16.dp, end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
Text(
|
||||
"Hex: ",
|
||||
modifier = Modifier.weight(2f),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = hex,
|
||||
onValueChange = {
|
||||
if (it.matches(Regex("^#([a-fA-F0-9]{6})$"))) {
|
||||
val colorInt = it.substring(1).toInt(16)
|
||||
onValueChanged(Color(colorInt).copy(alpha = 1f))
|
||||
} else {
|
||||
hex = it
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SliderRow(
|
||||
label: String,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp, horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
label,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.weight(7f)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,159 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarBorder
|
||||
import androidx.compose.material.icons.rounded.Visibility
|
||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.theme.divider
|
||||
import org.koin.androidx.compose.inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun DefaultSwipeActions(
|
||||
item: Searchable,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
val repository: FavoritesRepository by inject()
|
||||
|
||||
val isPinned by repository.isPinned(item).collectAsState(false)
|
||||
val isHidden by repository.isHidden(item).collectAsState(false)
|
||||
|
||||
val state = androidx.compose.material.rememberSwipeableState(
|
||||
SwipeAction.Default,
|
||||
confirmStateChange = {
|
||||
if (it == SwipeAction.Favorites) {
|
||||
if (isPinned == true) {
|
||||
repository.unpinItem(item)
|
||||
} else {
|
||||
repository.pinItem(item)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
)
|
||||
|
||||
val bgColor =
|
||||
if (state.offset.value > 0f) colorResource(id = R.color.amber)
|
||||
else colorResource(id = R.color.blue)
|
||||
|
||||
val isDismissing =
|
||||
state.targetValue == SwipeAction.Favorites || state.targetValue == SwipeAction.Hide
|
||||
|
||||
BoxWithConstraints(modifier) {
|
||||
val width = constraints.maxWidth.toFloat()
|
||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||
|
||||
val anchors = mapOf(
|
||||
0f to SwipeAction.Default,
|
||||
width to SwipeAction.Favorites,
|
||||
-width to SwipeAction.Hide
|
||||
)
|
||||
|
||||
val thresholds = { _: SwipeAction, _: SwipeAction ->
|
||||
FractionalThreshold(0.5f)
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier.swipeable(
|
||||
state = state,
|
||||
anchors = anchors,
|
||||
thresholds = thresholds,
|
||||
orientation = Orientation.Horizontal,
|
||||
enabled = enabled && state.currentValue == SwipeAction.Default,
|
||||
reverseDirection = isRtl,
|
||||
velocityThreshold = 10000.dp
|
||||
)
|
||||
) {
|
||||
if (enabled) {
|
||||
Row(
|
||||
modifier = Modifier.matchParentSize()
|
||||
) {
|
||||
Card(
|
||||
backgroundColor = MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.divider),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
elevation = 0.dp
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = if (state.offset.value > 0f) {
|
||||
Alignment.CenterStart
|
||||
} else {
|
||||
Alignment.CenterEnd
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(bgColor)
|
||||
.fillMaxWidth(
|
||||
animateFloatAsState(
|
||||
if (isDismissing) 1f else 0f,
|
||||
tween(200)
|
||||
).value
|
||||
)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
Icon(
|
||||
imageVector = if (state.offset.value > 0f) {
|
||||
if (isPinned == true) {
|
||||
Icons.Rounded.StarBorder
|
||||
} else {
|
||||
Icons.Rounded.Star
|
||||
}
|
||||
} else {
|
||||
if (isHidden == true) {
|
||||
Icons.Rounded.Visibility
|
||||
} else {
|
||||
Icons.Rounded.VisibilityOff
|
||||
}
|
||||
},
|
||||
tint = animateColorAsState(if (isDismissing) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface).value,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.scale(animateFloatAsState(if (isDismissing) 1.2f else 1f).value),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Row(
|
||||
content = content,
|
||||
modifier = Modifier.offset { IntOffset(state.offset.value.roundToInt(), 0) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class SwipeAction {
|
||||
Default,
|
||||
Favorites,
|
||||
Hide
|
||||
}
|
||||
@ -1,169 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
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.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import org.koin.androidx.compose.viewModel
|
||||
|
||||
/**
|
||||
* Search bar
|
||||
* @param pageTransition 0..1 how much the search bar should be shown (this will be 0 on widget page, 1 on
|
||||
* search page, and anything in between while swiping between those two pages
|
||||
*/
|
||||
@OptIn(ExperimentalAnimationGraphicsApi::class)
|
||||
@ExperimentalPagerApi
|
||||
@Composable
|
||||
fun SearchBar(
|
||||
modifier: Modifier = Modifier,
|
||||
pagerState: PagerState,
|
||||
widgetColumnState: ScrollState,
|
||||
offScreen: Float,
|
||||
onFocus: () -> Unit = {}
|
||||
) {
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
|
||||
val viewModel: SearchVM by viewModel()
|
||||
|
||||
LaunchedEffect(searchQuery) {
|
||||
viewModel.search(searchQuery)
|
||||
}
|
||||
|
||||
val pageTransition = (pagerState.currentPage + pagerState.currentPageOffset).coerceIn(0f, 1f)
|
||||
|
||||
|
||||
val elevationTransition =
|
||||
(2 * widgetColumnState.value / LocalWindowSize.current.height).coerceIn(0f, 1f)
|
||||
|
||||
Card(
|
||||
modifier = modifier
|
||||
.offset(y = (-100.dp * offScreen * (1 - pageTransition))),
|
||||
elevation = 8.dp * (pageTransition + elevationTransition).coerceIn(0f, 1f),
|
||||
) {
|
||||
val textStyle = TextStyle(
|
||||
color = LocalContentColor.current,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Search,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
|
||||
BasicTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = {
|
||||
searchQuery = it
|
||||
},
|
||||
cursorBrush = SolidColor(LocalContentColor.current),
|
||||
textStyle = textStyle,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) onFocus()
|
||||
}
|
||||
)
|
||||
if (searchQuery.isEmpty()) {
|
||||
BasicText(
|
||||
text = stringResource(id = R.string.edit_text_search_hint),
|
||||
style = textStyle,
|
||||
modifier = Modifier.alpha(ContentAlpha.medium)
|
||||
)
|
||||
}
|
||||
}
|
||||
var showOverflowMenu by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (searchQuery.isNotEmpty()) {
|
||||
searchQuery = ""
|
||||
} else {
|
||||
showOverflowMenu = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
val menuClearIcon = AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_menu_clear)
|
||||
Icon(
|
||||
painter = rememberAnimatedVectorPainter(menuClearIcon, atEnd = searchQuery.isNotEmpty()),
|
||||
null
|
||||
)
|
||||
}
|
||||
val navController = LocalNavController.current
|
||||
val context = LocalContext.current
|
||||
DropdownMenu(
|
||||
expanded = showOverflowMenu,
|
||||
onDismissRequest = { showOverflowMenu = false }) {
|
||||
DropdownMenuItem(onClick = {
|
||||
showOverflowMenu = false
|
||||
context.startActivity(
|
||||
Intent.createChooser(
|
||||
Intent(Intent.ACTION_SET_WALLPAPER),
|
||||
null
|
||||
)
|
||||
)
|
||||
}) {
|
||||
Text(
|
||||
stringResource(id = R.string.wallpaper),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
showOverflowMenu = false
|
||||
navController?.navigate("settings")
|
||||
}) {
|
||||
Text(
|
||||
stringResource(id = R.string.title_activity_settings),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.insets.navigationBarsWithImePadding
|
||||
import com.google.accompanist.insets.statusBarsPadding
|
||||
import de.mm20.launcher2.ui.search.*
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SearchColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
listState: LazyListState
|
||||
) {
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxHeight()
|
||||
.statusBarsPadding()
|
||||
.navigationBarsWithImePadding()
|
||||
) {
|
||||
val apps = applicationResults()
|
||||
val favorites = favoriteResults()
|
||||
val files = fileResults()
|
||||
|
||||
val calculator = calculatorItem()
|
||||
val wikipedia = wikipediaResult()
|
||||
|
||||
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
state = listState
|
||||
) {
|
||||
|
||||
item {
|
||||
// Search bar space
|
||||
Spacer(
|
||||
modifier = Modifier.requiredHeight(
|
||||
64.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
favorites(listState)
|
||||
apps(listState)
|
||||
calculator()
|
||||
wikipedia()
|
||||
files()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.SectionDivider() {
|
||||
item {
|
||||
Divider(
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun TextClock(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontStyle: FontStyle? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||
textDecoration: TextDecoration? = null,
|
||||
textAlign: TextAlign? = null,
|
||||
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
softWrap: Boolean = true,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
format: DateFormat
|
||||
) {
|
||||
TextClock(
|
||||
modifier,
|
||||
color,
|
||||
fontSize,
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
letterSpacing,
|
||||
textDecoration,
|
||||
textAlign,
|
||||
lineHeight,
|
||||
overflow,
|
||||
softWrap,
|
||||
maxLines,
|
||||
onTextLayout,
|
||||
style,
|
||||
formatFunction = { format.format(Date(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextClock(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontStyle: FontStyle? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||
textDecoration: TextDecoration? = null,
|
||||
textAlign: TextAlign? = null,
|
||||
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
softWrap: Boolean = true,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
formatFlags: Int = DateUtils.FORMAT_SHOW_TIME
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
TextClock(
|
||||
modifier,
|
||||
color,
|
||||
fontSize,
|
||||
fontStyle,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
letterSpacing,
|
||||
textDecoration,
|
||||
textAlign,
|
||||
lineHeight,
|
||||
overflow,
|
||||
softWrap,
|
||||
maxLines,
|
||||
onTextLayout,
|
||||
style,
|
||||
formatFunction = { DateUtils.formatDateTime(context, it, formatFlags)}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextClock(
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontStyle: FontStyle? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
letterSpacing: TextUnit = TextUnit.Unspecified,
|
||||
textDecoration: TextDecoration? = null,
|
||||
textAlign: TextAlign? = null,
|
||||
lineHeight: TextUnit = TextUnit.Unspecified,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
softWrap: Boolean = true,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
formatFunction: (time: Long) -> String
|
||||
) {
|
||||
var time by remember { mutableStateOf(System.currentTimeMillis()) }
|
||||
val context = LocalContext.current
|
||||
|
||||
DisposableEffect(null) {
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
time = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
val filter = IntentFilter(Intent.ACTION_TIME_TICK).also {
|
||||
it.addAction(Intent.ACTION_TIME_CHANGED)
|
||||
it.addAction(Intent.ACTION_TIMEZONE_CHANGED)
|
||||
}
|
||||
context.registerReceiver(receiver, filter)
|
||||
onDispose {
|
||||
context.unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = formatFunction(time),
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
letterSpacing = letterSpacing,
|
||||
textDecoration = textDecoration,
|
||||
textAlign = textAlign,
|
||||
lineHeight = lineHeight,
|
||||
overflow = overflow,
|
||||
softWrap = softWrap,
|
||||
maxLines = maxLines,
|
||||
onTextLayout = onTextLayout,
|
||||
style = style
|
||||
)
|
||||
}
|
||||
@ -162,45 +162,3 @@ data class ToggleToolbarAction(
|
||||
val isChecked: Boolean,
|
||||
val onCheckedChange: (Boolean) -> Unit
|
||||
) : ToolbarAction
|
||||
|
||||
@Composable
|
||||
fun favoritesToolbarAction(item: Searchable): ToggleToolbarAction {
|
||||
val viewModel: FavoritesRepository by inject()
|
||||
val isPinned by viewModel.isPinned(item).collectAsState(false)
|
||||
|
||||
return ToggleToolbarAction(
|
||||
label = stringResource(
|
||||
if (isPinned) R.string.favorites_menu_unpin else R.string.favorites_menu_pin
|
||||
),
|
||||
icon = if (isPinned) Icons.Rounded.Star else Icons.Rounded.StarBorder,
|
||||
isChecked = isPinned,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
viewModel.pinItem(item)
|
||||
} else {
|
||||
viewModel.unpinItem(item)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun hideToolbarAction(item: Searchable): ToggleToolbarAction {
|
||||
val viewModel: FavoritesRepository by inject()
|
||||
val isHidden by viewModel.isHidden(item).collectAsState(false)
|
||||
|
||||
return ToggleToolbarAction(
|
||||
label = stringResource(
|
||||
if (isHidden) R.string.menu_unhide else R.string.menu_hide
|
||||
),
|
||||
icon = if (isHidden) Icons.Rounded.Visibility else Icons.Rounded.VisibilityOff,
|
||||
isChecked = isHidden,
|
||||
onCheckedChange = {
|
||||
if (it) {
|
||||
viewModel.hideItem(item)
|
||||
} else {
|
||||
viewModel.unhideItem(item)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.google.accompanist.insets.navigationBarsPadding
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import de.mm20.launcher2.ui.ClockWidget
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import de.mm20.launcher2.ui.widget.WidgetCard
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import de.mm20.launcher2.widgets.WidgetViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalComposeUiApi::class, ExperimentalAnimationGraphicsApi::class
|
||||
)
|
||||
@Composable
|
||||
fun WidgetColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
scrollState: ScrollState
|
||||
) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
|
||||
var widgets by remember { mutableStateOf(listOf<Widget>()) }
|
||||
|
||||
val viewModel: WidgetViewModel = getViewModel()
|
||||
|
||||
var editMode by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(null) {
|
||||
widgets = viewModel.getWidgets()
|
||||
}
|
||||
|
||||
val isLightTheme = androidx.compose.material.MaterialTheme.colors.isLight
|
||||
|
||||
val windowHeight = LocalWindowSize.current.height
|
||||
|
||||
val background = 1f - (scrollState.value * 2 / windowHeight).coerceIn(0f, 1f)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
background to Color.Transparent,
|
||||
background to MaterialTheme.colorScheme.background
|
||||
)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.verticalScroll(scrollState)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
ClockWidget()
|
||||
|
||||
AnimatedVisibility(visible = scrollState.value == 0) {
|
||||
NavBarSpacer()
|
||||
}
|
||||
|
||||
for (widget in widgets) {
|
||||
WidgetCard(widget = widget)
|
||||
}
|
||||
|
||||
val icon = AnimatedImageVector.animatedVectorResource(id = R.drawable.anim_ic_edit_add)
|
||||
ExtendedFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
text = {
|
||||
Text(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
text = stringResource(if (editMode) R.string.widget_add_widget else R.string.menu_edit_widgets)
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(painter = rememberAnimatedVectorPainter(icon, atEnd = editMode), contentDescription = null)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
onClick = {
|
||||
editMode = !editMode
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component.preferences
|
||||
|
||||
import android.R
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import de.mm20.launcher2.ui.component.ColorPicker
|
||||
|
||||
@Composable
|
||||
fun ColorPreference(
|
||||
title: String,
|
||||
icon: ImageVector? = null,
|
||||
value: Color,
|
||||
summary: String? = null,
|
||||
onValueChanged: (Color) -> Unit,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
title = title,
|
||||
summary = summary,
|
||||
icon = icon,
|
||||
enabled = enabled,
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
controls = {
|
||||
ColorPreview(color = value)
|
||||
}
|
||||
)
|
||||
if (showDialog) {
|
||||
var selectedValue by remember { mutableStateOf(value) }
|
||||
Dialog(onDismissRequest = { showDialog = false }) {
|
||||
Card(
|
||||
elevation = 16.dp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(
|
||||
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
|
||||
)
|
||||
)
|
||||
ColorPicker(
|
||||
value = selectedValue,
|
||||
onValueChanged = { selectedValue = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp, end = 8.dp, top = 16.dp, start = 8.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
onValueChanged(selectedValue)
|
||||
showDialog = false
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ColorPreview(color: Color) {
|
||||
Surface(
|
||||
modifier = Modifier.size(32.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = color
|
||||
) {}
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
package de.mm20.launcher2.ui
|
||||
package de.mm20.launcher2.ui.ktx
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@Composable fun Dp.toPixels(): Float {
|
||||
@Composable
|
||||
fun Dp.toPixels(): Float {
|
||||
return value * LocalDensity.current.density
|
||||
}
|
||||
@ -16,7 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import de.mm20.launcher2.search.data.Calculator
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.databinding.ViewCalculatorBinding
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import de.mm20.launcher2.ui.search.CalculatorItem
|
||||
@ -47,7 +47,7 @@ class CalculatorView : FrameLayout {
|
||||
|
||||
binding.composeView.setContent {
|
||||
val converter by calculator.observeAsState()
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
|
||||
@ -2,8 +2,6 @@ package de.mm20.launcher2.ui.legacy.component
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@ -16,12 +14,11 @@ 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.MdcLauncherTheme
|
||||
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
|
||||
@ -56,7 +53,7 @@ class SearchBar @JvmOverloads constructor(
|
||||
CompositionLocalProvider(
|
||||
LocalCardStyle provides cardStyle
|
||||
) {
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
Box(contentAlignment = Alignment.TopCenter) {
|
||||
SearchBar(
|
||||
level,
|
||||
|
||||
@ -16,7 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import de.mm20.launcher2.search.data.UnitConverter
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.databinding.ViewUnitconverterBinding
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import de.mm20.launcher2.ui.search.UnitConverterItem
|
||||
@ -47,7 +47,7 @@ class UnitConverterView : FrameLayout {
|
||||
|
||||
binding.composeView.setContent {
|
||||
val converter by unitConverter.observeAsState()
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
|
||||
@ -9,7 +9,7 @@ import androidx.transition.Scene
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.search.data.MissingPermission
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
@ -29,7 +29,7 @@ class PermissionListRepresentation : Representation, KoinComponent {
|
||||
scene.setEnterAction {
|
||||
val permissionsManager: PermissionsManager = get()
|
||||
rootView.findViewById<ComposeView>(R.id.composeView).setContent {
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
MissingPermissionBanner(
|
||||
text = missingPermission.label,
|
||||
onClick = {
|
||||
|
||||
@ -6,7 +6,7 @@ import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import de.mm20.launcher2.ui.ClockWidget
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
|
||||
class ClockWidget : FrameLayout {
|
||||
|
||||
@ -34,7 +34,7 @@ class ClockWidget : FrameLayout {
|
||||
|
||||
|
||||
composeView.setContent {
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
ClockWidget()
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
||||
|
||||
@ -28,7 +28,7 @@ class MusicWidget : LauncherWidget {
|
||||
val composeView = ComposeView(context)
|
||||
composeView.id = FrameLayout.generateViewId()
|
||||
composeView.setContent {
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
|
||||
@ -8,7 +8,7 @@ import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
||||
|
||||
@ -30,7 +30,7 @@ class WeatherWidget : LauncherWidget {
|
||||
val composeView = ComposeView(context)
|
||||
composeView.id = FrameLayout.generateViewId()
|
||||
composeView.setContent {
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
|
||||
@ -5,18 +5,11 @@ 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
|
||||
|
||||
val LocalWindowSize = compositionLocalOf { Size(0f, 0f) }
|
||||
|
||||
val LocalAppWidgetHost = compositionLocalOf<AppWidgetHost?>(defaultFactory = { null })
|
||||
|
||||
val LocalWallpaperColors = compositionLocalOf<WallpaperColors?> { null }
|
||||
|
||||
val LocalColorScheme = compositionLocalOf<ColorPalette> { DefaultColorPalette() }
|
||||
|
||||
val LocalNavController = compositionLocalOf<NavController?> { null }
|
||||
|
||||
val LocalCardStyle = compositionLocalOf<Settings.CardSettings> { Settings.CardSettings.getDefaultInstance() }
|
||||
@ -1,163 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.*
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun ApplicationItem(
|
||||
modifier: Modifier = Modifier,
|
||||
app: Application,
|
||||
representation: Representation,
|
||||
initialRepresentation: Representation,
|
||||
onRepresentationChange: ((Representation) -> Unit)
|
||||
) {
|
||||
|
||||
val padding by animateDpAsState(
|
||||
if (representation == Representation.Grid) 0.dp else 16.dp
|
||||
)
|
||||
val iconSize by animateDpAsState(
|
||||
if (representation == Representation.Grid) 52.dp else 84.dp
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f, true)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
representation == Representation.Full,
|
||||
enter = expandIn() + fadeIn(),
|
||||
exit = shrinkOut() + fadeOut(),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = app.label,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
app.version?.let {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = app.`package`,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val width by animateDpAsState(
|
||||
if (representation == Representation.Grid) LocalGridColumnWidth.current else iconSize,
|
||||
spring(Spring.StiffnessHigh)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = width)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
item = app,
|
||||
size = iconSize,
|
||||
onLongClick = {
|
||||
onRepresentationChange(Representation.Full)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(representation == Representation.Full) {
|
||||
val leftActions = listOf(
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_back),
|
||||
Icons.Rounded.ArrowBack
|
||||
) { onRepresentationChange(initialRepresentation) }
|
||||
)
|
||||
val storeDetails = app.getStoreDetails(LocalContext.current)
|
||||
val rightActions = listOf(
|
||||
favoritesToolbarAction(app),
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_app_info),
|
||||
Icons.Rounded.Info
|
||||
) { },
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_uninstall),
|
||||
Icons.Rounded.Delete
|
||||
) { },
|
||||
if (storeDetails == null) {
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_share),
|
||||
Icons.Rounded.Share,
|
||||
{}
|
||||
)
|
||||
} else {
|
||||
SubmenuToolbarAction(
|
||||
stringResource(id = R.string.menu_share),
|
||||
Icons.Rounded.Share,
|
||||
listOf(
|
||||
DefaultToolbarAction(
|
||||
stringResource(
|
||||
id = R.string.share_menu_store_link,
|
||||
storeDetails.label
|
||||
),
|
||||
Icons.Rounded.Share,
|
||||
{}
|
||||
),
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.share_menu_apk_file),
|
||||
Icons.Rounded.Share,
|
||||
{}
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
hideToolbarAction(app),
|
||||
)
|
||||
Toolbar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leftActions = leftActions,
|
||||
rightActions = rightActions
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(representation == Representation.Grid) {
|
||||
GridItemLabel(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
|
||||
@Composable
|
||||
fun applicationResults(): LazyListScope.(listState: LazyListState) -> Unit {
|
||||
val viewModel: SearchVM by viewModel()
|
||||
val apps by viewModel.appResults.observeAsState(emptyList())
|
||||
return {
|
||||
SearchableGrid(items = apps, listState = it)
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.ShapedLauncherIcon
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun BasicGridItem(
|
||||
modifier: Modifier,
|
||||
item: Searchable,
|
||||
iconSize: Dp,
|
||||
showLabel: Boolean = true,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
) {
|
||||
|
||||
ShapedLauncherIcon(
|
||||
item = item,
|
||||
size = iconSize,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
AnimatedVisibility(
|
||||
showLabel
|
||||
) {
|
||||
GridItemLabel(
|
||||
item
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.component.SectionDivider
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
|
||||
@Composable
|
||||
fun calculatorItem(): LazyListScope.() -> Unit {
|
||||
val viewModel: SearchVM by viewModel()
|
||||
val calculator by viewModel.calculatorResult.observeAsState(null)
|
||||
return {
|
||||
calculator?.let {
|
||||
item {
|
||||
Card(
|
||||
elevation = 0.dp
|
||||
) {
|
||||
CalculatorItem(it)
|
||||
}
|
||||
}
|
||||
SectionDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
|
||||
@Composable
|
||||
fun favoriteResults(): LazyListScope.(listState: LazyListState) -> Unit {
|
||||
val viewModel: SearchVM by viewModel()
|
||||
|
||||
val favorites by viewModel.favorites.observeAsState(emptyList())
|
||||
return {
|
||||
SearchableGrid(items = favorites, listState = it)
|
||||
}
|
||||
}
|
||||
@ -1,177 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import android.text.format.Formatter
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.data.File
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.DefaultToolbarAction
|
||||
import de.mm20.launcher2.ui.component.Toolbar
|
||||
import de.mm20.launcher2.ui.component.favoritesToolbarAction
|
||||
import de.mm20.launcher2.ui.component.hideToolbarAction
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun FileItem(
|
||||
modifier: Modifier = Modifier,
|
||||
file: File,
|
||||
representation: Representation,
|
||||
initialRepresentation: Representation,
|
||||
onRepresentationChange: ((Representation) -> Unit)
|
||||
) {
|
||||
|
||||
val iconSize = 52.dp
|
||||
|
||||
val padding by animateDpAsState(
|
||||
if (representation == Representation.Grid) 0.dp else 16.dp
|
||||
)
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
enabled = representation == Representation.List,
|
||||
onClick = {},
|
||||
onLongClick = {
|
||||
onRepresentationChange(Representation.Full)
|
||||
}
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(padding),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f, true)
|
||||
.padding(end = 8.dp)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
representation != Representation.Grid
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = file.label,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
representation == Representation.List
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = file.getFileType(LocalContext.current),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(representation == Representation.Full) {
|
||||
Column {
|
||||
Text(
|
||||
text = "${stringResource(R.string.file_meta_type)}: ${file.mimeType}",
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (file.path.isNotBlank()) {
|
||||
Text(
|
||||
text = "${stringResource(R.string.file_meta_path)}: ${file.path}",
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
if (!file.isDirectory) {
|
||||
Text(
|
||||
text = "${stringResource(R.string.file_meta_size)}: ${
|
||||
Formatter.formatShortFileSize(
|
||||
LocalContext.current, file.size
|
||||
)
|
||||
}",
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
for ((k, v) in file.metaData) {
|
||||
Text(
|
||||
text = "${stringResource(k)}: ${v}",
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val width by animateDpAsState(
|
||||
if (representation == Representation.Grid) LocalGridColumnWidth.current else iconSize,
|
||||
spring(Spring.StiffnessHigh)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = width)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
ShapedLauncherIcon(
|
||||
item = file,
|
||||
size = iconSize,
|
||||
onLongClick = {
|
||||
onRepresentationChange(Representation.Full)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(representation == Representation.Full) {
|
||||
val leftActions = listOf(
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_back),
|
||||
Icons.Rounded.ArrowBack
|
||||
) { onRepresentationChange(initialRepresentation) }
|
||||
)
|
||||
val rightActions = listOf(
|
||||
favoritesToolbarAction(file),
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_delete),
|
||||
Icons.Rounded.Delete
|
||||
) { },
|
||||
hideToolbarAction(file),
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_share),
|
||||
Icons.Rounded.Share
|
||||
) {}
|
||||
)
|
||||
Toolbar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leftActions = leftActions,
|
||||
rightActions = rightActions
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(representation == Representation.Grid) {
|
||||
GridItemLabel(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
|
||||
@Composable
|
||||
fun fileResults(): LazyListScope.() -> Unit {
|
||||
val viewModel: SearchVM by viewModel()
|
||||
val files by viewModel.fileResults.observeAsState(emptyList())
|
||||
return {
|
||||
files?.let { SearchableList(items = it) }
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.GridItemLabel(
|
||||
item: Searchable
|
||||
) {
|
||||
Text(
|
||||
text = item.label,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
softWrap = false,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(
|
||||
top = 8.dp, bottom = 4.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -1,232 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInWindow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.google.accompanist.insets.LocalWindowInsets
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.component.SectionDivider
|
||||
import de.mm20.launcher2.ui.ktx.toDp
|
||||
import de.mm20.launcher2.ui.legacy.search.SearchGridView
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import de.mm20.launcher2.ui.toPixels
|
||||
|
||||
fun LazyListScope.LegacySearchableGrid(
|
||||
items: List<Searchable>,
|
||||
columns: Int = 5,
|
||||
listState: LazyListState
|
||||
) {
|
||||
item {
|
||||
|
||||
AndroidView(
|
||||
{
|
||||
SearchGridView(it).apply {
|
||||
columnCount = columns
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
}, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.animateContentSize()
|
||||
) {
|
||||
it.submitItems(items)
|
||||
}
|
||||
}
|
||||
if (items.isNotEmpty()) {
|
||||
SectionDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
fun LazyListScope.NotSoLazySearchableGrid(
|
||||
items: List<Searchable>,
|
||||
columns: Int = 5,
|
||||
listState: LazyListState
|
||||
) {
|
||||
val rows = (items.size + columns - 1) / columns
|
||||
item {
|
||||
for (rowIndex in 0 until rows) {
|
||||
var focusedItem by remember { mutableStateOf(-1) }
|
||||
if (focusedItem != -1 && listState.isScrollInProgress) focusedItem = -1
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.requiredHeight(100.dp)
|
||||
.zIndex(
|
||||
animateFloatAsState(
|
||||
if (focusedItem != -1 && rowIndex == focusedItem / columns) 100f else 0f
|
||||
).value
|
||||
)
|
||||
) {
|
||||
for (colIndex in 0 until columns) {
|
||||
val itemIndex = rowIndex * columns + colIndex
|
||||
if (itemIndex < items.size) {
|
||||
GridItem(
|
||||
item = items[itemIndex],
|
||||
column = colIndex,
|
||||
totalColumns = columns,
|
||||
hasFocus = itemIndex == focusedItem,
|
||||
requestFocus = {
|
||||
focusedItem = if (it) itemIndex else -1
|
||||
})
|
||||
} else {
|
||||
Spacer(Modifier.weight(1f, fill = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
|
||||
fun LazyListScope.SearchableGrid(
|
||||
items: List<Searchable>,
|
||||
columns: Int = 5,
|
||||
listState: LazyListState,
|
||||
) {
|
||||
val rows = (items.size + columns - 1) / columns
|
||||
|
||||
items(rows) { rowIndex ->
|
||||
var focusedItem by remember { mutableStateOf(-1) }
|
||||
if (focusedItem != -1 && listState.isScrollInProgress) focusedItem = -1
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.requiredHeight(100.dp)
|
||||
//.animateItemPlacement()
|
||||
.zIndex(
|
||||
animateFloatAsState(
|
||||
if (focusedItem != -1 && rowIndex == focusedItem / columns) 100f else 0f
|
||||
).value
|
||||
)
|
||||
) {
|
||||
for (colIndex in 0 until columns) {
|
||||
val itemIndex = rowIndex * columns + colIndex
|
||||
if (itemIndex < items.size) {
|
||||
GridItem(
|
||||
item = items[itemIndex],
|
||||
column = colIndex,
|
||||
totalColumns = columns,
|
||||
hasFocus = itemIndex == focusedItem,
|
||||
requestFocus = {
|
||||
focusedItem = if (it) itemIndex else -1
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.weight(1f, fill = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (items.isNotEmpty()) {
|
||||
SectionDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowScope.GridItem(
|
||||
item: Searchable,
|
||||
column: Int,
|
||||
totalColumns: Int,
|
||||
hasFocus: Boolean,
|
||||
requestFocus: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val insets = LocalWindowInsets.current.systemBars
|
||||
|
||||
val topSpace = insets.top + 64.dp.toPixels()
|
||||
|
||||
val gridWidth =
|
||||
LocalWindowSize.current.width.toDp() - 16.dp - (insets.left + insets.right).toDp()
|
||||
val representation = if (hasFocus) Representation.Full else Representation.Grid
|
||||
|
||||
|
||||
val offsetX by animateDpAsState(
|
||||
if (representation == Representation.Grid) 0.dp
|
||||
else gridWidth / totalColumns * ((totalColumns - 1) / 2 - column)
|
||||
)
|
||||
|
||||
var calculatedYOffset by remember { mutableStateOf(0f) }
|
||||
val offsetY by animateDpAsState(
|
||||
if (representation == Representation.Grid) 0.dp
|
||||
else calculatedYOffset.toDp()
|
||||
)
|
||||
|
||||
val width by animateDpAsState(
|
||||
if (representation == Representation.Grid) gridWidth / totalColumns
|
||||
else gridWidth
|
||||
)
|
||||
val z by animateFloatAsState(
|
||||
if (representation == Representation.Grid) 0f
|
||||
else 100f
|
||||
)
|
||||
|
||||
val windowSize = LocalWindowSize.current
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.weight(1f, fill = true)
|
||||
.fillMaxHeight()
|
||||
.zIndex(z)
|
||||
.onGloballyPositioned {
|
||||
|
||||
calculatedYOffset = if (representation == Representation.Full) {
|
||||
val position = it.positionInWindow()
|
||||
val size = it.size
|
||||
val topOffset = -position.y + topSpace + size.height / 2
|
||||
if (topOffset > 0) {
|
||||
topOffset
|
||||
} else {
|
||||
val bottom = position.y + size.height
|
||||
val bottomOffset =
|
||||
-(bottom - windowSize.height) - size.height / 2 - insets.bottom
|
||||
if (bottomOffset < 0) {
|
||||
bottomOffset
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
) {
|
||||
key(item.key) {
|
||||
CompositionLocalProvider(LocalGridColumnWidth provides width) {
|
||||
SearchableItem(
|
||||
item = item,
|
||||
modifier = Modifier
|
||||
.offset(offsetX, offsetY)
|
||||
.requiredWidth(width)
|
||||
.wrapContentHeight(unbounded = true)
|
||||
.align(Alignment.BottomCenter),
|
||||
representation = representation,
|
||||
initialRepresentation = Representation.Grid,
|
||||
onRepresentationChange = {
|
||||
requestFocus(it == Representation.Full)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val LocalGridColumnWidth = compositionLocalOf { 0.dp }
|
||||
@ -1,113 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.search.data.Wikipedia
|
||||
import de.mm20.launcher2.ui.component.DefaultSwipeActions
|
||||
|
||||
@Composable
|
||||
fun SearchableItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: Searchable,
|
||||
initialRepresentation: Representation = Representation.List
|
||||
) {
|
||||
var representation by remember { mutableStateOf(initialRepresentation) }
|
||||
SearchableItem(
|
||||
modifier = modifier,
|
||||
item = item,
|
||||
representation = representation,
|
||||
initialRepresentation = initialRepresentation,
|
||||
onRepresentationChange = { representation = it }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SearchableItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: Searchable,
|
||||
representation: Representation,
|
||||
initialRepresentation: Representation,
|
||||
onRepresentationChange: ((Representation) -> Unit)
|
||||
) {
|
||||
|
||||
DefaultSwipeActions(
|
||||
modifier = modifier
|
||||
.padding(vertical = 4.dp, horizontal = 4.dp),
|
||||
item = item,
|
||||
enabled = representation == Representation.List
|
||||
) {
|
||||
|
||||
val transition = updateTransition(representation, label = "SearchableItem")
|
||||
|
||||
val cardElevation by transition.animateDp(
|
||||
label = "cardElevation",
|
||||
transitionSpec = {
|
||||
if (targetState == Representation.Full) tween(200, delayMillis = 200)
|
||||
else tween(200)
|
||||
}) {
|
||||
if (it == Representation.Full) 4.dp else 0.dp
|
||||
}
|
||||
|
||||
|
||||
val cardAlpha by transition.animateFloat(
|
||||
label = "cardAlpha",
|
||||
transitionSpec = {
|
||||
if (targetState == Representation.Full) tween(300)
|
||||
else tween(300, delayMillis = 100)
|
||||
}) {
|
||||
if (it == Representation.Grid) 0f else 1f
|
||||
}
|
||||
|
||||
|
||||
Card(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface.copy(alpha = cardAlpha),
|
||||
elevation = cardElevation
|
||||
) {
|
||||
|
||||
when (item) {
|
||||
is Application -> {
|
||||
ApplicationItem(
|
||||
app = item,
|
||||
representation = representation,
|
||||
initialRepresentation = initialRepresentation,
|
||||
onRepresentationChange = onRepresentationChange
|
||||
)
|
||||
}
|
||||
is File -> {
|
||||
FileItem(
|
||||
file = item,
|
||||
representation = representation,
|
||||
initialRepresentation = initialRepresentation,
|
||||
onRepresentationChange = onRepresentationChange
|
||||
)
|
||||
}
|
||||
is Wikipedia -> {
|
||||
WikipediaItem(
|
||||
wikipedia = item,
|
||||
representation = representation,
|
||||
initialRepresentation = initialRepresentation,
|
||||
onRepresentationChange = onRepresentationChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Representation {
|
||||
Grid,
|
||||
List,
|
||||
Full
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.component.SectionDivider
|
||||
|
||||
fun LazyListScope.SearchableList(
|
||||
items: List<Searchable>
|
||||
) {
|
||||
items(items) {
|
||||
ListItem(it)
|
||||
}
|
||||
if (items.isNotEmpty()) {
|
||||
SectionDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LazyItemScope.ListItem(item: Searchable) {
|
||||
SearchableItem(item = item, modifier = Modifier/*.animateItemPlacement()*/)
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.data.Wikipedia
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.DefaultToolbarAction
|
||||
import de.mm20.launcher2.ui.component.Toolbar
|
||||
import de.mm20.launcher2.ui.component.favoritesToolbarAction
|
||||
|
||||
@Composable
|
||||
fun WikipediaItem(
|
||||
wikipedia: Wikipedia,
|
||||
representation: Representation,
|
||||
initialRepresentation: Representation,
|
||||
onRepresentationChange: ((Representation) -> Unit)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 24.dp, top = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = wikipedia.label,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
text = stringResource(R.string.wikipedia_source),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = wikipedia.text
|
||||
)
|
||||
}
|
||||
val leftActions = if (initialRepresentation == Representation.Full) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_back),
|
||||
Icons.Rounded.ArrowBack
|
||||
) { onRepresentationChange(initialRepresentation) }
|
||||
)
|
||||
}
|
||||
val rightActions = listOf(
|
||||
favoritesToolbarAction(wikipedia),
|
||||
DefaultToolbarAction(
|
||||
stringResource(id = R.string.menu_share),
|
||||
Icons.Rounded.Share
|
||||
) {
|
||||
}
|
||||
)
|
||||
Toolbar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leftActions = leftActions,
|
||||
rightActions = rightActions
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.component.SectionDivider
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import org.koin.androidx.compose.viewModel
|
||||
|
||||
@Composable
|
||||
fun wikipediaResult(): LazyListScope.() -> Unit {
|
||||
val viewModel: SearchVM by viewModel()
|
||||
val wikipedia by viewModel.wikipediaResult.observeAsState()
|
||||
return {
|
||||
wikipedia?.let {
|
||||
item {
|
||||
Card(
|
||||
elevation = 0.dp
|
||||
) {
|
||||
WikipediaItem(
|
||||
wikipedia = it,
|
||||
representation = Representation.Full,
|
||||
initialRepresentation = Representation.Full,
|
||||
onRepresentationChange = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,7 @@ 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.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
@ -64,7 +64,7 @@ class SettingsActivity : BaseActivity() {
|
||||
LocalNavController provides navController,
|
||||
LocalCardStyle provides cardStyle
|
||||
) {
|
||||
LegacyLauncherTheme {
|
||||
MdcLauncherTheme {
|
||||
AnimatedNavHost(
|
||||
navController = navController,
|
||||
startDestination = "settings",
|
||||
|
||||
@ -41,7 +41,7 @@ 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.toHexString
|
||||
import de.mm20.launcher2.ui.toPixels
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
class BlackWhiteColorPalette: ColorPalette() {
|
||||
override val neutral: ColorSwatch
|
||||
get() = ColorSwatch(
|
||||
Color.White,
|
||||
Color.White,
|
||||
Color.White,
|
||||
Color.White,
|
||||
Color.White,
|
||||
Color.White,
|
||||
Color.White,
|
||||
Color.Black,
|
||||
Color.Black,
|
||||
Color.Black,
|
||||
Color.Black,
|
||||
Color.Black,
|
||||
Color.Black,
|
||||
)
|
||||
override val neutralVariant: ColorSwatch
|
||||
get() = neutral
|
||||
override val primary: ColorSwatch
|
||||
get() = neutral
|
||||
override val secondary: ColorSwatch
|
||||
get() = neutral
|
||||
override val tertiary: ColorSwatch
|
||||
get() = neutral
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
|
||||
abstract class ColorPalette {
|
||||
abstract val neutral: ColorSwatch
|
||||
abstract val neutralVariant: ColorSwatch
|
||||
|
||||
abstract val primary: ColorSwatch
|
||||
abstract val secondary: ColorSwatch
|
||||
abstract val tertiary: ColorSwatch
|
||||
}
|
||||
|
||||
fun ColorPalette.toDarkColorScheme() : ColorScheme {
|
||||
return darkColorScheme(
|
||||
primary = primary.shade80,
|
||||
onPrimary = primary.shade20,
|
||||
primaryContainer = primary.shade30,
|
||||
onPrimaryContainer = primary.shade90,
|
||||
secondary = secondary.shade80,
|
||||
onSecondary = secondary.shade20,
|
||||
secondaryContainer = secondary.shade30,
|
||||
onSecondaryContainer = secondary.shade90,
|
||||
tertiary = tertiary.shade80,
|
||||
onTertiary = tertiary.shade20,
|
||||
tertiaryContainer = tertiary.shade30,
|
||||
onTertiaryContainer = tertiary.shade90,
|
||||
background = neutral.shade10,
|
||||
onBackground = neutral.shade90,
|
||||
surface = neutral.shade10,
|
||||
onSurface = neutral.shade80,
|
||||
surfaceVariant = neutralVariant.shade30,
|
||||
onSurfaceVariant = neutralVariant.shade80,
|
||||
outline = neutralVariant.shade60,
|
||||
inverseOnSurface = neutralVariant.shade20,
|
||||
inverseSurface = neutralVariant.shade90,
|
||||
)
|
||||
}
|
||||
|
||||
fun ColorPalette.toLightColorScheme() : ColorScheme {
|
||||
return lightColorScheme(
|
||||
primary = primary.shade40,
|
||||
onPrimary = primary.shade100,
|
||||
primaryContainer = primary.shade90,
|
||||
onPrimaryContainer = primary.shade10,
|
||||
secondary = secondary.shade40,
|
||||
onSecondary = secondary.shade100,
|
||||
secondaryContainer = secondary.shade90,
|
||||
onSecondaryContainer = secondary.shade10,
|
||||
tertiary = tertiary.shade40,
|
||||
onTertiary = tertiary.shade100,
|
||||
tertiaryContainer = tertiary.shade90,
|
||||
onTertiaryContainer = tertiary.shade10,
|
||||
background = neutral.shade99,
|
||||
onBackground = neutral.shade10,
|
||||
surface = neutral.shade99,
|
||||
onSurface = neutral.shade10,
|
||||
surfaceVariant = neutralVariant.shade90,
|
||||
onSurfaceVariant = neutralVariant.shade30,
|
||||
outline = neutralVariant.shade50,
|
||||
inverseOnSurface = neutralVariant.shade95,
|
||||
inverseSurface = neutralVariant.shade20,
|
||||
)
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.green
|
||||
import androidx.core.graphics.red
|
||||
|
||||
data class ColorSwatch(
|
||||
val shade100: Color,
|
||||
val shade99: Color,
|
||||
val shade95: Color,
|
||||
val shade90: Color,
|
||||
val shade80: Color,
|
||||
val shade70: Color,
|
||||
val shade60: Color,
|
||||
val shade50: Color,
|
||||
val shade40: Color,
|
||||
val shade30: Color,
|
||||
val shade20: Color,
|
||||
val shade10: Color,
|
||||
val shade0: Color,
|
||||
)
|
||||
|
||||
fun colorSwatch(color: Color): ColorSwatch {
|
||||
val hsl = floatArrayOf(0f, 0f, 0f)
|
||||
val rgb = color.toArgb()
|
||||
ColorUtils.RGBToHSL(rgb.red, rgb.green, rgb.blue, hsl)
|
||||
|
||||
return ColorSwatch(
|
||||
shade100 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 1f })),
|
||||
shade99 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.99f })),
|
||||
shade95 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.95f })),
|
||||
shade90 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.9f })),
|
||||
shade80 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.8f })),
|
||||
shade70 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.7f })),
|
||||
shade60 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.6f })),
|
||||
shade50 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.49f })),
|
||||
shade40 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.4f })),
|
||||
shade30 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.3f })),
|
||||
shade20 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.2f })),
|
||||
shade10 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0.1f })),
|
||||
shade0 = Color(ColorUtils.HSLToColor(hsl.also { it[2] = 0f })),
|
||||
)
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
|
||||
class DefaultColorPalette: ColorPalette() {
|
||||
|
||||
override val neutral = colorSwatch(Color.Black)
|
||||
|
||||
override val neutralVariant = neutral
|
||||
|
||||
override val primary = colorSwatch(Color(0xFF39A0ED))
|
||||
|
||||
override val secondary = colorSwatch(Color(0xFF4C6085))
|
||||
|
||||
override val tertiary = colorSwatch(Color(0xFFF59CA9))
|
||||
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
class MM20ColorPalette: ColorPalette() {
|
||||
override val neutral = colorSwatch(Color(0xff233139))
|
||||
override val neutralVariant = colorSwatch(Color(0xff233139))
|
||||
override val primary = colorSwatch(Color(0xfface330))
|
||||
override val secondary = colorSwatch(Color(0xff496777))
|
||||
override val tertiary = colorSwatch(Color(0xfface330))
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
class SystemColorPalette(context: Context) : ColorPalette() {
|
||||
override val neutral = ColorSwatch(
|
||||
shade100 = Color(context.getColor(android.R.color.system_neutral1_0)),
|
||||
shade99 = Color(context.getColor(android.R.color.system_neutral1_10)),
|
||||
shade95 = Color(context.getColor(android.R.color.system_neutral1_50)),
|
||||
shade90 = Color(context.getColor(android.R.color.system_neutral1_100)),
|
||||
shade80 = Color(context.getColor(android.R.color.system_neutral1_200)),
|
||||
shade70 = Color(context.getColor(android.R.color.system_neutral1_300)),
|
||||
shade60 = Color(context.getColor(android.R.color.system_neutral1_400)),
|
||||
shade50 = Color(context.getColor(android.R.color.system_neutral1_500)),
|
||||
shade40 = Color(context.getColor(android.R.color.system_neutral1_600)),
|
||||
shade30 = Color(context.getColor(android.R.color.system_neutral1_700)),
|
||||
shade20 = Color(context.getColor(android.R.color.system_neutral1_800)),
|
||||
shade10 = Color(context.getColor(android.R.color.system_neutral1_900)),
|
||||
shade0 = Color(context.getColor(android.R.color.system_neutral1_1000)),
|
||||
)
|
||||
|
||||
override val neutralVariant = ColorSwatch(
|
||||
shade100 = Color(context.getColor(android.R.color.system_neutral2_0)),
|
||||
shade99 = Color(context.getColor(android.R.color.system_neutral2_10)),
|
||||
shade95 = Color(context.getColor(android.R.color.system_neutral2_50)),
|
||||
shade90 = Color(context.getColor(android.R.color.system_neutral2_100)),
|
||||
shade80 = Color(context.getColor(android.R.color.system_neutral2_200)),
|
||||
shade70 = Color(context.getColor(android.R.color.system_neutral2_300)),
|
||||
shade60 = Color(context.getColor(android.R.color.system_neutral2_400)),
|
||||
shade50 = Color(context.getColor(android.R.color.system_neutral2_500)),
|
||||
shade40 = Color(context.getColor(android.R.color.system_neutral2_600)),
|
||||
shade30 = Color(context.getColor(android.R.color.system_neutral2_700)),
|
||||
shade20 = Color(context.getColor(android.R.color.system_neutral2_800)),
|
||||
shade10 = Color(context.getColor(android.R.color.system_neutral2_900)),
|
||||
shade0 = Color(context.getColor(android.R.color.system_neutral2_1000)),
|
||||
)
|
||||
|
||||
override val primary = ColorSwatch(
|
||||
shade100 = Color(context.getColor(android.R.color.system_accent1_0)),
|
||||
shade99 = Color(context.getColor(android.R.color.system_accent1_10)),
|
||||
shade95 = Color(context.getColor(android.R.color.system_accent1_50)),
|
||||
shade90 = Color(context.getColor(android.R.color.system_accent1_100)),
|
||||
shade80 = Color(context.getColor(android.R.color.system_accent1_200)),
|
||||
shade70 = Color(context.getColor(android.R.color.system_accent1_300)),
|
||||
shade60 = Color(context.getColor(android.R.color.system_accent1_400)),
|
||||
shade50 = Color(context.getColor(android.R.color.system_accent1_500)),
|
||||
shade40 = Color(context.getColor(android.R.color.system_accent1_600)),
|
||||
shade30 = Color(context.getColor(android.R.color.system_accent1_700)),
|
||||
shade20 = Color(context.getColor(android.R.color.system_accent1_800)),
|
||||
shade10 = Color(context.getColor(android.R.color.system_accent1_900)),
|
||||
shade0 = Color(context.getColor(android.R.color.system_accent1_1000)),
|
||||
)
|
||||
|
||||
override val secondary = ColorSwatch(
|
||||
shade100 = Color(context.getColor(android.R.color.system_accent2_0)),
|
||||
shade99 = Color(context.getColor(android.R.color.system_accent2_10)),
|
||||
shade95 = Color(context.getColor(android.R.color.system_accent2_50)),
|
||||
shade90 = Color(context.getColor(android.R.color.system_accent2_100)),
|
||||
shade80 = Color(context.getColor(android.R.color.system_accent2_200)),
|
||||
shade70 = Color(context.getColor(android.R.color.system_accent2_300)),
|
||||
shade60 = Color(context.getColor(android.R.color.system_accent2_400)),
|
||||
shade50 = Color(context.getColor(android.R.color.system_accent2_500)),
|
||||
shade40 = Color(context.getColor(android.R.color.system_accent2_600)),
|
||||
shade30 = Color(context.getColor(android.R.color.system_accent2_700)),
|
||||
shade20 = Color(context.getColor(android.R.color.system_accent2_800)),
|
||||
shade10 = Color(context.getColor(android.R.color.system_accent2_900)),
|
||||
shade0 = Color(context.getColor(android.R.color.system_accent2_1000)),
|
||||
)
|
||||
|
||||
override val tertiary = ColorSwatch(
|
||||
shade100 = Color(context.getColor(android.R.color.system_accent3_0)),
|
||||
shade99 = Color(context.getColor(android.R.color.system_accent3_10)),
|
||||
shade95 = Color(context.getColor(android.R.color.system_accent3_50)),
|
||||
shade90 = Color(context.getColor(android.R.color.system_accent3_100)),
|
||||
shade80 = Color(context.getColor(android.R.color.system_accent3_200)),
|
||||
shade70 = Color(context.getColor(android.R.color.system_accent3_300)),
|
||||
shade60 = Color(context.getColor(android.R.color.system_accent3_400)),
|
||||
shade50 = Color(context.getColor(android.R.color.system_accent3_500)),
|
||||
shade40 = Color(context.getColor(android.R.color.system_accent3_600)),
|
||||
shade30 = Color(context.getColor(android.R.color.system_accent3_700)),
|
||||
shade20 = Color(context.getColor(android.R.color.system_accent3_800)),
|
||||
shade10 = Color(context.getColor(android.R.color.system_accent3_900)),
|
||||
shade0 = Color(context.getColor(android.R.color.system_accent3_1000)),
|
||||
)
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package de.mm20.launcher2.ui.theme.colors
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import de.mm20.launcher2.ui.theme.WallpaperColors
|
||||
|
||||
class WallpaperColorPalette(
|
||||
wallpaperColors: WallpaperColors
|
||||
) : ColorPalette() {
|
||||
override val neutral: ColorSwatch
|
||||
override val neutralVariant: ColorSwatch
|
||||
override val primary: ColorSwatch
|
||||
override val secondary: ColorSwatch
|
||||
override val tertiary: ColorSwatch
|
||||
|
||||
init {
|
||||
val primary = wallpaperColors.primary
|
||||
val secondary = wallpaperColors.secondary
|
||||
val tertiary = wallpaperColors.tertiary
|
||||
|
||||
val neutral = primary.takeIf { !isBrown(it) }
|
||||
?: secondary?.takeIf { !isBrown(it) }
|
||||
?: tertiary?.takeIf { isBrown(it) }
|
||||
?: primary
|
||||
|
||||
val acc1: Color = tertiary?.takeIf { it != neutral }
|
||||
?: primary.takeIf { it != neutral }
|
||||
?: secondary?.takeIf { it != neutral }
|
||||
?: primary
|
||||
|
||||
val acc2: Color = secondary?.takeIf { it != neutral }
|
||||
?: primary.takeIf { it != neutral }
|
||||
?: tertiary?.takeIf { it != neutral }
|
||||
?: primary
|
||||
|
||||
|
||||
this.neutral = colorSwatch(neutral)
|
||||
neutralVariant = this.neutral
|
||||
this.primary = colorSwatch(acc1)
|
||||
this.secondary = colorSwatch(acc2)
|
||||
this.tertiary = this.neutral
|
||||
}
|
||||
|
||||
private fun isBrown(color: Color): Boolean {
|
||||
val hsl = floatArrayOf(0f, 0f, 0f)
|
||||
ColorUtils.colorToHSL(color.toArgb(), hsl)
|
||||
return hsl[0] in 0.0..50.0
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package de.mm20.launcher2.ui.widget
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun CalendarWidget() {
|
||||
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package de.mm20.launcher2.ui.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.os.Build
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import de.mm20.launcher2.ui.locals.LocalAppWidgetHost
|
||||
import de.mm20.launcher2.ui.toPixels
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
|
||||
@Composable
|
||||
fun PlatformWidget(widget: Widget) {
|
||||
val context = LocalContext.current.applicationContext
|
||||
|
||||
val widgetId = widget.data.toInt()
|
||||
|
||||
val appWidgetHost = LocalAppWidgetHost.current
|
||||
val widgetInfo = remember {
|
||||
AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId)
|
||||
}
|
||||
|
||||
val height = widget.height.dp.toPixels().toInt()
|
||||
val isLightTheme = androidx.compose.material.MaterialTheme.colors.isLight
|
||||
|
||||
AndroidView(
|
||||
factory = {
|
||||
val view = FrameLayout(context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, height)
|
||||
}
|
||||
val widgetView = appWidgetHost!!.createView(context, widgetId, widgetInfo).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
||||
}
|
||||
view.addView(widgetView)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
widgetView.setOnLightBackground(isLightTheme)
|
||||
}
|
||||
return@AndroidView view
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
package de.mm20.launcher2.ui.widget
|
||||
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateIntOffsetAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import de.mm20.launcher2.widgets.WidgetType
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun WidgetCard(widget: Widget, editMode: Boolean = false) {
|
||||
var dragOffset by remember { mutableStateOf(IntOffset.Zero) }
|
||||
val animatedOffset by animateIntOffsetAsState(dragOffset)
|
||||
var dragged by remember { mutableStateOf(false)}
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.offset { animatedOffset }
|
||||
.zIndex(if (dragged) 1f else 0f),
|
||||
elevation = animateDpAsState(if (dragged) 8.dp else 0.dp).value
|
||||
) {
|
||||
Column {
|
||||
if (widget.type == WidgetType.INTERNAL) {
|
||||
when (widget.data) {
|
||||
"weather" -> WeatherWidget()
|
||||
"music" -> MusicWidget()
|
||||
"calendar" -> CalendarWidget()
|
||||
}
|
||||
} else{
|
||||
PlatformWidget(widget)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user