Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,13 @@ class SettingsRepository(
const val KEY_ISLAND_SHOW_CAFFEINATE = "island_show_caffeinate"
const val KEY_ISLAND_CAFFEINATE_SHOW_TIME = "island_caffeinate_show_time"
const val KEY_ISLAND_SHOW_DEVICES = "island_show_devices"
const val KEY_ISLAND_SHOW_NAVIGATION = "island_show_navigation"
const val KEY_ISLAND_NAVIGATION_ON_NEW = "island_navigation_on_new"
const val KEY_ISLAND_NAVIGATION_PEEK_AFTER_EXPAND = "island_navigation_peek_after_expand"
const val ISLAND_NAVIGATION_ON_NEW_EXPAND = "expand"
const val ISLAND_NAVIGATION_ON_NEW_PEEK = "peek"
const val ISLAND_NAVIGATION_ON_NEW_OFF = "off"
const val KEY_ISLAND_NAVIGATION_EXPAND_DISTANCE = "island_navigation_expand_distance"
const val KEY_ISLAND_SHOW_HOTSPOT = "island_show_hotspot"
const val KEY_ISLAND_BRIEF_ENABLED = "island_brief_enabled"
const val KEY_ISLAND_BRIEF_SHOW_ALARM = "island_brief_show_alarm"
Expand Down Expand Up @@ -4184,6 +4191,19 @@ class SettingsRepository(
fun isIslandShowDevicesEnabled(): Boolean = getBoolean(KEY_ISLAND_SHOW_DEVICES, true)
fun setIslandShowDevicesEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_SHOW_DEVICES, enabled)

fun isIslandShowNavigationEnabled(): Boolean = getBoolean(KEY_ISLAND_SHOW_NAVIGATION, true)
fun setIslandShowNavigationEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_SHOW_NAVIGATION, enabled)

fun getIslandNavigationOnNew(): String =
getString(KEY_ISLAND_NAVIGATION_ON_NEW, ISLAND_NAVIGATION_ON_NEW_EXPAND) ?: ISLAND_NAVIGATION_ON_NEW_EXPAND
fun setIslandNavigationOnNew(mode: String) = putString(KEY_ISLAND_NAVIGATION_ON_NEW, mode)

fun isIslandNavigationPeekAfterExpandEnabled(): Boolean = getBoolean(KEY_ISLAND_NAVIGATION_PEEK_AFTER_EXPAND, false)
fun setIslandNavigationPeekAfterExpandEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NAVIGATION_PEEK_AFTER_EXPAND, enabled)

fun getIslandNavigationExpandDistance(): Int = getInt(KEY_ISLAND_NAVIGATION_EXPAND_DISTANCE, 200)
fun setIslandNavigationExpandDistance(meters: Int) = putInt(KEY_ISLAND_NAVIGATION_EXPAND_DISTANCE, meters)

fun isIslandShowHotspotEnabled(): Boolean = getBoolean(KEY_ISLAND_SHOW_HOTSPOT, false)
fun setIslandShowHotspotEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_SHOW_HOTSPOT, enabled)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.sameerasw.essentials.domain.model

import android.app.PendingIntent
import android.graphics.Bitmap

data class NavigationNotificationData(
val key: String,
val packageName: String,
val instruction: String?,
val distance: String?,
val eta: String?,
val maneuver: Bitmap?,
val contentIntent: PendingIntent?,
val exitIntent: PendingIntent?,
val postTime: Long,
)

object NavigationParser {
data class Parsed(val instruction: String?, val distance: String?, val eta: String?)

private val unitMeters = mapOf(
"km" to 1000.0, "m" to 1.0, "mi" to 1609.344, "ft" to 0.3048, "yd" to 0.9144,
"км" to 1000.0, "м" to 1.0,
"公里" to 1000.0, "千米" to 1000.0, "公尺" to 1.0, "米" to 1.0,
)
private val units = unitMeters.keys.sortedByDescending { it.length }.joinToString("|")
private val distanceRegex = Regex("""\d[\d.,]*\s?($units)(?!\p{L})""", RegexOption.IGNORE_CASE)
private val exitRegex = Regex(
"""\b(exit|stop|end|close|quit|salir|terminar|detener|finalizar|beenden|quitter|arrêter|sair|encerrar)\b""",
RegexOption.IGNORE_CASE,
)

fun isDistance(s: String?) = s != null && s.length <= 24 && distanceRegex.containsMatchIn(s)

fun parse(title: String?, text: String?, subText: String?, chip: String?): Parsed {
val titleIsDistance = isDistance(title)
val textIsDistance = !titleIsDistance && isDistance(text)
val instruction = when {
titleIsDistance -> text
textIsDistance -> title
else -> title ?: text
}
val leftover = if (!titleIsDistance && !textIsDistance && title != null) text else null
val distance = chip?.takeIf { it.isNotBlank() } ?: when {
titleIsDistance -> title
textIsDistance -> text
else -> null
}
return Parsed(
instruction = instruction?.takeIf { it.isNotBlank() },
distance = distance,
eta = (subText ?: leftover)?.takeIf { it.isNotBlank() },
)
}

private val meterRegex = Regex("""(\d{1,3}(?:,\d{3})+|\d+(?:[.,]\d+)?)\s?($units)(?!\p{L})""", RegexOption.IGNORE_CASE)

fun meters(distance: String?): Double? {
val match = meterRegex.find(distance ?: return null) ?: return null
val (number, unit) = match.destructured
val value = if (Regex("""\d{1,3}(,\d{3})+""").matches(number)) number.replace(",", "") else number.replace(',', '.')
return value.toDoubleOrNull()?.times(unitMeters.getValue(unit.lowercase()))
}

fun exitActionIndex(titles: List<String?>): Int =
titles.indexOfFirst { it != null && exitRegex.containsMatchIn(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,11 @@ object FeatureRegistry {
R.string.island_stopwatch_default,
"island_show_timers",
),
SearchSetting(
R.string.island_show_navigation_title,
R.string.island_navigation_desc,
"island_show_navigation",
),
SearchSetting(
R.string.island_show_hotspot_title,
R.string.island_hotspot_desc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object IslandPriority {
const val CALENDAR_OVERRIDE = 6

const val NOTIFICATION = 10
const val NAVIGATION = 14
const val PROGRESS = 15
const val FLASHLIGHT = 20
const val TIMER = 25
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ object IslandPriorityEntries {
val all = listOf(
Entry("battery", setOf("battery_alerts_plugin", "battery"), IslandPriority.BATTERY_ALERT),
Entry("notifications", setOf("notifications"), IslandPriority.NOTIFICATION),
Entry("navigation", setOf("navigation"), IslandPriority.NAVIGATION),
Entry("flashlight", setOf("flashlight"), IslandPriority.FLASHLIGHT),
Entry("timer", setOf("timer"), IslandPriority.TIMER),
Entry("media", setOf("media"), IslandPriority.MEDIA),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.sameerasw.essentials.island.plugins.navigation

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.sameerasw.essentials.R
import com.sameerasw.essentials.domain.model.NavigationNotificationData
import com.sameerasw.essentials.island.model.IslandExpandedScope
import com.sameerasw.essentials.island.ui.IslandLayoutSpec
import com.sameerasw.essentials.island.ui.IslandTextStyles
import com.sameerasw.essentials.island.ui.components.CameraRow
import com.sameerasw.essentials.island.ui.components.ConnectedButtonRow
import com.sameerasw.essentials.island.ui.components.ConnectedItem
import com.sameerasw.essentials.island.ui.components.ConnectedTextLabel
import com.sameerasw.essentials.ui.theme.EssentialsTheme

@Composable
fun NavigationExpanded(
data: NavigationNotificationData,
accent: Color,
onOpen: () -> Unit,
onExit: (() -> Unit)?,
scope: IslandExpandedScope,
) {
val spec = scope.spec
val sidePadding = spec.expandedPadding + spec.expandedCorner * 0.35f
Box(propagateMinConstraints = true) {
Column(Modifier.fillMaxWidth().padding(spec.expandedOutset)) {
Spacer(Modifier.height(spec.expandedTopPadding))
scope.CameraRow(
horizontalPadding = spec.cameraGap + spec.expandedCorner * 0.35f,
start = { Maneuver(data, spec.cellSize.value.toInt()) },
end = {
data.distance?.let {
Text(it, style = IslandTextStyles.title.copy(color = accent), maxLines = 1)
}
},
)
if (!data.instruction.isNullOrBlank()) {
Spacer(Modifier.height(8.dp))
Text(
text = data.instruction,
style = IslandTextStyles.title.copy(color = Color.White, fontSize = 20.sp, lineHeight = 25.sp),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth().padding(horizontal = sidePadding),
)
}
if (!data.eta.isNullOrBlank()) {
Spacer(Modifier.height(2.dp))
Text(
text = data.eta,
style = IslandTextStyles.body.copy(color = Color.White.copy(alpha = 0.7f)),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth().padding(horizontal = sidePadding),
)
}
Spacer(Modifier.height(16.dp))
ConnectedButtonRow(
height = 44.dp,
modifier = Modifier.padding(horizontal = sidePadding).padding(bottom = spec.expandedBottomPadding),
container = Color.White.copy(alpha = 0.2f),
items = listOfNotNull(
ConnectedItem({
onOpen()
scope.collapse()
}) { ConnectedTextLabel(stringResource(R.string.island_progress_open)) },
onExit?.let { exit ->
ConnectedItem({
exit()
scope.collapse()
}) { ConnectedTextLabel(stringResource(R.string.island_navigation_exit)) }
},
),
)
}
}
}
Loading
Loading