From 6670089e3394c1372ed2e9904c85c70db4a2774e Mon Sep 17 00:00:00 2001 From: ialokim <ialokim@mailbox.org> Date: Mon, 18 Oct 2021 01:35:44 +0200 Subject: [PATCH] move add contact logic to separate view model --- .../briar/desktop/contact/ContactList.kt | 12 ++- .../desktop/contact/ContactsViewModel.kt | 80 --------------- .../{ => add/remote}/AddContactDialog.kt | 23 +++-- .../contact/add/remote/AddContactViewModel.kt | 99 +++++++++++++++++++ .../conversation/PrivateMessageView.kt | 10 +- .../briarproject/briar/desktop/ui/BriarUi.kt | 4 +- .../briar/desktop/ui/MainScreen.kt | 4 +- 7 files changed, 135 insertions(+), 97 deletions(-) rename src/main/kotlin/org/briarproject/briar/desktop/contact/{ => add/remote}/AddContactDialog.kt (80%) create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt index 2c858b284b..7fde6fe404 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactList.kt @@ -10,8 +10,14 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.contact.add.remote.AddContactDialog +import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants.CONTACTLIST_WIDTH import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE @@ -19,8 +25,10 @@ import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE @Composable fun ContactList( viewModel: ContactsViewModel, + addContactViewModel: AddContactViewModel, ) { - if (viewModel.addContactDialogVisible.value) AddContactDialog(viewModel) + var isContactDialogVisible by remember { mutableStateOf(false) } + if (isContactDialogVisible) AddContactDialog(addContactViewModel) { isContactDialogVisible = false } Scaffold( modifier = Modifier.fillMaxHeight().width(CONTACTLIST_WIDTH), backgroundColor = MaterialTheme.colors.surfaceVariant, @@ -31,7 +39,7 @@ fun ContactList( SearchTextField( viewModel.filterBy.value, onValueChange = viewModel::setFilterBy, - onContactAdd = viewModel::openAddContactDialog + onContactAdd = { isContactDialogVisible = true } ) } }, diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt index a3a6fe7813..32da44de50 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt @@ -3,28 +3,22 @@ package org.briarproject.briar.desktop.contact import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf -import org.briarproject.bramble.api.FormatException import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.event.ContactAddedEvent import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent import org.briarproject.bramble.api.contact.event.ContactRemovedEvent -import org.briarproject.bramble.api.db.ContactExistsException -import org.briarproject.bramble.api.db.PendingContactExistsException import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.event.EventListener -import org.briarproject.bramble.api.identity.AuthorConstants import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent -import org.briarproject.bramble.util.StringUtils import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent import org.briarproject.briar.api.identity.AuthorManager import org.briarproject.briar.desktop.utils.removeFirst import org.briarproject.briar.desktop.utils.replaceFirst -import java.security.GeneralSecurityException import java.util.logging.Logger import javax.inject.Inject @@ -53,19 +47,10 @@ constructor( private var _selectedContactIndex = -1; private val _selectedContact = mutableStateOf<ContactItem?>(null) - private val _addContactDialogVisible = mutableStateOf(false) - private val _addContactAlias = mutableStateOf("") - private val _addContactLink = mutableStateOf("") - val contactList: List<ContactItem> = _filteredContactList val filterBy: State<String> = _filterBy val selectedContact: State<ContactItem?> = _selectedContact - val addContactDialogVisible: State<Boolean> = _addContactDialogVisible - val addContactAlias: State<String> = _addContactAlias - val addContactLink: State<String> = _addContactLink - var addContactOwnLink = "" - internal fun loadContacts() { _contactList.apply { clear() @@ -79,8 +64,6 @@ constructor( }) } updateFilteredList() - //todo: do in event instead? - addContactOwnLink = contactManager.handshakeLink } fun selectContact(index: Int) { @@ -105,69 +88,6 @@ constructor( updateFilteredList() } - fun openAddContactDialog() { - _addContactDialogVisible.value = true - } - - fun closeAddContactDialog() { - _addContactDialogVisible.value = false - } - - fun setAddContactAlias(alias: String) { - _addContactAlias.value = alias - } - - fun setAddContactLink(link: String) { - _addContactLink.value = link - } - - fun onSubmitAddContactDialog() { - val link = _addContactLink.value - val alias = _addContactAlias.value - addPendingContact(link, alias) - closeAddContactDialog() - } - - private fun addPendingContact(link: String, alias: String) { - if (addContactOwnLink.equals(link)) { - println("Please enter contact's link, not your own") - return - } - if (aliasIsInvalid(alias)) { - println("Alias is invalid") - return - } - - try { - contactManager.addPendingContact(link, alias) - } catch (e: FormatException) { - println("Link is invalid") - println(e.stackTrace) - } catch (e: GeneralSecurityException) { - println("Public key is invalid") - println(e.stackTrace) - } - /* - TODO: Warn user that the following two errors might be an attack - - Use `e.pendingContact.id.bytes` and `e.pendingContact.alias` to implement the following logic: - https://code.briarproject.org/briar/briar-gtk/-/merge_requests/97 - - */ - catch (e: ContactExistsException) { - println("Contact already exists") - println(e.stackTrace) - } catch (e: PendingContactExistsException) { - println("Pending Contact already exists") - println(e.stackTrace) - } - } - - private fun aliasIsInvalid(alias: String): Boolean { - val aliasUtf8 = StringUtils.toUtf8(alias) - return aliasUtf8.isEmpty() || aliasUtf8.size > AuthorConstants.MAX_AUTHOR_NAME_LENGTH - } - override fun eventOccurred(e: Event?) { when (e) { is ContactAddedEvent -> { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/AddContactDialog.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt similarity index 80% rename from src/main/kotlin/org/briarproject/briar/desktop/contact/AddContactDialog.kt rename to src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt index 7a623d2e8d..f6da667938 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/AddContactDialog.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt @@ -1,4 +1,4 @@ -package org.briarproject.briar.desktop.contact +package org.briarproject.briar.desktop.contact.add.remote import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -15,6 +15,7 @@ import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.material.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -22,9 +23,13 @@ import androidx.compose.ui.unit.sp @OptIn(ExperimentalMaterialApi::class) @Composable -fun AddContactDialog(viewModel: ContactsViewModel) { +fun AddContactDialog(viewModel: AddContactViewModel, onClose: () -> Unit) { + LaunchedEffect("fetchHandshake") { + //todo: should instead be done automatically as soon as DB is loaded -> in view model + viewModel.fetchHandshakeLink() + } AlertDialog( - onDismissRequest = viewModel::closeAddContactDialog, + onDismissRequest = onClose, text = { Column(modifier = Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth().padding(vertical = 16.dp)) { @@ -40,8 +45,8 @@ fun AddContactDialog(viewModel: ContactsViewModel) { Modifier.width(128.dp).align(Alignment.CenterVertically), ) TextField( - viewModel.addContactLink.value, - viewModel::setAddContactLink, + viewModel.remoteHandshakeLink.value, + viewModel::setRemoteHandshakeLink, modifier = Modifier.fillMaxWidth() ) } @@ -51,7 +56,7 @@ fun AddContactDialog(viewModel: ContactsViewModel) { Modifier.width(128.dp).align(Alignment.CenterVertically), ) TextField( - viewModel.addContactAlias.value, + viewModel.alias.value, viewModel::setAddContactAlias, modifier = Modifier.fillMaxWidth() ) @@ -62,7 +67,7 @@ fun AddContactDialog(viewModel: ContactsViewModel) { modifier = Modifier.width(128.dp).align(Alignment.CenterVertically), ) TextField( - viewModel.addContactOwnLink, + viewModel.handshakeLink.value, onValueChange = {}, modifier = Modifier.fillMaxWidth() ) @@ -70,13 +75,13 @@ fun AddContactDialog(viewModel: ContactsViewModel) { } }, confirmButton = { - Button(onClick = viewModel::onSubmitAddContactDialog) { + Button(onClick = { viewModel.onSubmitAddContactDialog(); onClose() }) { Text("Add") } }, dismissButton = { TextButton( - onClick = viewModel::closeAddContactDialog + onClick = onClose ) { Text("Cancel", color = MaterialTheme.colors.onSurface) } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt new file mode 100644 index 0000000000..a0b5d3ca04 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt @@ -0,0 +1,99 @@ +package org.briarproject.briar.desktop.contact.add.remote + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import org.briarproject.bramble.api.FormatException +import org.briarproject.bramble.api.contact.ContactManager +import org.briarproject.bramble.api.contact.HandshakeLinkConstants +import org.briarproject.bramble.api.db.ContactExistsException +import org.briarproject.bramble.api.db.PendingContactExistsException +import org.briarproject.bramble.api.identity.AuthorConstants +import org.briarproject.bramble.util.StringUtils +import java.security.GeneralSecurityException +import java.util.logging.Logger +import javax.inject.Inject + +class AddContactViewModel +@Inject +constructor( + private val contactManager: ContactManager, +) { + + companion object { + private val LOG = Logger.getLogger(AddContactViewModel::class.java.name) + } + + private val _alias = mutableStateOf("") + private val _remoteHandshakeLink = mutableStateOf("") + private val _handshakeLink = mutableStateOf("") + + val alias: State<String> = _alias + val remoteHandshakeLink: State<String> = _remoteHandshakeLink + val handshakeLink: State<String> = _handshakeLink + + fun setAddContactAlias(alias: String) { + _alias.value = alias + } + + fun setRemoteHandshakeLink(link: String) { + _remoteHandshakeLink.value = link + } + + fun fetchHandshakeLink() { + _handshakeLink.value = contactManager.handshakeLink + } + + fun onSubmitAddContactDialog() { + val link = _remoteHandshakeLink.value + val alias = _alias.value + addPendingContact(link, alias) + } + + private fun addPendingContact(link: String, alias: String) { + if (_handshakeLink.value == link) { + println("Please enter contact's link, not your own") + return + } + if (remoteHandshakeLinkIsInvalid(link)) { + println("Remote handshake link is invalid") + return + } + if (aliasIsInvalid(alias)) { + println("Alias is invalid") + return + } + + try { + contactManager.addPendingContact(link, alias) + } catch (e: FormatException) { + println("Link is invalid") + println(e.stackTrace) + } catch (e: GeneralSecurityException) { + println("Public key is invalid") + println(e.stackTrace) + } + /* + TODO: Warn user that the following two errors might be an attack + + Use `e.pendingContact.id.bytes` and `e.pendingContact.alias` to implement the following logic: + https://code.briarproject.org/briar/briar-gtk/-/merge_requests/97 + + */ + catch (e: ContactExistsException) { + println("Contact already exists") + println(e.stackTrace) + } catch (e: PendingContactExistsException) { + println("Pending Contact already exists") + println(e.stackTrace) + } + } + + private fun remoteHandshakeLinkIsInvalid(link: String): Boolean { + return !HandshakeLinkConstants.LINK_REGEX.matcher(link).find() + } + + private fun aliasIsInvalid(alias: String): Boolean { + val aliasUtf8 = StringUtils.toUtf8(alias) + return aliasUtf8.isEmpty() || aliasUtf8.size > AuthorConstants.MAX_AUTHOR_NAME_LENGTH + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt index 48370f4fbf..80d1585025 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageView.kt @@ -11,24 +11,26 @@ import androidx.compose.ui.Modifier import org.briarproject.briar.desktop.contact.ContactInfoDrawerState.MakeIntro import org.briarproject.briar.desktop.contact.ContactList import org.briarproject.briar.desktop.contact.ContactsViewModel +import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel import org.briarproject.briar.desktop.ui.UiPlaceholder import org.briarproject.briar.desktop.ui.VerticalDivider @Composable fun PrivateMessageView( - contacts: ContactsViewModel, + contactsViewModel: ContactsViewModel, + addContactViewModel: AddContactViewModel, ) { val (dropdownExpanded, setExpanded) = remember { mutableStateOf(false) } val (infoDrawer, setInfoDrawer) = remember { mutableStateOf(false) } val (contactDrawerState, setDrawerState) = remember { mutableStateOf(MakeIntro) } Row(modifier = Modifier.fillMaxWidth()) { - ContactList(contacts) + ContactList(contactsViewModel, addContactViewModel) VerticalDivider() Column(modifier = Modifier.weight(1f).fillMaxHeight()) { - contacts.selectedContact.value?.also { selectedContact -> + contactsViewModel.selectedContact.value?.also { selectedContact -> Conversation( selectedContact.contact, - contacts.contactList.map { c -> c.contact }, + contactsViewModel.contactList.map { c -> c.contact }, dropdownExpanded, setExpanded, infoDrawer, diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt index 5fa8ea0d60..d20fffa6f2 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt @@ -16,6 +16,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RU import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.messaging.MessagingManager import org.briarproject.briar.desktop.contact.ContactsViewModel +import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel import org.briarproject.briar.desktop.login.Login import org.briarproject.briar.desktop.login.LoginViewModel import org.briarproject.briar.desktop.login.Registration @@ -53,6 +54,7 @@ constructor( private val registrationViewModel: RegistrationViewModel, private val loginViewModel: LoginViewModel, private val contactsViewModel: ContactsViewModel, + private val addContactViewModel: AddContactViewModel, private val accountManager: AccountManager, private val contactManager: ContactManager, private val conversationManager: ConversationManager, @@ -115,7 +117,7 @@ constructor( MM provides messagingManager, IM provides identityManager, ) { - MainScreen(contactsViewModel, isDark, setDark) + MainScreen(contactsViewModel, addContactViewModel, isDark, setDark) } } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt index b5da952354..74f57ea804 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/MainScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import org.briarproject.briar.desktop.contact.ContactsViewModel +import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel import org.briarproject.briar.desktop.conversation.PrivateMessageView import org.briarproject.briar.desktop.navigation.BriarSidebar import org.briarproject.briar.desktop.settings.PlaceHolderSettingsView @@ -17,6 +18,7 @@ import org.briarproject.briar.desktop.settings.PlaceHolderSettingsView @Composable fun MainScreen( contactsViewModel: ContactsViewModel, + addContactViewModel: AddContactViewModel, isDark: Boolean, setDark: (Boolean) -> Unit ) { @@ -27,7 +29,7 @@ fun MainScreen( BriarSidebar(uiMode, setUiMode) VerticalDivider() when (uiMode) { - UiMode.CONTACTS -> PrivateMessageView(contactsViewModel) + UiMode.CONTACTS -> PrivateMessageView(contactsViewModel, addContactViewModel) UiMode.SETTINGS -> PlaceHolderSettingsView(isDark, setDark) else -> UiPlaceholder() } -- GitLab