diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ConnectionIndicator.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ConnectionIndicator.kt new file mode 100644 index 0000000000000000000000000000000000000000..d08cda40a6d18bb3bb99201a21cbdb693353de46 --- /dev/null +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ConnectionIndicator.kt @@ -0,0 +1,42 @@ +/* + * 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.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.theme.outline + +@Composable +fun ConnectionIndicator( + modifier: Modifier = Modifier.size(16.dp), + isConnected: Boolean, + notConnectedColor: Color = Color.Transparent, +) = Box( + modifier = modifier + .border(1.dp, MaterialTheme.colors.outline, CircleShape) + .background(if (isConnected) MaterialTheme.colors.secondary else notConnectedColor, CircleShape) +) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt index 73772d5611d06d8060fb4e34eaf4e093439e7cb3..0d3dfcfd7c1911b6442f13c51e486c688809a561 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactCard.kt @@ -18,20 +18,17 @@ package org.briarproject.briar.desktop.contact -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.clickable +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset 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.foundation.selection.selectable import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -40,13 +37,14 @@ import androidx.compose.material.icons.filled.Delete import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis import androidx.compose.ui.unit.dp import org.briarproject.bramble.api.contact.ContactId +import org.briarproject.bramble.api.contact.PendingContactId +import org.briarproject.bramble.api.contact.PendingContactState import org.briarproject.bramble.api.identity.AuthorId -import org.briarproject.briar.desktop.theme.outline import org.briarproject.briar.desktop.theme.selectedCard -import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.HorizontalDivider import org.briarproject.briar.desktop.ui.MessageCounter @@ -65,20 +63,31 @@ fun main() = preview( "timestamp" to Instant.now().toEpochMilli(), "selected" to false, ) { - ContactCard( - ContactItem( - idWrapper = RealContactIdWrapper(ContactId(0)), - authorId = AuthorId(getRandomIdPersistent()), - name = getStringParameter("name"), - alias = getStringParameter("alias"), - isConnected = getBooleanParameter("isConnected"), - isEmpty = getBooleanParameter("isEmpty"), - unread = getIntParameter("unread"), - timestamp = getLongParameter("timestamp"), - avatar = null, - ), - {}, getBooleanParameter("selected"), {} - ) + Column { + ContactCard( + ContactItem( + idWrapper = RealContactIdWrapper(ContactId(0)), + authorId = AuthorId(getRandomIdPersistent()), + name = getStringParameter("name"), + alias = getStringParameter("alias"), + isConnected = getBooleanParameter("isConnected"), + isEmpty = getBooleanParameter("isEmpty"), + unread = getIntParameter("unread"), + timestamp = getLongParameter("timestamp"), + avatar = null, + ), + {}, getBooleanParameter("selected"), {} + ) + ContactCard( + PendingContactItem( + idWrapper = PendingContactIdWrapper(PendingContactId(getRandomId())), + alias = getStringParameter("alias"), + timestamp = getLongParameter("timestamp"), + state = PendingContactState.ADDING_CONTACT + ), + {}, false, {} + ) + } } @Composable @@ -87,22 +96,24 @@ fun ContactCard( onSel: () -> Unit, selected: Boolean, onRemovePending: () -> Unit, - padding: PaddingValues = PaddingValues(0.dp), ) { - val bgColor = if (selected) MaterialTheme.colors.selectedCard else MaterialTheme.colors.surfaceVariant + val bgColor = if (selected) MaterialTheme.colors.selectedCard else Color.Transparent - Card( - modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = HEADER_SIZE).clickable(onClick = onSel), - shape = RoundedCornerShape(0.dp), - backgroundColor = bgColor, - contentColor = MaterialTheme.colors.onSurface + Column( + modifier = Modifier + .fillMaxWidth() + .defaultMinSize(minHeight = HEADER_SIZE) + .selectable(selected, onClick = onSel) + .background(bgColor) + .padding(vertical = 8.dp), + verticalArrangement = Arrangement.Center ) { when (contactItem) { is ContactItem -> { - RealContactRow(contactItem, padding) + RealContactRow(contactItem) } is PendingContactItem -> { - PendingContactRow(contactItem, onRemovePending, padding) + PendingContactRow(contactItem, onRemovePending) } } } @@ -110,17 +121,16 @@ fun ContactCard( } @Composable -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)) { +private fun RealContactRow(contactItem: ContactItem) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { Row( - modifier = Modifier.align(Alignment.CenterVertically).padding(start = 16.dp, end = 8.dp) - .weight(1f, fill = false) + modifier = Modifier.padding(start = 16.dp, end = 8.dp) ) { - Box(modifier = Modifier.align(Alignment.CenterVertically)) { + Box { ProfileCircle(36.dp, contactItem) MessageCounter( unread = contactItem.unread, @@ -129,41 +139,35 @@ private fun RealContactRow(contactItem: ContactItem, padding: PaddingValues) { } RealContactInfo( contactItem = contactItem, - modifier = Modifier.align(Alignment.CenterVertically) ) } - Canvas( - modifier = Modifier.size(24.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 - ) - } + ConnectionIndicator( + modifier = Modifier.padding(end = (16 + 4).dp).size(16.dp), + isConnected = contactItem.isConnected ) } } @Composable -private fun PendingContactRow(contactItem: PendingContactItem, onRemove: () -> Unit, padding: PaddingValues) { - Row(horizontalArrangement = Arrangement.SpaceBetween) { +private fun PendingContactRow(contactItem: PendingContactItem, onRemove: () -> Unit) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { Row( - modifier = Modifier.align(Alignment.CenterVertically).padding(start = 16.dp, end = 8.dp) - .weight(1f, fill = false) + modifier = Modifier.padding(start = 16.dp, end = 8.dp) ) { ProfileCircle(36.dp) PendingContactInfo( contactItem = contactItem, - modifier = Modifier.align(Alignment.CenterVertically) ) } IconButton( icon = Icons.Filled.Delete, contentDescription = i18n("access.contacts.pending.remove"), onClick = onRemove, - modifier = Modifier.padding(end = 4.dp).align(Alignment.CenterVertically) + modifier = Modifier.padding(end = 4.dp) ) } } 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 b0324ffa504334e1c6e59874df4246b2e4a0c1c4..8aff4a2d84bae10b7d1c745cfd90eea7a2b8aa1f 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 @@ -26,14 +26,14 @@ import androidx.compose.foundation.layout.fillMaxHeight 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.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold +import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -54,10 +54,11 @@ fun ContactList( ) { val scrollState = rememberLazyListState() - Scaffold( + Surface( modifier = Modifier.fillMaxHeight().width(COLUMN_WIDTH), - backgroundColor = MaterialTheme.colors.surfaceVariant, - topBar = { + color = MaterialTheme.colors.surfaceVariant + ) { + Column { Column( modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp), ) { @@ -67,17 +68,15 @@ fun ContactList( onContactAdd = onContactAdd, ) } - }, - content = { padding -> - Box(modifier = Modifier.padding(padding).fillMaxSize()) { - LazyColumn(state = scrollState) { + + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn(state = scrollState, modifier = Modifier.selectableGroup()) { items(contactList) { contactItem -> ContactCard( contactItem, onSel = { selectContact(contactItem) }, selected = isSelected(contactItem), onRemovePending = { if (contactItem is PendingContactItem) removePendingContact(contactItem) }, - padding = PaddingValues(end = 16.dp), ) } } @@ -87,6 +86,6 @@ fun ContactList( modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight() ) } - }, - ) + } + } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt index eb6d737b942254902cd17f1688fdb8c45d2ae0ff..b02571f4e96bf91f9412907431575b2d854e82ff 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationHeader.kt @@ -18,7 +18,6 @@ package org.briarproject.briar.desktop.conversation -import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -26,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxHeight 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.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -36,13 +36,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.drawscope.withTransform import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.contact.ConnectionIndicator import org.briarproject.briar.desktop.contact.ContactDropDown import org.briarproject.briar.desktop.contact.ContactItem import org.briarproject.briar.desktop.contact.ProfileCircle -import org.briarproject.briar.desktop.theme.outline import org.briarproject.briar.desktop.theme.surfaceVariant import org.briarproject.briar.desktop.ui.Constants.HEADER_SIZE import org.briarproject.briar.desktop.ui.HorizontalDivider @@ -57,9 +56,6 @@ fun ConversationHeader( onDeleteContact: () -> Unit, ) { val (menuState, setMenuState) = remember { mutableStateOf(ContactDropDown.State.CLOSED) } - val onlineColor = - if (contactItem.isConnected) MaterialTheme.colors.secondary else MaterialTheme.colors.surfaceVariant - val outlineColor = MaterialTheme.colors.outline Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { Row( @@ -69,15 +65,10 @@ fun ConversationHeader( Row(modifier = Modifier.fillMaxHeight().padding(start = 8.dp).weight(1f, fill = false)) { Box(modifier = Modifier.align(Alignment.CenterVertically)) { ProfileCircle(36.dp, contactItem) - Canvas( - modifier = Modifier, - onDraw = { - val size = 10.dp.toPx() - withTransform({ translate(left = 30f, top = 30f) }) { - drawCircle(color = outlineColor, radius = (size + 2.dp.toPx()) / 2f) - drawCircle(color = onlineColor, radius = size / 2f) - } - } + ConnectionIndicator( + modifier = Modifier.align(Alignment.BottomEnd).size(12.dp), + isConnected = contactItem.isConnected, + notConnectedColor = MaterialTheme.colors.surfaceVariant, ) } Text( diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt index 54b0b9ce8b2d99867dd01acd788e8b5b60aa5568..49e33ff1fbac87d5a974408ca156e7c032143f36 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/introduction/ContactDrawerMakeIntro.kt @@ -90,7 +90,6 @@ fun ContactDrawerMakeIntro( onSel = { viewModel.setSecondContact(contactItem) }, selected = false, onRemovePending = {}, - padding = PaddingValues(end = 16.dp), ) } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt index 6d5bdd8b69b382c798525b3140778c56909ee140..a9c17ba486bdb89c157ad0bce81c9b44d809599c 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/navigation/BriarSidebar.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -47,7 +48,19 @@ fun BriarSidebar( uiMode: UiMode, setUiMode: (UiMode) -> Unit, ) { - Surface(modifier = Modifier.width(SIDEBAR_WIDTH).fillMaxHeight(), color = MaterialTheme.colors.sidebarSurface) { + val displayButton = @Composable { selectedMode: UiMode, mode: UiMode, icon: ImageVector -> + BriarSidebarButton( + selectedMode == mode, + { setUiMode(mode) }, + icon, + mode.toString() + ) + } + + Surface( + modifier = Modifier.width(SIDEBAR_WIDTH).fillMaxHeight().selectableGroup(), + color = MaterialTheme.colors.sidebarSurface + ) { Column(verticalArrangement = Arrangement.Top) { // profile button Box(