Commit 783a9294 authored by Sebastian's avatar Sebastian
Browse files

WIP: Show pending contacts in contact list

parent 6cc07c4b
Pipeline #8385 passed with stage
in 1 minute and 42 seconds
......@@ -46,8 +46,8 @@ fun PreviewContactCard() {
MaterialTheme(colors = DarkColors) {
Surface {
ContactCard(
ContactItem(
contactId = ContactId(0),
RealContactItem(
contactId = DesktopRealContactId(ContactId(0)),
authorId = AuthorId(ByteArray(0)),
name = "Paul",
alias = "UI Master",
......@@ -83,7 +83,8 @@ fun ContactCard(
Row(modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)) {
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
// TODO Pull profile pictures
ProfileCircle(36.dp, contactItem.authorId.bytes)
if (contactItem is RealContactItem)
ProfileCircle(36.dp, contactItem.authorId.bytes)
// Draw new message counter
if (contactItem.unread > 0) {
Box(
......
......@@ -13,7 +13,7 @@ enum class ContactInfoDrawerState {
@Composable
fun ContactInfoDrawer(
contactItem: ContactItem,
contactItem: RealContactItem,
setInfoDrawer: (Boolean) -> Unit,
drawerState: ContactInfoDrawerState
) {
......
package org.briarproject.briar.desktop.contact
import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.identity.AuthorId
import org.briarproject.briar.api.client.MessageTracker
import kotlin.math.max
sealed interface ContactItem {
data class ContactItem(
val contactId: ContactId,
val authorId: AuthorId,
val name: String,
val alias: String?,
val isConnected: Boolean,
val isEmpty: Boolean,
val unread: Int,
val contactId: DesktopContactId
val displayName: String
val isConnected: Boolean
val isEmpty: Boolean
val unread: Int
val timestamp: Long
) {
val displayName = if (alias == null) name else "$alias ($name)"
constructor(contact: Contact, isConnected: Boolean, groupCount: MessageTracker.GroupCount) :
this(
contactId = contact.id,
authorId = contact.author.id,
name = contact.author.name,
alias = contact.alias,
isConnected = isConnected,
isEmpty = groupCount.msgCount == 0,
unread = groupCount.unreadCount,
timestamp = groupCount.latestMsgTime
)
fun updateTimestampAndUnread(timestamp: Long, read: Boolean): ContactItem =
copy(
isEmpty = false,
unread = if (read) unread else unread + 1,
timestamp = max(timestamp, this.timestamp)
)
fun updateIsConnected(c: Boolean): ContactItem {
return copy(isConnected = c)
}
fun updateAlias(a: String?): ContactItem {
return copy(alias = a)
}
fun updateFromMessagesRead(c: Int): ContactItem =
copy(unread = unread - c)
}
......@@ -16,7 +16,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.briar.desktop.contact.add.remote.AddContactDialog
import org.briarproject.briar.desktop.theme.surfaceVariant
import org.briarproject.briar.desktop.ui.Constants.CONTACTLIST_WIDTH
......@@ -25,8 +24,8 @@ import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
@Composable
fun ContactList(
contactList: List<ContactItem>,
isSelected: (ContactId) -> Boolean,
selectContact: (ContactId) -> Unit,
isSelected: (DesktopContactId) -> Boolean,
selectContact: (DesktopContactId) -> Unit,
filterBy: String,
setFilterBy: (String) -> Unit,
) {
......
......@@ -4,7 +4,6 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import mu.KotlinLogging
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.ContactAliasChangedEvent
import org.briarproject.bramble.api.event.Event
......@@ -33,16 +32,16 @@ constructor(
}
private val _filterBy = mutableStateOf("")
private val _selectedContactId = mutableStateOf<ContactId?>(null)
private val _selectedContactId = mutableStateOf<DesktopContactId?>(null)
val filterBy: State<String> = _filterBy
val selectedContactId: State<ContactId?> = _selectedContactId
val selectedContactId: State<DesktopContactId?> = _selectedContactId
fun selectContact(contactId: ContactId) {
fun selectContact(contactId: DesktopContactId) {
_selectedContactId.value = contactId
}
fun isSelected(contactId: ContactId) = _selectedContactId.value == contactId
fun isSelected(contactId: DesktopContactId) = _selectedContactId.value == contactId
override fun filterContactItem(contactItem: ContactItem) =
contactItem.displayName.contains(_filterBy.value, ignoreCase = true)
......@@ -67,15 +66,26 @@ constructor(
when (e) {
is ConversationMessageTrackedEvent -> {
LOG.info { "Conversation message tracked, updating item" }
updateItem(e.contactId) { it.updateTimestampAndUnread(e.timestamp, e.read) }
updateItem(e.contactId) {
if (it is RealContactItem)
it.updateTimestampAndUnread(e.timestamp, e.read)
else it
}
}
// is AvatarUpdatedEvent -> {}
is ContactAliasChangedEvent -> {
updateItem(e.contactId) { it.updateAlias(e.alias) }
updateItem(e.contactId) {
if (it is RealContactItem)
it.updateAlias(e.alias) else it
}
}
is ConversationMessagesReadEvent -> {
LOG.info("${e.count} conversation messages read, updating item")
updateItem(e.contactId) { it.updateFromMessagesRead(e.count) }
updateItem(e.contactId) {
if (it is RealContactItem) {
it.updateFromMessagesRead(e.count)
} else it
}
}
}
}
......
......@@ -39,13 +39,18 @@ abstract class ContactsViewModel(
clear()
addAll(
contactManager.contacts.map { contact ->
ContactItem(
RealContactItem(
contact,
connectionRegistry.isConnected(contact.id),
conversationManager.getGroupCount(contact.id),
)
}
)
addAll(
contactManager.pendingContacts.map { contact ->
PendingContactItem(contact.first)
}
)
}
updateFilteredList()
}
......@@ -66,11 +71,19 @@ abstract class ContactsViewModel(
}
is ContactConnectedEvent -> {
LOG.info("Contact connected, update state")
updateItem(e.contactId) { it.updateIsConnected(true) }
updateItem(e.contactId) {
if (it is RealContactItem)
it.updateIsConnected(true)
else it
}
}
is ContactDisconnectedEvent -> {
LOG.info("Contact disconnected, update state")
updateItem(e.contactId) { it.updateIsConnected(false) }
updateItem(e.contactId) {
if (it is RealContactItem)
it.updateIsConnected(false)
else it
}
}
is ContactRemovedEvent -> {
LOG.info("Contact removed, removing item")
......@@ -80,12 +93,23 @@ abstract class ContactsViewModel(
}
protected open fun updateItem(contactId: ContactId, update: (ContactItem) -> ContactItem) {
_fullContactList.replaceFirst({ it.contactId == contactId }, update)
_fullContactList.replaceFirst(
{
if (it is RealContactItem)
it.contactId.contactId == contactId
else false
},
update
)
updateFilteredList()
}
protected open fun removeItem(contactId: ContactId) {
_fullContactList.removeFirst { it.contactId == contactId }
_fullContactList.removeFirst {
if (it is RealContactItem)
it.contactId.contactId == contactId
else false
}
updateFilteredList()
}
}
package org.briarproject.briar.desktop.contact
sealed interface DesktopContactId
package org.briarproject.briar.desktop.contact
import org.briarproject.bramble.api.contact.PendingContactId
class DesktopPendingContactId(val pendingContactId: PendingContactId) : DesktopContactId
package org.briarproject.briar.desktop.contact
import org.briarproject.bramble.api.contact.ContactId
class DesktopRealContactId(val contactId: ContactId) : DesktopContactId
package org.briarproject.briar.desktop.contact
import org.briarproject.bramble.api.contact.PendingContact
data class PendingContactItem(
override val contactId: DesktopPendingContactId,
val name: String,
val alias: String?,
override val isConnected: Boolean,
override val isEmpty: Boolean,
override val unread: Int,
override val timestamp: Long
) : ContactItem {
override val displayName = if (alias == null) name else "$alias ($name)"
constructor(contact: PendingContact) : this(
contactId = DesktopPendingContactId(contact.id),
name = contact.alias,
alias = contact.alias,
isConnected = false,
isEmpty = true,
unread = 0,
timestamp = contact.timestamp
)
fun updateAlias(a: String?): PendingContactItem {
return copy(alias = a)
}
}
package org.briarproject.briar.desktop.contact
import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.identity.AuthorId
import org.briarproject.briar.api.client.MessageTracker
import kotlin.math.max
data class RealContactItem(
override val contactId: DesktopRealContactId,
val authorId: AuthorId,
val name: String,
val alias: String?,
override val isConnected: Boolean,
override val isEmpty: Boolean,
override val unread: Int,
override val timestamp: Long
) : ContactItem {
override val displayName = if (alias == null) name else "$alias ($name)"
constructor(contact: Contact, isConnected: Boolean, groupCount: MessageTracker.GroupCount) : this(
contactId = DesktopRealContactId(contact.id),
authorId = contact.author.id,
name = contact.author.name,
alias = contact.alias,
isConnected = isConnected,
isEmpty = groupCount.msgCount == 0,
unread = groupCount.unreadCount,
timestamp = groupCount.latestMsgTime
)
fun updateTimestampAndUnread(timestamp: Long, read: Boolean): RealContactItem =
copy(
isEmpty = false,
unread = if (read) unread else unread + 1,
timestamp = max(timestamp, this.timestamp)
)
fun updateIsConnected(c: Boolean): RealContactItem {
return copy(isConnected = c)
}
fun updateAlias(a: String?): RealContactItem {
return copy(alias = a)
}
fun updateFromMessagesRead(c: Int): RealContactItem =
copy(unread = unread - c)
}
......@@ -24,6 +24,7 @@ import androidx.compose.ui.unit.sp
import org.briarproject.briar.desktop.contact.ContactDropDown
import org.briarproject.briar.desktop.contact.ContactItem
import org.briarproject.briar.desktop.contact.ProfileCircle
import org.briarproject.briar.desktop.contact.RealContactItem
import org.briarproject.briar.desktop.theme.outline
import org.briarproject.briar.desktop.theme.surfaceVariant
import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
......@@ -42,7 +43,8 @@ fun ConversationHeader(
Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) {
Row(modifier = Modifier.align(Alignment.Center)) {
ProfileCircle(36.dp, contactItem.authorId.bytes)
if (contactItem is RealContactItem)
ProfileCircle(36.dp, contactItem.authorId.bytes)
Canvas(
modifier = Modifier.align(Alignment.CenterVertically),
onDraw = {
......
......@@ -29,7 +29,7 @@ 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.PrivateMessageHeader
import org.briarproject.briar.desktop.contact.ContactItem
import org.briarproject.briar.desktop.contact.RealContactItem
import org.briarproject.briar.desktop.utils.KLoggerUtils.logDuration
import org.briarproject.briar.desktop.utils.replaceIf
import org.briarproject.briar.desktop.utils.replaceIfIndexed
......@@ -53,12 +53,12 @@ constructor(
}
private val _contactId = mutableStateOf<ContactId?>(null)
private val _contactItem = mutableStateOf<ContactItem?>(null)
private val _contactItem = mutableStateOf<RealContactItem?>(null)
private val _messages = mutableStateListOf<ConversationItem>()
private val _newMessage = mutableStateOf("")
val contactItem: State<ContactItem?> = _contactItem
val contactItem: State<RealContactItem?> = _contactItem
val messages: List<ConversationItem> = _messages
val newMessage: State<String> = _newMessage
......@@ -68,7 +68,7 @@ constructor(
return
_contactId.value = id
_contactItem.value = ContactItem(
_contactItem.value = RealContactItem(
contactManager.getContact(id),
connectionRegistry.isConnected(id),
conversationManager.getGroupCount(id),
......@@ -119,12 +119,12 @@ constructor(
count++
it.markRead()
}
eventBus.broadcast(ConversationMessagesReadEvent(count, contactItem.value!!.contactId))
eventBus.broadcast(ConversationMessagesReadEvent(count, contactItem.value!!.contactId.contactId))
}
@Throws(DbException::class)
private fun createMessage(text: String): PrivateMessage {
val groupId = messagingManager.getConversationId(_contactItem.value!!.contactId)
val groupId = messagingManager.getConversationId(_contactItem.value!!.contactId.contactId)
// todo: this API call needs a database transaction context
// val timestamp = conversationManager.getTimestampForOutgoingMessage(_contactId.value!!)
val timestamp = Date().time
......
......@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import org.briarproject.briar.desktop.contact.ContactList
import org.briarproject.briar.desktop.contact.ContactListViewModel
import org.briarproject.briar.desktop.contact.DesktopRealContactId
import org.briarproject.briar.desktop.ui.UiPlaceholder
import org.briarproject.briar.desktop.ui.VerticalDivider
import org.briarproject.briar.desktop.viewmodel.viewModel
......@@ -27,8 +28,8 @@ fun PrivateMessageScreen(
VerticalDivider()
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
val id = viewModel.selectedContactId.value
if (id != null) {
ConversationScreen(id)
if (id != null && id is DesktopRealContactId) {
ConversationScreen(id.contactId)
} else {
UiPlaceholder()
}
......
......@@ -27,8 +27,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.briarproject.briar.desktop.contact.ContactCard
import org.briarproject.briar.desktop.contact.ContactItem
import org.briarproject.briar.desktop.contact.ProfileCircle
import org.briarproject.briar.desktop.contact.RealContactItem
import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE
import org.briarproject.briar.desktop.ui.HorizontalDivider
import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
......@@ -38,7 +38,7 @@ import java.util.Locale
@Composable
fun ContactDrawerMakeIntro(
contactItem: ContactItem,
contactItem: RealContactItem,
setInfoDrawer: (Boolean) -> Unit,
viewModel: IntroductionViewModel = viewModel(),
) {
......@@ -64,11 +64,12 @@ fun ContactDrawerMakeIntro(
HorizontalDivider()
LazyColumn {
items(viewModel.contactList) { contactItem ->
ContactCard(
contactItem,
{ viewModel.setSecondContact(contactItem) },
false
)
if (contactItem is RealContactItem)
ContactCard(
contactItem,
{ viewModel.setSecondContact(contactItem) },
false
)
}
}
}
......
......@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.event.EventBus
import org.briarproject.briar.api.conversation.ConversationManager
import org.briarproject.briar.desktop.contact.ContactItem
import org.briarproject.briar.desktop.contact.ContactsViewModel
import org.briarproject.briar.desktop.contact.RealContactItem
import javax.inject.Inject
class IntroductionViewModel
......@@ -19,23 +20,23 @@ constructor(
eventBus: EventBus,
) : ContactsViewModel(contactManager, conversationManager, connectionRegistry, eventBus) {
private val _firstContact = mutableStateOf<ContactItem?>(null)
private val _secondContact = mutableStateOf<ContactItem?>(null)
private val _firstContact = mutableStateOf<RealContactItem?>(null)
private val _secondContact = mutableStateOf<RealContactItem?>(null)
private val _secondScreen = mutableStateOf(false)
private val _introductionMessage = mutableStateOf("")
val firstContact: State<ContactItem?> = _firstContact
val secondContact: State<ContactItem?> = _secondContact
val firstContact: State<RealContactItem?> = _firstContact
val secondContact: State<RealContactItem?> = _secondContact
val secondScreen: State<Boolean> = _secondScreen
val introductionMessage: State<String> = _introductionMessage
fun setFirstContact(contactItem: ContactItem) {
fun setFirstContact(contactItem: RealContactItem) {
_firstContact.value = contactItem
loadContacts()
backToFirstScreen()
}
fun setSecondContact(contactItem: ContactItem) {
fun setSecondContact(contactItem: RealContactItem) {
_secondContact.value = contactItem
_secondScreen.value = true
}
......@@ -50,6 +51,8 @@ constructor(
}
override fun filterContactItem(contactItem: ContactItem): Boolean {
return _firstContact.value?.contactId != contactItem.contactId
return if (contactItem is RealContactItem) {
_firstContact.value?.contactId?.contactId != contactItem.contactId.contactId
} else false
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment