diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt index e89cedcc1e806170e2023924e3e093325b0d5bd2..3a15dc7a06173669c4c87aabbf9a4ef7d6a9a68c 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt @@ -31,7 +31,6 @@ fun ContactCard( contactItem: ContactItem, onSel: () -> Unit, selected: Boolean, - drawNotifications: Boolean ) { val bgColor = if (selected) MaterialTheme.colors.selectedCard else MaterialTheme.colors.surfaceVariant val outlineColor = MaterialTheme.colors.outline @@ -50,7 +49,7 @@ fun ContactCard( // TODO Pull profile pictures ProfileCircle(36.dp, contactItem.contact.author.id.bytes) // Draw notification badges - if (drawNotifications) { + if (contactItem.unread > 0) { Canvas( modifier = Modifier.align(Alignment.CenterVertically), onDraw = { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt index df2a19806f06e2b0ab2bff21385ecce9fac0da2b..10c12cbdce721addc9455b316ecf766bb6d54b83 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactItem.kt @@ -2,15 +2,37 @@ package org.briarproject.briar.desktop.contact import org.briarproject.bramble.api.contact.Contact import org.briarproject.briar.api.client.MessageTracker +import org.briarproject.briar.api.conversation.ConversationMessageHeader import org.briarproject.briar.api.identity.AuthorInfo +import kotlin.math.max data class ContactItem( val contact: Contact, private val authorInfo: AuthorInfo, + private val groupCount: MessageTracker.GroupCount, + val isConnected: Boolean, - private val groupCount: MessageTracker.GroupCount + val isEmpty: Boolean = groupCount.msgCount == 0, + val unread: Int = groupCount.unreadCount, + val timestamp: Long = groupCount.latestMsgTime ) { - val isEmpty = groupCount.msgCount == 0 - val unread = groupCount.unreadCount - val timestamp = groupCount.latestMsgTime + fun updateFromMessageHeader(h: ConversationMessageHeader): ContactItem { + return copy( + isEmpty = false, + unread = if (h.isRead) unread else unread + 1, + timestamp = max(h.timestamp, timestamp) + ) + } + + fun updateIsConnected(c: Boolean): ContactItem { + return copy(isConnected = c) + } + + fun updateAlias(a: String?): ContactItem { + return copy(contact = contact.updateAlias(a)) + } + + private fun Contact.updateAlias(a: String?): Contact { + return Contact(id, author, localAuthorId, a, handshakePublicKey, isVerified) + } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt index 345e3f80b4bb003099c0c49fc4aa20bc7e357d2e..2c858b284bb6e7e4f77745a27e8632f2175cc57c 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt @@ -38,7 +38,7 @@ fun ContactList( content = { LazyColumn { itemsIndexed(viewModel.contactList) { index, contactItem -> - ContactCard(contactItem, { viewModel.selectContact(index) }, viewModel.isSelected(index), true) + ContactCard(contactItem, { viewModel.selectContact(index) }, viewModel.isSelected(index)) } } }, diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt index 01235ac0fd47bb96ee6bc4f516691565394786c7..a3a6fe7813f1ee790a146569702d59026aaddcc1 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt @@ -5,13 +5,25 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import org.briarproject.bramble.api.FormatException import org.briarproject.bramble.api.connection.ConnectionRegistry +import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactManager +import org.briarproject.bramble.api.contact.event.ContactAddedEvent +import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent +import org.briarproject.bramble.api.contact.event.ContactRemovedEvent import org.briarproject.bramble.api.db.ContactExistsException import org.briarproject.bramble.api.db.PendingContactExistsException +import org.briarproject.bramble.api.event.Event +import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.event.EventListener import org.briarproject.bramble.api.identity.AuthorConstants +import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent +import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent import org.briarproject.bramble.util.StringUtils import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent import org.briarproject.briar.api.identity.AuthorManager +import org.briarproject.briar.desktop.utils.removeFirst +import org.briarproject.briar.desktop.utils.replaceFirst import java.security.GeneralSecurityException import java.util.logging.Logger import javax.inject.Inject @@ -23,12 +35,18 @@ constructor( private val authorManager: AuthorManager, private val conversationManager: ConversationManager, private val connectionRegistry: ConnectionRegistry, -) { + private val eventBus: EventBus, +) : EventListener { companion object { private val LOG = Logger.getLogger(ContactsViewModel::class.java.name) } + init { + //todo: where/when to remove listener again? + eventBus.addListener(this) + } + private val _contactList = mutableListOf<ContactItem>() private val _filteredContactList = mutableStateListOf<ContactItem>() private val _filterBy = mutableStateOf("") @@ -55,12 +73,13 @@ constructor( ContactItem( contact, authorManager.getAuthorInfo(contact), - connectionRegistry.isConnected(contact.id), - conversationManager.getGroupCount(contact.id) + conversationManager.getGroupCount(contact.id), + connectionRegistry.isConnected(contact.id) ) }) } updateFilteredList() + //todo: do in event instead? addContactOwnLink = contactManager.handshakeLink } @@ -148,4 +167,43 @@ constructor( val aliasUtf8 = StringUtils.toUtf8(alias) return aliasUtf8.isEmpty() || aliasUtf8.size > AuthorConstants.MAX_AUTHOR_NAME_LENGTH } + + override fun eventOccurred(e: Event?) { + when (e) { + is ContactAddedEvent -> { + LOG.info("Contact added, reloading") + loadContacts() + } + is ContactConnectedEvent -> { + LOG.info("Contact connected, update state") + updateItem(e.contactId) { it.updateIsConnected(true) } + } + is ContactDisconnectedEvent -> { + LOG.info("Contact disconnected, update state") + updateItem(e.contactId) { it.updateIsConnected(false) } + } + is ContactRemovedEvent -> { + LOG.info("Contact removed, removing item") + removeItem(e.contactId) + } + is ConversationMessageReceivedEvent<*> -> { + LOG.info("Conversation message received, updating item") + updateItem(e.contactId) { it.updateFromMessageHeader(e.messageHeader) } + } + //is AvatarUpdatedEvent -> {} + is ContactAliasChangedEvent -> { + updateItem(e.contactId) { it.updateAlias(e.alias) } + } + } + } + + private fun updateItem(contactId: ContactId, update: (ContactItem) -> ContactItem) { + _contactList.replaceFirst({ it.contact.id == contactId }, update) + updateFilteredList() + } + + private fun removeItem(contactId: ContactId) { + _contactList.removeFirst { it.contact.id == contactId } + updateFilteredList() + } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..3adad7c3f2e0d7746102862e9cefa120c5e2740c --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt @@ -0,0 +1,23 @@ +package org.briarproject.briar.desktop.utils + +fun <T> MutableList<T>.replaceFirst(predicate: (T) -> Boolean, transformation: (T) -> T) { + val li = listIterator() + while (li.hasNext()) { + val n = li.next() + if (predicate(n)) { + li.set(transformation(n)) + break + } + } +} + +fun <T> MutableList<T>.removeFirst(predicate: (T) -> Boolean) { + val li = listIterator() + while (li.hasNext()) { + val n = li.next() + if (predicate(n)) { + li.remove() + break + } + } +}