diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt index 7a81b5975..426f90845 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt @@ -62,6 +62,8 @@ import androidx.compose.ui.window.DialogProperties import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.model.Contact +import org.meshtastic.core.database.entity.ContactSettings +import org.meshtastic.core.model.util.formatMuteRemainingTime import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.MainAppBar import org.meshtastic.proto.AppOnlyProtos @@ -201,8 +203,13 @@ fun ContactsScreen( }, ) + // Get contact settings for the dialog + val contactSettings by viewModel.getContactSettings().collectAsStateWithLifecycle(initialValue = emptyMap()) + MuteNotificationsDialog( showDialog = showMuteDialog, + selectedContactKeys = selectedContactKeys.toList(), + contactSettings = contactSettings, onDismiss = { showMuteDialog = false }, onConfirm = { muteUntil -> showMuteDialog = false @@ -216,6 +223,8 @@ fun ContactsScreen( @Composable fun MuteNotificationsDialog( showDialog: Boolean, + selectedContactKeys: List, + contactSettings: Map, onDismiss: () -> Unit, onConfirm: (Long) -> Unit, // Lambda to handle the confirmed mute duration ) { @@ -238,6 +247,35 @@ fun MuteNotificationsDialog( title = { Text(text = stringResource(R.string.mute_notifications)) }, text = { Column { + // Show current mute status + selectedContactKeys.forEach { contactKey -> + contactSettings[contactKey]?.let { settings -> + val now = System.currentTimeMillis() + val statusText = + when { + settings.muteUntil > 0 && settings.muteUntil != Long.MAX_VALUE -> { + val remaining = settings.muteUntil - now + if (remaining > 0) { + val (days, hours) = formatMuteRemainingTime(remaining) + if (days >= 1) { + stringResource(R.string.mute_status_muted_for_days, days, hours) + } else { + stringResource(R.string.mute_status_muted_for_hours, hours) + } + } else { + stringResource(R.string.mute_status_unmuted) + } + } + settings.muteUntil == Long.MAX_VALUE -> stringResource(R.string.mute_status_always) + else -> stringResource(R.string.mute_status_unmuted) + } + Text( + text = stringResource(R.string.currently) + " " + statusText, + modifier = Modifier.padding(bottom = 8.dp), + ) + } + } + muteOptions.forEachIndexed { index, (stringRes, _) -> val isSelected = index == selectedOptionIndex val text = stringResource(stringRes) diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt index 26de6916f..958b893b6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt @@ -121,5 +121,7 @@ constructor( fun setMuteUntil(contacts: List, until: Long) = viewModelScope.launch(Dispatchers.IO) { packetRepository.setMuteUntil(contacts, until) } + fun getContactSettings() = packetRepository.getContactSettings() + private fun getUser(userId: String?) = nodeRepository.getUser(userId ?: DataPacket.ID_BROADCAST) } diff --git a/core/database/src/main/kotlin/org/meshtastic/core/database/dao/PacketDao.kt b/core/database/src/main/kotlin/org/meshtastic/core/database/dao/PacketDao.kt index 05b240412..20d0e1111 100644 --- a/core/database/src/main/kotlin/org/meshtastic/core/database/dao/PacketDao.kt +++ b/core/database/src/main/kotlin/org/meshtastic/core/database/dao/PacketDao.kt @@ -230,8 +230,18 @@ interface PacketDao { suspend fun setMuteUntil(contacts: List, until: Long) { val contactList = contacts.map { contact -> - getContactSettings(contact)?.copy(muteUntil = until) - ?: ContactSettings(contact_key = contact, muteUntil = until) + // Always mute + val absoluteMuteUntil = + if (until == Long.MAX_VALUE) { + Long.MAX_VALUE + } else if (until == 0L) { // unmute + 0L + } else { + System.currentTimeMillis() + until + } + + getContactSettings(contact)?.copy(muteUntil = absoluteMuteUntil) + ?: ContactSettings(contact_key = contact, muteUntil = absoluteMuteUntil) } upsertContactSettings(contactList) } diff --git a/core/model/src/main/kotlin/org/meshtastic/core/model/util/DateTimeUtils.kt b/core/model/src/main/kotlin/org/meshtastic/core/model/util/DateTimeUtils.kt index f51a97802..38ca11ff3 100644 --- a/core/model/src/main/kotlin/org/meshtastic/core/model/util/DateTimeUtils.kt +++ b/core/model/src/main/kotlin/org/meshtastic/core/model/util/DateTimeUtils.kt @@ -21,6 +21,8 @@ import java.text.DateFormat import java.util.Date import java.util.concurrent.TimeUnit +private const val ONLINE_WINDOW_HOURS = 2 + // return time if within 24 hours, otherwise date fun getShortDate(time: Long): String? { val date = if (time != 0L) Date(time) else return null @@ -62,5 +64,21 @@ private fun formatUptime(seconds: Long): String { .joinToString(" ") } -@Suppress("MagicNumber") -fun onlineTimeThreshold() = (System.currentTimeMillis() / 1000 - 2 * 60 * 60).toInt() +fun onlineTimeThreshold(): Int { + val currentSeconds = System.currentTimeMillis() / TimeUnit.SECONDS.toMillis(1) + return (currentSeconds - TimeUnit.HOURS.toSeconds(ONLINE_WINDOW_HOURS.toLong())).toInt() +} + +/** + * Calculates the remaining mute time in days and hours. + * + * @param remainingMillis The remaining time in milliseconds + * @return Pair of (days, hours), where days is Int and hours is Double + */ +fun formatMuteRemainingTime(remainingMillis: Long): Pair { + if (remainingMillis <= 0) return Pair(0, 0.0) + val totalHours = remainingMillis.toDouble() / TimeUnit.HOURS.toMillis(1) + val days = (totalHours / TimeUnit.DAYS.toHours(1)).toInt() + val hours = totalHours % TimeUnit.DAYS.toHours(1) + return Pair(days, hours) +} diff --git a/core/strings/src/main/res/values/strings.xml b/core/strings/src/main/res/values/strings.xml index 2dd634898..c6be10f50 100644 --- a/core/strings/src/main/res/values/strings.xml +++ b/core/strings/src/main/res/values/strings.xml @@ -337,6 +337,11 @@ 8 hours 1 week Always + Currently: + Always muted + Not muted + Muted for %1d days, %.1f hours + Muted for %.1f hours Replace Scan WiFi QR code Invalid WiFi Credential QR code format