Polish UI + freshen core deps
- Rewrote MainScreen: top bar, prominent status hero (idle/clear/watching/alert), active vs trusted sections, per-tracker cards with status icons, and a detail bottom sheet with co-movement evidence + trust action. - Sensitivity selector with per-mode explanation; on-device/no-network footer. - core-ktx 1.13.1->1.15.0, activity-compose 1.9.3->1.10.1 (kept the proven Kotlin/AGP/Compose matrix for build stability). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0187UiEtasyowhEBYs6s9iF5
This commit is contained in:
@@ -1,31 +1,60 @@
|
||||
package org.soulstone.vigil.ui
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.GppMaybe
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material.icons.filled.Radar
|
||||
import androidx.compose.material.icons.filled.Shield
|
||||
import androidx.compose.material.icons.filled.Verified
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.soulstone.vigil.BuildConfig
|
||||
import org.soulstone.vigil.data.db.TrackerEntity
|
||||
import org.soulstone.vigil.model.Sensitivity
|
||||
import org.soulstone.vigil.model.TrackerEcosystem
|
||||
@@ -34,6 +63,7 @@ import org.soulstone.vigil.ui.theme.VigilGreen
|
||||
import org.soulstone.vigil.ui.theme.VigilPeach
|
||||
import org.soulstone.vigil.ui.theme.VigilRed
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
running: Boolean,
|
||||
@@ -44,98 +74,231 @@ fun MainScreen(
|
||||
onSetSensitivity: (Sensitivity) -> Unit,
|
||||
onApprove: (String, Boolean) -> Unit
|
||||
) {
|
||||
val alerting = trackers.count { statusOf(it) == TrackerStatus.ALERTING }
|
||||
val suspicious = trackers.count { statusOf(it) == TrackerStatus.SUSPICIOUS }
|
||||
var detail by remember { mutableStateOf<TrackerEntity?>(null) }
|
||||
|
||||
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||
Column(Modifier.fillMaxSize().padding(16.dp)) {
|
||||
Text("VIGIL", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
"what's been following you",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val active = trackers.filter { !isTrusted(it) }.sortedByDescending { riskRank(it) }
|
||||
val trusted = trackers.filter { isTrusted(it) }
|
||||
val alerting = active.count { statusOf(it) == TrackerStatus.ALERTING }
|
||||
val suspicious = active.count { statusOf(it) == TrackerStatus.SUSPICIOUS }
|
||||
|
||||
StatusBanner(running, alerting, suspicious)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = onStartStop,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (running) MaterialTheme.colorScheme.surfaceVariant
|
||||
else MaterialTheme.colorScheme.primary
|
||||
Scaffold(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Shield, null, tint = MaterialTheme.colorScheme.primary)
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text("VIGIL", fontWeight = FontWeight.Bold)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(end = 12.dp)) {
|
||||
Icon(
|
||||
Icons.Filled.Lock, null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
Spacer(Modifier.size(4.dp))
|
||||
Text(
|
||||
"on-device",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
titleContentColor = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
) {
|
||||
Text(if (running) "STOP WATCHING" else "START WATCHING")
|
||||
)
|
||||
}
|
||||
) { pad ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth().padding(pad).padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
item { Spacer(Modifier.height(4.dp)) }
|
||||
item { StatusHero(running, alerting, suspicious) }
|
||||
item {
|
||||
Button(
|
||||
onClick = onStartStop,
|
||||
modifier = Modifier.fillMaxWidth().height(52.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (running) MaterialTheme.colorScheme.surfaceVariant
|
||||
else MaterialTheme.colorScheme.primary,
|
||||
contentColor = if (running) MaterialTheme.colorScheme.onSurface
|
||||
else MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
) {
|
||||
Icon(if (running) Icons.Filled.Radar else Icons.Filled.Shield, null)
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text(if (running) "STOP WATCHING" else "START WATCHING", fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
permissionMessage?.let {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(it, style = MaterialTheme.typography.bodySmall, color = VigilPeach)
|
||||
item {
|
||||
Text(it, style = MaterialTheme.typography.bodySmall, color = VigilPeach)
|
||||
}
|
||||
}
|
||||
item { SensitivitySelector(sensitivity, onSetSensitivity) }
|
||||
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text("Sensitivity", style = MaterialTheme.typography.labelLarge)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Sensitivity.entries.forEach { s ->
|
||||
FilterChip(
|
||||
selected = s == sensitivity,
|
||||
onClick = { onSetSensitivity(s) },
|
||||
label = { Text(s.name.lowercase().replaceFirstChar { it.uppercase() }) }
|
||||
item {
|
||||
SectionHeader(
|
||||
if (active.isEmpty()) "Nothing following you" else "Active (${active.size})"
|
||||
)
|
||||
}
|
||||
if (active.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
if (running) "Listening… no trackers are moving with you."
|
||||
else "Not scanning. Tap Start Watching.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text("Trackers seen (${trackers.size})", style = MaterialTheme.typography.labelLarge)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
if (trackers.isEmpty()) {
|
||||
Text(
|
||||
if (running) "Listening… nothing near you yet." else "Not scanning.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
} else {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(trackers, key = { it.stableId }) { t -> TrackerRow(t, onApprove) }
|
||||
items(active, key = { it.stableId }) { t ->
|
||||
TrackerCard(t, onClick = { detail = t }, onApprove = onApprove)
|
||||
}
|
||||
}
|
||||
|
||||
if (trusted.isNotEmpty()) {
|
||||
item { SectionHeader("Trusted (${trusted.size})") }
|
||||
items(trusted, key = { it.stableId }) { t ->
|
||||
TrackerCard(t, onClick = { detail = t }, onApprove = onApprove)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Text(
|
||||
"VIGIL ${BuildConfig.VERSION_NAME} · no network, no accounts, no data leaves this phone",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detail?.let { t ->
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { detail = null },
|
||||
sheetState = sheetState,
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
) {
|
||||
TrackerDetail(t, onApprove = { id, a -> onApprove(id, a); detail = null })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatusBanner(running: Boolean, alerting: Int, suspicious: Int) {
|
||||
val (label, color) = when {
|
||||
!running -> "Idle" to MaterialTheme.colorScheme.surfaceVariant
|
||||
alerting > 0 -> "$alerting tracker(s) following you" to VigilRed
|
||||
suspicious > 0 -> "Watching $suspicious possible follower(s)" to VigilPeach
|
||||
else -> "Clear — nothing is following you" to VigilGreen
|
||||
private fun StatusHero(running: Boolean, alerting: Int, suspicious: Int) {
|
||||
data class Look(val title: String, val subtitle: String, val icon: ImageVector, val color: Color)
|
||||
|
||||
val look = when {
|
||||
!running -> Look("Idle", "Tap Start Watching to begin.", Icons.Filled.Shield, MaterialTheme.colorScheme.surfaceVariant)
|
||||
alerting > 0 -> Look(
|
||||
if (alerting == 1) "A tracker is following you" else "$alerting trackers following you",
|
||||
"Moving with you across places and time.", Icons.Filled.Warning, VigilRed
|
||||
)
|
||||
suspicious > 0 -> Look(
|
||||
"Keeping watch",
|
||||
"$suspicious possible follower(s) — not yet confirmed.", Icons.Filled.GppMaybe, VigilPeach
|
||||
)
|
||||
else -> Look("You're clear", "Nothing has been following you.", Icons.Filled.CheckCircle, VigilGreen)
|
||||
}
|
||||
|
||||
Card(
|
||||
Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = color)
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = look.color)
|
||||
) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
Modifier.size(56.dp).clip(CircleShape).background(Color(0x22111111)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(look.icon, null, tint = Color(0xFF11111B), modifier = Modifier.size(32.dp))
|
||||
}
|
||||
Spacer(Modifier.size(16.dp))
|
||||
Column {
|
||||
Text(
|
||||
look.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF11111B)
|
||||
)
|
||||
Text(look.subtitle, style = MaterialTheme.typography.bodyMedium, color = Color(0xCC11111B))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SensitivitySelector(current: Sensitivity, onSet: (Sensitivity) -> Unit) {
|
||||
Column {
|
||||
SectionHeader("Sensitivity")
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Sensitivity.entries.forEach { s ->
|
||||
FilterChip(
|
||||
selected = s == current,
|
||||
onClick = { onSet(s) },
|
||||
label = { Text(s.name.lowercase().replaceFirstChar { it.uppercase() }) },
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||
selectedLabelColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
label,
|
||||
Modifier.padding(16.dp),
|
||||
color = Color(0xFF11111B),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
when (current) {
|
||||
Sensitivity.HIGH -> "Alerts fastest — more early warnings, more false alarms."
|
||||
Sensitivity.MEDIUM -> "Balanced — the recommended default."
|
||||
Sensitivity.LOW -> "Alerts only on strong, sustained evidence."
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TrackerRow(t: TrackerEntity, onApprove: (String, Boolean) -> Unit) {
|
||||
private fun SectionHeader(text: String) {
|
||||
Text(
|
||||
text.uppercase(),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
letterSpacing = 1.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TrackerCard(t: TrackerEntity, onClick: () -> Unit, onApprove: (String, Boolean) -> Unit) {
|
||||
val status = statusOf(t)
|
||||
Card(Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val accent = statusColor(status)
|
||||
Card(
|
||||
Modifier.fillMaxWidth().clickable(onClick = onClick),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth().padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
Modifier.size(40.dp).clip(CircleShape).background(accent.copy(alpha = 0.18f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(statusIcon(status), null, tint = accent, modifier = Modifier.size(22.dp))
|
||||
}
|
||||
Spacer(Modifier.size(12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(ecosystemDisplay(t.ecosystem), fontWeight = FontWeight.SemiBold)
|
||||
Text(
|
||||
@@ -144,15 +307,79 @@ private fun TrackerRow(t: TrackerEntity, onApprove: (String, Boolean) -> Unit) {
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
if (status == TrackerStatus.SAFE_APPROVED) {
|
||||
TextButton(onClick = { onApprove(t.stableId, false) }) { Text("Un-approve") }
|
||||
} else if (status != TrackerStatus.SAFE_BASELINE) {
|
||||
TextButton(onClick = { onApprove(t.stableId, true) }) { Text("This is mine") }
|
||||
when (status) {
|
||||
TrackerStatus.SAFE_APPROVED ->
|
||||
TextButton(onClick = { onApprove(t.stableId, false) }) { Text("Undo") }
|
||||
TrackerStatus.SAFE_BASELINE -> Icon(
|
||||
Icons.Filled.Verified, null,
|
||||
tint = VigilGreen, modifier = Modifier.size(20.dp)
|
||||
)
|
||||
else -> TextButton(onClick = { onApprove(t.stableId, true) }) { Text("It's mine") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TrackerDetail(t: TrackerEntity, onApprove: (String, Boolean) -> Unit) {
|
||||
val status = statusOf(t)
|
||||
Column(Modifier.fillMaxWidth().padding(24.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Bluetooth, null, tint = statusColor(status))
|
||||
Spacer(Modifier.size(10.dp))
|
||||
Text(ecosystemDisplay(t.ecosystem), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(statusLabel(status), color = statusColor(status), fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
DetailRow("Sightings", t.sightingCount.toString())
|
||||
DetailRow("First seen", relative(t.firstSeen))
|
||||
DetailRow("Last seen", relative(t.lastSeen))
|
||||
if (t.anchorDayCount > 0) {
|
||||
DetailRow("Days seen at your places", "${t.anchorDayCount} (trusted at 3)")
|
||||
}
|
||||
DetailRow("Identity", t.stableId.take(22) + "…")
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
explanation(status),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(Modifier.height(20.dp))
|
||||
if (status == TrackerStatus.SAFE_APPROVED) {
|
||||
Button(onClick = { onApprove(t.stableId, false) }, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Remove from approved")
|
||||
}
|
||||
} else if (status != TrackerStatus.SAFE_BASELINE) {
|
||||
Button(onClick = { onApprove(t.stableId, true) }, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("This is mine — stop alerting")
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailRow(label: String, value: String) {
|
||||
Row(Modifier.fillMaxWidth().padding(vertical = 6.dp), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium)
|
||||
Text(value, fontWeight = FontWeight.Medium, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- pure helpers ----------------------------------------------------------
|
||||
|
||||
private fun isTrusted(t: TrackerEntity) = t.approved || t.baselineSafe
|
||||
|
||||
private fun riskRank(t: TrackerEntity): Int = when (statusOf(t)) {
|
||||
TrackerStatus.ALERTING -> 3
|
||||
TrackerStatus.SUSPICIOUS -> 2
|
||||
TrackerStatus.OBSERVED -> 1
|
||||
else -> 0
|
||||
}
|
||||
|
||||
private fun statusOf(t: TrackerEntity): TrackerStatus = when {
|
||||
t.approved -> TrackerStatus.SAFE_APPROVED
|
||||
t.baselineSafe -> TrackerStatus.SAFE_BASELINE
|
||||
@@ -163,12 +390,34 @@ private fun statusOf(t: TrackerEntity): TrackerStatus = when {
|
||||
}
|
||||
}
|
||||
|
||||
private fun statusColor(s: TrackerStatus): Color = when (s) {
|
||||
TrackerStatus.ALERTING -> VigilRed
|
||||
TrackerStatus.SUSPICIOUS -> VigilPeach
|
||||
TrackerStatus.SAFE_APPROVED, TrackerStatus.SAFE_BASELINE -> VigilGreen
|
||||
TrackerStatus.OBSERVED -> Color(0xFF89B4FA)
|
||||
}
|
||||
|
||||
private fun statusIcon(s: TrackerStatus): ImageVector = when (s) {
|
||||
TrackerStatus.ALERTING -> Icons.Filled.Warning
|
||||
TrackerStatus.SUSPICIOUS -> Icons.Filled.GppMaybe
|
||||
TrackerStatus.SAFE_APPROVED, TrackerStatus.SAFE_BASELINE -> Icons.Filled.Verified
|
||||
TrackerStatus.OBSERVED -> Icons.Filled.Bluetooth
|
||||
}
|
||||
|
||||
private fun statusLabel(s: TrackerStatus): String = when (s) {
|
||||
TrackerStatus.SAFE_APPROVED -> "Approved (yours)"
|
||||
TrackerStatus.SAFE_BASELINE -> "Known (home)"
|
||||
TrackerStatus.ALERTING -> "⚠ Following you"
|
||||
TrackerStatus.SAFE_BASELINE -> "Known (around home)"
|
||||
TrackerStatus.ALERTING -> "Following you"
|
||||
TrackerStatus.SUSPICIOUS -> "Watching"
|
||||
TrackerStatus.OBSERVED -> "Seen once"
|
||||
TrackerStatus.OBSERVED -> "Seen nearby"
|
||||
}
|
||||
|
||||
private fun explanation(s: TrackerStatus): String = when (s) {
|
||||
TrackerStatus.ALERTING -> "This tracker has been close to you across several distinct places over a sustained window — the signature of something travelling with you. If it isn't yours, treat it seriously."
|
||||
TrackerStatus.SUSPICIOUS -> "Seen repeatedly and close, but not yet across enough places/time to confirm it's following you. VIGIL keeps watching."
|
||||
TrackerStatus.OBSERVED -> "Seen nearby but with no co-movement pattern — most likely ambient."
|
||||
TrackerStatus.SAFE_APPROVED -> "You marked this as yours, so VIGIL won't alert on it."
|
||||
TrackerStatus.SAFE_BASELINE -> "Regularly around the places you live — learned as part of your normal environment, entirely on-device."
|
||||
}
|
||||
|
||||
private fun ecosystemDisplay(name: String): String =
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
agp = "8.7.3"
|
||||
kotlin = "2.0.21"
|
||||
ksp = "2.0.21-1.0.28"
|
||||
coreKtx = "1.13.1"
|
||||
coreKtx = "1.15.0"
|
||||
lifecycle = "2.8.7"
|
||||
activityCompose = "1.9.3"
|
||||
activityCompose = "1.10.1"
|
||||
composeBom = "2024.12.01"
|
||||
material3 = "1.3.1"
|
||||
playServicesLocation = "21.3.0"
|
||||
|
||||
Reference in New Issue
Block a user