diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt index be304ce31c545f63455b93d24b567d73b9b7f57d..c2b33acd9b8ce8e2a57f545cc017611b01b7ffb4 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.unit.dp 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.SearchTextField import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n @Composable @@ -70,6 +71,7 @@ fun ContactList( placeholder = i18n("contacts.search.title"), icon = Icons.Filled.PersonAdd, searchValue = filterBy, + addButtonDescription = i18n("access.contacts.add"), onValueChange = setFilterBy, onAddButtonClicked = onContactAdd, ) @@ -89,7 +91,11 @@ fun ContactList( contactItem, onSel = { selectContact(contactItem) }, selected = isSelected(contactItem), - onRemovePending = { if (contactItem is PendingContactItem) removePendingContact(contactItem) }, + onRemovePending = { + if (contactItem is PendingContactItem) { + removePendingContact(contactItem) + } + }, ) } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt index 26511da0f91cd29d2cd0fde54df6fb1dcec73ec3..c0f6e74bc5621b8fa2ebfb7c75eb158a5ad1b86a 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt @@ -52,10 +52,10 @@ import androidx.compose.ui.window.rememberDialogState import org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH import org.briarproject.briar.desktop.utils.AccessibilityUtils.description import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n -import org.briarproject.briar.desktop.utils.PreviewUtils +import org.briarproject.briar.desktop.utils.PreviewUtils.preview import java.awt.Dimension -fun main() = PreviewUtils.preview { +fun main() = preview { val visible = mutableStateOf(true) AddForumDialog(visible, {}, { visible.value = false }) } @@ -66,13 +66,13 @@ fun AddForumDialog( onCreate: (String) -> Unit, onCancelButtonClicked: () -> Unit, ) { + if (!visible.value) return Dialog( title = i18n("forum.add.title"), onCloseRequest = onCancelButtonClicked, state = rememberDialogState( position = WindowPosition(Alignment.Center), ), - visible = visible.value, ) { window.minimumSize = Dimension(360, 180) val scaffoldState = rememberScaffoldState() @@ -101,7 +101,6 @@ fun AddForumDialog( okButtonEnabled = isValidForumName(name.value), onOkButtonClicked = { onCreate(name.value) - name.value = "" }, onCancelButtonClicked = onCancelButtonClicked, ) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsItem.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt similarity index 55% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsItem.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt index a8765ab1cd793ff2f80e3122726f72fe6a448acc..b3b4820f949ae91d285faed21e3ef3141f2a169d 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsItem.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumItem.kt @@ -18,9 +18,16 @@ package org.briarproject.briar.desktop.forums +import androidx.compose.ui.text.AnnotatedString import org.briarproject.bramble.api.sync.GroupId import org.briarproject.briar.api.client.MessageTracker import org.briarproject.briar.api.forum.Forum +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.getFormattedTimestamp +import org.briarproject.briar.desktop.utils.appendCommaSeparated +import org.briarproject.briar.desktop.utils.buildBlankAnnotatedString interface GroupItem { val id: GroupId @@ -28,9 +35,10 @@ interface GroupItem { val msgCount: Int val unread: Int val timestamp: Long + val description: AnnotatedString } -data class ForumsItem( +data class ForumItem( val forum: Forum, override val msgCount: Int, override val unread: Int, @@ -46,4 +54,25 @@ data class ForumsItem( override val id: GroupId get() = forum.id override val name: String get() = forum.name + + override val description: AnnotatedString + get() = buildBlankAnnotatedString { + append(name) + if (unread > 0) appendCommaSeparated(i18nP("access.forums.unread_count", unread)) + if (msgCount == 0) appendCommaSeparated(i18n("group.card.no_posts")) + else appendCommaSeparated( + i18nF( + "access.forums.last_message_timestamp", + getFormattedTimestamp(timestamp) + ) + ) + } + + override fun equals(other: Any?): Boolean { + return other is ForumItem && other.id == id + } + + override fun hashCode(): Int { + return forum.hashCode() + } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt similarity index 94% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsScreen.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt index 1b4cca977de2b246e187208e271bec30116d4ec7..55b598364c8b765cd9938d27a0adc3ed39abba62 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt @@ -36,8 +36,8 @@ import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.viewmodel.viewModel @Composable -fun ForumsScreen( - viewModel: ForumsViewModel = viewModel(), +fun ForumScreen( + viewModel: ForumViewModel = viewModel(), ) { val addDialogVisible = remember { mutableStateOf(false) } AddForumDialog( @@ -49,12 +49,12 @@ fun ForumsScreen( onCancelButtonClicked = { addDialogVisible.value = false } ) - if (viewModel.groupList.value.isEmpty()) { + if (viewModel.forumList.value.isEmpty()) { NoForumsYet { addDialogVisible.value = true } } else { Row(modifier = Modifier.fillMaxWidth()) { - ForumsList( - list = viewModel.groupList, + GroupListComposable( + list = viewModel.forumList, isSelected = viewModel::isSelected, filterBy = viewModel.filterBy, onFilterSet = viewModel::setFilterBy, diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumViewModel.kt similarity index 91% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsViewModel.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumViewModel.kt index 8755aa850d50cbcab3ee722f07324ad19ac0fda9..7b70e4b40c4a64615b31a1f09b7815d7ea352a82 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumViewModel.kt @@ -18,7 +18,6 @@ package org.briarproject.briar.desktop.forums -import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -57,7 +56,7 @@ class Loaded( val posts: MutableList<ThreadItem> get() = messageTree.depthFirstOrder() } -class ForumsViewModel @Inject constructor( +class ForumViewModel @Inject constructor( private val forumManager: ForumManager, private val identityManager: IdentityManager, private val clock: Clock, @@ -68,22 +67,22 @@ class ForumsViewModel @Inject constructor( eventBus: EventBus, ) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) { - private val _fullGroupList = mutableStateListOf<ForumsItem>() - val groupList = derivedStateOf { + private val _fullForumList = mutableStateListOf<ForumItem>() + val forumList = derivedStateOf { val filter = _filterBy.value - _fullGroupList.filter { item -> + _fullForumList.filter { item -> item.name.contains(filter, ignoreCase = true) }.sortedByDescending { it.timestamp } } private val _selectedGroupItem = mutableStateOf<GroupItem?>(null) - val selectedGroupItem: State<GroupItem?> = _selectedGroupItem + val selectedGroupItem = _selectedGroupItem.asState() private val _filterBy = mutableStateOf("") val filterBy = _filterBy.asState() private val _posts = mutableStateOf<PostsState>(Loading) - val posts: State<PostsState> = _posts + val posts = _posts.asState() override fun onInit() { super.onInit() @@ -108,13 +107,13 @@ class ForumsViewModel @Inject constructor( private fun loadGroups() { runOnDbThreadWithTransaction(true) { txn -> val list = forumManager.getForums(txn).map { forums -> - ForumsItem( - forums, - forumManager.getGroupCount(txn, forums.id), + ForumItem( + forum = forums, + groupCount = forumManager.getGroupCount(txn, forums.id), ) } txn.attach { - _fullGroupList.clearAndAddAll(list) + _fullForumList.clearAndAddAll(list) } } } @@ -174,6 +173,6 @@ class ForumsViewModel @Inject constructor( } fun deleteGroup(groupItem: GroupItem) { - forumManager.removeForum((groupItem as ForumsItem).forum) + forumManager.removeForum((groupItem as ForumItem).forum) } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupsCard.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCard.kt similarity index 86% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupsCard.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCard.kt index 68f3a255c00d2a4d22de957c31674054dd3c89fc..7ef6bb884bcbe7ccd2ac242956a2f7a0d24dc0b0 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupsCard.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCard.kt @@ -18,13 +18,13 @@ package org.briarproject.briar.desktop.forums -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement.SpaceBetween 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.padding +import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card @@ -35,8 +35,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Alignment.Companion.Start import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.text +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import org.briarproject.bramble.api.sync.GroupId import org.briarproject.briar.desktop.theme.selectedCard @@ -44,13 +47,14 @@ import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP -import org.briarproject.briar.desktop.utils.PreviewUtils +import org.briarproject.briar.desktop.utils.PreviewUtils.preview import org.briarproject.briar.desktop.utils.TimeUtils.getFormattedTimestamp +import org.briarproject.briar.desktop.utils.buildBlankAnnotatedString @Suppress("HardCodedStringLiteral") -fun main() = PreviewUtils.preview { +fun main() = preview { Column(Modifier.selectableGroup()) { - GroupsCard( + GroupCard( item = object : GroupItem { override val id: GroupId = GroupId(getRandomId()) override val name: String = @@ -58,6 +62,7 @@ fun main() = PreviewUtils.preview { override val msgCount: Int = 42 override val unread: Int = 23 override val timestamp: Long = System.currentTimeMillis() + override val description: AnnotatedString = buildBlankAnnotatedString { } }, onGroupItemSelected = {}, selected = false, @@ -66,7 +71,7 @@ fun main() = PreviewUtils.preview { } @Composable -fun GroupsCard( +fun GroupCard( item: GroupItem, onGroupItemSelected: (GroupItem) -> Unit, selected: Boolean, @@ -75,7 +80,7 @@ fun GroupsCard( modifier = Modifier .fillMaxWidth() .defaultMinSize(minHeight = HEADER_SIZE) - .clickable(onClick = { onGroupItemSelected(item) }) + .selectable(selected, onClick = { onGroupItemSelected(item) }, role = Role.Button) .semantics { contentDescription = if (selected) i18n("access.list.selected.yes") @@ -90,7 +95,11 @@ fun GroupsCard( contentColor = MaterialTheme.colors.onSurface, ) { Row( - modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 4.dp) + .semantics { + text = item.description + }, ) { GroupCircle(item, modifier = Modifier.align(Alignment.Top).padding(vertical = 12.dp)) Column( 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/forums/GroupCircle.kt index 76874e0a7b603285e487dd3682bb1b30f3f2e1ea..c3b244d2598b87e7ad8f6a216c11f4448a404934 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCircle.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupCircle.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.unit.sp import org.briarproject.bramble.api.sync.GroupId import org.briarproject.briar.desktop.theme.outline import org.briarproject.briar.desktop.ui.NumberBadge +import org.briarproject.briar.desktop.utils.InternationalizationUtils.locale @Composable fun GroupCircle(item: GroupItem, showMessageCount: Boolean = true, modifier: Modifier = Modifier) { @@ -54,7 +55,7 @@ fun GroupCircle(item: GroupItem, showMessageCount: Boolean = true, modifier: Mod .background(item.id.getBackgroundColor()), ) { Text( - text = item.name.substring(0..0).uppercase(), + text = item.name.substring(0..0).uppercase(locale), color = Color.White, style = MaterialTheme.typography.body1.copy( fontSize = 24.sp, @@ -75,12 +76,12 @@ fun GroupCircle(item: GroupItem, showMessageCount: Boolean = true, modifier: Mod fun GroupId.getBackgroundColor(): Color { return Color( - red = getByte(bytes, 0) * 3 / 4 + 96, - green = getByte(bytes, 1) * 3 / 4 + 96, - blue = getByte(bytes, 2) * 3 / 4 + 96, + red = bytes.getByte(0) * 3 / 4 + 96, + green = bytes.getByte(1) * 3 / 4 + 96, + blue = bytes.getByte(2) * 3 / 4 + 96, ) } -private fun getByte(bytes: ByteArray, index: Int): Byte { - return bytes[index % bytes.size] +private fun ByteArray.getByte(index: Int): Byte { + return this[index % size] } 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 679d8a76f72a738733f0fb3cd792c1f8bc18c5d2..600b8fbd17cb4d12172027e2edbb4e0ee33e1ce3 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 @@ -60,7 +60,7 @@ import org.briarproject.briar.desktop.viewmodel.viewModel @Composable fun GroupConversationScreen( groupItem: GroupItem, - viewModel: ForumsViewModel = viewModel(), + viewModel: ForumViewModel = viewModel(), ) { val selectedPost = remember { mutableStateOf<MessageId?>(null) } Scaffold( @@ -87,7 +87,7 @@ private fun GroupConversationHeader( groupItem: GroupItem, onGroupDelete: () -> Unit, ) { - val deleteGroupDialogState = remember { mutableStateOf(false) } + val deleteGroupDialogVisible = remember { mutableStateOf(false) } val menuState = remember { mutableStateOf(CLOSED) } val close = { menuState.value = CLOSED } Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { @@ -130,7 +130,7 @@ private fun GroupConversationHeader( DropdownMenuItem( onClick = { close() - deleteGroupDialogState.value = true + deleteGroupDialogVisible.value = true } ) { Text( @@ -143,9 +143,9 @@ private fun GroupConversationHeader( } HorizontalDivider(modifier = Modifier.align(BottomCenter)) } - if (deleteGroupDialogState.value) { + if (deleteGroupDialogVisible.value) { DeleteForumDialog( - close = { deleteGroupDialogState.value = false }, + close = { deleteGroupDialogVisible.value = false }, onDelete = onGroupDelete, ) } 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/forums/GroupInputComposable.kt index 152c39e77b5d27cab8c007dcd8cc7c1becae2d9d..1b1b7120e949613b14de6922a9b1db78546baec6 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupInputComposable.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupInputComposable.kt @@ -73,7 +73,7 @@ fun GroupInputComposable( text = if (selectedPost.value == null) { i18n("forum.message.hint") } else { - i18n("forum.message.replay.hint") + i18n("forum.message.reply.hint") }, style = MaterialTheme.typography.body1, ) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsList.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupListComposable.kt similarity index 88% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsList.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupListComposable.kt index e90dd40aea326ce21b11bf710d866a714c7a167c..7ced0abe86fdff5e0996e1ba559ed226b21d7f73 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumsList.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/GroupListComposable.kt @@ -39,17 +39,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +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.contact.SearchTextField import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants import org.briarproject.briar.desktop.ui.Constants.COLUMN_WIDTH import org.briarproject.briar.desktop.ui.HorizontalDivider +import org.briarproject.briar.desktop.ui.SearchTextField import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n @Composable -fun ForumsList( +fun GroupListComposable( list: State<List<GroupItem>>, isSelected: (GroupId) -> Boolean, filterBy: State<String>, @@ -70,6 +72,7 @@ fun ForumsList( placeholder = i18n("forum.search.title"), icon = Icons.Filled.AddComment, searchValue = filterBy.value, + addButtonDescription = i18n("forum.add.title"), onValueChange = onFilterSet, onAddButtonClicked = onAddButtonClicked, ) @@ -77,10 +80,13 @@ fun ForumsList( Box(modifier = Modifier.fillMaxSize()) { LazyColumn( state = scrollState, - modifier = Modifier.selectableGroup() + modifier = Modifier + .semantics { + contentDescription = i18n("access.forums.list") + }.selectableGroup() ) { items(list.value) { item -> - GroupsCard( + GroupCard( item = item, onGroupItemSelected = onGroupItemSelected, selected = isSelected(item.id) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadItem.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadItem.kt index bd1dc746ed71c64ae6408b379129eec083d693a3..ee751e90dd109bf6c4b54e350e227bce82a7a046 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadItem.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ThreadItem.kt @@ -55,21 +55,10 @@ abstract class ThreadItem( private var level: Int = UNDEFINED var isHighlighted = false - fun getLevel(): Int { - return level - } - - override fun getId(): MessageId { - return messageId - } - - override fun getParentId(): MessageId? { - return parentId - } - - override fun getTimestamp(): Long { - return timestamp - } + override fun getId(): MessageId = messageId + override fun getParentId(): MessageId? = parentId + override fun getTimestamp(): Long = timestamp + fun getLevel(): Int = level /** * Returns the author's name, with an alias if one exists. @@ -81,9 +70,7 @@ abstract class ThreadItem( this.level = level } - override fun hashCode(): Int { - return messageId.hashCode() - } + override fun hashCode(): Int = messageId.hashCode() override fun equals(other: Any?): Boolean { return other is ThreadItem && messageId == other.messageId diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt index 545c12cd7969033e0df5fdb5f7ad82613e1501d2..e18685288a955c709acdd84e74bd37d5ec2c67b2 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutors.kt @@ -30,6 +30,11 @@ interface BriarExecutors { @DatabaseExecutor task: (Transaction) -> Unit, ) + suspend fun <T> runOnDbThread( + readOnly: Boolean, + @DatabaseExecutor task: (Transaction) -> T, + ): T + fun onUiThread(@UiExecutor task: () -> Unit) fun onIoThread(@IoExecutor task: () -> Unit) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt index f002b48e0a265e5b889dc3e9f606db58d5ef8238..0700aa2092aefa6bd9f67e23ca8818b28ee197a6 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threading/BriarExecutorsImpl.kt @@ -20,6 +20,7 @@ package org.briarproject.briar.desktop.threading import mu.KotlinLogging import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.DbCallable import org.briarproject.bramble.api.db.Transaction import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.lifecycle.IoExecutor @@ -27,6 +28,8 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.briar.desktop.utils.KLoggerUtils.w import java.util.concurrent.Executor import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine class BriarExecutorsImpl @Inject @@ -75,6 +78,18 @@ constructor( } } + override suspend fun <T> runOnDbThread( + readOnly: Boolean, + @DatabaseExecutor task: (Transaction) -> T + ) = suspendCoroutine<T> { cont -> + // The coroutine suspends until the DatabaseExecutor has finished the task + // and ended the transaction. It then resumes with the returned value. + onDbThread { + val t = db.transactionWithResult(readOnly, DbCallable { txn -> task(txn) }) + cont.resume(t) + } + } + override fun onUiThread(@UiExecutor task: () -> Unit) = uiExecutor.execute(task) override fun onIoThread(@IoExecutor task: () -> Unit) = ioExecutor.execute(task) 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 43245ff4560f94e538aa4466531639a0f8af1a94..0e2dfd36d70490888ba8ce97b7497e99f6c2d8b8 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 @@ -21,7 +21,7 @@ package org.briarproject.briar.desktop.ui import androidx.compose.foundation.layout.Row import androidx.compose.runtime.Composable import org.briarproject.briar.desktop.conversation.PrivateMessageScreen -import org.briarproject.briar.desktop.forums.ForumsScreen +import org.briarproject.briar.desktop.forums.ForumScreen import org.briarproject.briar.desktop.navigation.BriarSidebar import org.briarproject.briar.desktop.navigation.SidebarViewModel import org.briarproject.briar.desktop.privategroups.PrivateGroupScreen @@ -45,7 +45,7 @@ fun MainScreen(viewModel: SidebarViewModel = viewModel()) { when (viewModel.uiMode.value) { UiMode.CONTACTS -> PrivateMessageScreen() UiMode.GROUPS -> PrivateGroupScreen() - UiMode.FORUMS -> ForumsScreen() + UiMode.FORUMS -> ForumScreen() UiMode.SETTINGS -> SettingsScreen() UiMode.ABOUT -> AboutScreen() else -> UiPlaceholder() diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/SearchTextField.kt similarity index 93% rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/SearchTextField.kt index 6d3e564dfb76184dfd532e37f97ecea736287ae5..b61a0466abd269944416fe7c0b46b42f18f1ba1d 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/SearchTextField.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/ui/SearchTextField.kt @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -package org.briarproject.briar.desktop.contact +package org.briarproject.briar.desktop.ui import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -32,7 +32,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp -import org.briarproject.briar.desktop.ui.ColoredIconButton import org.briarproject.briar.desktop.utils.AccessibilityUtils.description import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n @@ -41,6 +40,7 @@ fun SearchTextField( placeholder: String, icon: ImageVector, searchValue: String, + addButtonDescription: String, onValueChange: (String) -> Unit, onAddButtonClicked: () -> Unit, ) { @@ -61,7 +61,7 @@ fun SearchTextField( ColoredIconButton( icon = icon, iconSize = 20.dp, - contentDescription = i18n("access.contacts.add"), + contentDescription = addButtonDescription, onClick = onAddButtonClicked, modifier = Modifier.padding(end = 8.dp) ) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt index b82cb09a2fa6579730447c577bcf455dda25fd20..8547165856fb888d493aec9fd1cd1524b39d764c 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt @@ -18,26 +18,18 @@ package org.briarproject.briar.desktop.viewmodel -import mu.KotlinLogging import org.briarproject.bramble.api.db.DatabaseExecutor -import org.briarproject.bramble.api.db.DbCallable import org.briarproject.bramble.api.db.Transaction import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.briar.desktop.threading.BriarExecutors -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine abstract class DbViewModel( private val briarExecutors: BriarExecutors, private val lifecycleManager: LifecycleManager, - private val db: TransactionManager + private val db: TransactionManager, ) : ViewModel { - companion object { - private val LOG = KotlinLogging.logger {} - } - /** * Waits for the DB to open and runs the given [task] on the [DatabaseExecutor]. * To avoid inconsistent state between the database and the UI @@ -54,16 +46,15 @@ abstract class DbViewModel( */ protected fun runOnDbThreadWithTransaction( readOnly: Boolean, - task: (Transaction) -> Unit + task: (Transaction) -> Unit, ) = briarExecutors.onDbThreadWithTransaction(readOnly, task) + /** + * Waits for the DB to open and runs the given [task] on the [DatabaseExecutor], + * returning its result. + */ protected suspend fun <T> runOnDbThread( readOnly: Boolean, - task: (Transaction) -> T - ) = suspendCoroutine<T> { cont -> - briarExecutors.onDbThread { - val t = db.transactionWithResult(readOnly, DbCallable { txn -> task(txn) }) - cont.resume(t) - } - } + @DatabaseExecutor task: (Transaction) -> T, + ): T = briarExecutors.runOnDbThread(readOnly, task) } 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 9f29948d41552c0491696786e78b53717e7904f9..bcb43539967d35b874c42033ecf5b643927ed529 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 @@ -25,7 +25,7 @@ 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.ForumsViewModel +import org.briarproject.briar.desktop.forums.ForumViewModel import org.briarproject.briar.desktop.introduction.IntroductionViewModel import org.briarproject.briar.desktop.login.StartupViewModel import org.briarproject.briar.desktop.navigation.SidebarViewModel @@ -76,8 +76,8 @@ abstract class ViewModelModule { @Binds @IntoMap - @ViewModelKey(ForumsViewModel::class) - abstract fun bindForumsViewModel(forumsViewModel: ForumsViewModel): ViewModel + @ViewModelKey(ForumViewModel::class) + abstract fun bindForumsViewModel(forumViewModel: ForumViewModel): ViewModel @Binds @IntoMap diff --git a/briar-desktop/src/main/resources/strings/BriarDesktop.properties b/briar-desktop/src/main/resources/strings/BriarDesktop.properties index 28a7b003cd1691f9bb38fa39dff7a96fd20b5427..0ccafe4e803b7f07a32ba9acceb40809e9f65661 100644 --- a/briar-desktop/src/main/resources/strings/BriarDesktop.properties +++ b/briar-desktop/src/main/resources/strings/BriarDesktop.properties @@ -60,6 +60,9 @@ access.settings.click_to_toggle_notifications=Click to toggle notifications access.return_to_previous_screen=Return to previous screen access.menu=Show menu access.forums.add=Add forum +access.forums.list=forum list +access.forums.unread_count={0, plural, one {one unread posts} other {{0} unread posts}} +access.forums.last_message_timestamp=last message: {0} # Contacts contacts.none_selected.title=No contact selected @@ -115,7 +118,7 @@ forum.delete.dialog.title=Confirm Leaving Forum forum.delete.dialog.message=Are you sure that you want to leave this forum?\n\nAny contacts you\'ve shared this forum with might stop receiving updates. forum.delete.dialog.button=Leave forum.message.hint=New Post -forum.message.replay.hint=New Reply +forum.message.reply.hint=New Reply group.card.no_posts=No posts group.card.posts={0, plural, one {{0} post} other {{0} posts}}