Clean :ui module

This commit is contained in:
MM20 2022-01-31 14:22:46 +01:00
parent bb4a7a1e04
commit 35132b4e4b
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
53 changed files with 21 additions and 2987 deletions

View File

@ -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
)
}
}

View File

@ -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))
}
}

View File

@ -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)
)
}
}
}

View File

@ -1,8 +0,0 @@
package de.mm20.launcher2.ui
import androidx.compose.runtime.Composable
@Composable
fun LauncherCard() {
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
}
}*/

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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()
}
}
}

View File

@ -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
}

View File

@ -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
)
}
}
}
}
}
}

View File

@ -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)
)
}
}

View File

@ -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
)
}

View File

@ -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)
}
}
)
}

View File

@ -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
})
}
}
}

View File

@ -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
) {}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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 = {

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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() }

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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
)
}
}
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View 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) }
}
}

View File

@ -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
)
)
}

View File

@ -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 }

View File

@ -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
}

View File

@ -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()*/)
}

View File

@ -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
)
}
}

View File

@ -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()
}
}
}

View File

@ -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",

View File

@ -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

View 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
}

View File

@ -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,
)
}

View File

@ -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 })),
)
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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)),
)
}

View File

@ -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
}
}

View File

@ -1,8 +0,0 @@
package de.mm20.launcher2.ui.widget
import androidx.compose.runtime.Composable
@Composable
fun CalendarWidget() {
}

View File

@ -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
}
)
}

View File

@ -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)
}
}
}
}