diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumSharingViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumSharingViewModel.kt index 3516d975d1b38004cd4d91bf2f30ae507cab8df1..da70d650622e05877a6284800eb0c548f823fc3c 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumSharingViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumSharingViewModel.kt @@ -19,6 +19,7 @@ package org.briarproject.briar.desktop.forums import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import mu.KotlinLogging import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.contact.ContactManager @@ -43,6 +44,7 @@ import org.briarproject.briar.desktop.utils.clearAndAddAll import org.briarproject.briar.desktop.utils.removeFirst import org.briarproject.briar.desktop.utils.replaceFirst import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel +import org.briarproject.briar.desktop.viewmodel.asState import javax.inject.Inject class ForumSharingViewModel @Inject constructor( @@ -67,8 +69,12 @@ class ForumSharingViewModel @Inject constructor( private val _currentlySharedWith = mutableStateListOf<ContactItem>() val currentlySharedWith: List<ContactItem> = _currentlySharedWith + private val _sharingInfo = mutableStateOf(SharingInfo(0, 0)) + val sharingInfo = _sharingInfo.asState() + @UiExecutor fun setGroupId(groupId: GroupId) { + if (this::groupId.isInitialized && groupId == this.groupId) return this.groupId = groupId loadSharedWith() } @@ -80,44 +86,76 @@ class ForumSharingViewModel @Inject constructor( runOnDbThreadWithTransaction(false) { txn -> val contact = contactManager.getContact(txn, e.contactId) val authorInfo = authorManager.getAuthorInfo(txn, contact) + val connected = connectionRegistry.isConnected(contact.id) val item = ContactItem( contact, authorInfo, - connectionRegistry.isConnected(contact.id), + connected, conversationManager.getGroupCount(txn, contact.id), // todo: not necessary to be shown here authorInfo.avatarHeader?.let { loadImage(txn, attachmentReader, it) }, ) - txn.attach { _currentlySharedWith.add(item) } + txn.attach { + _currentlySharedWith.add(item) + _sharingInfo.value = _sharingInfo.value.addContact(connected) + } } - e is ContactLeftShareableEvent && e.groupId == groupId -> + e is ContactLeftShareableEvent && e.groupId == groupId -> { _currentlySharedWith.removeFirst { it.idWrapper.contactId == e.contactId } + val connected = connectionRegistry.isConnected(e.contactId) + _sharingInfo.value = _sharingInfo.value.removeContact(connected) + } - e is ContactConnectedEvent -> - _currentlySharedWith.replaceFirst({ it.idWrapper.contactId == e.contactId }) { it.updateIsConnected(true) } + e is ContactConnectedEvent -> { + val isMember = _currentlySharedWith.replaceFirst({ it.idWrapper.contactId == e.contactId }) { + it.updateIsConnected(true) + } + if (isMember) _sharingInfo.value = _sharingInfo.value.updateContactConnected(true) + } - e is ContactDisconnectedEvent -> - _currentlySharedWith.replaceFirst({ it.idWrapper.contactId == e.contactId }) { - it.updateIsConnected( - false - ) + e is ContactDisconnectedEvent -> { + val isMember = _currentlySharedWith.replaceFirst({ it.idWrapper.contactId == e.contactId }) { + it.updateIsConnected(false) } + if (isMember) _sharingInfo.value = _sharingInfo.value.updateContactConnected(false) + } } } private fun loadSharedWith() = runOnDbThreadWithTransaction(true) { txn -> + var online = 0 val list = forumSharingManager.getSharedWith(txn, groupId).map { contact -> val authorInfo = authorManager.getAuthorInfo(txn, contact) + val connected = connectionRegistry.isConnected(contact.id) + if (connected) online++ ContactItem( contact, authorInfo, - connectionRegistry.isConnected(contact.id), + connected, conversationManager.getGroupCount(txn, contact.id), // todo: not necessary to be shown here authorInfo.avatarHeader?.let { loadImage(txn, attachmentReader, it) }, ) } txn.attach { _currentlySharedWith.clearAndAddAll(list) + _sharingInfo.value = SharingInfo(list.size, online) } } + + data class SharingInfo(val total: Int, val online: Int) { + fun addContact(connected: Boolean) = copy( + total = total + 1, + online = if (connected) online + 1 else online + ) + + fun removeContact(connected: Boolean) = copy( + total = total - 1, + online = if (connected) online - 1 else online + ) + + fun updateContactConnected(connected: Boolean) = copy( + total = total, + online = if (connected) online + 1 else online - 1 + ) + } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupConversationScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupConversationScreen.kt index c6aa4e1239277d217234589cacf555900a594219..160a9c7be613bc436d4d6b95bd1af07f4a0a371b 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupConversationScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupConversationScreen.kt @@ -19,7 +19,9 @@ package org.briarproject.briar.desktop.forums import androidx.compose.foundation.layout.Arrangement.SpaceBetween +import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize.Max import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight @@ -41,6 +43,7 @@ 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.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.BottomCenter @@ -55,6 +58,8 @@ import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.HorizontalDivider import org.briarproject.briar.desktop.ui.getInfoDrawerHandler import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF +import org.briarproject.briar.desktop.viewmodel.viewModel @Composable fun GroupConversationScreen( @@ -87,8 +92,14 @@ fun GroupConversationScreen( @Composable private fun GroupConversationHeader( groupItem: GroupItem, + viewModel: ForumSharingViewModel = viewModel(), + // todo: using the same viewModel twice could lead to problems when one of the occurrences goes out of scope + // -> viewModel.onCleared() will be called and it will, e.g., stop listening to events onGroupDelete: () -> Unit, ) { + LaunchedEffect(groupItem) { + viewModel.setGroupId(groupItem.id) + } val deleteGroupDialogVisible = remember { mutableStateOf(false) } val menuState = remember { mutableStateOf(CLOSED) } val close = { menuState.value = CLOSED } @@ -103,21 +114,23 @@ private fun GroupConversationHeader( .fillMaxHeight() .padding(start = 8.dp) .weight(1f, fill = false), + horizontalArrangement = spacedBy(12.dp), + verticalAlignment = CenterVertically ) { - GroupCircle( - item = groupItem, - modifier = Modifier.align(CenterVertically), - ) - Text( - modifier = Modifier - .align(CenterVertically) - .padding(start = 12.dp) - .weight(1f, fill = false), - text = groupItem.name, - maxLines = 2, - overflow = Ellipsis, - style = MaterialTheme.typography.h2, - ) + GroupCircle(groupItem) + Column { + Text( + modifier = Modifier, + text = groupItem.name, + maxLines = 2, + overflow = Ellipsis, + style = MaterialTheme.typography.h2, + ) + val sharingInfo = viewModel.sharingInfo.value + Text( + text = i18nF("forum.sharing.status.with", sharingInfo.total, sharingInfo.online) + ) + } } IconButton( icon = Icons.Filled.MoreVert, diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt index dc0a55cce19bd010cee9a66212c3f5a84f4fce38..f6ebda2998632b87a9e2af78db6134dd2569526c 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/utils/ListUtils.kt @@ -60,15 +60,16 @@ fun <T> MutableList<T>.replaceIfIndexed(predicate: (Int, T) -> Boolean, transfor } } -inline fun <T, reified U : T> MutableList<T>.replaceFirst(predicate: (U) -> Boolean, transformation: (U) -> U) { +inline fun <T, reified U : T> MutableList<T>.replaceFirst(predicate: (U) -> Boolean, transformation: (U) -> U): Boolean { val li = listIterator() while (li.hasNext()) { val n = li.next() if (n is U && predicate(n)) { li.set(transformation(n)) - break + return true } } + return false } inline fun <T, reified U : T> MutableList<T>.removeFirst(predicate: (U) -> Boolean) {