Skip to content
Snippets Groups Projects
Verified Commit 03c06b03 authored by Nico's avatar Nico Committed by Mikolai Gütschow
Browse files

Kick out all the unused logic

parent 62a8fc90
No related branches found
No related tags found
1 merge request!42Show private groups in list
Showing
with 10 additions and 622 deletions
package org.briarproject.briar.desktop.privategroups
import androidx.compose.runtime.Composable
import org.briarproject.briar.api.privategroup.PrivateGroup
import org.briarproject.briar.desktop.contact.ContactInfoDrawerState
// Right drawer state
enum class PrivateGroupInfoDrawerState {
MakeIntro,
ConnectBT,
ConnectRD
}
@Composable
fun ContactInfoDrawer(
privateGroup: PrivateGroup,
setInfoDrawer: (Boolean) -> Unit,
drawerState: ContactInfoDrawerState
) {
/* TODO
when (drawerState) {
MakeIntro -> ContactDrawerMakeIntro(privateGroup, setInfoDrawer)
}
*/
}
package org.briarproject.briar.desktop.privategroups package org.briarproject.briar.desktop.privategroups
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
...@@ -11,16 +8,10 @@ import androidx.compose.material.MaterialTheme ...@@ -11,16 +8,10 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.briarproject.bramble.api.sync.GroupId import org.briarproject.bramble.api.sync.GroupId
import org.briarproject.briar.desktop.contact.SearchTextField
import org.briarproject.briar.desktop.contact.add.remote.AddContactDialog
import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.theme.surfaceVariant
import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
import org.briarproject.briar.desktop.ui.Constants.PRIVATE_GROUP_LIST_WIDTH import org.briarproject.briar.desktop.ui.Constants.PRIVATE_GROUP_LIST_WIDTH
@Composable @Composable
...@@ -28,25 +19,12 @@ fun PrivateGroupList( ...@@ -28,25 +19,12 @@ fun PrivateGroupList(
privateGroupList: List<PrivateGroupItem>, privateGroupList: List<PrivateGroupItem>,
isSelected: (GroupId) -> Boolean, isSelected: (GroupId) -> Boolean,
selectPrivateGroup: (GroupId) -> Unit, selectPrivateGroup: (GroupId) -> Unit,
filterBy: String,
setFilterBy: (String) -> Unit,
) { ) {
var isCreatePrivateGroupDialogVisible by remember { mutableStateOf(false) } // TODO AddPrivateGroupDialog
if (isCreatePrivateGroupDialogVisible) AddContactDialog(onClose = { isCreatePrivateGroupDialogVisible = false })
Scaffold( Scaffold(
modifier = Modifier.fillMaxHeight().width(PRIVATE_GROUP_LIST_WIDTH), modifier = Modifier.fillMaxHeight().width(PRIVATE_GROUP_LIST_WIDTH),
backgroundColor = MaterialTheme.colors.surfaceVariant, backgroundColor = MaterialTheme.colors.surfaceVariant,
topBar = { // TODO SearchTextField
Column(
modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp),
) {
SearchTextField(
filterBy,
onValueChange = setFilterBy,
onContactAdd = { isCreatePrivateGroupDialogVisible = true }
)
}
},
content = { content = {
LazyColumn { LazyColumn {
items(privateGroupList) { privateGroupItem -> items(privateGroupList) { privateGroupItem ->
......
package org.briarproject.briar.desktop.privategroups package org.briarproject.briar.desktop.privategroups
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.connection.ConnectionRegistry
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.Event
import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.event.EventBus
import org.briarproject.bramble.api.sync.GroupId import org.briarproject.bramble.api.sync.GroupId
import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.ConversationManager
import org.briarproject.briar.api.privategroup.PrivateGroup
import org.briarproject.briar.api.privategroup.PrivateGroupManager import org.briarproject.briar.api.privategroup.PrivateGroupManager
import org.briarproject.briar.desktop.utils.removeFirst
import org.briarproject.briar.desktop.utils.replaceFirst
import org.briarproject.briar.desktop.viewmodel.BriarEventListenerViewModel import org.briarproject.briar.desktop.viewmodel.BriarEventListenerViewModel
import java.util.logging.Logger
import javax.inject.Inject import javax.inject.Inject
class PrivateGroupListViewModel class PrivateGroupListViewModel
@Inject @Inject
constructor( constructor(
val privateGroupManager: PrivateGroupManager, private val privateGroupManager: PrivateGroupManager,
val conversationManager: ConversationManager, val conversationManager: ConversationManager,
val connectionRegistry: ConnectionRegistry, val connectionRegistry: ConnectionRegistry,
eventBus: EventBus, eventBus: EventBus,
) : BriarEventListenerViewModel(eventBus) { ) : BriarEventListenerViewModel(eventBus) {
companion object { private val _fullPrivateGroupList = mutableListOf<PrivateGroupItem>()
private val LOG = Logger.getLogger(PrivateGroupListViewModel::class.java.name)
}
private val _fullContactList = mutableListOf<PrivateGroupItem>()
private val _filteredContactList = mutableStateListOf<PrivateGroupItem>()
val privateGroupList: List<PrivateGroupItem> = _filteredContactList val privateGroupList: List<PrivateGroupItem> = _fullPrivateGroupList
private fun loadPrivateGroups() { private fun loadPrivateGroups() {
_fullContactList.apply { _fullPrivateGroupList.apply {
clear() clear()
addAll( addAll(
privateGroupManager.privateGroups.map { privateGroup -> privateGroupManager.privateGroups.map { privateGroup ->
...@@ -47,17 +36,6 @@ constructor( ...@@ -47,17 +36,6 @@ constructor(
} }
) )
} }
updateFilteredList()
}
private fun updateItem(contactId: ContactId, update: (PrivateGroupItem) -> PrivateGroupItem) {
_fullContactList.replaceFirst({ it.privateGroup.id == contactId }, update)
updateFilteredList()
}
private fun removeItem(groupId: GroupId) {
_fullContactList.removeFirst { it.privateGroup.id == groupId }
updateFilteredList()
} }
override fun onInit() { override fun onInit() {
...@@ -65,10 +43,8 @@ constructor( ...@@ -65,10 +43,8 @@ constructor(
loadPrivateGroups() loadPrivateGroups()
} }
private val _filterBy = mutableStateOf("")
private val _selectedContactId = mutableStateOf<GroupId?>(null) private val _selectedContactId = mutableStateOf<GroupId?>(null)
val filterBy: State<String> = _filterBy
val selectedPrivateGroupId: State<GroupId?> = _selectedContactId val selectedPrivateGroupId: State<GroupId?> = _selectedContactId
fun selectPrivateGroup(privateGroupId: GroupId) { fun selectPrivateGroup(privateGroupId: GroupId) {
...@@ -77,55 +53,7 @@ constructor( ...@@ -77,55 +53,7 @@ constructor(
fun isSelected(privateGroupId: GroupId) = _selectedContactId.value == privateGroupId fun isSelected(privateGroupId: GroupId) = _selectedContactId.value == privateGroupId
private fun filterContact(privateGroup: PrivateGroup) =
// todo: also filter on alias
privateGroup.name.contains(_filterBy.value, ignoreCase = true)
fun setFilterBy(filter: String) {
_filterBy.value = filter
updateFilteredList()
}
// todo: when migrated to StateFlow, this could be done implicitly instead
fun updateFilteredList() {
_filteredContactList.apply {
clear()
addAll(_fullContactList.filter { filterContact(it.privateGroup) }.sortedByDescending { it.timestamp })
}
// reset selected contact to null if not available after filtering
val id = _selectedContactId.value
if (id != null && !privateGroupList.map { it.privateGroup.id }.contains(id)) {
_selectedContactId.value = null
}
}
override fun eventOccurred(e: Event?) { override fun eventOccurred(e: Event?) {
/* // TODO
when (e) {
is ContactAddedEvent -> {
LOG.info("Contact added, reloading")
loadPrivateGroups()
}
is ContactRemovedEvent -> {
LOG.info("Contact removed, removing item")
removeItem(e.contactId)
}
}
when (e) {
is ConversationMessageReceivedEvent<*> -> {
LOG.info("Conversation message received, updating item")
updateItem(e.contactId) { it.updateFromMessageHeader(e.messageHeader) }
}
is ConversationMessageToBeSentEvent -> {
LOG.info("Conversation message added, updating item")
updateItem(e.contactId) { it.updateFromMessageHeader(e.messageHeader) }
}
// is AvatarUpdatedEvent -> {}
is ContactAliasChangedEvent -> {
updateItem(e.contactId) { it.updateAlias(e.alias) }
}
}
*/
} }
} }
...@@ -19,8 +19,6 @@ fun PrivateGroupScreen( ...@@ -19,8 +19,6 @@ fun PrivateGroupScreen(
viewModel.privateGroupList, viewModel.privateGroupList,
viewModel::isSelected, viewModel::isSelected,
viewModel::selectPrivateGroup, viewModel::selectPrivateGroup,
viewModel.filterBy.value,
viewModel::setFilterBy
) )
VerticalDivider() VerticalDivider()
Column(modifier = Modifier.weight(1f).fillMaxHeight()) { Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
......
package org.briarproject.briar.desktop.privategroups
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
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.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.briarproject.briar.desktop.contact.ContactDropDown
import org.briarproject.briar.desktop.contact.ProfileCircle
import org.briarproject.briar.desktop.theme.outline
import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
import org.briarproject.briar.desktop.ui.HorizontalDivider
import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
@Composable
fun ThreadedConversationHeader(
privateGroupItem: PrivateGroupItem,
onMakeIntroduction: () -> Unit,
) {
val (isExpanded, setExpanded) = remember { mutableStateOf(false) }
val outlineColor = MaterialTheme.colors.outline
Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) {
Row(modifier = Modifier.align(Alignment.Center)) {
ProfileCircle(36.dp, privateGroupItem.privateGroup.id.bytes)
Text(
privateGroupItem.privateGroup.name,
modifier = Modifier.align(Alignment.CenterVertically).padding(start = 12.dp),
fontSize = 20.sp
)
}
IconButton(
onClick = { setExpanded(!isExpanded) },
modifier = Modifier.align(Alignment.CenterEnd).padding(end = 16.dp)
) {
Icon(Icons.Filled.MoreVert, i18n("access.contact.menu"), modifier = Modifier.size(24.dp))
ContactDropDown(isExpanded, setExpanded, onMakeIntroduction)
}
HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
}
}
package org.briarproject.briar.desktop.privategroups
import org.briarproject.bramble.api.sync.GroupId
import org.briarproject.bramble.api.sync.MessageId
sealed class ThreadedConversationItem {
abstract val id: MessageId
abstract val groupId: GroupId
abstract val time: Long
abstract val autoDeleteTimer: Long
abstract val isIncoming: Boolean
/**
* Only useful for incoming messages.
*/
abstract val isRead: Boolean
/**
* Only useful for outgoing messages.
*/
abstract val isSent: Boolean
/**
* Only useful for outgoing messages.
*/
abstract val isSeen: Boolean
abstract fun mark(sent: Boolean, seen: Boolean): ThreadedConversationItem
abstract fun markRead(): ThreadedConversationItem
}
package org.briarproject.briar.desktop.privategroups
import org.briarproject.bramble.api.sync.GroupId
import org.briarproject.bramble.api.sync.MessageId
import org.briarproject.briar.api.conversation.ConversationMessageHeader
data class ThreadedConversationMessageItem(
var text: String? = null,
override val id: MessageId,
override val groupId: GroupId,
override val time: Long,
override val autoDeleteTimer: Long,
override val isIncoming: Boolean,
override var isRead: Boolean,
override var isSent: Boolean,
override var isSeen: Boolean,
// todo: support attachments
// val attachments: List<AttachmentItem>
) : ThreadedConversationItem() {
constructor(h: ConversationMessageHeader) :
this(
id = h.id,
groupId = h.groupId,
time = h.timestamp,
autoDeleteTimer = h.autoDeleteTimer,
isRead = h.isRead,
isSent = h.isSent,
isSeen = h.isSeen,
isIncoming = !h.isLocal,
)
override fun mark(sent: Boolean, seen: Boolean): ThreadedConversationItem {
return copy(isSent = sent, isSeen = seen)
}
override fun markRead(): ThreadedConversationItem {
return copy(isRead = true)
}
}
package org.briarproject.briar.desktop.privategroups package org.briarproject.briar.desktop.privategroups
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import org.briarproject.bramble.api.sync.GroupId import org.briarproject.bramble.api.sync.GroupId
import org.briarproject.briar.desktop.contact.ContactInfoDrawer import org.briarproject.briar.desktop.ui.UiPlaceholder
import org.briarproject.briar.desktop.contact.ContactInfoDrawerState
import org.briarproject.briar.desktop.conversation.ConversationInput
import org.briarproject.briar.desktop.navigation.SIDEBAR_WIDTH
import org.briarproject.briar.desktop.theme.surfaceVariant
import org.briarproject.briar.desktop.ui.Constants.CONTACTLIST_WIDTH
import org.briarproject.briar.desktop.ui.Loader
import org.briarproject.briar.desktop.viewmodel.viewModel import org.briarproject.briar.desktop.viewmodel.viewModel
@Composable @Composable
fun ThreadedConversationScreen( fun ThreadedConversationScreen(
groupId: GroupId, groupId: GroupId,
viewModel: ThreadedConversationViewModel = viewModel(), viewModel: ThreadedConversationViewModel = viewModel(),
) { ) = UiPlaceholder()
LaunchedEffect(groupId) {
viewModel.setGroupId(groupId)
}
val contactItem = viewModel.contactItem.value
if (contactItem == null) {
Loader()
return
}
val (infoDrawer, setInfoDrawer) = remember { mutableStateOf(false) }
val (contactDrawerState, setDrawerState) = remember { mutableStateOf(ContactInfoDrawerState.MakeIntro) }
BoxWithConstraints(Modifier.fillMaxSize()) {
val animatedInfoDrawerOffsetX by animateDpAsState(if (infoDrawer) (-275).dp else 0.dp)
Scaffold(
topBar = {
ThreadedConversationHeader(
contactItem,
onMakeIntroduction = {
setInfoDrawer(true)
}
)
},
content = { padding ->
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
// reverseLayout to display most recent message (index 0) at the bottom
reverseLayout = true,
contentPadding = PaddingValues(8.dp),
modifier = Modifier.padding(padding).fillMaxHeight()
) {
items(viewModel.messages) { m ->
if (m is ThreadedConversationMessageItem)
ThreadedText(m)
}
}
},
bottomBar = {
ConversationInput(
viewModel.newMessage.value,
viewModel::setNewMessage,
viewModel::sendMessage
)
},
)
if (infoDrawer) {
// TODO Find non-hacky way of setting scrim on entire app
Box(
Modifier.offset(-(CONTACTLIST_WIDTH + SIDEBAR_WIDTH))
.requiredSize(maxWidth + CONTACTLIST_WIDTH + SIDEBAR_WIDTH, maxHeight)
.background(Color(0, 0, 0, 100))
.clickable(
// prevent visual indication
interactionSource = remember { MutableInteractionSource() },
indication = null
) { setInfoDrawer(false) }
)
Column(
modifier = Modifier.fillMaxHeight().width(CONTACTLIST_WIDTH)
.offset(maxWidth + animatedInfoDrawerOffsetX)
.background(
MaterialTheme.colors.surfaceVariant,
RoundedCornerShape(topStart = 10.dp, bottomStart = 10.dp)
)
) {
ContactInfoDrawer(contactItem.privateGroup, setInfoDrawer, contactDrawerState)
}
}
}
}
package org.briarproject.briar.desktop.privategroups package org.briarproject.briar.desktop.privategroups
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import org.briarproject.bramble.api.FormatException
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent
import org.briarproject.bramble.api.db.DbException
import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.Event
import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.event.EventBus
import org.briarproject.bramble.api.sync.GroupId
import org.briarproject.bramble.api.sync.MessageId
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent
import org.briarproject.bramble.api.sync.event.MessagesSentEvent
import org.briarproject.bramble.api.versioning.event.ClientVersionUpdatedEvent
import org.briarproject.bramble.util.LogUtils
import org.briarproject.briar.api.autodelete.UnexpectedTimerException
import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent
import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.ConversationManager
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent
import org.briarproject.briar.api.messaging.MessagingManager import org.briarproject.briar.api.messaging.MessagingManager
import org.briarproject.briar.api.messaging.PrivateMessage
import org.briarproject.briar.api.messaging.PrivateMessageFactory import org.briarproject.briar.api.messaging.PrivateMessageFactory
import org.briarproject.briar.api.messaging.PrivateMessageHeader
import org.briarproject.briar.api.privategroup.PrivateGroupManager import org.briarproject.briar.api.privategroup.PrivateGroupManager
import org.briarproject.briar.desktop.utils.replaceIf
import org.briarproject.briar.desktop.viewmodel.BriarEventListenerViewModel import org.briarproject.briar.desktop.viewmodel.BriarEventListenerViewModel
import java.util.Date
import java.util.logging.Level
import java.util.logging.Logger import java.util.logging.Logger
import javax.inject.Inject import javax.inject.Inject
...@@ -45,188 +24,7 @@ constructor( ...@@ -45,188 +24,7 @@ constructor(
private val LOG = Logger.getLogger(ThreadedConversationViewModel::class.java.name) private val LOG = Logger.getLogger(ThreadedConversationViewModel::class.java.name)
} }
private val _privateGroupId = mutableStateOf<GroupId?>(null)
private val _privateGroupItem = mutableStateOf<PrivateGroupItem?>(null)
private val _messages = mutableStateListOf<ThreadedConversationItem>()
private val _newMessage = mutableStateOf("")
val contactItem: State<PrivateGroupItem?> = _privateGroupItem
val messages: List<ThreadedConversationItem> = _messages
val newMessage: State<String> = _newMessage
fun setGroupId(id: GroupId) {
if (_privateGroupId.value == id)
return
_privateGroupId.value = id
_privateGroupItem.value = PrivateGroupItem(
privateGroupManager.getPrivateGroup(id),
privateGroupManager.getGroupCount(id),
)
loadMessages()
setNewMessage("")
}
fun setNewMessage(msg: String) {
_newMessage.value = msg
}
fun sendMessage() {
try {
val text = _newMessage.value
_newMessage.value = ""
// don't send empty or blank messages
if (text.isBlank()) return
val start = LogUtils.now()
val m = createMessage(text)
messagingManager.addLocalMessage(m)
LogUtils.logDuration(LOG, "Storing message", start)
val message = m.message
val h = PrivateMessageHeader(
message.id, message.groupId,
message.timestamp, true, true, false, false,
m.hasText(), m.attachmentHeaders,
m.autoDeleteTimer
)
_messages.add(0, messageHeaderToItem(h))
// eventBus.broadcast(ConversationMessageToBeSentEvent(h, _contactId.value!!))
} catch (e: UnexpectedTimerException) {
LogUtils.logException(LOG, Level.WARNING, e)
} catch (e: DbException) {
LogUtils.logException(LOG, Level.WARNING, e)
}
}
@Throws(DbException::class)
private fun createMessage(text: String): PrivateMessage {
val groupId = _privateGroupItem.value!!.privateGroup.id
// todo: this API call needs a database transaction context
// val timestamp = conversationManager.getTimestampForOutgoingMessage(_contactId.value!!)
val timestamp = Date().time
try {
return privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, text
)
} catch (e: FormatException) {
throw AssertionError(e)
}
}
private fun loadMessages() {
try {
val start = LogUtils.now()
val headers = privateGroupManager.getHeaders(_privateGroupId.value!!)
LogUtils.logDuration(LOG, "Loading message headers", start)
// Sort headers by timestamp in *descending* order
val sorted = headers.sortedByDescending { it.timestamp }
_messages.apply {
clear()
val start = LogUtils.now()
addAll(
// todo: use ConversationVisitor to also display Request and Notice Messages
sorted.filterIsInstance<PrivateMessageHeader>().map(::messageHeaderToItem)
)
LogUtils.logDuration(LOG, "Loading messages", start)
}
} catch (e: NoSuchContactException) {
LogUtils.logException(LOG, Level.WARNING, e)
} catch (e: DbException) {
LogUtils.logException(LOG, Level.WARNING, e)
}
}
private fun messageHeaderToItem(h: PrivateMessageHeader): ThreadedConversationMessageItem {
// todo: use ConversationVisitor instead and support other MessageHeader
val item = ThreadedConversationMessageItem(h)
if (h.hasText()) {
item.text = loadMessageText(h.id)
} else {
LOG.warning { "private message without text" }
}
return item
}
private fun loadMessageText(m: MessageId): String? {
try {
return messagingManager.getMessageText(m)
} catch (e: DbException) {
LogUtils.logException(LOG, Level.WARNING, e)
}
return null
}
override fun eventOccurred(e: Event?) { override fun eventOccurred(e: Event?) {
when (e) { // TODO
is ContactRemovedEvent -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Contact removed")
// todo: we probably don't need to react to this here as the ContactsViewModel should already handle it
}
}
is ConversationMessageReceivedEvent<*> -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Message received, adding")
val h = e.messageHeader
if (h is PrivateMessageHeader) {
// insert at start of list according to descending sort order
_messages.add(0, messageHeaderToItem(h))
}
}
}
is MessagesSentEvent -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Messages sent")
markMessages(e.messageIds, sent = true, seen = false)
}
}
is MessagesAckedEvent -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Messages acked")
markMessages(e.messageIds, sent = true, seen = true)
}
}
is ConversationMessagesDeletedEvent -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Messages auto-deleted")
val messages = HashSet(e.messageIds)
_messages.removeIf { messages.contains(it.id) }
}
}
/*
is ContactConnectedEvent -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Contact connected")
_privateGroupItem.value = _privateGroupItem.value!!.updateIsConnected(true)
}
}
is ContactDisconnectedEvent -> {
if (e.contactId == _privateGroupId.value) {
LOG.info("Contact disconnected")
_privateGroupItem.value = _privateGroupItem.value!!.updateIsConnected(false)
}
}
*/
is ClientVersionUpdatedEvent -> {
if (e.contactId == _privateGroupId.value) {
// todo: still not implemented
}
}
}
}
private fun markMessages(
messageIds: Collection<MessageId>,
sent: Boolean,
seen: Boolean
) {
val messages = HashSet(messageIds)
_messages.replaceIf({ !it.isIncoming && messages.contains(it.id) }) {
it.mark(sent, seen)
}
} }
} }
package org.briarproject.briar.desktop.privategroups
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.DoneAll
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.briarproject.briar.desktop.theme.awayMsgBubble
import org.briarproject.briar.desktop.theme.localMsgBubble
import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
import org.briarproject.briar.desktop.utils.TimeUtils
@Composable
fun ThreadedText(m: ThreadedConversationMessageItem) {
val alignment = if (m.isIncoming) Alignment.Start else Alignment.End
val color = if (m.isIncoming) MaterialTheme.colors.awayMsgBubble else MaterialTheme.colors.localMsgBubble
val shape = if (m.isIncoming)
RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp, bottomEnd = 10.dp)
else
RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp, bottomStart = 10.dp)
Column(Modifier.fillMaxWidth()) {
Column(Modifier.fillMaxWidth(fraction = 0.8f).align(alignment)) {
Card(Modifier.align(alignment), backgroundColor = color, shape = shape) {
Column(
Modifier.padding(8.dp)
) {
Text(m.text!!, fontSize = 14.sp, modifier = Modifier.align(Alignment.Start))
Row(modifier = Modifier.padding(top = 4.dp)) {
Text(TimeUtils.getFormattedTimestamp(m.time), Modifier.padding(end = 4.dp), fontSize = 10.sp)
if (!m.isIncoming) {
val modifier = Modifier.size(12.dp).align(Alignment.CenterVertically)
val icon =
if (m.isSeen) Icons.Filled.DoneAll // acknowledged
else if (m.isSent) Icons.Filled.Done // sent
else Icons.Filled.Schedule // waiting
Icon(icon, i18n("access.message.sent"), modifier)
}
}
}
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment