mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: add waypoint expiration picker (#2051)
This commit is contained in:
parent
89462be97a
commit
5b6c8483e8
3 changed files with 170 additions and 9 deletions
|
|
@ -370,6 +370,8 @@ fun MapView(
|
|||
model.getUser(id).longName
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("MagicNumber")
|
||||
fun MapView.onWaypointChanged(waypoints: Collection<Packet>): List<MarkerWithLabel> {
|
||||
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
|
||||
return waypoints.mapNotNull { waypoint ->
|
||||
|
|
@ -378,10 +380,29 @@ fun MapView(
|
|||
val time = dateFormat.format(waypoint.received_time)
|
||||
val label = pt.name + " " + formatAgo((waypoint.received_time / 1000).toInt())
|
||||
val emoji = String(Character.toChars(if (pt.icon == 0) 128205 else pt.icon))
|
||||
val timeLeft = pt.expire * 1000L - System.currentTimeMillis()
|
||||
val expireTimeStr = when {
|
||||
pt.expire == 0 || pt.expire == Int.MAX_VALUE -> "Never"
|
||||
timeLeft <= 0 -> "Expired"
|
||||
timeLeft < 60_000 -> "${timeLeft / 1000} seconds"
|
||||
timeLeft < 3_600_000 -> "${timeLeft / 60_000} minute${if (timeLeft / 60_000 != 1L) "s" else ""}"
|
||||
timeLeft < 86_400_000 -> {
|
||||
val hours = (timeLeft / 3_600_000).toInt()
|
||||
val minutes = ((timeLeft % 3_600_000) / 60_000).toInt()
|
||||
if (minutes >= 30) {
|
||||
"${hours + 1} hour${if (hours + 1 != 1) "s" else ""}"
|
||||
} else if (minutes > 0) {
|
||||
"$hours hour${if (hours != 1) "s" else ""}, $minutes minute${if (minutes != 1) "s" else ""}"
|
||||
} else {
|
||||
"$hours hour${if (hours != 1) "s" else ""}"
|
||||
}
|
||||
}
|
||||
else -> "${timeLeft / 86_400_000} day${if (timeLeft / 86_400_000 != 1L) "s" else ""}"
|
||||
}
|
||||
MarkerWithLabel(this, label, emoji).apply {
|
||||
id = "${pt.id}"
|
||||
title = "${pt.name} (${getUsername(waypoint.data.from)}$lock)"
|
||||
snippet = "[$time] " + pt.description
|
||||
snippet = "[$time] ${pt.description} " + stringResource(R.string.expires) + ": $expireTimeStr"
|
||||
position = GeoPoint(pt.latitudeI * 1e-7, pt.longitudeI * 1e-7)
|
||||
setVisible(false)
|
||||
setOnLongClickListener {
|
||||
|
|
@ -642,7 +663,8 @@ fun MapView(
|
|||
model.sendWaypoint(
|
||||
waypoint.copy {
|
||||
if (id == 0) id = model.generatePacketId() ?: return@EditWaypointDialog
|
||||
expire = Int.MAX_VALUE // TODO add expire picker
|
||||
if (name == "") name = "Dropped Pin"
|
||||
if (expire == 0) expire = Int.MAX_VALUE
|
||||
lockedTo = if (waypoint.lockedTo != 0) model.myNodeNum ?: 0 else 0
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package com.geeksville.mesh.ui.map.components
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.widget.DatePicker
|
||||
import android.widget.TimePicker
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -33,6 +36,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CalendarMonth
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
|
|
@ -49,6 +53,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
|
|
@ -64,6 +69,9 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.EmojiPickerDialog
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.waypoint
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
|
|
@ -77,9 +85,45 @@ internal fun EditWaypointDialog(
|
|||
) {
|
||||
var waypointInput by remember { mutableStateOf(waypoint) }
|
||||
val title = if (waypoint.id == 0) R.string.waypoint_new else R.string.waypoint_edit
|
||||
@Suppress("MagicNumber")
|
||||
val emoji = if (waypointInput.icon == 0) 128205 else waypointInput.icon
|
||||
var showEmojiPickerView by remember { mutableStateOf(false) }
|
||||
|
||||
// State to hold selected date and time
|
||||
var selectedDate by remember { mutableStateOf("") }
|
||||
var selectedTime by remember { mutableStateOf("") }
|
||||
var epochTime by remember { mutableStateOf<Long?>(null) }
|
||||
|
||||
// Get current context for dialogs
|
||||
val context = LocalContext.current
|
||||
val calendar = Calendar.getInstance()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
calendar.timeInMillis = currentTime
|
||||
@Suppress("MagicNumber")
|
||||
calendar.add(Calendar.HOUR_OF_DAY, 8)
|
||||
|
||||
// Current time for initializing pickers
|
||||
val year = calendar.get(Calendar.YEAR)
|
||||
val month = calendar.get(Calendar.MONTH)
|
||||
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
val hour = calendar.get(Calendar.HOUR_OF_DAY)
|
||||
val minute = calendar.get(Calendar.MINUTE)
|
||||
|
||||
// Determine locale-specific date format
|
||||
val locale = Locale.getDefault()
|
||||
val dateFormat = if (locale.country == "US") {
|
||||
SimpleDateFormat("MM/dd/yyyy", locale)
|
||||
} else {
|
||||
SimpleDateFormat("dd/MM/yyyy", locale)
|
||||
}
|
||||
// Check if 24-hour format is preferred
|
||||
val is24Hour = android.text.format.DateFormat.is24HourFormat(context)
|
||||
val timeFormat = if (is24Hour) {
|
||||
SimpleDateFormat("HH:mm", locale)
|
||||
} else {
|
||||
SimpleDateFormat("hh:mm a", locale)
|
||||
}
|
||||
|
||||
if (!showEmojiPickerView) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
|
@ -99,7 +143,7 @@ internal fun EditWaypointDialog(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.name),
|
||||
value = waypointInput.name,
|
||||
maxSize = 29, // name max_size:30
|
||||
maxSize = 29,
|
||||
enabled = true,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
|
|
@ -123,7 +167,7 @@ internal fun EditWaypointDialog(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.description),
|
||||
value = waypointInput.description,
|
||||
maxSize = 99, // description max_size:100
|
||||
maxSize = 99,
|
||||
enabled = true,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
|
|
@ -149,13 +193,103 @@ internal fun EditWaypointDialog(
|
|||
.wrapContentWidth(Alignment.End),
|
||||
checked = waypointInput.lockedTo != 0,
|
||||
onCheckedChange = {
|
||||
waypointInput =
|
||||
waypointInput.copy { lockedTo = if (it) 1 else 0 }
|
||||
waypointInput = waypointInput.copy { lockedTo = if (it) 1 else 0 }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
val datePickerDialog = DatePickerDialog(
|
||||
context,
|
||||
{ _: DatePicker, selectedYear: Int, selectedMonth: Int, selectedDay: Int ->
|
||||
selectedDate = "$selectedDay/${selectedMonth + 1}/$selectedYear"
|
||||
calendar.set(selectedYear, selectedMonth, selectedDay)
|
||||
epochTime = calendar.timeInMillis
|
||||
if (epochTime != null) {
|
||||
selectedDate = dateFormat.format(calendar.time)
|
||||
}
|
||||
}, year, month, day
|
||||
)
|
||||
|
||||
val timePickerDialog = android.app.TimePickerDialog(
|
||||
context,
|
||||
{ _: TimePicker, selectedHour: Int, selectedMinute: Int ->
|
||||
selectedTime = String.format(Locale.getDefault(), "%02d:%02d", selectedHour, selectedMinute)
|
||||
calendar.set(Calendar.HOUR_OF_DAY, selectedHour)
|
||||
calendar.set(Calendar.MINUTE, selectedMinute)
|
||||
epochTime = calendar.timeInMillis
|
||||
selectedTime = timeFormat.format(calendar.time)
|
||||
@Suppress("MagicNumber")
|
||||
waypointInput = waypointInput.copy { expire = (epochTime!! / 1000).toInt() }
|
||||
}, hour, minute, is24Hour
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.size(48.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
imageVector = Icons.Default.CalendarMonth,
|
||||
contentDescription = stringResource(R.string.expires),
|
||||
)
|
||||
Text(stringResource(R.string.expires))
|
||||
Switch(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.End),
|
||||
checked = waypointInput.expire != Int.MAX_VALUE && waypointInput.expire != 0,
|
||||
onCheckedChange = { isChecked ->
|
||||
waypointInput = waypointInput.copy {
|
||||
expire = if (isChecked) {
|
||||
@Suppress("MagicNumber")
|
||||
calendar.timeInMillis / 1000
|
||||
} else {
|
||||
Int.MAX_VALUE
|
||||
}.toInt()
|
||||
}
|
||||
if (isChecked) {
|
||||
selectedDate = dateFormat.format(calendar.time)
|
||||
selectedTime = timeFormat.format(calendar.time)
|
||||
} else {
|
||||
selectedDate = ""
|
||||
selectedTime = ""
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (waypointInput.expire != Int.MAX_VALUE && waypointInput.expire != 0) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Button(onClick = { datePickerDialog.show() }) {
|
||||
Text(stringResource(R.string.date))
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = "$selectedDate",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Button(onClick = { timePickerDialog.show() }) {
|
||||
Text(stringResource(R.string.time))
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = "$selectedTime",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} },
|
||||
confirmButton = {
|
||||
FlowRow(
|
||||
modifier = modifier.padding(start = 20.dp, end = 20.dp, bottom = 16.dp),
|
||||
|
|
@ -176,7 +310,7 @@ internal fun EditWaypointDialog(
|
|||
Button(
|
||||
modifier = modifier.weight(1f),
|
||||
onClick = { onSendClicked(waypointInput) },
|
||||
enabled = waypointInput.name.isNotEmpty(),
|
||||
enabled = true,
|
||||
) { Text(stringResource(R.string.send)) }
|
||||
}
|
||||
},
|
||||
|
|
@ -191,6 +325,7 @@ internal fun EditWaypointDialog(
|
|||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
@Suppress("MagicNumber")
|
||||
private fun EditWaypointFormPreview() {
|
||||
AppTheme {
|
||||
EditWaypointDialog(
|
||||
|
|
@ -199,6 +334,7 @@ private fun EditWaypointFormPreview() {
|
|||
name = "Test 123"
|
||||
description = "This is only a test"
|
||||
icon = 128169
|
||||
expire = (System.currentTimeMillis() / 1000 + 8 * 3600).toInt()
|
||||
},
|
||||
onSendClicked = { },
|
||||
onDeleteClicked = { },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue