diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt index 266c7bc7c88bbbfaff0175f59a3d992802640e85..22c555b23f48b6a3d947ad48bc4fda10c1300ef5 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -20,18 +20,11 @@ package org.briarproject.briar.desktop.forums import org.briarproject.bramble.api.sync.GroupId import org.briarproject.briar.api.client.MessageTracker +import org.briarproject.briar.api.client.PostHeader import org.briarproject.briar.api.forum.Forum -import org.briarproject.briar.api.forum.ForumPostHeader +import org.briarproject.briar.desktop.group.GroupItem import kotlin.math.max -interface GroupItem { - val id: GroupId - val name: String - val msgCount: Int - val unread: Int - val timestamp: Long -} - data class ForumItem( val forum: Forum, override val msgCount: Int, @@ -46,10 +39,10 @@ data class ForumItem( timestamp = groupCount.latestMsgTime, ) - override val id: GroupId get() = forum.id - override val name: String get() = forum.name + override val id: GroupId = forum.id + override val name: String = forum.name - fun updateOnPostReceived(header: ForumPostHeader) = + fun updateOnPostReceived(header: PostHeader) = copy( msgCount = msgCount + 1, unread = if (header.isRead) unread else unread + 1, diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..6fbb5d74cea942fb5c591fe3589aadb4246419d1 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt @@ -0,0 +1,96 @@ +/* + * Briar Desktop + * Copyright (C) 2021-2023 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.briarproject.briar.desktop.forums + +import androidx.compose.runtime.mutableStateListOf +import mu.KotlinLogging +import org.briarproject.bramble.api.db.Transaction +import org.briarproject.bramble.api.db.TransactionManager +import org.briarproject.bramble.api.event.Event +import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.LifecycleManager +import org.briarproject.bramble.api.sync.ClientId +import org.briarproject.bramble.api.sync.GroupId +import org.briarproject.briar.api.client.PostHeader +import org.briarproject.briar.api.forum.ForumManager +import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent +import org.briarproject.briar.desktop.group.GroupListViewModel +import org.briarproject.briar.desktop.threading.BriarExecutors +import org.briarproject.briar.desktop.utils.removeFirst +import org.briarproject.briar.desktop.utils.replaceFirst +import javax.inject.Inject + +class ForumListViewModel +@Inject constructor( + private val forumManager: ForumManager, + threadViewModel: ThreadedConversationViewModel, + briarExecutors: BriarExecutors, + lifecycleManager: LifecycleManager, + db: TransactionManager, + eventBus: EventBus, +) : GroupListViewModel<ForumItem>(threadViewModel, briarExecutors, lifecycleManager, db, eventBus) { + + companion object { + private val LOG = KotlinLogging.logger {} + } + + override val clientId: ClientId = ForumManager.CLIENT_ID + + override val _groupList = mutableStateListOf<ForumItem>() + + override fun eventOccurred(e: Event) { + super.eventOccurred(e) + when (e) { + is ForumPostReceivedEvent -> { + updateItem(e.groupId) { it.updateOnPostReceived(e.header) } + } + + is ForumPostReadEvent -> { + updateItem(e.groupId) { it.updateOnPostsRead(e.numMarkedRead) } + } + } + } + + override fun createGroupItem(txn: Transaction, id: GroupId) = ForumItem( + forum = forumManager.getForum(txn, id), + groupCount = forumManager.getGroupCount(txn, id), + ) + + fun createForum(name: String) = runOnDbThread { + forumManager.addForum(name) + } + + override fun loadGroups(txn: Transaction) = + forumManager.getForums(txn).map { forums -> + ForumItem( + forum = forums, + groupCount = forumManager.getGroupCount(txn, forums.id), + ) + } + + override fun addOwnMessage(header: PostHeader) { + selectedGroupId.value?.let { id -> updateItem(id) { it.updateOnPostReceived(header) } } + } + + private fun updateItem(groupId: GroupId, update: (ForumItem) -> ForumItem) = + _groupList.replaceFirst({ it.id == groupId }, update) + + override fun removeItem(groupId: GroupId) = + _groupList.removeFirst { it.id == groupId } +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt index c815b1e7e4c7563d3f955aa085a58c300628ad63..0305332497297e30fefec9ee3d143b11119f9c74 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -18,76 +18,25 @@ package org.briarproject.briar.desktop.forums -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AddComment import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import org.briarproject.briar.desktop.conversation.Explainer -import org.briarproject.briar.desktop.ui.ColoredIconButton -import org.briarproject.briar.desktop.ui.VerticalDivider -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.group.GroupScreen import org.briarproject.briar.desktop.viewmodel.viewModel @Composable fun ForumScreen( - viewModel: ForumViewModel = viewModel(), -) { - val addDialogVisible = remember { mutableStateOf(false) } - AddForumDialog( - visible = addDialogVisible.value, - onCreate = { name -> - viewModel.createForum(name) - addDialogVisible.value = false - }, - onCancelButtonClicked = { addDialogVisible.value = false } - ) - - if (viewModel.noForumsYet.value) { - NoForumsYet { addDialogVisible.value = true } - } else { - Row(modifier = Modifier.fillMaxWidth()) { - GroupList( - list = viewModel.forumList.value, - isSelected = viewModel::isSelected, - filterBy = viewModel.filterBy.value, - onFilterSet = viewModel::setFilterBy, - onGroupItemSelected = viewModel::selectGroup, - onAddButtonClicked = { addDialogVisible.value = true }, - ) - VerticalDivider() - Column(modifier = Modifier.weight(1f).fillMaxHeight()) { - if (viewModel.selectedGroupId.value == null) { - NoForumSelected() - } else { - GroupConversationScreen(viewModel.threadViewModel) - } - } - } - } -} - -@Composable -fun NoForumsYet(onContactAdd: () -> Unit) = Explainer( - headline = i18n("welcome.title"), - text = i18n("forum.empty_state.text"), -) { - ColoredIconButton( - icon = Icons.Filled.AddComment, - iconSize = 20.dp, - contentDescription = i18n("access.forums.add"), - onClick = onContactAdd, - ) -} - -@Composable -fun NoForumSelected() = Explainer( - headline = i18n("forum.none_selected.title"), - text = i18n("forum.none_selected.hint"), + viewModel: ForumListViewModel = viewModel(), +) = GroupScreen( + strings = ForumStrings, + viewModel = viewModel, + addGroupDialog = { visible -> + AddForumDialog( + visible = visible.value, + onCreate = { name -> + viewModel.createForum(name) + visible.value = false + }, + onCancelButtonClicked = { visible.value = false } + ) + }, + conversationScreen = { GroupConversationScreen(viewModel.threadViewModel) } ) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt new file mode 100644 index 0000000000000000000000000000000000000000..ceee63746d7066c186866c2a34601fe7e253c45e --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt @@ -0,0 +1,45 @@ +/* + * Briar Desktop + * Copyright (C) 2023 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.briarproject.briar.desktop.forums + +import org.briarproject.briar.desktop.group.GroupStrings +import org.briarproject.briar.desktop.utils.InternationalizationUtils +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP +import org.briarproject.briar.desktop.utils.TimeUtils + +object ForumStrings : GroupStrings( + listTitle = i18n("forum.search.title"), + listDescription = i18n("access.forums.list"), + addButtonDescription = i18n("forum.add.title"), + noGroupsYet = i18n("forum.empty_state.text"), + noGroupSelectedTitle = i18n("forum.none_selected.title"), + noGroupSelectedText = i18n("forum.none_selected.hint"), + messageCount = { count -> + if (count > 0) i18nP("group.card.posts", count) + else i18n("group.card.no_posts") + }, + unreadCount = { count -> + i18nP("access.forums.unread_count", count) + }, + lastMessage = { timestamp -> + i18nF("access.forums.last_post_timestamp", timestamp) + }, +) 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 11fafd1e389e7e15002aad6980d033ba1d24f0b1..5392282bc61eff32dc83960d2e4d169f7d962139 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 @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -56,6 +56,9 @@ import org.briarproject.briar.desktop.contact.ContactDropDown.State.MAIN import org.briarproject.briar.desktop.forums.sharing.ForumSharingActionDrawerContent import org.briarproject.briar.desktop.forums.sharing.ForumSharingStatusDrawerContent import org.briarproject.briar.desktop.forums.sharing.ForumSharingViewModel +import org.briarproject.briar.desktop.group.GroupCircle +import org.briarproject.briar.desktop.group.GroupInputComposable +import org.briarproject.briar.desktop.group.GroupItem import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.HorizontalDivider import org.briarproject.briar.desktop.ui.getInfoDrawerHandler diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadedConversationViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadedConversationViewModel.kt index b1561a4adca2e3009829109591f8be8ad904dbf7..ba6c0cea348af62d2c990a3c32640665a76eb784 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadedConversationViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadedConversationViewModel.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -41,6 +41,7 @@ import org.briarproject.briar.api.forum.ForumPostHeader import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent import org.briarproject.briar.client.MessageTreeImpl import org.briarproject.briar.desktop.forums.sharing.ForumSharingViewModel +import org.briarproject.briar.desktop.group.GroupItem import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.UiExecutor import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCircle.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupCircle.kt similarity index 96% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCircle.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupCircle.kt index 1a41ac1f4dd732c1a4cabde33c2943d0de611b6e..599dd6770f07a8a5b5cba35ca0d271b835896642 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCircle.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupCircle.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.forums +package org.briarproject.briar.desktop.group import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupInputComposable.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupInputComposable.kt similarity index 96% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupInputComposable.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupInputComposable.kt index 09225a8e2fd347669d74c2e47d2b4d467ffd5c8d..9d096aadfd5d9bc2ee363c8ade9533263c4d5341 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupInputComposable.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupInputComposable.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.forums +package org.briarproject.briar.desktop.group import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -45,6 +45,8 @@ import androidx.compose.ui.input.pointer.PointerIconDefaults import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.unit.dp import org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEXT_LENGTH +import org.briarproject.briar.desktop.forums.ThreadItem +import org.briarproject.briar.desktop.forums.ThreadItemContentComposable import org.briarproject.briar.desktop.theme.divider import org.briarproject.briar.desktop.theme.sendButton import org.briarproject.briar.desktop.ui.HorizontalDivider diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/ThreadedConversationScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupItem.kt similarity index 72% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/ThreadedConversationScreen.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupItem.kt index 3708d9ad8e52c39440f2f6148b9b8a68fbeaa430..588cc76afef22212010a6a4a64bb4ec414fcad00 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/ThreadedConversationScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupItem.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,13 +16,14 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.privategroups +package org.briarproject.briar.desktop.group -import androidx.compose.runtime.Composable import org.briarproject.bramble.api.sync.GroupId -import org.briarproject.briar.desktop.ui.UiPlaceholder -@Composable -fun ThreadedConversationScreen( - groupId: GroupId, -) = UiPlaceholder() +interface GroupItem { + val id: GroupId + val name: String + val msgCount: Int + val unread: Int + val timestamp: Long +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupItemView.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupItemView.kt similarity index 80% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupItemView.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupItemView.kt index 49d380505f74b4ec9851a85c9cad26a81a61a791..fff90eb904ab27bc73aebb68f380cad08e2ad205 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupItemView.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupItemView.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.forums +package org.briarproject.briar.desktop.group import androidx.compose.foundation.layout.Arrangement.SpaceBetween import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -43,10 +43,8 @@ import androidx.compose.ui.semantics.text import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import org.briarproject.bramble.api.sync.GroupId +import org.briarproject.briar.desktop.forums.ForumStrings import org.briarproject.briar.desktop.ui.NumberBadge -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP import org.briarproject.briar.desktop.utils.PreviewUtils.preview import org.briarproject.briar.desktop.utils.TimeUtils.getFormattedTimestamp import org.briarproject.briar.desktop.utils.appendCommaSeparated @@ -67,11 +65,12 @@ fun main() = preview( override val unread: Int = getIntParameter("unread") override val timestamp: Long = getLongParameter("timestamp") } - GroupItemView(item) + GroupItemView(ForumStrings, item) } @Composable fun GroupItemView( + strings: GroupStrings, groupItem: GroupItem, modifier: Modifier = Modifier, ) = Row( @@ -81,7 +80,7 @@ fun GroupItemView( // allows content to be bottom-aligned .height(IntrinsicSize.Min) .semantics { - text = getDescription(groupItem) + text = getDescription(strings, groupItem) }, ) { Box(Modifier.align(Top).padding(vertical = 8.dp)) { @@ -91,23 +90,18 @@ fun GroupItemView( modifier = Modifier.align(TopEnd).offset(6.dp, (-6).dp) ) } - GroupItemViewInfo(groupItem) + GroupItemViewInfo(strings, groupItem) } -private fun getDescription(item: GroupItem) = buildBlankAnnotatedString { +private fun getDescription(strings: GroupStrings, item: GroupItem) = buildBlankAnnotatedString { append(item.name) - if (item.unread > 0) appendCommaSeparated(i18nP("access.forums.unread_count", item.unread)) - if (item.msgCount == 0) appendCommaSeparated(i18n("group.card.no_posts")) - else appendCommaSeparated( - i18nF( - "access.forums.last_post_timestamp", - getFormattedTimestamp(item.timestamp) - ) - ) + if (item.unread > 0) appendCommaSeparated(strings.unreadCount(item.unread)) + appendCommaSeparated(strings.messageCount(item.msgCount)) + if (item.msgCount > 0) appendCommaSeparated(strings.lastMessage(getFormattedTimestamp(item.timestamp))) } @Composable -private fun GroupItemViewInfo(groupItem: GroupItem) = Column( +private fun GroupItemViewInfo(strings: GroupStrings, groupItem: GroupItem) = Column( horizontalAlignment = Start, ) { Spacer(Modifier.weight(1f, fill = true)) @@ -123,8 +117,7 @@ private fun GroupItemViewInfo(groupItem: GroupItem) = Column( modifier = Modifier.fillMaxWidth() ) { Text( - text = if (groupItem.msgCount > 0) i18nP("group.card.posts", groupItem.msgCount) - else i18nP("group.card.no_posts", groupItem.msgCount), + text = strings.messageCount(groupItem.msgCount), style = MaterialTheme.typography.caption ) if (groupItem.msgCount > 0) { diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupList.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt similarity index 93% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupList.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt index ddfd369bd432b6bc79b8b37dffd8c215fe34a4a1..30ab73ee5a83692acab86095c6da74062c3663d9 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupList.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.forums +package org.briarproject.briar.desktop.group import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -41,13 +41,13 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import org.briarproject.bramble.api.sync.GroupId +import org.briarproject.briar.desktop.forums.ForumStrings import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants.COLUMN_WIDTH import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.ListItemView import org.briarproject.briar.desktop.ui.SearchTextField import org.briarproject.briar.desktop.ui.VerticallyScrollableArea -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.utils.PreviewUtils.preview import java.time.Instant @@ -90,6 +90,7 @@ fun main() = preview { } GroupList( + strings = ForumStrings, list = filteredList, isSelected = { selected?.id == it }, filterBy = filterBy, @@ -101,6 +102,7 @@ fun main() = preview { @Composable fun GroupList( + strings: GroupStrings, list: List<GroupItem>, isSelected: (GroupId) -> Boolean, filterBy: String, @@ -115,10 +117,10 @@ fun GroupList( modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp), ) { SearchTextField( - placeholder = i18n("forum.search.title"), + placeholder = strings.listTitle, icon = Icons.Filled.AddComment, searchValue = filterBy, - addButtonDescription = i18n("forum.add.title"), + addButtonDescription = strings.addButtonDescription, onValueChange = onFilterSet, onAddButtonClicked = onAddButtonClicked, ) @@ -128,7 +130,7 @@ fun GroupList( state = scrollState, modifier = Modifier .semantics { - contentDescription = i18n("access.forums.list") + contentDescription = strings.listDescription }.selectableGroup() ) { items( @@ -142,6 +144,7 @@ fun GroupList( dividerOffsetFromStart = (16 + 36 + 12).dp, ) { GroupItemView( + strings = strings, groupItem = item, modifier = Modifier .heightIn(min = HEADER_SIZE) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt similarity index 60% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumViewModel.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt index 507d80a609dffa08a3cfe1bc43d8638b43cea030..225da54ce6044c4a68d8f400e5843f4579a1447a 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,33 +16,30 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.forums +package org.briarproject.briar.desktop.group import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import mu.KotlinLogging +import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.Transaction import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.lifecycle.LifecycleManager +import org.briarproject.bramble.api.sync.ClientId import org.briarproject.bramble.api.sync.GroupId import org.briarproject.bramble.api.sync.event.GroupAddedEvent import org.briarproject.bramble.api.sync.event.GroupRemovedEvent -import org.briarproject.briar.api.forum.ForumManager -import org.briarproject.briar.api.forum.ForumPostHeader -import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent +import org.briarproject.briar.api.client.PostHeader +import org.briarproject.briar.desktop.forums.ThreadedConversationViewModel import org.briarproject.briar.desktop.threading.BriarExecutors 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 ForumViewModel @Inject constructor( +abstract class GroupListViewModel<T : GroupItem>( val threadViewModel: ThreadedConversationViewModel, - private val forumManager: ForumManager, briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, @@ -53,21 +50,23 @@ class ForumViewModel @Inject constructor( private val LOG = KotlinLogging.logger {} } - private val _fullForumList = mutableStateListOf<ForumItem>() - val forumList = derivedStateOf { + protected abstract val clientId: ClientId + + protected abstract val _groupList: MutableList<T> //todo: check internal + val list = derivedStateOf { val filter = _filterBy.value - _fullForumList.filter { item -> + _groupList.filter { item -> item.name.contains(filter, ignoreCase = true) }.sortedByDescending { it.timestamp } } - val noForumsYet = derivedStateOf { _fullForumList.isEmpty() } + val noGroupsYet = derivedStateOf { _groupList.isEmpty() } private val _selectedGroupId = mutableStateOf<GroupId?>(null) val selectedGroupId = derivedStateOf { // reset selected group item to null if not part of list after filtering val groupId = _selectedGroupId.value - if (groupId == null || forumList.value.any { it.id == groupId }) { + if (groupId == null || list.value.any { it.id == groupId }) { groupId } else { _selectedGroupId.value = null @@ -94,54 +93,42 @@ class ForumViewModel @Inject constructor( override fun eventOccurred(e: Event) { when { - e is GroupAddedEvent && e.group.clientId == ForumManager.CLIENT_ID -> + e is GroupAddedEvent && e.group.clientId == clientId -> onGroupAdded(e.group.id) - e is GroupRemovedEvent && e.group.clientId == ForumManager.CLIENT_ID -> { + e is GroupRemovedEvent && e.group.clientId == clientId -> { removeItem(e.group.id) if (_selectedGroupId.value == e.group.id) _selectedGroupId.value = null } - - e is ForumPostReceivedEvent -> { - updateItem(e.groupId) { it.updateOnPostReceived(e.header) } - } - - e is ForumPostReadEvent -> { - updateItem(e.groupId) { it.updateOnPostsRead(e.numMarkedRead) } - } } } + @DatabaseExecutor + protected abstract fun createGroupItem(txn: Transaction, id: GroupId): T + private fun onGroupAdded(id: GroupId) = runOnDbThreadWithTransaction(true) { txn -> - val item = ForumItem( - forum = forumManager.getForum(txn, id), - groupCount = forumManager.getGroupCount(txn, id), - ) + val item = createGroupItem(txn, id) txn.attach { addItem(item) } } - fun createForum(name: String) = runOnDbThread { - forumManager.addForum(name) - } + @DatabaseExecutor + protected abstract fun loadGroups(txn: Transaction): List<T> private fun loadGroups() = runOnDbThreadWithTransaction(true) { txn -> - val list = forumManager.getForums(txn).map { forums -> - ForumItem( - forum = forums, - groupCount = forumManager.getGroupCount(txn, forums.id), - ) - } + val list = loadGroups(txn) txn.attach { - _fullForumList.clearAndAddAll(list) + _groupList.clearAndAddAll(list) } } + protected abstract fun addOwnMessage(header: PostHeader) + fun selectGroup(groupItem: GroupItem) { if (_selectedGroupId.value == groupItem.id) return _selectedGroupId.value = groupItem.id - threadViewModel.setGroupItem(groupItem, this::addOwnPost) + threadViewModel.setGroupItem(groupItem, this::addOwnMessage) } fun isSelected(groupId: GroupId) = _selectedGroupId.value == groupId @@ -150,22 +137,7 @@ class ForumViewModel @Inject constructor( _filterBy.value = filter } - private fun addOwnPost(header: ForumPostHeader) { - selectedGroupId.value?.let { id -> updateItem(id) { it.updateOnPostReceived(header) } } - } - - private fun addItem(forumItem: ForumItem) = _fullForumList.add(forumItem) + private fun addItem(groupItem: T) = _groupList.add(groupItem) - private fun updateItem(forumId: GroupId, update: (ForumItem) -> ForumItem) { - _fullForumList.replaceFirst( - { it.id == forumId }, - update - ) - } - - private fun removeItem(forumId: GroupId) { - _fullForumList.removeFirst { - it.id == forumId - } - } + protected abstract fun removeItem(groupId: GroupId): Boolean } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..1eb9101a70a20399a3bb7cfb82b4735c3e2c2fb8 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt @@ -0,0 +1,90 @@ +/* + * Briar Desktop + * Copyright (C) 2021-2023 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.briarproject.briar.desktop.group + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddComment +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.conversation.Explainer +import org.briarproject.briar.desktop.ui.ColoredIconButton +import org.briarproject.briar.desktop.ui.VerticalDivider +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n + +@Composable +fun <T : GroupItem> GroupScreen( + strings: GroupStrings, + viewModel: GroupListViewModel<T>, + addGroupDialog: @Composable (MutableState<Boolean>) -> Unit, + conversationScreen: @Composable () -> Unit, +) { + val addDialogVisible = remember { mutableStateOf(false) } + addGroupDialog(addDialogVisible) + + if (viewModel.noGroupsYet.value) { + NoGroupsYet(strings) { addDialogVisible.value = true } + } else { + Row(modifier = Modifier.fillMaxWidth()) { + GroupList( + strings = strings, + list = viewModel.list.value, + isSelected = viewModel::isSelected, + filterBy = viewModel.filterBy.value, + onFilterSet = viewModel::setFilterBy, + onGroupItemSelected = viewModel::selectGroup, + onAddButtonClicked = { addDialogVisible.value = true }, + ) + VerticalDivider() + Column(modifier = Modifier.weight(1f).fillMaxHeight()) { + if (viewModel.selectedGroupId.value == null) { + NoGroupSelected(strings) + } else { + conversationScreen() + } + } + } + } +} + +@Composable +fun NoGroupsYet(strings: GroupStrings, onAdd: () -> Unit) = Explainer( + headline = i18n("welcome.title"), + text = strings.noGroupsYet, +) { + ColoredIconButton( + icon = Icons.Filled.AddComment, + iconSize = 20.dp, + contentDescription = strings.addButtonDescription, + onClick = onAdd, + ) +} + +@Composable +fun NoGroupSelected(strings: GroupStrings) = Explainer( + headline = strings.noGroupSelectedTitle, + text = strings.noGroupSelectedText, +) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt new file mode 100644 index 0000000000000000000000000000000000000000..5ae2f103736e28790ab4bf0fa31b4c88ffdae874 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt @@ -0,0 +1,31 @@ +/* + * Briar Desktop + * Copyright (C) 2023 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.briarproject.briar.desktop.group + +abstract class GroupStrings( + val listTitle: String, + val listDescription: String, + val addButtonDescription: String, + val noGroupsYet: String, + val noGroupSelectedTitle: String, + val noGroupSelectedText: String, + val messageCount: (Int) -> String, + val unreadCount: (Int) -> String, + val lastMessage: (String) -> String, +) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupItem.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt similarity index 59% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupItem.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt index fac142d0d897e4ae95dcff3cdf9cb320e587f10b..2f50b43eb7abdd0bc8cc231f8b7d79dcf63fbe2a 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupItem.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupItem.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,17 +16,21 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.privategroups +package org.briarproject.briar.desktop.privategroup +import org.briarproject.bramble.api.sync.GroupId import org.briarproject.briar.api.client.MessageTracker +import org.briarproject.briar.api.client.PostHeader import org.briarproject.briar.api.privategroup.PrivateGroup +import org.briarproject.briar.desktop.group.GroupItem +import kotlin.math.max data class PrivateGroupItem( val privateGroup: PrivateGroup, - val msgCount: Int, - val unread: Int, - val timestamp: Long -) { + override val msgCount: Int, + override val unread: Int, + override val timestamp: Long, +) : GroupItem { constructor(privateGroup: PrivateGroup, groupCount: MessageTracker.GroupCount) : this( @@ -35,4 +39,17 @@ data class PrivateGroupItem( unread = groupCount.unreadCount, timestamp = groupCount.latestMsgTime ) + + override val id: GroupId = privateGroup.id + override val name: String = privateGroup.name + + fun updateOnPostReceived(header: PostHeader) = + copy( + msgCount = msgCount + 1, + unread = if (header.isRead) unread else unread + 1, + timestamp = max(header.timestamp, this.timestamp) + ) + + fun updateOnPostsRead(num: Int) = + copy(unread = unread - num) } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..c273f1c594ec05a3238fb8374dba70c0b081402f --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt @@ -0,0 +1,95 @@ +/* + * Briar Desktop + * Copyright (C) 2021-2023 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.briarproject.briar.desktop.privategroup + +import androidx.compose.runtime.mutableStateListOf +import org.briarproject.bramble.api.db.Transaction +import org.briarproject.bramble.api.db.TransactionManager +import org.briarproject.bramble.api.event.Event +import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.LifecycleManager +import org.briarproject.bramble.api.sync.ClientId +import org.briarproject.bramble.api.sync.GroupId +import org.briarproject.briar.api.client.PostHeader +import org.briarproject.briar.api.forum.ForumManager +import org.briarproject.briar.api.privategroup.PrivateGroupManager +import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent +import org.briarproject.briar.desktop.forums.ThreadedConversationViewModel +import org.briarproject.briar.desktop.group.GroupListViewModel +import org.briarproject.briar.desktop.threading.BriarExecutors +import org.briarproject.briar.desktop.utils.removeFirst +import org.briarproject.briar.desktop.utils.replaceFirst +import javax.inject.Inject + +class PrivateGroupListViewModel +@Inject constructor( + private val privateGroupManager: PrivateGroupManager, + threadViewModel: ThreadedConversationViewModel, // todo: subclass + briarExecutors: BriarExecutors, + lifecycleManager: LifecycleManager, + db: TransactionManager, + eventBus: EventBus, +) : GroupListViewModel<PrivateGroupItem>(threadViewModel, briarExecutors, lifecycleManager, db, eventBus) { + + override val clientId: ClientId = ForumManager.CLIENT_ID + + override val _groupList = mutableStateListOf<PrivateGroupItem>() + + override fun eventOccurred(e: Event) { + super.eventOccurred(e) + when (e) { + is GroupMessageAddedEvent -> { + updateItem(e.groupId) { it.updateOnPostReceived(e.header) } + } + + // TODO + /*is ForumPostReadEvent -> { + updateItem(e.groupId) { it.updateOnPostsRead(e.numMarkedRead) } + }*/ + } + } + + override fun createGroupItem(txn: Transaction, id: GroupId) = PrivateGroupItem( + privateGroup = privateGroupManager.getPrivateGroup(txn, id), + groupCount = privateGroupManager.getGroupCount(txn, id), + ) + + fun createPrivateGroup(name: String) = runOnDbThread { + TODO() + //privateGroupManager.addForum(name) + } + + override fun loadGroups(txn: Transaction) = + privateGroupManager.getPrivateGroups(txn).map { privateGroup -> + PrivateGroupItem( + privateGroup = privateGroup, + groupCount = privateGroupManager.getGroupCount(txn, privateGroup.id), + ) + } + + override fun addOwnMessage(header: PostHeader) { + selectedGroupId.value?.let { id -> updateItem(id) { it.updateOnPostReceived(header) } } + } + + private fun updateItem(groupId: GroupId, update: (PrivateGroupItem) -> PrivateGroupItem) = + _groupList.replaceFirst({ it.id == groupId }, update) + + override fun removeItem(groupId: GroupId) = + _groupList.removeFirst { it.id == groupId } +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt similarity index 50% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupScreen.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt index 4dde277f0bdd1506defb1822354d2861117c12fe..0f3d5a83875ccf1716ec70d900923686a1e46f8a 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -16,36 +16,21 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.privategroups +package org.briarproject.briar.desktop.privategroup -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier +import org.briarproject.briar.desktop.group.GroupScreen import org.briarproject.briar.desktop.ui.UiPlaceholder -import org.briarproject.briar.desktop.ui.VerticalDivider import org.briarproject.briar.desktop.viewmodel.viewModel @Composable fun PrivateGroupScreen( viewModel: PrivateGroupListViewModel = viewModel(), -) { - Row(modifier = Modifier.fillMaxWidth()) { - PrivateGroupList( - viewModel.privateGroupList, - viewModel::isSelected, - viewModel::selectPrivateGroup, - ) - VerticalDivider() - Column(modifier = Modifier.weight(1f).fillMaxHeight()) { - val id = viewModel.selectedPrivateGroupId.value - if (id != null) { - ThreadedConversationScreen(id) - } else { - UiPlaceholder() - } - } - } -} +) = GroupScreen( + strings = PrivateGroupStrings, + viewModel = viewModel, + addGroupDialog = { visible -> + // TODO + }, + conversationScreen = { UiPlaceholder() } +) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt new file mode 100644 index 0000000000000000000000000000000000000000..e3994aee389ba072bd99b48ed6c7e2cb978040a3 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt @@ -0,0 +1,44 @@ +/* + * Briar Desktop + * Copyright (C) 2023 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.briarproject.briar.desktop.privategroup + +import org.briarproject.briar.desktop.group.GroupStrings +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP + +// todo: replace with strings for private groups +object PrivateGroupStrings : GroupStrings( + listTitle = i18n("forum.search.title"), + listDescription = i18n("access.forums.list"), + addButtonDescription = i18n("forum.add.title"), + noGroupsYet = i18n("forum.empty_state.text"), + noGroupSelectedTitle = i18n("forum.none_selected.title"), + noGroupSelectedText = i18n("forum.none_selected.hint"), + messageCount = { count -> + if (count > 0) i18nP("group.card.posts", count) + else i18n("group.card.no_posts") + }, + unreadCount = { count -> + i18nP("access.forums.unread_count", count) + }, + lastMessage = { timestamp -> + i18nF("access.forums.last_post_timestamp", timestamp) + }, +) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupCard.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupCard.kt deleted file mode 100644 index ec3deef2ce57b3e3993bfc01662ea6a9d5c652d9..0000000000000000000000000000000000000000 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupCard.kt +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -package org.briarproject.briar.desktop.privategroups - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import org.briarproject.briar.desktop.contact.ProfileCircle -import org.briarproject.briar.desktop.theme.outline -import org.briarproject.briar.desktop.theme.selectedCard -import org.briarproject.briar.desktop.theme.surfaceVariant -import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE -import org.briarproject.briar.desktop.ui.HorizontalDivider -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF -import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP -import org.briarproject.briar.desktop.utils.TimeUtils.getFormattedTimestamp - -@Composable -fun PrivateGroupCard( - privateGroupItem: PrivateGroupItem, - onSel: () -> Unit, - selected: Boolean, -) { - val bgColor = if (selected) MaterialTheme.colors.selectedCard else MaterialTheme.colors.surfaceVariant - val outlineColor = MaterialTheme.colors.outline - val briarSecondary = MaterialTheme.colors.secondary - - Card( - modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = HEADER_SIZE).clickable(onClick = onSel), - shape = RoundedCornerShape(0.dp), - backgroundColor = bgColor, - contentColor = MaterialTheme.colors.onSurface - ) { - Row(horizontalArrangement = Arrangement.SpaceBetween) { - Row(modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)) { - Box(modifier = Modifier.align(Alignment.CenterVertically)) { - // TODO Do like `TextAvatarView` in Android - ProfileCircle(36.dp, privateGroupItem.privateGroup.id.bytes) - // Draw new message counter - if (privateGroupItem.unread > 0) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .offset(6.dp, (-6).dp) - .height(20.dp) - .widthIn(min = 20.dp, max = Dp.Infinity) - .border(2.dp, outlineColor, CircleShape) - .background(briarSecondary, CircleShape) - .padding(horizontal = 6.dp) - ) { - Text( - modifier = Modifier.align(Alignment.Center), - fontSize = 8.sp, - textAlign = TextAlign.Center, - text = privateGroupItem.unread.toString(), - maxLines = 1 - ) - } - } - } - Column(modifier = Modifier.align(Alignment.CenterVertically).padding(start = 16.dp)) { - Text( - privateGroupItem.privateGroup.name, - fontSize = 14.sp, - modifier = Modifier.align(Alignment.Start).padding(bottom = 4.dp) - ) - Text( - i18nF("groups.card.created", privateGroupItem.privateGroup.creator.name), - fontSize = 10.sp, - modifier = Modifier.align(Alignment.Start).padding(bottom = 2.dp) - ) - Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { - Text( - i18nP("groups.card.messages", privateGroupItem.msgCount), - fontSize = 10.sp, - ) - Text( - getFormattedTimestamp(privateGroupItem.timestamp), - fontSize = 10.sp, - ) - } - } - } - } - } - HorizontalDivider() -} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupList.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupList.kt deleted file mode 100644 index 379ef6fea9c89037579b159bc28d21193557aa9f..0000000000000000000000000000000000000000 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupList.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -package org.briarproject.briar.desktop.privategroups - -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import org.briarproject.bramble.api.sync.GroupId -import org.briarproject.briar.desktop.theme.surfaceVariant -import org.briarproject.briar.desktop.ui.Constants.COLUMN_WIDTH - -@Composable -fun PrivateGroupList( - privateGroupList: List<PrivateGroupItem>, - isSelected: (GroupId) -> Boolean, - selectPrivateGroup: (GroupId) -> Unit, -) { - // TODO AddPrivateGroupDialog - Scaffold( - modifier = Modifier.fillMaxHeight().width(COLUMN_WIDTH), - backgroundColor = MaterialTheme.colors.surfaceVariant, - // TODO SearchTextField - content = { - LazyColumn { - items(privateGroupList) { privateGroupItem -> - PrivateGroupCard( - privateGroupItem, - { selectPrivateGroup(privateGroupItem.privateGroup.id) }, - isSelected(privateGroupItem.privateGroup.id) - ) - } - } - }, - ) -} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupListViewModel.kt deleted file mode 100644 index 5863874f197d98c9f2b8344ddfa9aedf50d0cf52..0000000000000000000000000000000000000000 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/PrivateGroupListViewModel.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -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.connection.ConnectionRegistry -import org.briarproject.bramble.api.db.TransactionManager -import org.briarproject.bramble.api.event.Event -import org.briarproject.bramble.api.event.EventBus -import org.briarproject.bramble.api.lifecycle.LifecycleManager -import org.briarproject.bramble.api.sync.GroupId -import org.briarproject.briar.api.conversation.ConversationManager -import org.briarproject.briar.api.privategroup.PrivateGroupManager -import org.briarproject.briar.desktop.threading.BriarExecutors -import org.briarproject.briar.desktop.utils.clearAndAddAll -import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel -import javax.inject.Inject - -class PrivateGroupListViewModel -@Inject -constructor( - private val privateGroupManager: PrivateGroupManager, - val conversationManager: ConversationManager, - val connectionRegistry: ConnectionRegistry, - briarExecutors: BriarExecutors, - lifecycleManager: LifecycleManager, - db: TransactionManager, - eventBus: EventBus, -) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) { - - private val _fullPrivateGroupList = mutableStateListOf<PrivateGroupItem>() - - val privateGroupList: List<PrivateGroupItem> = _fullPrivateGroupList - - private fun loadPrivateGroups() { - val privateGroupList = mutableListOf<PrivateGroupItem>() - runOnDbThreadWithTransaction(true) { txn -> - privateGroupList.addAll( - privateGroupManager.getPrivateGroups(txn).map { privateGroup -> - PrivateGroupItem( - privateGroup, - privateGroupManager.getGroupCount(txn, privateGroup.id), - ) - } - ) - txn.attach { - _fullPrivateGroupList.clearAndAddAll(privateGroupList) - } - } - } - - override fun onInit() { - super.onInit() - loadPrivateGroups() - } - - private val _selectedContactId = mutableStateOf<GroupId?>(null) - - val selectedPrivateGroupId: State<GroupId?> = _selectedContactId - - fun selectPrivateGroup(privateGroupId: GroupId) { - _selectedContactId.value = privateGroupId - } - - fun isSelected(privateGroupId: GroupId) = _selectedContactId.value == privateGroupId - - override fun eventOccurred(e: Event) { - // TODO - } -} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/ThreadedConversationViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/ThreadedConversationViewModel.kt deleted file mode 100644 index 32c94189e0e65dd70b1d72ed83bf9b2d7f2d437a..0000000000000000000000000000000000000000 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroups/ThreadedConversationViewModel.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -package org.briarproject.briar.desktop.privategroups - -import org.briarproject.bramble.api.db.TransactionManager -import org.briarproject.bramble.api.event.Event -import org.briarproject.bramble.api.event.EventBus -import org.briarproject.bramble.api.lifecycle.LifecycleManager -import org.briarproject.briar.api.conversation.ConversationManager -import org.briarproject.briar.api.messaging.MessagingManager -import org.briarproject.briar.api.messaging.PrivateMessageFactory -import org.briarproject.briar.api.privategroup.PrivateGroupManager -import org.briarproject.briar.desktop.threading.BriarExecutors -import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel -import java.util.logging.Logger -import javax.inject.Inject - -class ThreadedConversationViewModel -@Inject -constructor( - private val privateGroupManager: PrivateGroupManager, - private val conversationManager: ConversationManager, - private val messagingManager: MessagingManager, - private val privateMessageFactory: PrivateMessageFactory, - briarExecutors: BriarExecutors, - lifecycleManager: LifecycleManager, - db: TransactionManager, - eventBus: EventBus, -) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) { - - companion object { - private val LOG = Logger.getLogger(ThreadedConversationViewModel::class.java.name) - } - - override fun eventOccurred(e: Event) { - // TODO - } -} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt index 77a754c684209426c0b6af0863e3e8cc9d91f479..bd2ca81b882aa52bc4d6bc7d5a268a139b25f8e7 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt @@ -32,7 +32,7 @@ import org.briarproject.briar.desktop.forums.ForumScreen import org.briarproject.briar.desktop.mailbox.MailboxScreen import org.briarproject.briar.desktop.navigation.BriarSidebar import org.briarproject.briar.desktop.navigation.SidebarViewModel -import org.briarproject.briar.desktop.privategroups.PrivateGroupScreen +import org.briarproject.briar.desktop.privategroup.PrivateGroupScreen import org.briarproject.briar.desktop.settings.SettingsScreen import org.briarproject.briar.desktop.viewmodel.viewModel diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt index 595cab6a4728c9a8b1e7dab4c9dd5e5af89a1b28..f3f1c99d0781652372b6b41b6ce7c217ecf7de3b 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModelModule.kt @@ -1,6 +1,6 @@ /* * Briar Desktop - * Copyright (C) 2021-2022 The Briar Project + * Copyright (C) 2021-2023 The Briar Project * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -25,14 +25,13 @@ import dagger.multibindings.IntoMap import org.briarproject.briar.desktop.contact.ContactListViewModel import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel import org.briarproject.briar.desktop.conversation.ConversationViewModel -import org.briarproject.briar.desktop.forums.ForumViewModel +import org.briarproject.briar.desktop.forums.ForumListViewModel import org.briarproject.briar.desktop.forums.sharing.ForumSharingViewModel import org.briarproject.briar.desktop.introduction.IntroductionViewModel import org.briarproject.briar.desktop.login.StartupViewModel import org.briarproject.briar.desktop.mailbox.MailboxViewModel import org.briarproject.briar.desktop.navigation.SidebarViewModel -import org.briarproject.briar.desktop.privategroups.PrivateGroupListViewModel -import org.briarproject.briar.desktop.privategroups.ThreadedConversationViewModel +import org.briarproject.briar.desktop.privategroup.PrivateGroupListViewModel import org.briarproject.briar.desktop.settings.SettingsViewModel import kotlin.reflect.KClass @@ -78,13 +77,8 @@ abstract class ViewModelModule { @Binds @IntoMap - @ViewModelKey(ForumViewModel::class) - abstract fun bindForumsViewModel(forumViewModel: ForumViewModel): ViewModel - - @Binds - @IntoMap - @ViewModelKey(ThreadedConversationViewModel::class) - abstract fun bindThreadedConversationViewModel(threadedConversationViewModel: ThreadedConversationViewModel): ViewModel + @ViewModelKey(ForumListViewModel::class) + abstract fun bindForumListViewModel(forumListViewModel: ForumListViewModel): ViewModel @Binds @IntoMap