Add support for location plugins (#772)
* Cherry pick location refactor * Refactor :data:openstreetmaps to :data:locations * contract, plugin sdk * Implement serialization, module tweaks * Include check for out-of-date departures in SearchableItemVM.requestUpdatedSearchable() * settings for location plugins * Try not to be too lazy * more fiddling with the plugin SDK * add departures in MapView with mock data for debug builds * change icons * add boats * animate departure lazycolumn * Add MarqueeText for text overflow handling * Define height for Departures in case there is no map to display * Don't inclure railway / highway tags for OSM since there will be location plugins for that * sort by time * - Apply pre-merge changes to LocationSettings - Add banner warning about slowed down location search for large search radii * ditch `showLocationOnMap` * LocationItem: make `showOpeningSchedule` toggleable * LocationItem: make Navigation AssistChip work for people that don't have google maps installed * LocationItem: resolve TODOs * MapTiles: ditch unused code, animate userIndicator * Reintroduce departure list * Add LineColor * Add osm tag `stars` as `userRating` https://taginfo.openstreetmap.org/keys/stars#overview * typealias -> import * Don't add Navigation Chip when there is no way to resolve navigation intents * Add settings migration * Set plugin SDK version to 1.2.0-SNAPSHOT * Deduplicate shared plugin classes, use kotlinx.serialization * Fix imports * Use ZonedDateTime for depature times * Add more line types * Rewrite location serialization * Replace street/houseNumber with address * Add attribution field * Add plugin config * Reject location search requests without lat lon parameters * Add default values to plugin location class * Don't crash if column value is null * Add docs comments to LocationCategory values * Refactor OpeningSchedule as polymorphic * remove dead corpse *ahem* code * Split LocationCategory into category and icon (Also update to Kotlin 2.0, please don't do this at home) * Add more location icons * Fix (?) location deserializer * Add more location icons * More icons * Meh * Add Pub * Disable Github Maven repo if credentials are missing * Add location search specific settings to plugin details screen * Add language parameter * Unbreak the build * Refactor plugin SDK (with breaking changes) * Set plugin SDK version to 2.0.0-SNAPSHOT * Document SDK breaking changes * Implement LocationProvider.getQuery * Add a typesafe cursor API * Oops I did it again next time maybe check if the code is actually compiling before pushing * Add missing return statement * Fix list serialization * Departure time UI adjustment * Use typesafe cursor for weather plugins * Add userRatingCount and emailAddress fields * grrr * Rename and extend LineTypes * Add default lineType to Departure to fix serialization errors * Fix refreshing stored plugin locations * Adapt line name column width to available departures * Fix plugin settings screen category overlap * add LocationItem.GenericTransit * Fix crash during deserialization of locations * Update SDK docs * Replace plugin "official" mark with "verified developer" mark before anyone gets sued * show 'now' when departure is in less than one minute * Add typesafe Bundle API * Implement plugin API changes * Plugin SDK: Fix refresh result not being returned * Update docs * apply alpha to departures that have departured * better (maybe): reduce saturation instead of alpha * Add default values for Attribution * Display attribution * Rearrange location result layout * Reduce searchable update interval to 1 minute * Pass last update time to refresh function * Change refresh path and ensure that timestamp is only update when the item was updated * categorize osm location * Update docs * Optimize location search - run providers in parallel - flatten code * add experimental address parsing for OSM * add poi_category_townhall * Fix popup closing when favorites items are updated * Revert "Fix popup closing when favorites items are updated" This reverts commit fc517fd066c7f8109b6d6df2d4f536af66398207. * Fork AndroidAddressFormatter to `:libs:address-formatter` * migrate `:libs:address-formatter` dependencies to version catalog and update them * also consider addr:{suburb,hamlet} for `Address.city` if city tag is missing * Move poi strings back to strings.xml * Update Jetpack Compose * Move address-formatter back to its original package, add license and readme * Move address-formatter back to its original package --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.plugin.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -58,10 +59,6 @@ android {
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
@@ -141,7 +138,7 @@ dependencies {
|
||||
implementation(project(":core:crashreporter"))
|
||||
implementation(project(":data:notifications"))
|
||||
implementation(project(":data:contacts"))
|
||||
implementation(project(":data:openstreetmaps"))
|
||||
implementation(project(":data:locations"))
|
||||
implementation(project(":core:permissions"))
|
||||
implementation(project(":data:websites"))
|
||||
implementation(project(":data:unitconverter"))
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import androidx.compose.foundation.MarqueeAnimationMode
|
||||
import androidx.compose.foundation.MarqueeAnimationMode.Companion.Immediately
|
||||
import androidx.compose.foundation.MarqueeDefaults
|
||||
import androidx.compose.foundation.MarqueeSpacing
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
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.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import de.mm20.launcher2.ui.ktx.conditional
|
||||
import de.mm20.launcher2.ui.ktx.drawFadedEdge
|
||||
|
||||
@Composable
|
||||
fun MarqueeText(
|
||||
text: String,
|
||||
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,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
iterations: Int = MarqueeDefaults.Iterations,
|
||||
animationMode: MarqueeAnimationMode = Immediately,
|
||||
repeatDelayMillis: Int = MarqueeDefaults.RepeatDelayMillis,
|
||||
initialDelayMillis: Int = if (animationMode == Immediately) repeatDelayMillis else 0,
|
||||
spacing: MarqueeSpacing = MarqueeDefaults.Spacing,
|
||||
velocity: Dp = MarqueeDefaults.Velocity,
|
||||
fadeLeft: Dp? = null,
|
||||
fadeRight: Dp? = null,
|
||||
) {
|
||||
var textSize by remember { mutableIntStateOf(0) }
|
||||
var textLayout by remember { mutableStateOf<TextLayoutResult?>(null) }
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = style,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
letterSpacing = letterSpacing,
|
||||
textDecoration = textDecoration,
|
||||
textAlign = textAlign,
|
||||
lineHeight = lineHeight,
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
onTextLayout = { textLayout = it },
|
||||
modifier = modifier
|
||||
.onGloballyPositioned { textSize = it.size.width }
|
||||
.graphicsLayer {
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
}
|
||||
.conditional(
|
||||
textLayout != null && textLayout!!.size.width > textSize,
|
||||
Modifier
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
if (fadeLeft != null) {
|
||||
drawFadedEdge(leftEdge = true, fadeLeft)
|
||||
}
|
||||
if (fadeRight != null) {
|
||||
drawFadedEdge(leftEdge = false, fadeRight)
|
||||
}
|
||||
}
|
||||
)
|
||||
.basicMarquee(
|
||||
iterations = iterations,
|
||||
initialDelayMillis = initialDelayMillis,
|
||||
spacing = spacing,
|
||||
repeatDelayMillis = repeatDelayMillis,
|
||||
velocity = velocity
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import android.icu.text.DecimalFormat
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.StarHalf
|
||||
@@ -8,7 +10,9 @@ import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material3.Icon
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -25,7 +29,8 @@ fun RatingBar(
|
||||
rating: Float,
|
||||
modifier: Modifier = Modifier,
|
||||
tint: Color = MaterialTheme.colorScheme.primary,
|
||||
starSize: Dp = 16.dp
|
||||
starSize: Dp = 16.dp,
|
||||
ratingCount: Int? = null,
|
||||
) {
|
||||
val starRating = round(rating * 10f).toInt()
|
||||
val fullStars = starRating / 2
|
||||
@@ -34,6 +39,7 @@ fun RatingBar(
|
||||
val iconModifier = Modifier.size(starSize)
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
for (i in 0 until fullStars) {
|
||||
Icon(
|
||||
@@ -59,11 +65,18 @@ fun RatingBar(
|
||||
modifier = iconModifier
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
text = DecimalFormat("#.0").format(starRating / 2) +
|
||||
if (ratingCount == null) "" else " ($ratingCount)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RatingBarPreview() {
|
||||
RatingBar(0.67f)
|
||||
RatingBar(0.68f, ratingCount = 263)
|
||||
}
|
||||
@@ -8,14 +8,9 @@ import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -25,6 +20,7 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -67,17 +63,14 @@ import de.mm20.launcher2.badges.Badge
|
||||
import de.mm20.launcher2.icons.ClockLayer
|
||||
import de.mm20.launcher2.icons.ClockSublayer
|
||||
import de.mm20.launcher2.icons.ClockSublayerRole
|
||||
import de.mm20.launcher2.icons.ColorLayer
|
||||
import de.mm20.launcher2.icons.DynamicLauncherIcon
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.icons.LauncherIconLayer
|
||||
import de.mm20.launcher2.icons.LauncherIconRenderSettings
|
||||
import de.mm20.launcher2.icons.StaticIconLayer
|
||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||
import de.mm20.launcher2.icons.TextLayer
|
||||
import de.mm20.launcher2.icons.TintedClockLayer
|
||||
import de.mm20.launcher2.icons.TintedIconLayer
|
||||
import de.mm20.launcher2.icons.TransparentLayer
|
||||
import de.mm20.launcher2.icons.VectorLayer
|
||||
import de.mm20.launcher2.ktx.drawWithColorFilter
|
||||
import de.mm20.launcher2.preferences.IconShape
|
||||
import de.mm20.launcher2.ui.base.LocalTime
|
||||
@@ -85,10 +78,8 @@ import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||
import de.mm20.launcher2.ui.modifier.scale
|
||||
import kotlinx.coroutines.launch
|
||||
import palettes.TonalPalette
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
@@ -220,6 +211,17 @@ fun ShapedLauncherIcon(
|
||||
)
|
||||
}
|
||||
|
||||
is VectorLayer -> {
|
||||
Icon(
|
||||
imageVector = fg.vector, contentDescription = null,
|
||||
tint = if (fg.color == 0) {
|
||||
Color(renderSettings.fgThemeColor)
|
||||
} else {
|
||||
Color(getTone(fg.color, renderSettings.fgTone))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
@@ -303,98 +305,6 @@ fun ShapedLauncherIcon(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconLayer(
|
||||
layer: LauncherIconLayer,
|
||||
size: Dp,
|
||||
colorTone: Int,
|
||||
defaultTintColor: Color
|
||||
) {
|
||||
when (layer) {
|
||||
is ClockLayer -> {
|
||||
ClockLayer(
|
||||
layer.sublayers,
|
||||
scale = layer.scale,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultHour = layer.defaultHour,
|
||||
tintColor = null
|
||||
)
|
||||
}
|
||||
|
||||
is TintedClockLayer -> {
|
||||
ClockLayer(
|
||||
layer.sublayers,
|
||||
scale = layer.scale,
|
||||
defaultSecond = layer.defaultSecond,
|
||||
defaultMinute = layer.defaultMinute,
|
||||
defaultHour = layer.defaultHour,
|
||||
tintColor = if (layer.color == 0) defaultTintColor
|
||||
else Color(getTone(layer.color, colorTone))
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
is ColorLayer -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
if (layer.color == 0) {
|
||||
defaultTintColor
|
||||
} else {
|
||||
Color(getTone(layer.color, colorTone))
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is StaticIconLayer -> {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
withTransform({
|
||||
this.scale(layer.scale)
|
||||
}) {
|
||||
drawIntoCanvas {
|
||||
layer.icon.bounds = this.size.toRect().toAndroidRect()
|
||||
layer.icon.draw(it.nativeCanvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TextLayer -> {
|
||||
Text(
|
||||
text = layer.text,
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontSize = 20.sp * (size / 48.dp)
|
||||
),
|
||||
color = if (layer.color == 0) {
|
||||
defaultTintColor
|
||||
} else {
|
||||
Color(getTone(layer.color, colorTone))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is TintedIconLayer -> {
|
||||
val color =
|
||||
if (layer.color == 0) defaultTintColor.toArgb()
|
||||
else getTone(layer.color, colorTone)
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
drawIntoCanvas {
|
||||
layer.icon.bounds = this.size.toRect().toAndroidRect()
|
||||
layer.icon.drawWithColorFilter(
|
||||
it.nativeCanvas,
|
||||
PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TransparentLayer -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTone(argb: Int, tone: Int): Int {
|
||||
return TonalPalette
|
||||
.fromInt(argb)
|
||||
@@ -430,8 +340,13 @@ private fun ClockLayer(
|
||||
sublayer.drawable.level = (((hour - defaultHour + 12) % 12) * 60
|
||||
+ ((minute) % 60))
|
||||
}
|
||||
ClockSublayerRole.Minute -> sublayer.drawable.level = ((minute - defaultMinute + 60) % 60)
|
||||
ClockSublayerRole.Second -> sublayer.drawable.level = (((second - defaultSecond + 60) % 60) * 10)
|
||||
|
||||
ClockSublayerRole.Minute -> sublayer.drawable.level =
|
||||
((minute - defaultMinute + 60) % 60)
|
||||
|
||||
ClockSublayerRole.Second -> sublayer.drawable.level =
|
||||
(((second - defaultSecond + 60) % 60) * 10)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
drawIntoCanvas {
|
||||
|
||||
@@ -9,6 +9,7 @@ fun TextPreference(
|
||||
title: String,
|
||||
value: String,
|
||||
summary: String? = value,
|
||||
enabled: Boolean = true,
|
||||
onValueChanged: (String) -> Unit,
|
||||
placeholder: String? = null
|
||||
) {
|
||||
@@ -16,6 +17,7 @@ fun TextPreference(
|
||||
Preference(
|
||||
title = title,
|
||||
summary = summary,
|
||||
enabled = enabled,
|
||||
onClick = { showDialog = true }
|
||||
)
|
||||
|
||||
|
||||
@@ -23,8 +23,14 @@ import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.icons.WeatherCloud
|
||||
import de.mm20.launcher2.icons.WeatherFog
|
||||
import de.mm20.launcher2.icons.WeatherHailAnimatable
|
||||
import de.mm20.launcher2.icons.WeatherLightRainAnimatable
|
||||
import de.mm20.launcher2.icons.WeatherRainAnimatable
|
||||
import de.mm20.launcher2.icons.WeatherSleetRainAnimatable
|
||||
import de.mm20.launcher2.icons.WeatherSleetSnowAnimatable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.icons.*
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sin
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.icons.WeatherCloud
|
||||
import de.mm20.launcher2.ui.icons.WeatherFog
|
||||
import de.mm20.launcher2.ui.icons.WeatherHail
|
||||
import de.mm20.launcher2.ui.icons.WeatherLightRain
|
||||
import de.mm20.launcher2.ui.icons.WeatherRain
|
||||
import de.mm20.launcher2.ui.icons.WeatherSleetRain
|
||||
import de.mm20.launcher2.ui.icons.WeatherSleetSnow
|
||||
import de.mm20.launcher2.icons.WeatherCloud
|
||||
import de.mm20.launcher2.icons.WeatherFog
|
||||
import de.mm20.launcher2.icons.WeatherHail
|
||||
import de.mm20.launcher2.icons.WeatherLightRain
|
||||
import de.mm20.launcher2.icons.WeatherRain
|
||||
import de.mm20.launcher2.icons.WeatherSleetRain
|
||||
import de.mm20.launcher2.icons.WeatherSleetSnow
|
||||
|
||||
@Composable
|
||||
fun WeatherIcon(
|
||||
|
||||
@@ -1,538 +0,0 @@
|
||||
package de.mm20.launcher2.ui.icons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.ui.graphics.vector.group
|
||||
|
||||
val Icons.Rounded.Battery0Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery0Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(14f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery1Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery1Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(12f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery2Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery2Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(10f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery3Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery3Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(8f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery4Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery4Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(6f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery5Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery5Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(4f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.Battery6Bar
|
||||
get() = materialIcon("Icons.Rounded.Battery6Bar") {
|
||||
materialPath {
|
||||
moveTo(17f, 5f)
|
||||
verticalLineToRelative(16f)
|
||||
curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
|
||||
horizontalLineTo(8f)
|
||||
curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
|
||||
verticalLineTo(5f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
verticalLineTo(3f)
|
||||
curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
|
||||
verticalLineToRelative(1f)
|
||||
horizontalLineToRelative(2f)
|
||||
curveTo(16.55f, 4f, 17f, 4.45f, 17f, 5f)
|
||||
close()
|
||||
moveTo(15f, 6f)
|
||||
horizontalLineTo(9f)
|
||||
verticalLineToRelative(2f)
|
||||
horizontalLineToRelative(6f)
|
||||
verticalLineTo(6f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging0Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging0Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 75.589844f)
|
||||
lineTo(34.015625f, 75.589844f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(47.253906f, 32.126953f)
|
||||
curveTo(46.605486f, 32.112843f, 45.950535f, 32.41843f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(34.809919f, 53.290899f, 35.716123f, 54.802734f, 37.152344f, 54.802734f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(41.574219f, 68.03125f)
|
||||
curveTo(41.574219f, 69.996604f, 44.219867f, 70.640241f, 45.126953f, 68.939453f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.848696f, 48.201598f, 32.147575f, 47.253906f, 32.126953f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging1Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging1Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 68.03125f)
|
||||
lineTo(45.611328f, 68.03125f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(34.015625f, 68.03125f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(41.574219f, 68.03125f)
|
||||
lineTo(45.611328f, 68.03125f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(34.809919f, 53.290899f, 35.716123f, 54.802734f, 37.152344f, 54.802734f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(41.574219f, 68.03125f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging2Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging2Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 60.472656f)
|
||||
lineTo(49.648438f, 60.472656f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 60.472656f)
|
||||
lineTo(34.015625f, 60.472656f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(41.574219f, 60.472656f)
|
||||
lineTo(49.648438f, 60.472656f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(34.809919f, 53.290899f, 35.716123f, 54.802734f, 37.152344f, 54.802734f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(41.574219f, 60.472656f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging3Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging3Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 52.914062f)
|
||||
lineTo(53.683594f, 52.914062f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(36.048819f, 54.802734f, 35.258483f, 53.910469f, 35.259766f, 52.914062f)
|
||||
lineTo(34.015625f, 52.914062f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(35.259766f, 52.914062f)
|
||||
lineTo(53.683594f, 52.914062f)
|
||||
lineTo(55.21875f, 50.041016f)
|
||||
curveTo(55.899065f, 48.755976f, 54.955066f, 47.244141f, 53.556641f, 47.244141f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(35.490234f, 52.005859f)
|
||||
curveTo(35.332642f, 52.303535f, 35.260152f, 52.613661f, 35.259766f, 52.914062f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging4Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging4Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 45.353516f)
|
||||
lineTo(49.134766f, 45.353516f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(53.556641f, 47.244141f)
|
||||
curveTo(54.955066f, 47.244141f, 55.899065f, 48.755976f, 55.21875f, 50.041016f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(35.716123f, 54.802734f, 34.809919f, 53.290899f, 35.490234f, 52.005859f)
|
||||
lineTo(39.042969f, 45.353516f)
|
||||
lineTo(34.015625f, 45.353516f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(39.042969f, 45.353516f)
|
||||
lineTo(49.134766f, 45.353516f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(39.042969f, 45.353516f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging5Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging5Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 37.794922f)
|
||||
lineTo(49.134766f, 37.794922f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(53.556641f, 47.244141f)
|
||||
curveTo(54.955066f, 47.244141f, 55.899065f, 48.755976f, 55.21875f, 50.041016f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(35.716123f, 54.802734f, 34.809919f, 53.290899f, 35.490234f, 52.005859f)
|
||||
lineTo(43.080078f, 37.794922f)
|
||||
lineTo(34.015625f, 37.794922f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(43.080078f, 37.794922f)
|
||||
lineTo(49.134766f, 37.794922f)
|
||||
lineTo(49.134766f, 34.015625f)
|
||||
curveTo(49.134766f, 32.050271f, 46.489118f, 31.408588f, 45.582031f, 33.109375f)
|
||||
lineTo(43.080078f, 37.794922f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.BatteryCharging6Bar
|
||||
get() = materialIcon("Icons.Rounded.BatteryCharging6Bar") {
|
||||
group(
|
||||
scaleX = 0.26458333f,
|
||||
scaleY = 0.26458333f,
|
||||
) {
|
||||
materialPath {
|
||||
moveTo(41.574219f, 7.5585938f)
|
||||
curveTo(39.495479f, 7.5585937f, 37.794922f, 9.2591505f, 37.794922f, 11.337891f)
|
||||
lineTo(37.794922f, 15.117188f)
|
||||
lineTo(30.236328f, 15.117188f)
|
||||
curveTo(28.157588f, 15.117187f, 26.457031f, 16.819697f, 26.457031f, 18.898438f)
|
||||
lineTo(26.457031f, 79.369141f)
|
||||
curveTo(26.457031f, 81.447881f, 28.157588f, 83.150391f, 30.236328f, 83.150391f)
|
||||
lineTo(60.472656f, 83.150391f)
|
||||
curveTo(62.551396f, 83.150391f, 64.251953f, 81.447881f, 64.251953f, 79.369141f)
|
||||
lineTo(64.251953f, 18.898438f)
|
||||
curveTo(64.251953f, 16.819697f, 62.551396f, 15.117188f, 60.472656f, 15.117188f)
|
||||
lineTo(52.914062f, 15.117188f)
|
||||
lineTo(52.914062f, 11.337891f)
|
||||
curveTo(52.914063f, 9.2591505f, 51.213506f, 7.5585938f, 49.134766f, 7.5585938f)
|
||||
lineTo(41.574219f, 7.5585938f)
|
||||
close()
|
||||
moveTo(34.015625f, 22.677734f)
|
||||
lineTo(56.693359f, 22.677734f)
|
||||
lineTo(56.693359f, 30.236328f)
|
||||
lineTo(34.015625f, 30.236328f)
|
||||
lineTo(34.015625f, 22.677734f)
|
||||
close()
|
||||
moveTo(47.253906f, 32.126953f)
|
||||
curveTo(48.201598f, 32.147575f, 49.134766f, 32.848696f, 49.134766f, 34.015625f)
|
||||
lineTo(49.134766f, 47.244141f)
|
||||
lineTo(53.556641f, 47.244141f)
|
||||
curveTo(54.955066f, 47.244141f, 55.899065f, 48.755976f, 55.21875f, 50.041016f)
|
||||
lineTo(45.126953f, 68.939453f)
|
||||
curveTo(44.219867f, 70.640241f, 41.574219f, 69.996604f, 41.574219f, 68.03125f)
|
||||
lineTo(41.574219f, 54.802734f)
|
||||
lineTo(37.152344f, 54.802734f)
|
||||
curveTo(35.716123f, 54.802734f, 34.809919f, 53.290899f, 35.490234f, 52.005859f)
|
||||
lineTo(45.582031f, 33.109375f)
|
||||
curveTo(45.950535f, 32.41843f, 46.605486f, 32.112843f, 47.253906f, 32.126953f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
package de.mm20.launcher2.ui.ktx
|
||||
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
fun ContentDrawScope.drawFadedEdge(leftEdge: Boolean, edgeWidth: Dp) {
|
||||
// https://stackoverflow.com/a/75112743
|
||||
val edgeWidthPx = edgeWidth.toPx()
|
||||
drawRect(
|
||||
topLeft = Offset(if (leftEdge) 0f else size.width - edgeWidthPx, 0f),
|
||||
size = Size(edgeWidthPx, size.height),
|
||||
brush = Brush.horizontalGradient(
|
||||
colors = listOf(Color.Transparent, Color.Black),
|
||||
startX = if (leftEdge) 0f else size.width,
|
||||
endX = if (leftEdge) edgeWidthPx else size.width - edgeWidthPx
|
||||
),
|
||||
blendMode = BlendMode.DstIn
|
||||
)
|
||||
}
|
||||
@@ -17,7 +17,7 @@ suspend fun MutableState<Float>.animateTo(targetValue: Float) {
|
||||
|
||||
suspend inline fun <T, V: AnimationVector> MutableState<T>.animateTo(targetValue: T, converter: TwoWayConverter<T, V>) {
|
||||
val animatable = Animatable(this.value, converter)
|
||||
animatable.animateTo(targetValue) {
|
||||
animatable.animateTo(targetValue) animatable@ {
|
||||
this@animateTo.value = this.value
|
||||
}
|
||||
}
|
||||
@@ -225,10 +225,11 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
resultsList = resultsList.sortedWith { a, b ->
|
||||
val lastLocation = devicePoseProvider.lastLocation
|
||||
when {
|
||||
a is Location && b is Location && devicePoseProvider.lastLocation != null -> {
|
||||
a.distanceTo(devicePoseProvider.lastLocation!!)
|
||||
.compareTo(b.distanceTo(devicePoseProvider.lastLocation!!))
|
||||
a is Location && b is Location && lastLocation != null -> {
|
||||
a.distanceTo(lastLocation)
|
||||
.compareTo(b.distanceTo(lastLocation))
|
||||
}
|
||||
|
||||
a is SavableSearchable && b !is SavableSearchable -> -1
|
||||
@@ -250,7 +251,6 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
hiddenItemKeys.collectLatest { hiddenKeys ->
|
||||
val hidden = mutableListOf<SavableSearchable>()
|
||||
val apps = mutableListOf<Application>()
|
||||
@@ -349,7 +349,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
|
||||
val missingLocationPermission = combine(
|
||||
permissionsManager.hasPermission(PermissionGroup.Location),
|
||||
locationSearchSettings.enabled.distinctUntilChanged()
|
||||
locationSearchSettings.osmLocations.distinctUntilChanged()
|
||||
) { perm, enabled -> !perm && enabled }
|
||||
|
||||
fun requestLocationPermission(context: AppCompatActivity) {
|
||||
@@ -357,7 +357,7 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
fun disableLocationSearch() {
|
||||
locationSearchSettings.setEnabled(false)
|
||||
locationSearchSettings.setOsmLocations(false)
|
||||
}
|
||||
|
||||
val missingFilesPermission = combine(
|
||||
|
||||
+10
-8
@@ -6,6 +6,7 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.customview.view.AbsSavedState
|
||||
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
||||
import de.mm20.launcher2.badges.BadgeService
|
||||
import de.mm20.launcher2.devicepose.DevicePoseProvider
|
||||
@@ -19,6 +20,7 @@ import de.mm20.launcher2.preferences.search.LocationSearchSettings
|
||||
import de.mm20.launcher2.search.AppShortcut
|
||||
import de.mm20.launcher2.search.Application
|
||||
import de.mm20.launcher2.search.File
|
||||
import de.mm20.launcher2.search.Location
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.UpdatableSearchable
|
||||
import de.mm20.launcher2.search.UpdateResult
|
||||
@@ -40,6 +42,7 @@ import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SearchableItemVM : ListItemViewModel(), KoinComponent {
|
||||
@@ -184,21 +187,24 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
|
||||
val searchable = searchable.value ?: return
|
||||
if (searchable is UpdatableSearchable<*>) {
|
||||
val updatedSelf = searchable.updatedSelf ?: return
|
||||
if (!shouldRetryUpdate && System.currentTimeMillis() < searchable.timestamp + 1.hours.inWholeMilliseconds) return
|
||||
val sinceTimestamp = System.currentTimeMillis() - searchable.timestamp
|
||||
|
||||
val isOutOfDate = 1.minutes.inWholeMilliseconds < sinceTimestamp
|
||||
|
||||
if (!shouldRetryUpdate && !isOutOfDate) return
|
||||
|
||||
viewModelScope.launch {
|
||||
this@SearchableItemVM.searchable.value = with(updatedSelf()) {
|
||||
with(updatedSelf(searchable)) {
|
||||
when (this) {
|
||||
is UpdateResult.Success -> {
|
||||
isUpToDate.value = true
|
||||
shouldRetryUpdate = false
|
||||
favoritesService.upsert(this.result)
|
||||
this.result
|
||||
}
|
||||
|
||||
is UpdateResult.TemporarilyUnavailable -> {
|
||||
isUpToDate.value = false
|
||||
shouldRetryUpdate = true
|
||||
return@launch
|
||||
}
|
||||
|
||||
is UpdateResult.PermanentlyUnavailable -> {
|
||||
@@ -211,7 +217,6 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Log.d("requestUpdatedSearchable", "PermanentlyUnavailable", this.cause)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,9 +237,6 @@ class SearchableItemVM : ListItemViewModel(), KoinComponent {
|
||||
val applyMapTheming = locationSearchSettings.themeMap
|
||||
.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||
|
||||
val showPositionOnMap = locationSearchSettings.showPositionOnMap
|
||||
.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||
|
||||
val mapTileServerUrl = locationSearchSettings.tileServer
|
||||
.stateIn(viewModelScope, SharingStarted.Lazily, "")
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -90,8 +89,6 @@ fun GridItem(
|
||||
viewModel.init(item, iconSize.toInt())
|
||||
}
|
||||
|
||||
val item = viewModel.searchable.collectAsState().value ?: item
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
var showPopup by remember(item.key) { mutableStateOf(false) }
|
||||
|
||||
@@ -69,8 +69,6 @@ fun ListItem(
|
||||
)
|
||||
)
|
||||
|
||||
val item = viewModel.searchable.collectAsState().value ?: item
|
||||
|
||||
var bounds by remember { mutableStateOf(Rect.Zero) }
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package de.mm20.launcher2.ui.launcher.search.contacts
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.animation.expandIn
|
||||
@@ -25,7 +22,6 @@ import androidx.compose.material.icons.rounded.Email
|
||||
import androidx.compose.material.icons.rounded.Home
|
||||
import androidx.compose.material.icons.rounded.MoreHoriz
|
||||
import androidx.compose.material.icons.rounded.OpenInNew
|
||||
import androidx.compose.material.icons.rounded.Place
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material.icons.rounded.Visibility
|
||||
@@ -63,9 +59,8 @@ import de.mm20.launcher2.ui.component.DefaultToolbarAction
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.Toolbar
|
||||
import de.mm20.launcher2.ui.component.ToolbarAction
|
||||
import de.mm20.launcher2.ui.icons.Signal
|
||||
import de.mm20.launcher2.ui.icons.Telegram
|
||||
import de.mm20.launcher2.ui.icons.WhatsApp
|
||||
import de.mm20.launcher2.icons.Signal
|
||||
import de.mm20.launcher2.icons.Telegram
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
||||
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
|
||||
|
||||
-14
@@ -10,17 +10,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AppShortcut
|
||||
import androidx.compose.material.icons.rounded.Apps
|
||||
import androidx.compose.material.icons.rounded.Description
|
||||
import androidx.compose.material.icons.rounded.Handyman
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material.icons.rounded.Person
|
||||
import androidx.compose.material.icons.rounded.Place
|
||||
import androidx.compose.material.icons.rounded.Public
|
||||
import androidx.compose.material.icons.rounded.Today
|
||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -32,12 +21,9 @@ import androidx.compose.runtime.Composable
|
||||
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.unit.dp
|
||||
import de.mm20.launcher2.preferences.KeyboardFilterBarItem
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.icons.Wikipedia
|
||||
|
||||
@Composable
|
||||
fun KeyboardFilterBar(
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import de.mm20.launcher2.preferences.KeyboardFilterBarItem
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.icons.Wikipedia
|
||||
import de.mm20.launcher2.icons.Wikipedia
|
||||
|
||||
val KeyboardFilterBarItem.icon
|
||||
get() = when (this) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.icons.Wikipedia
|
||||
import de.mm20.launcher2.icons.Wikipedia
|
||||
|
||||
@Composable
|
||||
fun SearchFilters(
|
||||
|
||||
+473
-184
@@ -14,29 +14,49 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.MarqueeSpacing
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.rounded.NavigateNext
|
||||
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
|
||||
import androidx.compose.material.icons.rounded.AirplanemodeActive
|
||||
import androidx.compose.material.icons.rounded.BugReport
|
||||
import androidx.compose.material.icons.rounded.Commute
|
||||
import androidx.compose.material.icons.rounded.DirectionsBoat
|
||||
import androidx.compose.material.icons.rounded.DirectionsBus
|
||||
import androidx.compose.material.icons.rounded.DirectionsRailway
|
||||
import androidx.compose.material.icons.rounded.DirectionsTransit
|
||||
import androidx.compose.material.icons.rounded.Edit
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material.icons.rounded.Navigation
|
||||
import androidx.compose.material.icons.rounded.Phone
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material.icons.rounded.Subway
|
||||
import androidx.compose.material.icons.rounded.Train
|
||||
import androidx.compose.material.icons.rounded.Tram
|
||||
import androidx.compose.material.icons.rounded.Visibility
|
||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material3.AssistChip
|
||||
@@ -58,31 +78,48 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
import androidx.compose.ui.unit.roundToIntRect
|
||||
import androidx.compose.ui.unit.times
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import blend.Blend.harmonize
|
||||
import coil.compose.AsyncImage
|
||||
import de.mm20.launcher2.i18n.R
|
||||
import de.mm20.launcher2.icons.CableCar
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.Location
|
||||
import de.mm20.launcher2.search.LocationCategory
|
||||
import de.mm20.launcher2.search.OpeningHours
|
||||
import de.mm20.launcher2.search.OpeningSchedule
|
||||
import de.mm20.launcher2.search.isOpen
|
||||
import de.mm20.launcher2.search.location.Attribution
|
||||
import de.mm20.launcher2.search.location.Departure
|
||||
import de.mm20.launcher2.search.location.LineType
|
||||
import de.mm20.launcher2.search.location.OpeningHours
|
||||
import de.mm20.launcher2.search.location.OpeningSchedule
|
||||
import de.mm20.launcher2.ui.base.LocalTime
|
||||
import de.mm20.launcher2.ui.component.DefaultToolbarAction
|
||||
import de.mm20.launcher2.ui.component.MarqueeText
|
||||
import de.mm20.launcher2.ui.component.RatingBar
|
||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||
import de.mm20.launcher2.ui.component.Toolbar
|
||||
import de.mm20.launcher2.ui.component.ToolbarAction
|
||||
import de.mm20.launcher2.ui.ktx.blendIntoViewScale
|
||||
import de.mm20.launcher2.ui.ktx.metersToLocalizedString
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
||||
import de.mm20.launcher2.ui.launcher.search.listItemViewModel
|
||||
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
||||
@@ -92,7 +129,9 @@ import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
|
||||
import de.mm20.launcher2.ui.modifier.scale
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.time.format.TextStyle
|
||||
@@ -108,7 +147,6 @@ fun LocationItem(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${location.key}")
|
||||
val iconSize = LocalGridSettings.current.iconSize.dp.toPixels()
|
||||
|
||||
val userLocation by remember {
|
||||
viewModel.devicePoseProvider.getLocation()
|
||||
@@ -181,7 +219,8 @@ fun LocationItem(
|
||||
if (category != null || formattedDistance != null) {
|
||||
Text(
|
||||
when {
|
||||
category != null && formattedDistance != null -> "${stringResource(category.labelRes)} • ${formattedDistance}"
|
||||
category != null && formattedDistance != null -> "$category • $formattedDistance"
|
||||
|
||||
category != null -> category.toString()
|
||||
formattedDistance != null -> formattedDistance
|
||||
else -> ""
|
||||
@@ -258,7 +297,7 @@ fun LocationItem(
|
||||
end = 12.dp,
|
||||
bottom = 4.dp
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -279,7 +318,8 @@ fun LocationItem(
|
||||
if (category != null || formattedDistance != null) {
|
||||
Text(
|
||||
when {
|
||||
category != null && formattedDistance != null -> "${stringResource(category.labelRes)} • ${formattedDistance}"
|
||||
category != null && formattedDistance != null -> "$category • $formattedDistance"
|
||||
|
||||
category != null -> category.toString()
|
||||
formattedDistance != null -> formattedDistance
|
||||
else -> ""
|
||||
@@ -294,31 +334,176 @@ fun LocationItem(
|
||||
)
|
||||
)
|
||||
}
|
||||
// TODO: add rating to location
|
||||
if (!showMap && false) {
|
||||
RatingBar(0.66f, modifier = Modifier.padding(top = 4.dp))
|
||||
if (location.userRating != null) {
|
||||
RatingBar(
|
||||
location.userRating!!,
|
||||
modifier = Modifier
|
||||
.padding(top = 6.dp)
|
||||
.offset(-2.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (!showMap) {
|
||||
val attribution = location.attribution
|
||||
if (attribution != null) {
|
||||
Attribution(
|
||||
attribution,
|
||||
reverse = true,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 16.dp,
|
||||
bottom = 0.dp,
|
||||
)
|
||||
.clickable(
|
||||
enabled = attribution.url != null
|
||||
) {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(attribution.url)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: add rating to location
|
||||
if (showMap && false) {
|
||||
RatingBar(0.66f)
|
||||
}
|
||||
|
||||
if (!showMap) {
|
||||
Compass(
|
||||
targetHeading = targetHeading,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Top)
|
||||
.sharedBounds(
|
||||
rememberSharedContentState("compass"),
|
||||
this@AnimatedContent
|
||||
),
|
||||
size = 56.dp,
|
||||
)
|
||||
} else {
|
||||
val attribution = location.attribution
|
||||
if (attribution != null) {
|
||||
Attribution(
|
||||
attribution,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 4.dp,
|
||||
bottom = 4.dp,
|
||||
start = 12.dp)
|
||||
.clickable(
|
||||
enabled = attribution.url != null
|
||||
) {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(attribution.url)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val openingSchedule = location.openingSchedule
|
||||
if (openingSchedule != null && (openingSchedule.isTwentyFourSeven || openingSchedule.openingHours.isNotEmpty())) {
|
||||
val departures = remember(location.departures) {
|
||||
location.departures
|
||||
?.sortedBy { it.time }
|
||||
}
|
||||
if (departures != null) {
|
||||
val time = LocalTime.current
|
||||
val nextDeparture = remember(time) {
|
||||
departures.firstOrNull {
|
||||
it.time.plus(it.delay ?: Duration.ZERO).isAfter(ZonedDateTime.now())
|
||||
}
|
||||
}
|
||||
if (nextDeparture != null) {
|
||||
var showDepartureList by remember(departures) {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 12.dp, top = 12.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
onClick = { showDepartureList = !showDepartureList }
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
AnimatedContent(showDepartureList) { showList ->
|
||||
if (!showList) {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(12.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
nextDeparture.LineIcon(Modifier.padding(end = 8.dp))
|
||||
val lastStop = nextDeparture.lastStop
|
||||
if (lastStop != null) {
|
||||
MarqueeText(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = lastStop,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
iterations = Int.MAX_VALUE,
|
||||
repeatDelayMillis = 0,
|
||||
velocity = 20.dp,
|
||||
fadeLeft = 5.dp,
|
||||
fadeRight = 5.dp,
|
||||
)
|
||||
}
|
||||
|
||||
val formattedTime = remember(time) {
|
||||
val timeLeft = Duration.between(
|
||||
java.time.LocalTime.now(),
|
||||
nextDeparture.time + (nextDeparture.delay
|
||||
?: Duration.ZERO)
|
||||
).toMinutes()
|
||||
if (timeLeft < 1) "now" else "in $timeLeft min"
|
||||
}
|
||||
|
||||
Text(
|
||||
text = formattedTime,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(end = 12.dp)
|
||||
)
|
||||
Icon(Icons.AutoMirrored.Rounded.NavigateNext, null)
|
||||
}
|
||||
} else {
|
||||
val longestLine = remember(departures) {
|
||||
departures.maxOfOrNull { it.line.length }
|
||||
}
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = modifier
|
||||
.heightIn(max = 192.dp)
|
||||
.padding(12.dp)
|
||||
.fillMaxWidth()
|
||||
.pointerInput(Unit) { detectDragGestures { _, _ -> } },
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
itemsIndexed(
|
||||
departures,
|
||||
key = { idx, _ -> idx }) { idx, it ->
|
||||
it.LazyColumnPart(
|
||||
lineWidth = longestLine,
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.graphicsLayer {
|
||||
alpha =
|
||||
listState.layoutInfo.blendIntoViewScale(
|
||||
idx
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openingSchedule is OpeningSchedule.TwentyFourSeven || (openingSchedule is OpeningSchedule.Hours && openingSchedule.openingHours.isNotEmpty())) {
|
||||
var showOpeningSchedule by remember(openingSchedule) {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
@@ -328,8 +513,8 @@ fun LocationItem(
|
||||
.padding(start = 12.dp, end = 12.dp, top = 12.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
onClick = {
|
||||
if (!openingSchedule.isTwentyFourSeven) {
|
||||
showOpeningSchedule = true
|
||||
if (openingSchedule !is OpeningSchedule.TwentyFourSeven) {
|
||||
showOpeningSchedule = !showOpeningSchedule
|
||||
}
|
||||
}
|
||||
) {
|
||||
@@ -341,78 +526,84 @@ fun LocationItem(
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (openingSchedule.isTwentyFourSeven) {
|
||||
Text(
|
||||
text = stringResource(R.string.location_open_24_7),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
} else {
|
||||
val text = remember(openingSchedule) {
|
||||
val currentOpeningTime =
|
||||
openingSchedule.getCurrentOpeningHours()
|
||||
val timeFormat =
|
||||
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
return@remember if (currentOpeningTime != null) {
|
||||
val isSameDay =
|
||||
currentOpeningTime.dayOfWeek == LocalDateTime.now().dayOfWeek
|
||||
val formattedTime =
|
||||
timeFormat.format(currentOpeningTime.startTime + currentOpeningTime.duration)
|
||||
val closingTime = if (isSameDay) {
|
||||
context.getString(
|
||||
R.string.location_closes,
|
||||
formattedTime
|
||||
)
|
||||
} else {
|
||||
val dow =
|
||||
currentOpeningTime.dayOfWeek.getDisplayName(
|
||||
TextStyle.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
context.getString(
|
||||
R.string.location_closes_other_day,
|
||||
dow,
|
||||
formattedTime
|
||||
)
|
||||
}
|
||||
"${context.getString(R.string.location_open)} • $closingTime"
|
||||
} else {
|
||||
val nextOpeningTime =
|
||||
openingSchedule.getNextOpeningHours()
|
||||
val isSameDay =
|
||||
nextOpeningTime.dayOfWeek == LocalDateTime.now().dayOfWeek
|
||||
val formattedTime =
|
||||
timeFormat.format(nextOpeningTime.startTime)
|
||||
val openingTime = if (isSameDay) {
|
||||
context.getString(
|
||||
R.string.location_opens,
|
||||
formattedTime
|
||||
)
|
||||
} else {
|
||||
val dow =
|
||||
nextOpeningTime.dayOfWeek.getDisplayName(
|
||||
TextStyle.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
context.getString(
|
||||
R.string.location_opens_other_day,
|
||||
dow,
|
||||
formattedTime
|
||||
)
|
||||
}
|
||||
"${context.getString(R.string.location_closed)} • $openingTime"
|
||||
}
|
||||
when (openingSchedule) {
|
||||
is OpeningSchedule.TwentyFourSeven -> {
|
||||
Text(
|
||||
text = stringResource(R.string.location_open_24_7),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
is OpeningSchedule.Hours -> {
|
||||
val text = remember(openingSchedule) {
|
||||
val currentOpeningTime =
|
||||
openingSchedule.getCurrentOpeningHours()
|
||||
val timeFormat =
|
||||
DateTimeFormatter.ofLocalizedTime(
|
||||
FormatStyle.SHORT
|
||||
)
|
||||
return@remember if (currentOpeningTime != null) {
|
||||
val isSameDay =
|
||||
currentOpeningTime.dayOfWeek == LocalDateTime.now().dayOfWeek
|
||||
val formattedTime =
|
||||
timeFormat.format(currentOpeningTime.startTime + currentOpeningTime.duration)
|
||||
val closingTime = if (isSameDay) {
|
||||
context.getString(
|
||||
R.string.location_closes,
|
||||
formattedTime
|
||||
)
|
||||
} else {
|
||||
val dow =
|
||||
currentOpeningTime.dayOfWeek.getDisplayName(
|
||||
TextStyle.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
context.getString(
|
||||
R.string.location_closes_other_day,
|
||||
dow,
|
||||
formattedTime
|
||||
)
|
||||
}
|
||||
"${context.getString(R.string.location_open)} • $closingTime"
|
||||
} else {
|
||||
val nextOpeningTime =
|
||||
openingSchedule.getNextOpeningHours()
|
||||
val isSameDay =
|
||||
nextOpeningTime.dayOfWeek == LocalDateTime.now().dayOfWeek
|
||||
val formattedTime =
|
||||
timeFormat.format(nextOpeningTime.startTime)
|
||||
val openingTime = if (isSameDay) {
|
||||
context.getString(
|
||||
R.string.location_opens,
|
||||
formattedTime
|
||||
)
|
||||
} else {
|
||||
val dow =
|
||||
nextOpeningTime.dayOfWeek.getDisplayName(
|
||||
TextStyle.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
context.getString(
|
||||
R.string.location_opens_other_day,
|
||||
dow,
|
||||
formattedTime
|
||||
)
|
||||
}
|
||||
"${context.getString(R.string.location_closed)} • $openingTime"
|
||||
}
|
||||
}
|
||||
|
||||
Icon(Icons.AutoMirrored.Rounded.NavigateNext, null)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Icon(Icons.AutoMirrored.Rounded.NavigateNext, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (openingSchedule is OpeningSchedule.Hours) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 6.dp)
|
||||
) {
|
||||
@@ -469,24 +660,26 @@ fun LocationItem(
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(start = 12.dp, top = 8.dp)
|
||||
) {
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
onClick = {
|
||||
context.tryStartActivity(
|
||||
Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("google.navigation:q=${location.latitude},${location.longitude}")
|
||||
),
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(R.string.menu_navigation)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Navigation, null,
|
||||
modifier = Modifier.size(AssistChipDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
val navigationIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("google.navigation:q=${location.latitude},${location.longitude}")
|
||||
)
|
||||
val canResolveNavigationIntent = remember {
|
||||
null != context.packageManager.resolveActivity(navigationIntent, 0)
|
||||
}
|
||||
if (canResolveNavigationIntent) {
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
onClick = { context.tryStartActivity(navigationIntent) },
|
||||
label = { Text(stringResource(R.string.menu_navigation)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Rounded.Navigation, null,
|
||||
modifier = Modifier.size(AssistChipDefaults.IconSize)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
location.phoneNumber?.let {
|
||||
AssistChip(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
@@ -569,8 +762,6 @@ fun LocationItem(
|
||||
action = { sheetManager.showCustomizeSearchableModal(location) }
|
||||
))
|
||||
|
||||
|
||||
|
||||
location.fixMeUrl?.let {
|
||||
toolbarActions += DefaultToolbarAction(
|
||||
label = stringResource(id = R.string.menu_bugreport),
|
||||
@@ -715,11 +906,11 @@ private fun buildAddress(
|
||||
return if (summary.isEmpty()) null else summary.toString()
|
||||
}
|
||||
|
||||
private fun OpeningSchedule.getCurrentOpeningHours(): OpeningHours? {
|
||||
private fun OpeningSchedule.Hours.getCurrentOpeningHours(): OpeningHours? {
|
||||
return openingHours.find { it.isOpen() }
|
||||
}
|
||||
|
||||
private fun OpeningSchedule.getNextOpeningHours(): OpeningHours {
|
||||
private fun OpeningSchedule.Hours.getNextOpeningHours(): OpeningHours {
|
||||
val now = LocalDateTime.now()
|
||||
val sortedSchedule = this
|
||||
.openingHours
|
||||
@@ -737,80 +928,178 @@ private fun OpeningSchedule.getNextOpeningHours(): OpeningHours {
|
||||
} ?: sortedSchedule.first()
|
||||
}
|
||||
|
||||
private val LocationCategory.labelRes
|
||||
get() = when(this) {
|
||||
LocationCategory.ART -> R.string.poi_category_art
|
||||
LocationCategory.BANK -> R.string.poi_category_bank
|
||||
LocationCategory.BAR -> R.string.poi_category_bar
|
||||
LocationCategory.BEAUTY -> R.string.poi_category_beauty
|
||||
LocationCategory.BICYCLE -> R.string.poi_category_bicycle
|
||||
LocationCategory.RESTAURANT -> R.string.poi_category_restaurant
|
||||
LocationCategory.FAST_FOOD -> R.string.poi_category_fast_food
|
||||
LocationCategory.CAFE -> R.string.poi_category_coffee_shop
|
||||
LocationCategory.HOTEL -> R.string.poi_category_hotel
|
||||
LocationCategory.SUPERMARKET -> R.string.poi_category_supermarket
|
||||
LocationCategory.OTHER -> R.string.poi_category_other
|
||||
LocationCategory.SCHOOL -> R.string.poi_category_school
|
||||
LocationCategory.PARKING -> R.string.poi_category_parking
|
||||
LocationCategory.FUEL -> R.string.poi_category_fuel
|
||||
LocationCategory.TOILETS -> R.string.poi_category_toilets
|
||||
LocationCategory.PHARMACY -> R.string.poi_category_pharmacy
|
||||
LocationCategory.HOSPITAL -> R.string.poi_category_hospital
|
||||
LocationCategory.POST_OFFICE -> R.string.poi_category_post_office
|
||||
LocationCategory.PUB -> R.string.poi_category_pub
|
||||
LocationCategory.GRAVE_YARD -> R.string.poi_category_grave_yard
|
||||
LocationCategory.DOCTORS -> R.string.poi_category_doctors
|
||||
LocationCategory.POLICE -> R.string.poi_category_police
|
||||
LocationCategory.DENTIST -> R.string.poi_category_dentist
|
||||
LocationCategory.LIBRARY -> R.string.poi_category_library
|
||||
LocationCategory.COLLEGE -> R.string.poi_category_college
|
||||
LocationCategory.ICE_CREAM -> R.string.poi_category_ice_cream
|
||||
LocationCategory.THEATRE -> R.string.poi_category_theater
|
||||
LocationCategory.PUBLIC_BUILDING -> R.string.poi_category_public_building
|
||||
LocationCategory.CINEMA -> R.string.poi_category_cinema
|
||||
LocationCategory.NIGHTCLUB -> R.string.poi_category_nightclub
|
||||
LocationCategory.BIERGARTEN -> R.string.poi_category_biergarten
|
||||
LocationCategory.CLINIC -> R.string.poi_category_clinic
|
||||
LocationCategory.UNIVERSITY -> R.string.poi_category_university
|
||||
LocationCategory.DEPARTMENT_STORE -> R.string.poi_category_department_store
|
||||
LocationCategory.CLOTHES -> R.string.poi_category_clothes
|
||||
LocationCategory.CONVENIENCE -> R.string.poi_category_convenience
|
||||
LocationCategory.HAIRDRESSER -> R.string.poi_category_hairdresser
|
||||
LocationCategory.CAR_REPAIR -> R.string.poi_category_car_repair
|
||||
LocationCategory.BOOKS -> R.string.poi_category_books
|
||||
LocationCategory.BAKERY -> R.string.poi_category_bakery
|
||||
LocationCategory.CAR -> R.string.poi_category_car
|
||||
LocationCategory.MOBILE_PHONE -> R.string.poi_category_mobile_phone
|
||||
LocationCategory.FURNITURE -> R.string.poi_category_furniture
|
||||
LocationCategory.ALCOHOL -> R.string.poi_category_alcohol
|
||||
LocationCategory.FLORIST -> R.string.poi_category_florist
|
||||
LocationCategory.HARDWARE -> R.string.poi_category_hardware
|
||||
LocationCategory.ELECTRONICS -> R.string.poi_category_electronics
|
||||
LocationCategory.SHOES -> R.string.poi_category_shoes
|
||||
LocationCategory.MALL -> R.string.poi_category_mall
|
||||
LocationCategory.OPTICIAN -> R.string.poi_category_optician
|
||||
LocationCategory.JEWELRY -> R.string.poi_category_jewelry
|
||||
LocationCategory.GIFT -> R.string.poi_category_gift
|
||||
LocationCategory.LAUNDRY -> R.string.poi_category_laundry
|
||||
LocationCategory.COMPUTER -> R.string.poi_category_computer
|
||||
LocationCategory.TOBACCO -> R.string.poi_category_tobacco
|
||||
LocationCategory.WINE -> R.string.poi_category_wine
|
||||
LocationCategory.PHOTO -> R.string.poi_category_photo
|
||||
LocationCategory.COFFEE_SHOP -> R.string.poi_category_coffee_shop
|
||||
LocationCategory.SOCCER -> R.string.poi_category_soccer
|
||||
LocationCategory.BASKETBALL -> R.string.poi_category_basketball
|
||||
LocationCategory.TENNIS -> R.string.poi_category_tennis
|
||||
LocationCategory.FITNESS -> R.string.poi_category_fitness
|
||||
LocationCategory.TRAM_STOP -> R.string.poi_category_tram_stop
|
||||
LocationCategory.RAILWAY_STATION -> R.string.poi_category_railway_station
|
||||
LocationCategory.RAILWAY_STOP -> R.string.poi_category_railway_stop
|
||||
LocationCategory.BUS_STATION -> R.string.poi_category_bus_station
|
||||
LocationCategory.ATM -> R.string.poi_category_atm
|
||||
LocationCategory.KIOSK -> R.string.poi_category_kiosk
|
||||
LocationCategory.BUS_STOP -> R.string.poi_category_bus_stop
|
||||
LocationCategory.MUSEUM -> R.string.poi_category_museum
|
||||
LocationCategory.PARCEL_LOCKER -> R.string.poi_category_parcel_locker
|
||||
LocationCategory.CHEMIST -> R.string.poi_category_chemist
|
||||
LocationCategory.TRAVEL_AGENCY -> R.string.poi_category_travel_agency
|
||||
LocationCategory.FITNESS_CENTRE -> R.string.poi_category_fitness_center
|
||||
}
|
||||
@Composable
|
||||
fun Departure.LineIcon(
|
||||
modifier: Modifier
|
||||
) {
|
||||
val harmonizeArgb = MaterialTheme.colorScheme.primary.toArgb()
|
||||
var (lineBg, lineFg) = if (lineColor != null) {
|
||||
val bg = Color(
|
||||
harmonize(lineColor!!.toArgb(), harmonizeArgb)
|
||||
)
|
||||
val fg = Color(
|
||||
harmonize(
|
||||
if (0.5f < bg.luminance()) Color.Black.toArgb() else Color.White.toArgb(),
|
||||
harmonizeArgb
|
||||
)
|
||||
)
|
||||
bg to fg
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primaryContainer to MaterialTheme.colorScheme.onPrimaryContainer
|
||||
}
|
||||
|
||||
val hasDeparted = ZonedDateTime.now().isAfter(time + (delay ?: Duration.ZERO))
|
||||
|
||||
if (hasDeparted) {
|
||||
val hsv = FloatArray(3)
|
||||
android.graphics.Color.colorToHSV(lineBg.toArgb(), hsv)
|
||||
val (h, s, v) = hsv
|
||||
lineBg = Color.hsv(h, s / 2f, v, lineBg.alpha)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.wrapContentWidth(Alignment.Start)
|
||||
.background(
|
||||
lineBg,
|
||||
MaterialTheme.shapes.small
|
||||
)
|
||||
.padding(top = 4.dp, bottom = 4.dp, start = 4.dp, end = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = when (type) {
|
||||
LineType.Bus -> Icons.Rounded.DirectionsBus
|
||||
LineType.Tram -> Icons.Rounded.Tram
|
||||
LineType.Subway -> Icons.Rounded.Subway
|
||||
LineType.Monorail -> Icons.Rounded.DirectionsTransit
|
||||
LineType.CommuterTrain -> Icons.Rounded.DirectionsRailway
|
||||
LineType.Train, LineType.RegionalTrain, LineType.HighSpeedTrain -> Icons.Rounded.Train
|
||||
LineType.Boat -> Icons.Rounded.DirectionsBoat
|
||||
LineType.CableCar -> Icons.Rounded.CableCar
|
||||
LineType.Airplane -> Icons.Rounded.AirplanemodeActive
|
||||
null -> Icons.Rounded.Commute
|
||||
},
|
||||
contentDescription = type?.name, // TODO localize (maybe) with ?.let{ stringResource("departure_line_type_$it") }
|
||||
tint = lineFg,
|
||||
modifier = Modifier
|
||||
.padding(end = 2.dp)
|
||||
.size(16.dp),
|
||||
)
|
||||
MarqueeText(
|
||||
text = line,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = lineFg,
|
||||
textAlign = TextAlign.Center,
|
||||
fadeLeft = 2.5.dp,
|
||||
fadeRight = 2.5.dp,
|
||||
iterations = Int.MAX_VALUE,
|
||||
repeatDelayMillis = 0,
|
||||
spacing = MarqueeSpacing(10.dp),
|
||||
velocity = 20.dp,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.widthIn(max = 34.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Departure.LazyColumnPart(
|
||||
lineWidth: Int?,
|
||||
modifier: Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(0.8f)
|
||||
) {
|
||||
LineIcon(
|
||||
Modifier
|
||||
.padding(end = 8.dp)
|
||||
.widthIn(
|
||||
min = if (lineWidth == null) 0.dp
|
||||
else max(64.dp, lineWidth * 8.dp)
|
||||
)
|
||||
)
|
||||
if (lastStop != null) {
|
||||
MarqueeText(
|
||||
text = lastStop!!,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
iterations = Int.MAX_VALUE,
|
||||
repeatDelayMillis = 0,
|
||||
velocity = 20.dp,
|
||||
fadeLeft = 5.dp,
|
||||
fadeRight = 5.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = time.format(
|
||||
DateTimeFormatter.ofPattern(
|
||||
"HH:mm",
|
||||
Locale.getDefault()
|
||||
)
|
||||
),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier.padding(end = 2.dp)
|
||||
)
|
||||
val delayMinutes = delay?.toMinutes()
|
||||
if (null != delayMinutes && 0L < delayMinutes) {
|
||||
Text(
|
||||
text = "+$delayMinutes",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
fontSize = TextUnit(2f, TextUnitType.Em),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Attribution(
|
||||
attribution: Attribution,
|
||||
modifier: Modifier = Modifier,
|
||||
reverse: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (attribution.text != null && !reverse) {
|
||||
Text(
|
||||
text = attribution.text!!,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
|
||||
if (attribution.iconUrl != null) {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = if (reverse) 0.dp else 8.dp,
|
||||
end = if (reverse) 8.dp else 0.dp
|
||||
)
|
||||
.requiredHeight(16.dp),
|
||||
model = attribution.iconUrl!!,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
if (attribution.text != null && reverse) {
|
||||
Text(
|
||||
text = attribution.text!!,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.animation.core.EaseInOutCirc
|
||||
import androidx.compose.animation.core.EaseInOutSine
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.animateOffsetAsState
|
||||
import androidx.compose.animation.core.animateValueAsState
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
@@ -47,7 +48,6 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
@@ -62,23 +62,26 @@ import coil.request.ImageRequest
|
||||
import de.mm20.launcher2.ktx.PI
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.Location
|
||||
import de.mm20.launcher2.search.LocationCategory
|
||||
import de.mm20.launcher2.search.OpeningHours
|
||||
import de.mm20.launcher2.search.OpeningSchedule
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
import de.mm20.launcher2.search.location.Address
|
||||
import de.mm20.launcher2.search.location.Departure
|
||||
import de.mm20.launcher2.search.location.LineType
|
||||
import de.mm20.launcher2.search.location.LocationIcon
|
||||
import de.mm20.launcher2.search.location.OpeningSchedule
|
||||
import de.mm20.launcher2.ui.ktx.DegreesConverter
|
||||
import de.mm20.launcher2.ui.ktx.contrast
|
||||
import de.mm20.launcher2.ui.ktx.hue
|
||||
import de.mm20.launcher2.ui.ktx.hueRotate
|
||||
import de.mm20.launcher2.ui.ktx.invert
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.context.startKoin
|
||||
import java.time.Duration
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.cos
|
||||
@@ -216,7 +219,15 @@ fun MapTiles(
|
||||
)
|
||||
)
|
||||
if (userLocation != null) {
|
||||
val userTileCoordinates = getTileCoordinates(userLocation.lat, userLocation.lon, zoom)
|
||||
val userIndicatorOffset by animateOffsetAsState(
|
||||
targetValue = getTileCoordinates(userLocation.lat, userLocation.lon, zoom).let {
|
||||
Offset(
|
||||
(it.x - start.x) * tileSize.value - 8f,
|
||||
(it.y - start.y) * tileSize.value - 8f
|
||||
)
|
||||
},
|
||||
animationSpec = tween()
|
||||
)
|
||||
|
||||
if (userLocation.heading != null) {
|
||||
val headingAnim by animateValueAsState(
|
||||
@@ -232,8 +243,8 @@ fun MapTiles(
|
||||
.align(Alignment.TopStart)
|
||||
.size(16.dp)
|
||||
.absoluteOffset(
|
||||
x = (userTileCoordinates.x - start.x) * tileSize - 8.dp,
|
||||
y = (userTileCoordinates.y - start.y) * tileSize - 8.dp,
|
||||
userIndicatorOffset.x.dp,
|
||||
userIndicatorOffset.y.dp
|
||||
)
|
||||
.rotate(headingAnim)
|
||||
.absoluteOffset(y = -8.dp)
|
||||
@@ -245,8 +256,8 @@ fun MapTiles(
|
||||
.align(Alignment.TopStart)
|
||||
.size(16.dp)
|
||||
.absoluteOffset(
|
||||
x = (userTileCoordinates.x - start.x) * tileSize - 8.dp,
|
||||
y = (userTileCoordinates.y - start.y) * tileSize - 8.dp,
|
||||
userIndicatorOffset.x.dp,
|
||||
userIndicatorOffset.y.dp
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.tertiary, CircleShape)
|
||||
.border(2.dp, MaterialTheme.colorScheme.onTertiary, CircleShape)
|
||||
@@ -438,7 +449,7 @@ private fun MapTilesPreview() {
|
||||
)
|
||||
}
|
||||
|
||||
internal object MockLocation : Location {
|
||||
private object MockLocation : Location {
|
||||
|
||||
override val domain: String = "MOCKLOCATION"
|
||||
override val key: String = "MOCKLOCATION"
|
||||
@@ -448,19 +459,25 @@ internal object MockLocation : Location {
|
||||
override val latitude = 52.5162700
|
||||
override val longitude = 13.3777021
|
||||
|
||||
override var category: LocationCategory? = LocationCategory.OTHER
|
||||
override val icon: LocationIcon? = null
|
||||
override var category: String? = "Landmark"
|
||||
|
||||
override val street: String = "Pariser Platz"
|
||||
|
||||
override val houseNumber: String = "1"
|
||||
override val address: Address = Address(
|
||||
address = "Pariser Platz 1",
|
||||
city = "Berlin",
|
||||
postalCode = "10117",
|
||||
country = "Germany"
|
||||
)
|
||||
|
||||
override val openingSchedule: OpeningSchedule =
|
||||
OpeningSchedule(true, emptyList<OpeningHours>().toImmutableList())
|
||||
OpeningSchedule.TwentyFourSeven
|
||||
|
||||
override val websiteUrl: String = "https://en.wikipedia.org/wiki/Brandenburg_Gate"
|
||||
|
||||
override val phoneNumber: String = "+49 1234567"
|
||||
|
||||
override val emailAddress: String = "abc@de.fg"
|
||||
|
||||
override fun overrideLabel(label: String): SavableSearchable = TODO()
|
||||
|
||||
override fun launch(context: Context, options: Bundle?): Boolean =
|
||||
@@ -472,4 +489,20 @@ internal object MockLocation : Location {
|
||||
)
|
||||
|
||||
override fun getSerializer(): SearchableSerializer = TODO()
|
||||
|
||||
override val departures: List<Departure> = listOf(
|
||||
Departure(
|
||||
ZonedDateTime.now() + Duration.ofMinutes(3),
|
||||
Duration.ofMinutes(1),
|
||||
"B2",
|
||||
"heaven",
|
||||
LineType.Bus,
|
||||
android.graphics.Color.valueOf(0xFAFAFAFA)
|
||||
)
|
||||
)
|
||||
|
||||
override val userRating: Float
|
||||
get() = 0.9f
|
||||
|
||||
override val userRatingCount: Int = 553
|
||||
}
|
||||
|
||||
+14
-1
@@ -26,8 +26,21 @@ import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.getSystemService
|
||||
import de.mm20.launcher2.icons.Battery0Bar
|
||||
import de.mm20.launcher2.icons.Battery1Bar
|
||||
import de.mm20.launcher2.icons.Battery2Bar
|
||||
import de.mm20.launcher2.icons.Battery3Bar
|
||||
import de.mm20.launcher2.icons.Battery4Bar
|
||||
import de.mm20.launcher2.icons.Battery5Bar
|
||||
import de.mm20.launcher2.icons.Battery6Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging0Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging1Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging2Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging3Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging4Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging5Bar
|
||||
import de.mm20.launcher2.icons.BatteryCharging6Bar
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.icons.*
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@@ -40,14 +40,10 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
@@ -59,8 +55,8 @@ import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
|
||||
import de.mm20.launcher2.ui.component.weather.WeatherIcon
|
||||
import de.mm20.launcher2.ui.icons.HumidityPercentage
|
||||
import de.mm20.launcher2.ui.icons.Rain
|
||||
import de.mm20.launcher2.icons.HumidityPercentage
|
||||
import de.mm20.launcher2.icons.Rain
|
||||
import de.mm20.launcher2.ui.ktx.blendIntoViewScale
|
||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||
import de.mm20.launcher2.ui.modifier.consumeAllScrolling
|
||||
|
||||
@@ -14,9 +14,9 @@ import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.icons.Fdroid
|
||||
import de.mm20.launcher2.ui.icons.GitHub
|
||||
import de.mm20.launcher2.ui.icons.Telegram
|
||||
import de.mm20.launcher2.icons.Fdroid
|
||||
import de.mm20.launcher2.icons.GitHub
|
||||
import de.mm20.launcher2.icons.Telegram
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
+2
-2
@@ -191,7 +191,7 @@ fun FileSearchSettingsScreen() {
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: "You need to setup this plugin first",
|
||||
text = state.message ?: stringResource(id = R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.ErrorOutline,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
@@ -201,7 +201,7 @@ fun FileSearchSettingsScreen() {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text("Set up")
|
||||
Text(stringResource(id = R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+73
-14
@@ -1,9 +1,14 @@
|
||||
package de.mm20.launcher2.ui.settings.locations
|
||||
|
||||
import android.app.PendingIntent
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||
import androidx.compose.material.icons.rounded.WarningAmber
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -11,9 +16,14 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.plugin.PluginState
|
||||
import de.mm20.launcher2.preferences.search.LocationSearchSettings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
@@ -26,39 +36,86 @@ import de.mm20.launcher2.ui.ktx.metersToLocalizedString
|
||||
fun LocationsSettingsScreen() {
|
||||
val viewModel: LocationsSettingsScreenVM = viewModel()
|
||||
|
||||
val locations by viewModel.locations.collectAsState()
|
||||
val osmLocations by viewModel.osmLocations.collectAsState()
|
||||
val imperialUnits by viewModel.imperialUnits.collectAsState()
|
||||
val hideUncategorized by viewModel.hideUncategorized.collectAsState()
|
||||
val radius by viewModel.radius.collectAsState()
|
||||
val customOverpassUrl by viewModel.customOverpassUrl.collectAsState()
|
||||
val showMap by viewModel.showMap.collectAsState()
|
||||
val themeMap by viewModel.themeMap.collectAsState()
|
||||
val showPositionOnMap by viewModel.showPositionOnMap.collectAsState()
|
||||
val customTileServerUrl by viewModel.customTileServerUrl.collectAsState()
|
||||
|
||||
val plugins by viewModel.availablePlugins.collectAsStateWithLifecycle(
|
||||
initialValue = emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
val enabledPlugins by viewModel.enabledPlugins.collectAsStateWithLifecycle(initialValue = null)
|
||||
|
||||
val anyLocationProviderEnabled = osmLocations ?: false || enabledPlugins.isNullOrEmpty().not()
|
||||
|
||||
|
||||
PreferenceScreen(title = stringResource(R.string.preference_search_locations)) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_locations),
|
||||
summary = stringResource(R.string.preference_search_locations_summary),
|
||||
value = locations == true,
|
||||
title = stringResource(R.string.preference_search_osm_locations),
|
||||
summary = stringResource(R.string.preference_search_osm_locations_summary),
|
||||
value = osmLocations == true,
|
||||
onValueChanged = {
|
||||
viewModel.setLocations(it)
|
||||
viewModel.setOsmLocations(it)
|
||||
}
|
||||
)
|
||||
for (plugin in plugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message
|
||||
?: stringResource(id = R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.ErrorOutline,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setPluginEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
PreferenceCategory {
|
||||
if (5000 < radius) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
icon = Icons.Rounded.WarningAmber,
|
||||
text = stringResource(R.string.preference_search_locations_radius_large_radius_warning)
|
||||
)
|
||||
}
|
||||
ListPreference(
|
||||
title = stringResource(R.string.length_unit),
|
||||
items = listOf(
|
||||
stringResource(R.string.imperial) to true,
|
||||
stringResource(R.string.metric) to false
|
||||
),
|
||||
enabled = locations == true,
|
||||
enabled = anyLocationProviderEnabled,
|
||||
value = imperialUnits,
|
||||
onValueChanged = {
|
||||
viewModel.setImperialUnits(it)
|
||||
@@ -70,7 +127,7 @@ fun LocationsSettingsScreen() {
|
||||
min = 500,
|
||||
max = 10000,
|
||||
step = 500,
|
||||
enabled = locations == true,
|
||||
enabled = anyLocationProviderEnabled,
|
||||
onValueChanged = {
|
||||
viewModel.setRadius(it)
|
||||
},
|
||||
@@ -91,7 +148,7 @@ fun LocationsSettingsScreen() {
|
||||
title = stringResource(R.string.preference_search_locations_hide_uncategorized),
|
||||
summary = stringResource(R.string.preference_search_locations_hide_uncategorized_summary),
|
||||
value = hideUncategorized == true,
|
||||
enabled = locations == true,
|
||||
enabled = anyLocationProviderEnabled,
|
||||
onValueChanged = {
|
||||
viewModel.setHideUncategorized(it)
|
||||
}
|
||||
@@ -103,7 +160,7 @@ fun LocationsSettingsScreen() {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_search_locations_show_map),
|
||||
summary = stringResource(R.string.preference_search_locations_show_map_summary),
|
||||
enabled = locations == true,
|
||||
enabled = anyLocationProviderEnabled,
|
||||
value = showMap == true,
|
||||
onValueChanged = {
|
||||
viewModel.setShowMap(it)
|
||||
@@ -113,7 +170,7 @@ fun LocationsSettingsScreen() {
|
||||
title = stringResource(R.string.preference_search_locations_theme_map),
|
||||
summary = stringResource(R.string.preference_search_locations_theme_map_summary),
|
||||
value = themeMap == true,
|
||||
enabled = locations == true && showMap == true,
|
||||
enabled = anyLocationProviderEnabled && showMap == true,
|
||||
onValueChanged = {
|
||||
viewModel.setThemeMap(it)
|
||||
}
|
||||
@@ -127,11 +184,12 @@ fun LocationsSettingsScreen() {
|
||||
title = stringResource(R.string.preference_search_location_custom_overpass_url),
|
||||
value = customOverpassUrl,
|
||||
placeholder = LocationSearchSettings.DefaultOverpassUrl,
|
||||
summary = customOverpassUrl.takeIf { !it.isNullOrBlank() }
|
||||
summary = customOverpassUrl.takeIf { it.isNotBlank() }
|
||||
?: LocationSearchSettings.DefaultOverpassUrl,
|
||||
onValueChanged = {
|
||||
viewModel.setCustomOverpassUrl(it)
|
||||
}
|
||||
},
|
||||
enabled = osmLocations == true,
|
||||
)
|
||||
TextPreference(
|
||||
title = stringResource(R.string.preference_search_location_custom_tile_server_url),
|
||||
@@ -141,7 +199,8 @@ fun LocationsSettingsScreen() {
|
||||
?: LocationSearchSettings.DefaultTileServerUrl,
|
||||
onValueChanged = {
|
||||
viewModel.setCustomTileServerUrl(it)
|
||||
}
|
||||
},
|
||||
enabled = anyLocationProviderEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+27
-13
@@ -2,40 +2,52 @@ package de.mm20.launcher2.ui.settings.locations
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import de.mm20.launcher2.preferences.search.LocationSearchSettings
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class LocationsSettingsScreenVM: ViewModel(), KoinComponent {
|
||||
class LocationsSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val settings: LocationSearchSettings by inject()
|
||||
private val pluginService: PluginService by inject()
|
||||
|
||||
val locations = settings.enabled
|
||||
val availablePlugins = pluginService.getPluginsWithState(
|
||||
type = PluginType.LocationSearch,
|
||||
enabled = true,
|
||||
)
|
||||
val enabledPlugins = settings.enabledPlugins
|
||||
|
||||
val osmLocations = settings.osmLocations
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
fun setLocations(openStreetMaps: Boolean) {
|
||||
settings.setEnabled(openStreetMaps)
|
||||
|
||||
fun setOsmLocations(osmLocations: Boolean) {
|
||||
settings.setOsmLocations(osmLocations)
|
||||
}
|
||||
|
||||
val imperialUnits = settings.imperialUnits
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
|
||||
|
||||
fun setImperialUnits(imperialUnits: Boolean) {
|
||||
settings.setImperialUnits(imperialUnits)
|
||||
}
|
||||
|
||||
val radius = settings.searchRadius
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1500)
|
||||
|
||||
fun setRadius(radius: Int) {
|
||||
settings.setSearchRadius(radius)
|
||||
}
|
||||
|
||||
val customOverpassUrl = settings.overpassUrl
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
fun setCustomOverpassUrl(customUrl: String) {
|
||||
var url = customUrl
|
||||
if (url.endsWith('/')){
|
||||
if (url.endsWith('/')) {
|
||||
url = url.substringBeforeLast('/')
|
||||
}
|
||||
if (url.endsWith("/api/interpreter")) {
|
||||
@@ -47,31 +59,33 @@ class LocationsSettingsScreenVM: ViewModel(), KoinComponent {
|
||||
|
||||
val showMap = settings.showMap
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun setShowMap(showMap: Boolean) {
|
||||
settings.setShowMap(showMap)
|
||||
}
|
||||
|
||||
val showPositionOnMap = settings.showPositionOnMap
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
fun setShowPositionOnMap(showPositionOnMap: Boolean) {
|
||||
settings.setShowPositionOnMap(showPositionOnMap)
|
||||
}
|
||||
|
||||
val customTileServerUrl = settings.tileServer
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun setCustomTileServerUrl(customTileServerUrl: String) {
|
||||
settings.setTileServer(customTileServerUrl)
|
||||
}
|
||||
|
||||
val hideUncategorized = settings.hideUncategorized
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun setHideUncategorized(hideUncategorized: Boolean) {
|
||||
settings.setHideUncategorized(hideUncategorized)
|
||||
}
|
||||
|
||||
val themeMap = settings.themeMap
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun setThemeMap(themeMap: Boolean) {
|
||||
settings.setThemeMap(themeMap)
|
||||
}
|
||||
|
||||
fun setPluginEnabled(authority: String, enabled: Boolean) {
|
||||
settings.setPluginEnabled(authority, enabled)
|
||||
}
|
||||
}
|
||||
+164
-111
@@ -25,6 +25,7 @@ import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Error
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.LightMode
|
||||
import androidx.compose.material.icons.rounded.Place
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.material.icons.rounded.Verified
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -81,6 +82,11 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
val locationPlugins by viewModel.locationPlugins.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
val weatherPlugins by viewModel.weatherPlugins.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
@@ -97,6 +103,10 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
null
|
||||
)
|
||||
|
||||
val enabledLocationSearchPlugins by viewModel.enabledLocationSearchPlugins.collectAsStateWithLifecycle(
|
||||
null
|
||||
)
|
||||
|
||||
val weatherProviderId by viewModel.weatherProvider.collectAsStateWithLifecycle(
|
||||
null
|
||||
)
|
||||
@@ -176,30 +186,7 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
pluginPackage?.label ?: "",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
if (pluginPackage?.isOfficial == true) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.secondary,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.plugin_badge_official),
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondary,
|
||||
)
|
||||
Icon(
|
||||
Icons.Rounded.Verified, null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.onSecondary,
|
||||
)
|
||||
}
|
||||
} else if (pluginPackage?.author != null) {
|
||||
if (pluginPackage?.author != null) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
@@ -210,6 +197,15 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
if (pluginPackage?.isVerified == true) {
|
||||
Icon(
|
||||
Icons.Rounded.Verified, null,
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp)
|
||||
.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +242,7 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
when (type) {
|
||||
PluginType.FileSearch -> Icons.AutoMirrored.Rounded.InsertDriveFile
|
||||
PluginType.Weather -> Icons.Rounded.LightMode
|
||||
PluginType.LocationSearch -> Icons.Rounded.Place
|
||||
},
|
||||
null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
@@ -255,6 +252,7 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
when (type) {
|
||||
PluginType.FileSearch -> stringResource(R.string.plugin_type_filesearch)
|
||||
PluginType.Weather -> stringResource(R.string.plugin_type_weather)
|
||||
PluginType.LocationSearch -> stringResource(R.string.plugin_type_locationsearch)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
@@ -298,109 +296,164 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(pluginPackage?.enabled == true && hasPermission == true) {
|
||||
if (filePlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_filesearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in filePlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
Column {
|
||||
if (filePlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_filesearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in filePlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message
|
||||
?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(context)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledFileSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledFileSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setFileSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledFileSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledFileSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setFileSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (weatherPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_weather),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in weatherPlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
if (locationPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_locationsearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in locationPlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message
|
||||
?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(context)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledLocationSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledLocationSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setLocationSearchPluginEnabled(
|
||||
plugin.plugin.authority,
|
||||
it
|
||||
)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (weatherPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_weather),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in weatherPlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message
|
||||
?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.sendWithBackgroundPermission(context)
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready && weatherProviderId != plugin.plugin.authority,
|
||||
iconPadding = false,
|
||||
summary = if (weatherProviderId != plugin.plugin.authority) {
|
||||
stringResource(R.string.plugin_weather_provider_enable)
|
||||
} else {
|
||||
stringResource(R.string.plugin_weather_provider_enabled)
|
||||
},
|
||||
onClick = {
|
||||
viewModel.setWeatherProvider(plugin.plugin.authority)
|
||||
}
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready && weatherProviderId != plugin.plugin.authority,
|
||||
iconPadding = false,
|
||||
summary = if (weatherProviderId != plugin.plugin.authority) {
|
||||
stringResource(R.string.plugin_weather_provider_enable)
|
||||
} else {
|
||||
stringResource(R.string.plugin_weather_provider_enabled)
|
||||
},
|
||||
title = stringResource(R.string.widget_config_weather_integration_settings),
|
||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||
onClick = {
|
||||
viewModel.setWeatherProvider(plugin.plugin.authority)
|
||||
navController?.navigate("settings/integrations/weather")
|
||||
}
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = stringResource(R.string.widget_config_weather_integration_settings),
|
||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||
onClick = {
|
||||
navController?.navigate("settings/integrations/weather")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import de.mm20.launcher2.plugins.PluginWithState
|
||||
import de.mm20.launcher2.preferences.search.FileSearchSettings
|
||||
import de.mm20.launcher2.preferences.search.LocationSearchSettings
|
||||
import de.mm20.launcher2.preferences.weather.WeatherSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -31,6 +32,7 @@ import org.koin.core.component.inject
|
||||
class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val pluginService by inject<PluginService>()
|
||||
private val fileSearchSettings: FileSearchSettings by inject()
|
||||
private val locationSearchSettings: LocationSearchSettings by inject()
|
||||
private val weatherSettings: WeatherSettings by inject()
|
||||
|
||||
private var pluginPackageName = MutableStateFlow<String?>(null)
|
||||
@@ -72,6 +74,11 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
it.filter { it.plugin.type == PluginType.FileSearch }
|
||||
}
|
||||
|
||||
val locationPlugins = states
|
||||
.map {
|
||||
it.filter { it.plugin.type == PluginType.LocationSearch }
|
||||
}
|
||||
|
||||
val weatherPlugins = states
|
||||
.map {
|
||||
it.filter { it.plugin.type == PluginType.Weather }
|
||||
@@ -109,6 +116,11 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
fileSearchSettings.setPluginEnabled(authority, enabled)
|
||||
}
|
||||
|
||||
val enabledLocationSearchPlugins = locationSearchSettings.enabledPlugins
|
||||
fun setLocationSearchPluginEnabled(authority: String, enabled: Boolean) {
|
||||
locationSearchSettings.setPluginEnabled(authority, enabled)
|
||||
}
|
||||
|
||||
val weatherProvider = weatherSettings.providerId
|
||||
fun setWeatherProvider(providerId: String) {
|
||||
weatherSettings.setProvider(providerId)
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Extension
|
||||
import androidx.compose.material.icons.rounded.ExtensionOff
|
||||
import androidx.compose.material.icons.rounded.Verified
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -19,7 +18,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
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.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -98,13 +96,6 @@ private fun PluginPreference(viewModel: PluginsSettingsScreenVM, plugin: PluginP
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(plugin.label)
|
||||
if (plugin.isOfficial) {
|
||||
Icon(
|
||||
Icons.Rounded.Verified, null,
|
||||
modifier = Modifier.padding(start = 4.dp).size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
summary = plugin.description?.let { { Text(it) } },
|
||||
|
||||
@@ -22,8 +22,6 @@ import androidx.compose.material.icons.rounded.Today
|
||||
import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material.icons.rounded.Warning
|
||||
import androidx.compose.material.icons.rounded.Work
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -50,7 +48,7 @@ import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.icons.Wikipedia
|
||||
import de.mm20.launcher2.icons.Wikipedia
|
||||
import de.mm20.launcher2.ui.launcher.search.filters.SearchFilters
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
|
||||
@@ -227,15 +225,10 @@ fun SearchSettingsScreen() {
|
||||
}
|
||||
)
|
||||
|
||||
val locations by viewModel.locations.collectAsStateWithLifecycle(null)
|
||||
PreferenceWithSwitch(
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_search_locations),
|
||||
summary = stringResource(R.string.preference_search_locations_summary),
|
||||
icon = Icons.Rounded.Place,
|
||||
switchValue = locations == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setLocations(it)
|
||||
},
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/locations")
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import de.mm20.launcher2.preferences.search.WikipediaSearchSettings
|
||||
import de.mm20.launcher2.preferences.ui.SearchUiSettings
|
||||
import de.mm20.launcher2.search.SearchFilters
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
@@ -109,13 +108,6 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
websiteSearchSettings.setEnabled(websites)
|
||||
}
|
||||
|
||||
val locations = locationSearchSettings.enabled
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
fun setLocations(locations: Boolean) {
|
||||
locationSearchSettings.setEnabled(locations)
|
||||
}
|
||||
|
||||
val autoFocus = searchUiSettings.openKeyboard
|
||||
|
||||
fun setAutoFocus(autoFocus: Boolean) {
|
||||
|
||||
Reference in New Issue
Block a user