Skip to content
Snippets Groups Projects
Commit a903ecdf authored by Sebastian's avatar Sebastian Committed by Mikolai Gütschow
Browse files

Make it possible to delete pending contacts

parent 7ff1fc18
No related branches found
No related tags found
1 merge request!165Make it possible to delete pending contacts
Pipeline #9984 passed
Showing
with 226 additions and 56 deletions
Subproject commit 36670a8bf6bf056cfe8b749f982f9a4c05b05023
Subproject commit 6d22bab5eed26dbf4b03a309c4e371953c9c210c
/*
* 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.contact
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.width
import androidx.compose.material.AlertDialog
import androidx.compose.material.ButtonType
import androidx.compose.material.DialogButton
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
import org.briarproject.briar.desktop.utils.PreviewUtils.preview
@Suppress("HardCodedStringLiteral")
fun main() = preview(
"visible" to true,
) {
ConfirmRemovePendingContactDialog(
getBooleanParameter("visible"),
{ setBooleanParameter("visible", false) },
{ setBooleanParameter("visible", false) },
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ConfirmRemovePendingContactDialog(
isVisible: Boolean,
close: () -> Unit,
onRemove: () -> Unit,
) {
if (!isVisible) return
AlertDialog(
onDismissRequest = close,
title = {
Text(
text = i18n("contacts.pending.remove.dialog.title"),
modifier = Modifier.width(IntrinsicSize.Max),
style = MaterialTheme.typography.h6,
)
},
text = {
// Add empty box here with a minimum size to prevent overly narrow dialog
Box(modifier = Modifier.defaultMinSize(300.dp))
Text(i18n("contacts.pending.remove.dialog.message"))
},
dismissButton = {
DialogButton(
onClick = close,
text = i18n("cancel"),
type = ButtonType.NEUTRAL
)
},
confirmButton = {
DialogButton(
onClick = onRemove,
text = i18n("remove"),
type = ButtonType.DESTRUCTIVE
)
},
)
}
......@@ -32,8 +32,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
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.Delete
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
......@@ -74,7 +78,7 @@ fun main() = preview(
timestamp = getLongParameter("timestamp"),
avatar = null,
),
{}, getBooleanParameter("selected")
{}, getBooleanParameter("selected"), {}
)
}
......@@ -83,12 +87,10 @@ fun ContactCard(
contactItem: BaseContactItem,
onSel: () -> Unit,
selected: Boolean,
onRemovePending: () -> Unit,
padding: PaddingValues = PaddingValues(0.dp),
) {
val bgColor = if (selected) MaterialTheme.colors.selectedCard else MaterialTheme.colors.surfaceVariant
val outlineColor = MaterialTheme.colors.outline
val briarSecondary = MaterialTheme.colors.secondary
val briarSurfaceVar = MaterialTheme.colors.surfaceVariant
Card(
modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = HEADER_SIZE).clickable(onClick = onSel),
......@@ -96,54 +98,79 @@ fun ContactCard(
backgroundColor = bgColor,
contentColor = MaterialTheme.colors.onSurface
) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.padding(padding)) {
Row(
modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)
.weight(1f, fill = false)
) {
when (contactItem) {
is ContactItem -> {
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
ProfileCircle(36.dp, contactItem)
MessageCounter(
unread = contactItem.unread,
modifier = Modifier.align(Alignment.TopEnd).offset(6.dp, (-6).dp)
)
}
RealContactInfo(
contactItem = contactItem,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
is PendingContactItem -> {
ProfileCircle(36.dp)
PendingContactInfo(
contactItem = contactItem,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
}
when (contactItem) {
is ContactItem -> {
RealContactRow(contactItem, padding)
}
is PendingContactItem -> {
PendingContactRow(contactItem, onRemovePending, padding)
}
if (contactItem is ContactItem)
Canvas(
modifier = Modifier.padding(end = 18.dp).size(22.dp)
.align(Alignment.CenterVertically),
onDraw = {
val size = 16.dp
drawCircle(color = outlineColor, radius = size.toPx() / 2f)
drawCircle(
color = if (contactItem.isConnected) briarSecondary else briarSurfaceVar,
radius = (size - 2.dp).toPx() / 2f
)
}
)
}
}
HorizontalDivider()
}
@Composable
fun RealContactInfo(contactItem: ContactItem, modifier: Modifier = Modifier) {
private fun RealContactRow(contactItem: ContactItem, padding: PaddingValues) {
val outlineColor = MaterialTheme.colors.outline
val briarSecondary = MaterialTheme.colors.secondary
val briarSurfaceVar = MaterialTheme.colors.surfaceVariant
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.padding(padding)) {
Row(
modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)
.weight(1f, fill = false)
) {
Box(modifier = Modifier.align(Alignment.CenterVertically)) {
ProfileCircle(36.dp, contactItem)
MessageCounter(
unread = contactItem.unread,
modifier = Modifier.align(Alignment.TopEnd).offset(6.dp, (-6).dp)
)
}
RealContactInfo(
contactItem = contactItem,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
Canvas(
modifier = Modifier.padding(end = 18.dp).size(22.dp).align(Alignment.CenterVertically),
onDraw = {
val size = 16.dp
drawCircle(color = outlineColor, radius = size.toPx() / 2f)
drawCircle(
color = if (contactItem.isConnected) briarSecondary else briarSurfaceVar,
radius = (size - 2.dp).toPx() / 2f
)
}
)
}
}
@Composable
private fun PendingContactRow(contactItem: PendingContactItem, onRemove: () -> Unit, padding: PaddingValues) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.padding(padding)) {
Row(
modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)
.weight(1f, fill = false)
) {
ProfileCircle(36.dp)
PendingContactInfo(
contactItem = contactItem,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
IconButton(
onClick = onRemove,
modifier = Modifier.padding(end = 4.dp).align(Alignment.CenterVertically),
) {
Icon(Icons.Filled.Delete, i18n("access.contacts.pending.remove"))
}
}
}
@Composable
private fun RealContactInfo(contactItem: ContactItem, modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(start = 12.dp)) {
Text(
contactItem.displayName,
......@@ -163,7 +190,7 @@ fun RealContactInfo(contactItem: ContactItem, modifier: Modifier = Modifier) {
}
@Composable
fun PendingContactInfo(contactItem: PendingContactItem, modifier: Modifier = Modifier) {
private fun PendingContactInfo(contactItem: PendingContactItem, modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(start = 12.dp)) {
Text(
contactItem.displayName,
......
......@@ -47,6 +47,7 @@ fun ContactList(
contactList: List<BaseContactItem>,
isSelected: (BaseContactItem) -> Boolean,
selectContact: (BaseContactItem) -> Unit,
removePendingContact: (PendingContactItem) -> Unit,
filterBy: String,
setFilterBy: (String) -> Unit,
onContactAdd: () -> Unit,
......@@ -73,9 +74,10 @@ fun ContactList(
items(contactList) { contactItem ->
ContactCard(
contactItem,
{ selectContact(contactItem) },
isSelected(contactItem),
PaddingValues(end = 12.dp)
onSel = { selectContact(contactItem) },
selected = isSelected(contactItem),
onRemovePending = { if (contactItem is PendingContactItem) removePendingContact(contactItem) },
padding = PaddingValues(end = 12.dp),
)
}
}
......
......@@ -23,6 +23,8 @@ 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.contact.PendingContactId
import org.briarproject.bramble.api.contact.PendingContactState
import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent
import org.briarproject.bramble.api.db.TransactionManager
import org.briarproject.bramble.api.event.Event
......@@ -75,6 +77,7 @@ constructor(
private val _filterBy = mutableStateOf("")
private val _selectedContactId = mutableStateOf<ContactIdWrapper?>(null)
private val _contactIdToBeRemoved = mutableStateOf<PendingContactId?>(null)
val filterBy = _filterBy.asState()
val selectedContactId = derivedStateOf {
......@@ -87,6 +90,7 @@ constructor(
null
}
}
val removePendingContactDialogVisible = derivedStateOf { _contactIdToBeRemoved.value != null }
fun selectContact(contactItem: BaseContactItem) {
_selectedContactId.value = contactItem.idWrapper
......@@ -94,6 +98,36 @@ constructor(
fun isSelected(contactItem: BaseContactItem) = _selectedContactId.value == contactItem.idWrapper
fun removePendingContact(contactItem: PendingContactItem) {
if (contactItem.state == PendingContactState.FAILED) {
// no need to show warning dialog for failed pending contacts
removePendingContact(contactItem.idWrapper.contactId)
} else {
// show warning dialog
_contactIdToBeRemoved.value = contactItem.idWrapper.contactId
}
}
fun confirmRemovingPendingContact() {
_contactIdToBeRemoved.value?.let { id ->
removePendingContact(id)
}
}
fun dismissRemovePendingContactDialog() {
_contactIdToBeRemoved.value = null
}
private fun removePendingContact(contactId: PendingContactId) {
runOnDbThreadWithTransaction(false) { txn ->
contactManager.removePendingContact(txn, contactId)
_contactIdToBeRemoved.value = null
txn.attach {
removeItem(contactId)
}
}
}
override fun filterContactItem(contactItem: BaseContactItem) =
contactItem.displayName.contains(_filterBy.value, ignoreCase = true)
......
......@@ -24,6 +24,7 @@ import mu.KotlinLogging
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.PendingContactId
import org.briarproject.bramble.api.contact.event.ContactAddedEvent
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
......@@ -77,7 +78,7 @@ abstract class ContactsViewModel(
runOnDbThreadWithTransaction(true) { txn ->
contactList.addAll(
contactManager.getPendingContacts(txn).map { contact ->
PendingContactItem(contact.first)
PendingContactItem(contact.first, contact.second)
}
)
contactList.addAll(
......@@ -135,4 +136,10 @@ abstract class ContactsViewModel(
it.idWrapper.contactId == contactId
}
}
protected open fun removeItem(contactId: PendingContactId) {
_fullContactList.removeFirst<BaseContactItem, PendingContactItem> {
it.idWrapper.contactId == contactId
}
}
}
......@@ -19,18 +19,21 @@
package org.briarproject.briar.desktop.contact
import org.briarproject.bramble.api.contact.PendingContact
import org.briarproject.bramble.api.contact.PendingContactState
data class PendingContactItem(
override val idWrapper: PendingContactIdWrapper,
val alias: String,
override val timestamp: Long
override val timestamp: Long,
val state: PendingContactState,
) : BaseContactItem {
override val displayName = alias
constructor(contact: PendingContact) : this(
constructor(contact: PendingContact, state: PendingContactState) : this(
idWrapper = PendingContactIdWrapper(contact.id),
alias = contact.alias,
timestamp = contact.timestamp
timestamp = contact.timestamp,
state = state,
)
}
......@@ -42,6 +42,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import org.briarproject.briar.desktop.contact.ConfirmRemovePendingContactDialog
import org.briarproject.briar.desktop.contact.ContactList
import org.briarproject.briar.desktop.contact.ContactListViewModel
import org.briarproject.briar.desktop.contact.PendingContactIdWrapper
......@@ -61,6 +62,12 @@ fun PrivateMessageScreen(
) {
AddContactDialog()
ConfirmRemovePendingContactDialog(
viewModel.removePendingContactDialogVisible.value,
close = viewModel::dismissRemovePendingContactDialog,
onRemove = viewModel::confirmRemovingPendingContact,
)
if (viewModel.noContactsYet.value) {
NoContactsYet(onContactAdd = { addContactViewModel.showDialog() })
return
......@@ -71,6 +78,7 @@ fun PrivateMessageScreen(
viewModel.contactList.value,
viewModel::isSelected,
viewModel::selectContact,
viewModel::removePendingContact,
viewModel.filterBy.value,
viewModel::setFilterBy,
onContactAdd = { addContactViewModel.showDialog() }
......
......@@ -84,8 +84,9 @@ fun ContactDrawerMakeIntro(
if (contactItem is ContactItem)
ContactCard(
contactItem,
{ viewModel.setSecondContact(contactItem) },
false
onSel = { viewModel.setSecondContact(contactItem) },
selected = false,
onRemovePending = {}
)
}
}
......
......@@ -20,4 +20,6 @@ package org.briarproject.briar.desktop
fun main() = RunWithTemporaryAccount {
getDeterministicTestDataCreator().createTestData(5, 20, 50, 10, 20)
this.getContactManager()
.addPendingContact("briar://aatkjq4seoualafpwh4cfckdzr4vpr4slk3bbvpxklf7y7lv4ajw6", "Faythe")
}.run()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment