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
|
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.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.composethemeadapter.MdcTheme
|
||||||
import com.google.android.material.composethemeadapter3.Mdc3Theme
|
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
|
@Composable
|
||||||
fun LegacyLauncherTheme(content: @Composable () -> Unit) {
|
fun MdcLauncherTheme(content: @Composable () -> Unit) {
|
||||||
|
|
||||||
Mdc3Theme {
|
Mdc3Theme {
|
||||||
MdcTheme(content = content)
|
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 isChecked: Boolean,
|
||||||
val onCheckedChange: (Boolean) -> Unit
|
val onCheckedChange: (Boolean) -> Unit
|
||||||
) : ToolbarAction
|
) : 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.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
||||||
@Composable fun Dp.toPixels(): Float {
|
@Composable
|
||||||
|
fun Dp.toPixels(): Float {
|
||||||
return value * LocalDensity.current.density
|
return value * LocalDensity.current.density
|
||||||
}
|
}
|
||||||
@ -16,7 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import de.mm20.launcher2.search.data.Calculator
|
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.databinding.ViewCalculatorBinding
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||||
import de.mm20.launcher2.ui.search.CalculatorItem
|
import de.mm20.launcher2.ui.search.CalculatorItem
|
||||||
@ -47,7 +47,7 @@ class CalculatorView : FrameLayout {
|
|||||||
|
|
||||||
binding.composeView.setContent {
|
binding.composeView.setContent {
|
||||||
val converter by calculator.observeAsState()
|
val converter by calculator.observeAsState()
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@ -2,8 +2,6 @@ package de.mm20.launcher2.ui.legacy.component
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@ -16,12 +14,11 @@ import androidx.compose.ui.platform.ComposeView
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings
|
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.R
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchBar
|
import de.mm20.launcher2.ui.launcher.search.SearchBar
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
|
import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
|
||||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -56,7 +53,7 @@ class SearchBar @JvmOverloads constructor(
|
|||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalCardStyle provides cardStyle
|
LocalCardStyle provides cardStyle
|
||||||
) {
|
) {
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
Box(contentAlignment = Alignment.TopCenter) {
|
Box(contentAlignment = Alignment.TopCenter) {
|
||||||
SearchBar(
|
SearchBar(
|
||||||
level,
|
level,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import de.mm20.launcher2.search.data.UnitConverter
|
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.databinding.ViewUnitconverterBinding
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||||
import de.mm20.launcher2.ui.search.UnitConverterItem
|
import de.mm20.launcher2.ui.search.UnitConverterItem
|
||||||
@ -47,7 +47,7 @@ class UnitConverterView : FrameLayout {
|
|||||||
|
|
||||||
binding.composeView.setContent {
|
binding.composeView.setContent {
|
||||||
val converter by unitConverter.observeAsState()
|
val converter by unitConverter.observeAsState()
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import androidx.transition.Scene
|
|||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.search.data.MissingPermission
|
import de.mm20.launcher2.search.data.MissingPermission
|
||||||
import de.mm20.launcher2.search.data.Searchable
|
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.R
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||||
@ -29,7 +29,7 @@ class PermissionListRepresentation : Representation, KoinComponent {
|
|||||||
scene.setEnterAction {
|
scene.setEnterAction {
|
||||||
val permissionsManager: PermissionsManager = get()
|
val permissionsManager: PermissionsManager = get()
|
||||||
rootView.findViewById<ComposeView>(R.id.composeView).setContent {
|
rootView.findViewById<ComposeView>(R.id.composeView).setContent {
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
MissingPermissionBanner(
|
MissingPermissionBanner(
|
||||||
text = missingPermission.label,
|
text = missingPermission.label,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import android.util.AttributeSet
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import de.mm20.launcher2.ui.ClockWidget
|
import de.mm20.launcher2.ui.ClockWidget
|
||||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||||
|
|
||||||
class ClockWidget : FrameLayout {
|
class ClockWidget : FrameLayout {
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class ClockWidget : FrameLayout {
|
|||||||
|
|
||||||
|
|
||||||
composeView.setContent {
|
composeView.setContent {
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
ClockWidget()
|
ClockWidget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import androidx.compose.material3.LocalContentColor
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.ComposeView
|
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.R
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ class MusicWidget : LauncherWidget {
|
|||||||
val composeView = ComposeView(context)
|
val composeView = ComposeView(context)
|
||||||
composeView.id = FrameLayout.generateViewId()
|
composeView.id = FrameLayout.generateViewId()
|
||||||
composeView.setContent {
|
composeView.setContent {
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import androidx.compose.material3.LocalContentColor
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.ComposeView
|
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.R
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ class WeatherWidget : LauncherWidget {
|
|||||||
val composeView = ComposeView(context)
|
val composeView = ComposeView(context)
|
||||||
composeView.id = FrameLayout.generateViewId()
|
composeView.id = FrameLayout.generateViewId()
|
||||||
composeView.setContent {
|
composeView.setContent {
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@ -5,18 +5,11 @@ import androidx.compose.runtime.compositionLocalOf
|
|||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import de.mm20.launcher2.preferences.Settings
|
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 LocalWindowSize = compositionLocalOf { Size(0f, 0f) }
|
||||||
|
|
||||||
val LocalAppWidgetHost = compositionLocalOf<AppWidgetHost?>(defaultFactory = { null })
|
val LocalAppWidgetHost = compositionLocalOf<AppWidgetHost?>(defaultFactory = { null })
|
||||||
|
|
||||||
val LocalWallpaperColors = compositionLocalOf<WallpaperColors?> { null }
|
|
||||||
|
|
||||||
val LocalColorScheme = compositionLocalOf<ColorPalette> { DefaultColorPalette() }
|
|
||||||
|
|
||||||
val LocalNavController = compositionLocalOf<NavController?> { null }
|
val LocalNavController = compositionLocalOf<NavController?> { null }
|
||||||
|
|
||||||
val LocalCardStyle = compositionLocalOf<Settings.CardSettings> { Settings.CardSettings.getDefaultInstance() }
|
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.licenses.OpenSourceLicenses
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings
|
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.base.BaseActivity
|
||||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
@ -64,7 +64,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
LocalNavController provides navController,
|
LocalNavController provides navController,
|
||||||
LocalCardStyle provides cardStyle
|
LocalCardStyle provides cardStyle
|
||||||
) {
|
) {
|
||||||
LegacyLauncherTheme {
|
MdcLauncherTheme {
|
||||||
AnimatedNavHost(
|
AnimatedNavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = "settings",
|
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.PreferenceCategory
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
import de.mm20.launcher2.ui.ktx.toHexString
|
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 kotlinx.coroutines.launch
|
||||||
import java.io.File
|
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