Merge branch 'feat/off-the-record-ui' into 'main'
Off-the-record conversations: the control Closes #27 See merge request paulhitt/flamenet-messenger-android!130
This commit was merged in pull request #157.
This commit is contained in:
@@ -3609,6 +3609,17 @@ private fun ConversationMenu(
|
||||
expanded = open,
|
||||
onDismissRequest = { open = false },
|
||||
) {
|
||||
MenuHeading("Off the record")
|
||||
MenuChoice(
|
||||
if (chat.offTheRecord) "On, nothing is written down" else "Off",
|
||||
selected = chat.offTheRecord,
|
||||
tag = "otr-toggle",
|
||||
) {
|
||||
model.setOffTheRecord(!chat.offTheRecord)
|
||||
open = false
|
||||
}
|
||||
|
||||
androidx.compose.material3.HorizontalDivider()
|
||||
MenuHeading("Disappearing messages")
|
||||
DISAPPEAR_CHOICES.forEach { (label, seconds) ->
|
||||
MenuChoice(
|
||||
@@ -3706,6 +3717,7 @@ private fun ColumnScope.ConversationStatusLine(
|
||||
DISAPPEAR_LABELS[chat.disappearSeconds]?.let {
|
||||
add("disappearing after $it, on both phones")
|
||||
}
|
||||
if (chat.offTheRecord) add("off the record, nothing is written down")
|
||||
if (snoozed) add("snoozed")
|
||||
if (chat.replyReminderAt != 0L) add("reminder set")
|
||||
if (chat.scheduled.isNotEmpty()) {
|
||||
|
||||
@@ -217,6 +217,11 @@ class MessengerViewModel(
|
||||
val replyReminderAt: Long = 0,
|
||||
/** Messages queued to go out later: when, and what. Soonest first. */
|
||||
val scheduled: List<Pair<Long, String>> = emptyList(),
|
||||
/**
|
||||
* Held in memory by both sides and written to disk by neither.
|
||||
* False until the other side has echoed the proposal back.
|
||||
*/
|
||||
val offTheRecord: Boolean = false,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -714,6 +719,7 @@ class MessengerViewModel(
|
||||
replyReminderAt = current.replyReminder(peerId),
|
||||
scheduled = current.scheduledFor(peerId),
|
||||
pinnedMessageId = current.pinnedMessage(thread),
|
||||
offTheRecord = current.offTheRecord(peerId),
|
||||
),
|
||||
// Opening the thread is what marks it read.
|
||||
unread = it.unread - thread,
|
||||
@@ -1760,7 +1766,17 @@ class MessengerViewModel(
|
||||
onSuccess = {
|
||||
filesUpdate { it.copy(busy = null) }
|
||||
update {
|
||||
it.copy(openChat = it.openChat?.copy(lines = live.history(chat.threadId)))
|
||||
it.copy(
|
||||
openChat =
|
||||
it.openChat?.copy(
|
||||
lines = live.history(chat.threadId),
|
||||
// The peer's echo is what actually turns it on,
|
||||
// and it lands here rather than where it was
|
||||
// asked for.
|
||||
offTheRecord =
|
||||
chat.peerId?.let { id -> live.offTheRecord(id) } ?: false,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
onFailure = { e ->
|
||||
@@ -2044,6 +2060,29 @@ class MessengerViewModel(
|
||||
|
||||
// -- Disappearing messages (§6.11) ---------------------------------------.
|
||||
|
||||
/**
|
||||
* Ask the other side to hold this conversation off the record, or to stop.
|
||||
*
|
||||
* Nothing changes here on the strength of the ask: it takes effect when
|
||||
* they echo it, so the setting is re-read rather than assumed.
|
||||
*/
|
||||
fun setOffTheRecord(on: Boolean) =
|
||||
viewModelScope.launch {
|
||||
val current = session ?: return@launch
|
||||
val chat = signedIn()?.openChat ?: return@launch
|
||||
val peerId = chat.peerId ?: return@launch
|
||||
val done =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching { current.proposeOffTheRecord(peerId, on) }
|
||||
}
|
||||
update { state ->
|
||||
state.copy(
|
||||
openChat = state.openChat?.copy(offTheRecord = current.offTheRecord(peerId)),
|
||||
error = done.exceptionOrNull()?.let(::describe),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timer for the open conversation, on both sides.
|
||||
*/
|
||||
|
||||
@@ -85,4 +85,37 @@ class OffTheRecordTest {
|
||||
assertTrue(branch.contains("vault.saveOffTheRecord"))
|
||||
assertTrue(branch.contains("!Content.offTheRecordIsEcho(content)"), "an echo must not be echoed")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the chat offers the switch and says when it is on`() {
|
||||
val chat =
|
||||
read(
|
||||
"src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt",
|
||||
"app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt",
|
||||
)
|
||||
assertTrue(chat.contains("otr-toggle"), "no way to turn it on")
|
||||
assertTrue(chat.contains("off the record, nothing is written down"), "the chat never says so")
|
||||
|
||||
val vm =
|
||||
read(
|
||||
"src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt",
|
||||
"app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt",
|
||||
)
|
||||
assertTrue(vm.contains("fun setOffTheRecord("))
|
||||
assertTrue(vm.contains("proposeOffTheRecord(peerId, on)"))
|
||||
}
|
||||
|
||||
/**
|
||||
* The echo arrives during a poll, not while the menu is open, so the state
|
||||
* has to be re-read there or the banner lies until the chat is reopened.
|
||||
*/
|
||||
@Test
|
||||
fun `the echo turning it on reaches the open conversation`() {
|
||||
val vm =
|
||||
read(
|
||||
"src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt",
|
||||
"app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt",
|
||||
)
|
||||
assertTrue(vm.contains("live.offTheRecord(id)"), "polling never re-reads the setting")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user