diff --git a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt index 5182f2959dc571e88b9231c74b68344b4a548321..d1e8b6ba30475c06e9d6c54312e192f26da3a5d3 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt @@ -13,6 +13,7 @@ import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.crypto.DecryptionException import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator +import org.briarproject.bramble.api.identity.IdentityManager import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.messaging.MessagingManager @@ -37,7 +38,8 @@ interface BriarService { fun start( contactManager: ContactManager, conversationManager: ConversationManager, - messagingManager: MessagingManager + messagingManager: MessagingManager, + identityManager: IdentityManager, ) fun stop() @@ -46,6 +48,7 @@ interface BriarService { val CVM = compositionLocalOf<ConversationManager> { error("Undefined ConversationManager") } val CTM = compositionLocalOf<ContactManager> { error("Undefined ContactManager") } val MM = compositionLocalOf<MessagingManager> { error("Undefined MessagingManager") } +val IM = compositionLocalOf<IdentityManager> { error("Undefined IdentityManager") } @Immutable @Singleton @@ -70,7 +73,8 @@ constructor( override fun start( contactManager: ContactManager, conversationManager: ConversationManager, - messagingManager: MessagingManager + messagingManager: MessagingManager, + identityManager: IdentityManager, ) { val (isDark, setDark) = remember { mutableStateOf(true) } val title = "Briar Desktop" @@ -115,7 +119,8 @@ constructor( CompositionLocalProvider( CVM provides conversationManager, CTM provides contactManager, - MM provides messagingManager + MM provides messagingManager, + IM provides identityManager, ) { BriarUIStateManager(contacts, isDark, setDark) } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/UI.kt b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt index 871be40b97446240de7204fa2080f84e228bdb91..8f47294dbf2ef35476ab03029ee01fcf1b7baf9c 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/UI.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.account.AccountManager import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.identity.IdentityManager import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.introduction.IntroductionManager import org.briarproject.briar.api.messaging.MessagingManager @@ -25,6 +26,7 @@ constructor( private val messagingManager: MessagingManager, private val introductionManager: IntroductionManager, private val conversationManager: ConversationManager, + private val identityManager: IdentityManager, private val privateMessageFactory: PrivateMessageFactory, private val eventBus: EventBus, private val passwordStrengthEstimator: PasswordStrengthEstimator @@ -37,7 +39,12 @@ constructor( briarService.start( contactManager, conversationManager, - messagingManager + messagingManager, + identityManager ) } + + internal fun getContactManager(): ContactManager { + return contactManager + } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarSidebar.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarSidebar.kt index d9d4441b92e0dff1cbc90cf4583e260d4b3c712e..aae22ea21c3d93585ac2ad54352d09dd8d5bd605 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarSidebar.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarSidebar.kt @@ -2,13 +2,13 @@ package org.briarproject.briar.desktop.paul.views import androidx.compose.foundation.Image import androidx.compose.foundation.border +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme @@ -24,12 +24,12 @@ import androidx.compose.material.icons.filled.WifiTethering import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.loadImageBitmap -import androidx.compose.ui.res.useResource import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.IM +import org.briarproject.briar.desktop.paul.theme.briarBlack +import org.briarproject.briar.desktop.paul.theme.briarBlue import org.briarproject.briar.desktop.paul.theme.sidebarSurface val SIDEBAR_WIDTH = 56.dp @@ -42,13 +42,8 @@ fun BriarSidebar(uiMode: UiModes, setUiMode: (UiModes) -> Unit) { modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 5.dp, bottom = 4.dp), onClick = {} ) { - Image( - bitmap = useResource("images/profile_images/p0.png") { loadImageBitmap(it) }, - "my_profile_image", - modifier = Modifier.size(44.dp).align(Alignment.CenterHorizontally).clip( - CircleShape - ).border(2.dp, color = Color.White, CircleShape) - ) + val identityManager = IM.current + ProfileCircle(size = 45.dp, identityManager.localAuthor.id.bytes) } BriarSidebarButton( uiMode = uiMode, diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/Identicon.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/Identicon.kt new file mode 100644 index 0000000000000000000000000000000000000000..f94e7b36b4e58ddb41f193b0faf5ca0f531a44ab --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/Identicon.kt @@ -0,0 +1,94 @@ +package org.briarproject.briar.desktop.paul.views + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope + +/** + * Copyright 2014 www.delight.im (info@delight.im) + * + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +internal class Identicon(private val input: ByteArray, width: Float, height: Float) { + + companion object { + private const val ROWS = 9 + private const val COLUMNS = 9 + private const val CENTER_COLUMN_INDEX = COLUMNS / 2 + COLUMNS % 2 + } + + private val colors: Array<Array<Color>> + private var cellWidth = 0f + private var cellHeight = 0f + private fun getByte(index: Int): Byte { + return input[index % input.size] + } + + init { + require(input.isNotEmpty()) + + cellWidth = width / COLUMNS + cellHeight = height / ROWS + + colors = Array(ROWS) { Array(COLUMNS) { Color(0) } } + for (r in 0 until ROWS) { + for (c in 0 until COLUMNS) { + if (isCellVisible(r, c)) { + colors[r][c] = foregroundColor + } else { + colors[r][c] = backgroundColor + } + } + } + } + + private fun isCellVisible(row: Int, column: Int): Boolean { + val index = 3 + row * CENTER_COLUMN_INDEX + getSymmetricColumnIndex(column) + return getByte(index) >= 0 + } + + private fun getSymmetricColumnIndex(index: Int): Int { + return if (index < CENTER_COLUMN_INDEX) index else COLUMNS - index - 1 + } + + private val foregroundColor: Color + get() { + val r = getByte(0) * 3 / 4 + 96 + val g = getByte(1) * 3 / 4 + 96 + val b = getByte(2) * 3 / 4 + 96 + return Color(r, g, b) + } + + // http://www.google.com/design/spec/style/color.html#color-themes + private val backgroundColor: Color + get() = Color(0xFA, 0xFA, 0xFA) + + fun draw(g: DrawScope) { + for (r in 0 until ROWS) { + for (c in 0 until COLUMNS) { + val x = cellWidth * c + val y = cellHeight * r + + g.drawRect( + color = colors[r][c], + topLeft = Offset(x, y), + size = Size(cellWidth, cellHeight) + ) + } + } + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt index 11108b19463b963f836195b244991fccc7884baf..29ad9a10a08c7846fca3196ad8394edb9d3bed5f 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt @@ -3,7 +3,6 @@ package org.briarproject.briar.desktop.paul.views import androidx.compose.animation.core.animateDpAsState import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Canvas -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -67,15 +66,10 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.withTransform -import androidx.compose.ui.res.loadImageBitmap -import androidx.compose.ui.res.useResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.briarproject.bramble.api.FormatException @@ -294,15 +288,6 @@ fun SearchTextField(searchValue: String, onValueChange: (String) -> Unit, onCont ) } -@Composable -fun ProfileCircle(bitmap: ImageBitmap, size: Dp) { - Image( - bitmap, - "image", - Modifier.size(size).clip(CircleShape).border(2.dp, MaterialTheme.colors.outline, CircleShape) - ) -} - @Composable fun ContactCard( contact: Contact, @@ -326,7 +311,7 @@ fun ContactCard( Row(horizontalArrangement = Arrangement.SpaceBetween) { Row(modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)) { // TODO Pull profile pictures - ProfileCircle(useResource("images/profile_images/p0.png") { loadImageBitmap(it) }, 36.dp) + ProfileCircle(36.dp, contact.author.id.bytes) // Draw notification badges if (drawNotifications) { Canvas( @@ -340,7 +325,6 @@ fun ContactCard( } ) } - Column(modifier = Modifier.align(Alignment.CenterVertically).padding(start = 12.dp)) { Text( contact.author.name, @@ -578,7 +562,7 @@ fun ConversationHeader( Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { Row(modifier = Modifier.align(Alignment.Center)) { - ProfileCircle(useResource("images/profile_images/p0.png") { loadImageBitmap(it) }, 36.dp) + ProfileCircle(36.dp, contact.author.id.bytes) Canvas( modifier = Modifier.align(Alignment.CenterVertically), onDraw = { @@ -704,14 +688,14 @@ fun ContactDrawerMakeIntro(contact: Contact, contacts: List<Contact>, setInfoDra } Row(Modifier.fillMaxWidth().padding(12.dp), horizontalArrangement = Arrangement.SpaceAround) { Column(Modifier.align(Alignment.CenterVertically)) { - ProfileCircle(useResource("images/profile_images/p0.png") { loadImageBitmap(it) }, 40.dp) - Text(contact.author.name, Modifier.padding(top = 4.dp), fontSize = 16.sp) + ProfileCircle(40.dp, contact.author.id.bytes) + Text(contact.author.name, Modifier.padding(top = 4.dp), Color.White, 16.sp) } Icon(Filled.SwapHoriz, "swap", modifier = Modifier.size(48.dp)) Column(Modifier.align(Alignment.CenterVertically)) { // TODO Profile pic again - ProfileCircle(useResource("images/profile_images/p0.png") { loadImageBitmap(it) }, 40.dp) - Text(introContact.author.name, Modifier.padding(top = 4.dp), fontSize = 16.sp) + ProfileCircle(40.dp, contact.author.id.bytes) + Text(introContact.author.name, Modifier.padding(top = 4.dp), Color.White, 16.sp) } } var introText by remember { mutableStateOf(TextFieldValue("")) } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/ProfileCircle.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/ProfileCircle.kt new file mode 100644 index 0000000000000000000000000000000000000000..9fe7dfd346dbd80a8e7f02a588e82b76931075cc --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/ProfileCircle.kt @@ -0,0 +1,19 @@ +package org.briarproject.briar.desktop.paul.views + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun ProfileCircle(size: Dp, input: ByteArray) { + Canvas(Modifier.size(size).clip(CircleShape).border(2.dp, Color.White, CircleShape)) { + Identicon(input, this.size.width, this.size.height).draw(this) + } +}