diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt index 862f0360df3b60b7922d4e2e7ee4085eeeeb117b..90b493351cb6f2c9391e79fb78a370f8b25949ca 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt @@ -18,24 +18,58 @@ package org.briarproject.briar.desktop.blog +import androidx.compose.foundation.layout.Arrangement.SpaceBetween +import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Share import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment.Companion.BottomCenter import androidx.compose.ui.Alignment.Companion.Center +import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.blog.sharing.BlogSharingStatusDrawerContent +import org.briarproject.briar.desktop.blog.sharing.BlogSharingViewModel +import org.briarproject.briar.desktop.threadedgroup.SharingStrings +import org.briarproject.briar.desktop.threadedgroup.sharing.ThreadedGroupSharingActionDrawerContent +import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE +import org.briarproject.briar.desktop.ui.HorizontalDivider +import org.briarproject.briar.desktop.ui.getInfoDrawerHandler import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF import org.briarproject.briar.desktop.viewmodel.viewModel @Composable -fun BlogScreen(onSideBarClickedKey: Any, viewModel: FeedViewModel = viewModel()) { - LaunchedEffect(onSideBarClickedKey) { viewModel.selectBlog(null) } +fun BlogScreen(viewModel: FeedViewModel = viewModel()) { Scaffold( + topBar = { + // only show header if some blog is selected + if (viewModel.selectedBlog.value != null) { + BlogHeader( + blogSharingViewModel = viewModel.blogSharingViewModel, + onBackClick = { viewModel.selectBlog(null) } + ) + } + }, content = { padding -> if (viewModel.isLoading.value) { Box( @@ -65,10 +99,114 @@ fun BlogScreen(onSideBarClickedKey: Any, viewModel: FeedViewModel = viewModel()) } }, bottomBar = { - val onCloseReply = { viewModel.selectPost(null) } - BlogInput(viewModel.selectedPost.value, onCloseReply) { text -> - viewModel.createBlogPost(text) + // only show input field if no blog is selected, or if a blog and a post are selected + if (viewModel.selectedBlog.value == null || viewModel.selectedPost.value != null) { + val onCloseReply = { viewModel.selectPost(null) } + BlogInput(viewModel.selectedPost.value, onCloseReply) { text -> + viewModel.createBlogPost(text) + } } } ) } + +@Composable +private fun BlogHeader( + blogSharingViewModel: BlogSharingViewModel, + onBackClick: () -> Unit, +) { + val isMenuOpen = remember { mutableStateOf(false) } + Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { + Row( + horizontalArrangement = SpaceBetween, + verticalAlignment = CenterVertically, + modifier = Modifier.fillMaxWidth().align(Center).padding(horizontal = 16.dp), + ) { + Row( + horizontalArrangement = spacedBy(8.dp), + verticalAlignment = CenterVertically, + ) { + IconButton( + icon = Icons.Filled.ArrowBack, + contentDescription = i18n("blog.back"), + onClick = onBackClick, + ) + Column { + Text( + text = blogSharingViewModel.contactName.value, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h2, + ) + val sharingInfo = blogSharingViewModel.sharingInfo.value + Text( + text = i18nF("blog.sharing.status.with", sharingInfo.total, sharingInfo.online) + ) + } + } + Row( + horizontalArrangement = spacedBy(4.dp), + verticalAlignment = CenterVertically, + ) { + val infoDrawerHandler = getInfoDrawerHandler() + IconButton( + icon = Icons.Filled.Share, + contentDescription = i18n("blog.sharing.action.title"), + onClick = { + infoDrawerHandler.open { + ThreadedGroupSharingActionDrawerContent( + close = infoDrawerHandler::close, + viewModel = blogSharingViewModel, + strings = object : SharingStrings { + override val sharingActionTitle = i18n("blog.sharing.action.title") + override val sharingActionClose = i18n("access.forum.sharing.action.close") + override val sharingActionNoContacts = i18n("blog.sharing.action.no_contacts") + }, + ) + } + }, + ) + IconButton( + icon = Icons.Filled.MoreVert, + contentDescription = i18n("access.menu"), + onClick = { isMenuOpen.value = true }, + ) { + BlogDropdownMenu( + expanded = isMenuOpen.value, + onClose = { isMenuOpen.value = false }, + blogSharingViewModel = blogSharingViewModel, + ) + } + } + } + HorizontalDivider(modifier = Modifier.align(BottomCenter)) + } +} + +@Composable +private fun BlogDropdownMenu( + expanded: Boolean, + onClose: () -> Unit, + blogSharingViewModel: BlogSharingViewModel, +) = DropdownMenu( + expanded = expanded, + onDismissRequest = onClose, +) { + val infoDrawerHandler = getInfoDrawerHandler() + DropdownMenuItem( + onClick = { + onClose() + infoDrawerHandler.open { + BlogSharingStatusDrawerContent( + close = infoDrawerHandler::close, + viewModel = blogSharingViewModel, + ) + } + } + ) { + Text( + i18n("blog.sharing.status.title"), + style = MaterialTheme.typography.body2, + ) + } +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt index 5b0f3ab8148fc81d5c69e98eeefb2e5a02944d5c..14c720ec5e26efbab66b2062c9b3fbb25654b8d2 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt @@ -37,6 +37,7 @@ import org.briarproject.briar.api.blog.BlogManager import org.briarproject.briar.api.blog.BlogPostFactory import org.briarproject.briar.api.blog.BlogPostHeader import org.briarproject.briar.api.blog.event.BlogPostAddedEvent +import org.briarproject.briar.desktop.blog.sharing.BlogSharingViewModel import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.UiExecutor import org.briarproject.briar.desktop.ui.UnreadFabsInfo @@ -52,6 +53,7 @@ class FeedViewModel @Inject constructor( private val blogManager: BlogManager, private val blogPostFactory: BlogPostFactory, private val identityManager: IdentityManager, + val blogSharingViewModel: BlogSharingViewModel, briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, @@ -81,6 +83,16 @@ class FeedViewModel @Inject constructor( runOnDbThreadWithTransaction(true, this::loadAllBlogPosts) } + override fun onInit() { + super.onInit() + blogSharingViewModel.onEnterComposition() + } + + override fun onCleared() { + super.onCleared() + blogSharingViewModel.onExitComposition() + } + @Suppress("HardCodedStringLiteral") override fun eventOccurred(e: Event) { if (e is BlogPostAddedEvent) { @@ -136,6 +148,12 @@ class FeedViewModel @Inject constructor( @UiExecutor fun selectBlog(groupId: GroupId?) { _selectedBlog.value = groupId + if (groupId != null) { + blogSharingViewModel.setGroupId(groupId) + // abort re-posting when navigating to somebody else's blog + if (_selectedPost.value?.groupId != groupId) + _selectedPost.value = null + } } @UiExecutor diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/sharing/BlogSharingStatusDrawerContent.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/sharing/BlogSharingStatusDrawerContent.kt new file mode 100644 index 0000000000000000000000000000000000000000..99620cf531ea7f137489997476d6cd48122e13e1 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/sharing/BlogSharingStatusDrawerContent.kt @@ -0,0 +1,109 @@ +/* + * 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.blog.sharing + +import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Info +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.contact.ContactItemViewSmall +import org.briarproject.briar.desktop.ui.Constants +import org.briarproject.briar.desktop.ui.HorizontalDivider +import org.briarproject.briar.desktop.ui.ListItemView +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n + +@Composable +fun BlogSharingStatusDrawerContent( + close: () -> Unit, + viewModel: BlogSharingViewModel, +) = Column { + Row(Modifier.fillMaxWidth().height(Constants.HEADER_SIZE)) { + IconButton( + icon = Icons.Filled.Close, + contentDescription = i18n("access.forum.sharing.status.close"), + onClick = close, + modifier = Modifier.padding(start = 24.dp).size(24.dp).align(CenterVertically) + ) + Text( + text = i18n("blog.sharing.status.title"), + modifier = Modifier.align(CenterVertically).padding(start = 16.dp), + style = MaterialTheme.typography.h3, + ) + } + HorizontalDivider() + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = spacedBy(8.dp), + modifier = Modifier.padding(8.dp), + ) { + Icon( + imageVector = Icons.Filled.Info, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Text( + text = i18n("blog.sharing.status.info"), + style = MaterialTheme.typography.body2, + ) + } + HorizontalDivider() + Box(Modifier.fillMaxSize()) { + if (viewModel.currentlySharedWith.value.isEmpty()) { + // todo: this might be shown to the user while the list is still loading + Text( + text = i18n("blog.sharing.status.nobody"), + style = MaterialTheme.typography.body1, + modifier = Modifier.align(Alignment.Center), + ) + } else { + LazyColumn { + items( + items = viewModel.currentlySharedWith.value, + key = { it.id }, + ) { contactItem -> + ListItemView { + ContactItemViewSmall( + contactItem, + modifier = Modifier.padding(8.dp) + ) + } + } + } + } + } +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/sharing/BlogSharingViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/sharing/BlogSharingViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..9cfbc2b6f6229ad525777d52ac99b1456aca4c3d --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/sharing/BlogSharingViewModel.kt @@ -0,0 +1,153 @@ +/* + * 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.blog.sharing + +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.mutableStateOf +import mu.KotlinLogging +import org.briarproject.bramble.api.connection.ConnectionRegistry +import org.briarproject.bramble.api.contact.ContactManager +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.plugin.event.ContactConnectedEvent +import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent +import org.briarproject.bramble.api.sync.GroupId +import org.briarproject.briar.api.blog.BlogManager +import org.briarproject.briar.api.blog.BlogSharingManager +import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent +import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.identity.AuthorManager +import org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHAREABLE +import org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHARING +import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent +import org.briarproject.briar.desktop.threadedgroup.sharing.InvitationSentEvent +import org.briarproject.briar.desktop.threadedgroup.sharing.ThreadedGroupSharingViewModel +import org.briarproject.briar.desktop.threading.BriarExecutors +import org.briarproject.briar.desktop.threading.UiExecutor +import org.briarproject.briar.desktop.viewmodel.asState +import org.briarproject.briar.desktop.viewmodel.update +import javax.inject.Inject + +class BlogSharingViewModel @Inject constructor( + private val blogManager: BlogManager, + private val blogSharingManager: BlogSharingManager, + contactManager: ContactManager, + authorManager: AuthorManager, + conversationManager: ConversationManager, + private val connectionRegistry: ConnectionRegistry, + briarExecutors: BriarExecutors, + lifecycleManager: LifecycleManager, + db: TransactionManager, + eventBus: EventBus, +) : ThreadedGroupSharingViewModel( + contactManager, + authorManager, + conversationManager, + connectionRegistry, + briarExecutors, + lifecycleManager, + db, + eventBus, +) { + + companion object { + private val LOG = KotlinLogging.logger {} + } + + private val _contactName = mutableStateOf("") + val contactName = _contactName.asState() + + val currentlySharedWith = derivedStateOf { + _contactList.filter { _sharingStatus.value[it.id] == SHARING } + } + + override fun reload() { + _shareableSelected.value = emptySet() + _sharingMessage.value = "" + val groupId = _groupId ?: return + runOnDbThreadWithTransaction(true) { txn -> + val contactName = blogManager.getBlog(txn, groupId).author.name + loadSharingStatus(txn, groupId) + txn.attach { _contactName.value = contactName } + } + } + + @UiExecutor + override fun shareThreadedGroup() = runOnDbThreadWithTransaction(false) { txn -> + val groupId = _groupId ?: return@runOnDbThreadWithTransaction + val message = _sharingMessage.value.ifEmpty { null } + _shareableSelected.value.forEach { contactId -> + blogSharingManager.sendInvitation(txn, groupId, contactId, message) + } + // send custom event to force message reload if private chat is open for contactId + // todo: switch to a more generic approach where every locally sent message broadcasts an event per default + txn.attach(InvitationSentEvent(_shareableSelected.value.toList())) + txn.attach { reload() } + } + + @UiExecutor + override fun eventOccurred(e: Event) { + super.eventOccurred(e) + + val groupId = _groupId ?: return + when { + e is BlogInvitationResponseReceivedEvent && e.messageHeader.shareableId == groupId -> { + if (e.messageHeader.wasAccepted()) { + _sharingStatus.value += e.contactId to SHARING + val connected = connectionRegistry.isConnected(e.contactId) + _sharingInfo.update { addContact(connected) } + } else { + _sharingStatus.value += e.contactId to SHAREABLE + } + } + + e is ContactLeftShareableEvent && e.groupId == groupId -> { + _sharingStatus.value += e.contactId to SHAREABLE + val connected = connectionRegistry.isConnected(e.contactId) + _sharingInfo.update { removeContact(connected) } + } + + e is ContactConnectedEvent -> { + if (_sharingStatus.value[e.contactId] == SHARING) + _sharingInfo.update { updateContactConnected(true) } + } + + e is ContactDisconnectedEvent -> { + if (_sharingStatus.value[e.contactId] == SHARING) + _sharingInfo.update { updateContactConnected(false) } + } + } + } + + private fun loadSharingStatus(txn: Transaction, groupId: GroupId) { + val map = contactManager.getContacts(txn).associate { contact -> + contact.id to blogSharingManager.getSharingStatus(txn, groupId, contact) + } + val sharing = map.filterValues { it == SHARING }.keys + txn.attach { + val online = + sharing.fold(0) { acc, it -> if (connectionRegistry.isConnected(it)) acc + 1 else acc } + _sharingStatus.value = map + _sharingInfo.value = SharingInfo(sharing.size, online) + } + } +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt index 4c7a089c8c443807aec8412bc273eb6b40b0d51f..d211627e2f1878672ab5c25134f706da724d5fe8 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt @@ -47,6 +47,7 @@ import org.briarproject.briar.api.attachment.AttachmentHeader import org.briarproject.briar.api.attachment.AttachmentReader import org.briarproject.briar.api.autodelete.UnexpectedTimerException import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent +import org.briarproject.briar.api.blog.BlogSharingManager import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.DeletionResult import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent @@ -70,7 +71,6 @@ import org.briarproject.briar.desktop.conversation.ConversationRequestItem.Reque import org.briarproject.briar.desktop.threadedgroup.sharing.InvitationSentEvent import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.UiExecutor -import org.briarproject.briar.desktop.utils.KLoggerUtils.e import org.briarproject.briar.desktop.utils.KLoggerUtils.i import org.briarproject.briar.desktop.utils.KLoggerUtils.logDuration import org.briarproject.briar.desktop.utils.KLoggerUtils.w @@ -97,6 +97,7 @@ constructor( private val introductionManager: IntroductionManager, private val forumSharingManager: ForumSharingManager, private val groupInvitationManager: GroupInvitationManager, + private val blogSharingManager: BlogSharingManager, private val messagingManager: MessagingManager, private val privateMessageFactory: PrivateMessageFactory, briarExecutors: BriarExecutors, @@ -463,8 +464,17 @@ constructor( ) } - BLOG -> - LOG.e { "Blogs are not supported for the time being." } + BLOG -> { + require(desktopFeatureFlags.shouldEnableBlogs()) { + "Blog requests are not supported for this build." // NON-NLS + } + blogSharingManager.respondToInvitation( + /* txn = */ txn, + /* c = */ _contactId.value!!, + /* id = */ item.sessionId, + /* accept = */ accept + ) + } } // reload all messages to also show request response message // todo: might be better to have an event to react to, also for (other) outgoing messages diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt index d236e61f78a09fa1dcdaed15b8fec593f78f5cf5..d8f6441bf6268d367fe10dc77738e281671c297e 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/SidebarViewModel.kt @@ -37,7 +37,6 @@ import org.briarproject.briar.desktop.viewmodel.ViewModel import org.briarproject.briar.desktop.viewmodel.asState import org.briarproject.briar.desktop.viewmodel.update import javax.inject.Inject -import kotlin.random.Random class SidebarViewModel @Inject @@ -80,19 +79,12 @@ constructor( private val _uiMode = mutableStateOf(UiMode.CONTACTS) private val _account = mutableStateOf<LocalAuthor?>(null) private val _mailboxProblem = mutableStateOf(false) - private val _blogResetView = mutableStateOf(0L) private val _messageCount = mutableStateOf(MessageCount()) val uiMode = _uiMode.asState() val account = _account.asState() - /** - * A random number that changes everytime, the user changes the [UiMode] with [setUiMode] to [UiMode.BLOGS]. - * This can be useful to reset the blog screen to the main blog feed instead of showing an individual blog. - * When we have a proper navigation system in place, it can hopefully replace this hack. - */ - val blogResetView = _blogResetView.asState() val mailboxProblem = _mailboxProblem.asState() val messageCount = _messageCount.asState() @@ -100,7 +92,6 @@ constructor( @UiExecutor fun setUiMode(uiMode: UiMode) { _uiMode.value = uiMode - if (uiMode == UiMode.BLOGS) _blogResetView.value = Random.nextLong() if (uiMode == UiMode.MAILBOX) _mailboxProblem.value = false } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupStrings.kt index ce8bb7b8b91840d8f04b700320a6eb674cb14f67..4f3fb82882890e104993a55f6003fcced3bfb72f 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupStrings.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/ThreadedGroupStrings.kt @@ -43,7 +43,13 @@ abstract class ThreadedGroupStrings( val messageReplyClose: String, val messageReplyHint: String, val messageHint: String, - val sharingActionTitle: String, - val sharingActionClose: String, - val sharingActionNoContacts: String, -) + override val sharingActionTitle: String, + override val sharingActionClose: String, + override val sharingActionNoContacts: String, +) : SharingStrings + +interface SharingStrings { + val sharingActionTitle: String + val sharingActionClose: String + val sharingActionNoContacts: String +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/sharing/ThreadedGroupSharingActionDrawerContent.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/sharing/ThreadedGroupSharingActionDrawerContent.kt index 944af65750665bf6e2ce5b88562f21df6939ad49..ff076d2cf2995a6ef942e868bcca6ba3c32e03c7 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/sharing/ThreadedGroupSharingActionDrawerContent.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/threadedgroup/sharing/ThreadedGroupSharingActionDrawerContent.kt @@ -48,7 +48,7 @@ import org.briarproject.briar.api.sharing.SharingManager.SharingStatus.NOT_SUPPO import org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHAREABLE import org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHARING import org.briarproject.briar.desktop.contact.ContactItemViewSmall -import org.briarproject.briar.desktop.threadedgroup.ThreadedGroupStrings +import org.briarproject.briar.desktop.threadedgroup.SharingStrings import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.HorizontalDivider import org.briarproject.briar.desktop.ui.ListItemView @@ -59,7 +59,7 @@ import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n fun ThreadedGroupSharingActionDrawerContent( close: () -> Unit, viewModel: ThreadedGroupSharingViewModel, - strings: ThreadedGroupStrings, + strings: SharingStrings, ) = Column { Row( verticalAlignment = CenterVertically, 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 e014690de7f50fa5386e2ddd1369cc5d20ba58c1..549f6b401c4403278b79e2ec668b8589aeffe3f2 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 @@ -65,7 +65,7 @@ fun MainScreen(viewModel: SidebarViewModel = viewModel()) { UiMode.CONTACTS -> PrivateMessageScreen() UiMode.GROUPS -> PrivateGroupScreen() UiMode.FORUMS -> ForumScreen() - UiMode.BLOGS -> BlogScreen(viewModel.blogResetView.value) + UiMode.BLOGS -> BlogScreen() UiMode.MAILBOX -> MailboxScreen() UiMode.SETTINGS -> SettingsScreen() UiMode.ABOUT -> AboutScreen() diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModel.kt index 17af4811fa33cb65d8d45bafb8ded7f9a063bf69..20906425cbb6390111ce517b947374782bcf6594 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/ViewModel.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 @@ -64,7 +64,7 @@ abstract class ViewModel { * Composable function goes out of scope. * * This function can be overridden in child classes, - * but implementations should always call `super.onInit()` first. + * but implementations should always call `super.onCleared()` first. * * Apart from that, **do not call this function manually anywhere.** */ diff --git a/briar-desktop/src/main/resources/strings/BriarDesktop.properties b/briar-desktop/src/main/resources/strings/BriarDesktop.properties index e41d59318953cbfde67d999d4b64ef9a45148482..bc63011880be26ed513a07409009e8e3ef872d90 100644 --- a/briar-desktop/src/main/resources/strings/BriarDesktop.properties +++ b/briar-desktop/src/main/resources/strings/BriarDesktop.properties @@ -223,6 +223,13 @@ blog.post.reply.intro=Reblog this post: blog.post.hint=Type your blog post blog.post.reply.hint=Add a comment (optional) blog.open.from.author=Open blog of {0} +blog.back=Show all blogs +blog.sharing.status.title=Sharing Status +blog.sharing.status.info=Anyone who subscribes to a blog can share it with their contacts. You are sharing this blog with the following contacts. There may also be other subscribers who you can't see. +blog.sharing.status.with=Shared with {0} ({1} online) +blog.sharing.status.nobody=Nobody +blog.sharing.action.title=Share Blog +blog.sharing.action.no_contacts=No contacts yet. You can only share blogs with your contacts. # Introduction introduction.introduce=Make Introduction