From 6122c1842792e2a5612eef2260fbf43bc2ade5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCrten?= <sebastian@mobanisto.de> Date: Thu, 9 Sep 2021 22:46:49 +0200 Subject: [PATCH] Add UI stubs from Paul's prototype --- build.gradle.kts | 1 + .../briar/desktop/BriarService.kt | 8 +- .../briar/desktop/paul/data/Contacts.kt | 229 +++++++++++++ .../briar/desktop/paul/data/ContentType.kt | 16 + .../briar/desktop/paul/data/OptionType.kt | 14 + .../briar/desktop/paul/model/Contact.kt | 9 + .../briar/desktop/paul/model/ContentType.kt | 7 + .../briar/desktop/paul/model/Message.kt | 9 + .../briar/desktop/paul/model/OptionType.kt | 9 + .../briar/desktop/paul/theme/colors.kt | 32 ++ .../briar/desktop/paul/views/BriarSidebar.kt | 103 ++++++ .../desktop/paul/views/BriarUIStateManager.kt | 51 +++ .../desktop/paul/views/PrivateMessageView.kt | 319 ++++++++++++++++++ .../resources/images/profile_images/p0.png | Bin 0 -> 45426 bytes .../resources/images/profile_images/p1.png | Bin 0 -> 2804 bytes .../resources/images/profile_images/p2.png | Bin 0 -> 45426 bytes .../resources/images/profile_images/p3.png | Bin 0 -> 45426 bytes .../resources/images/profile_images/p4.png | Bin 0 -> 45426 bytes .../resources/images/profile_images/p5.png | Bin 0 -> 45426 bytes 19 files changed, 800 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/data/Contacts.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContentType.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/data/OptionType.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/model/Contact.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/model/ContentType.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/model/Message.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/model/OptionType.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/theme/colors.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarSidebar.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt create mode 100644 src/main/resources/images/profile_images/p0.png create mode 100644 src/main/resources/images/profile_images/p1.png create mode 100644 src/main/resources/images/profile_images/p2.png create mode 100644 src/main/resources/images/profile_images/p3.png create mode 100644 src/main/resources/images/profile_images/p4.png create mode 100644 src/main/resources/images/profile_images/p5.png diff --git a/build.gradle.kts b/build.gradle.kts index 66f5b38406..af4aeb877c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.10.0") implementation("com.github.ajalt:clikt:2.2.0") + implementation("org.jetbrains.compose.material:material-icons-extended:0.4.0") implementation(project(path = ":briar:briar-core", configuration = "default")) implementation(project(path = ":briar:bramble-java", configuration = "default")) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt index ad43f0bb61..18a11ca523 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt @@ -66,13 +66,7 @@ constructor( }) is Screen.Main -> - Column( - modifier = Modifier.padding(16.dp).fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Welcome to Briar") - } + briarUIStateManager() } } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/data/Contacts.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/Contacts.kt new file mode 100644 index 0000000000..2b7b47c203 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/Contacts.kt @@ -0,0 +1,229 @@ +package org.briarproject.briar.desktop.paul.data + +import org.briarproject.briar.desktop.paul.model.Contact +import org.briarproject.briar.desktop.paul.model.Message + +object ContactList { + val msg1: List<Message> = listOf<Message>( + Message(from = "Alice", message = "hello!", time = "2 days ago", delivered = true), + Message(from = null, message = "yes I hear you", time = "1 day ago", delivered = true), + Message( + from = null, + message = "I am messaging you through this fake Briar Desktop app", + time = "18 hrs. ago", + delivered = true + ), + Message(from = "Alice", message = "Ah I see, very neat", time = "12 min. ago", delivered = true), + Message(from = null, message = "Loren Ipsum", time = "2 min. ago", delivered = false) + ) + val msg2: List<Message> = listOf<Message>( + Message( + from = null, + message = "The air bites shrewdly; it is very cold.", + time = "2 days ago", + delivered = true + ), + Message(from = "Bob", message = "It is a nipping and an eager air.", time = "1 day ago", delivered = true), + Message(from = null, message = "What hour now?", time = "18 hrs. ago", delivered = true), + Message(from = "Bob", message = "I think it lacks of twelve.", time = "12 min. ago", delivered = true), + Message(from = null, message = "No, it is struck.", time = "2 min. ago", delivered = false), + Message( + from = "Bob", + message = "Indeed? I heard it not: then it draws near the season Wherein the spirit held his wont to walk. A flourish of trumpets, and ordnance shot off, within. What does this mean, my lord?", + time = "2 min. ago", + delivered = false + ), + Message( + from = null, + message = "The air bites shrewdly; it is very cold.", + time = "2 days ago", + delivered = true + ), + Message(from = "Bob", message = "It is a nipping and an eager air.", time = "1 day ago", delivered = true), + Message(from = null, message = "What hour now?", time = "18 hrs. ago", delivered = true), + Message(from = "Bob", message = "I think it lacks of twelve.", time = "12 min. ago", delivered = true), + Message(from = null, message = "No, it is struck.", time = "2 min. ago", delivered = false), + Message( + from = null, + message = "The air bites shrewdly; it is very cold.", + time = "2 days ago", + delivered = true + ), + Message( + from = null, + message = "The air bites shrewdly; it is very cold.", + time = "2 days ago", + delivered = true + ), + Message(from = "Bob", message = "It is a nipping and an eager air.", time = "1 day ago", delivered = true), + Message(from = null, message = "What hour now?", time = "18 hrs. ago", delivered = true), + Message(from = "Bob", message = "I think it lacks of twelve.", time = "12 min. ago", delivered = true), + Message(from = null, message = "No, it is struck.", time = "2 min. ago", delivered = false), + Message(from = "Bob", message = "It is a nipping and an eager air.", time = "1 day ago", delivered = true), + Message(from = null, message = "What hour now?", time = "18 hrs. ago", delivered = true), + Message(from = "Bob", message = "I think it lacks of twelve.", time = "12 min. ago", delivered = true), + Message(from = null, message = "No, it is struck.", time = "2 min. ago", delivered = false), + Message( + from = "Bob", + message = "Indeed? I heard it not: then it draws near the season Wherein the spirit held his wont to walk. A flourish of trumpets, and ordnance shot off, within. What does this mean, my lord?", + time = "2 min. ago", + delivered = false + ) + ) + val msg3: List<Message> = listOf<Message>( + Message( + from = null, + message = "Give him this money and these notes, Reynaldo.", + time = "2 days ago", + delivered = true + ), + Message(from = "Bob", message = "I will, my lord.", time = "1 day ago", delivered = true), + Message( + from = null, + message = "You shall do marvellous wisely, good Reynaldo,Before you visit him, to make inquireOf his behavior.", + time = "18 hrs. ago", + delivered = true + ), + Message(from = "Bob", message = "My lord, I did intend it.", time = "12 min. ago", delivered = true), + Message( + from = null, + message = "Marry, well said; very well said. Look you, sir,Inquire me first what Danskers are in Paris;And how, and who, what means, and where they keep,What company, at what expense; and findingBy this encompassment and drift of questionThat they do know my son, come you more nearerThan your particular demands will touch it:Take you, as ’twere, some distant knowledge of him;As thus, ‘I know his father and his friends,And in part him: ‘ do you mark this, Reynaldo?", + time = "2 min. ago", + delivered = false + ), + ) + val msg4: List<Message> = listOf<Message>( + Message( + from = "Bob", + message = "So art thou to revenge, when thou shalt hear.", + time = "2 days ago", + delivered = true + ), + Message(from = null, message = "What?", time = "1 day ago", delivered = true), + Message( + from = "Bob", + message = "I am your father’s spirit, doomed for a certain time to walk the night, and for the day to burn in fires, till the foul crimes done during my lifetime have been burnt and purged away. But that I am forbidden to tell the secrets of my prison-house I could tell a tale whose lightest word would shrivel up your soul, freeze your young blood, make your eyes start from their sockets and your hair stand up on end like the quills of a frightened porcupine. But this eternal torture is not for ears of flesh and blood. Listen, oh listen! If you ever loved your dear father ….", + time = "18 hrs. ago", + delivered = true + ), + Message(from = null, message = "Oh God!", time = "12 min. ago", delivered = true), + ) + val msg5: List<Message> = listOf<Message>( + Message( + from = null, + message = "Here's a knocking indeed! If a man were porter of hell-gate, he should have old turning the key. Knocking within Knock, knock, knock! Who's there, i' the name of Beelzebub? Here's a farmer, that hanged himself on the expectation of plenty: come in time; have napkins enow about you; here you'll sweat for't.", + time = "2 days ago", + delivered = true + ), + Message( + from = null, + message = "Knock, knock! Who's there, in the other devil's name? Faith, here's an equivocator, that could swear in both the scales against either scale; who committed treason enough for God's sake, yet could not equivocate to heaven: O, come in, equivocator.", + time = "1 day ago", + delivered = true + ), + ) + val contacts = listOf( + Contact(name = "Alice", online = true, profile_pic = "p1.png", last_heard = "now", privateMessages = msg2), + Contact( + name = "Bob", + online = false, + profile_pic = "p2.png", + last_heard = "22 min. ago", + privateMessages = msg1 + ), + Contact( + name = "Carl", + online = true, + profile_pic = "p3.png", + last_heard = "2 hr. ago ", + privateMessages = msg3 + ), + Contact(name = "Dan", online = false, profile_pic = "p4.png", last_heard = "1 day ago", privateMessages = msg4), + Contact( + name = "Eve", + online = false, + profile_pic = "p5.png", + last_heard = "3 days ago", + privateMessages = msg5 + ), + Contact( + name = "Fred", + online = false, + profile_pic = "p2.png", + last_heard = "22 min. ago", + privateMessages = msg1 + ), + Contact( + name = "Greg", + online = true, + profile_pic = "p3.png", + last_heard = "2 hr. ago ", + privateMessages = msg3 + ), + Contact( + name = "Harold", + online = false, + profile_pic = "p4.png", + last_heard = "1 day ago", + privateMessages = msg4 + ), + Contact( + name = "Irene", + online = false, + profile_pic = "p5.png", + last_heard = "3 days ago", + privateMessages = msg5 + ), + Contact( + name = "Jeanne", + online = false, + profile_pic = "p2.png", + last_heard = "22 min. ago", + privateMessages = msg1 + ), + Contact( + name = "Karl", + online = true, + profile_pic = "p3.png", + last_heard = "2 hr. ago ", + privateMessages = msg3 + ), + Contact( + name = "Lorn", + online = false, + profile_pic = "p4.png", + last_heard = "1 day ago", + privateMessages = msg4 + ), + Contact( + name = "Meg", + online = false, + profile_pic = "p5.png", + last_heard = "3 days ago", + privateMessages = msg5 + ), + Contact( + name = "Nile", + online = false, + profile_pic = "p2.png", + last_heard = "22 min. ago", + privateMessages = msg1 + ), + Contact( + name = "Oscar", + online = true, + profile_pic = "p3.png", + last_heard = "2 hr. ago ", + privateMessages = msg3 + ), + Contact( + name = "Paul", + online = false, + profile_pic = "p4.png", + last_heard = "1 day ago", + privateMessages = msg4 + ), + Contact(name = "Qi", online = false, profile_pic = "p5.png", last_heard = "3 days ago", privateMessages = msg5), + ) +} + diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContentType.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContentType.kt new file mode 100644 index 0000000000..e29adb5f0b --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContentType.kt @@ -0,0 +1,16 @@ +package org.briarproject.briar.desktop.paul.data + +import org.briarproject.briar.desktop.paul.model.ContentType + +object ContentTypeList { + val types = listOf( + ContentType(id = -1, name = "Profile"), + ContentType(id = 0, name = "Contacts"), + ContentType(id = 1, name = "Private Groups"), + ContentType(id = 2, name = "Forums"), + ContentType(id = 3, name = "Blogs"), + ContentType(id = 4, name = "Transports"), + ContentType(id = 5, name = "Settings"), + ContentType(id = 6, name = "Sign Out") + ) +} \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/data/OptionType.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/OptionType.kt new file mode 100644 index 0000000000..5dbb5d8e4c --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/OptionType.kt @@ -0,0 +1,14 @@ +package org.briarproject.briar.desktop.paul.data + +import org.briarproject.briar.desktop.paul.model.OptionType + +object OptionTypeList { + val msgTypes = listOf( + OptionType(name = "Alice", unread = 2, online = true), + OptionType(name = "Bob", unread = 0, online = true), + OptionType(name = "Carl", unread = 0, online = false), + OptionType(name = "Dan", unread = 1, online = false), + OptionType(name = "Eve", unread = 0, online = false), + ) +} + diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/model/Contact.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/Contact.kt new file mode 100644 index 0000000000..22085dff5b --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/Contact.kt @@ -0,0 +1,9 @@ +package org.briarproject.briar.desktop.paul.model + +data class Contact( + val name: String, + val online: Boolean, + val profile_pic: String, + val last_heard: String, + val privateMessages: List<Message> +) \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/model/ContentType.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/ContentType.kt new file mode 100644 index 0000000000..0db88d3071 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/ContentType.kt @@ -0,0 +1,7 @@ +package org.briarproject.briar.desktop.paul.model + +data class ContentType( + val id: Int, + val name: String, + //val icon: Icon, +) \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/model/Message.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/Message.kt new file mode 100644 index 0000000000..a7a79f553f --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/Message.kt @@ -0,0 +1,9 @@ +package org.briarproject.briar.desktop.paul.model + +data class Message( + val from: String?, + val message: String, + val time: String, + val delivered: Boolean, + //val read: Boolean, +) \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/model/OptionType.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/OptionType.kt new file mode 100644 index 0000000000..8367e1eb87 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/model/OptionType.kt @@ -0,0 +1,9 @@ +package org.briarproject.briar.desktop.paul.model + +data class OptionType( + val name: String, + val unread: Int, + val online: Boolean + //val icon: Icon, +) + diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/theme/colors.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/theme/colors.kt new file mode 100644 index 0000000000..2d443639ee --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/theme/colors.kt @@ -0,0 +1,32 @@ +package org.briarproject.briar.desktop.paul.theme + +import androidx.compose.material.darkColors +import androidx.compose.ui.graphics.Color + +val graySurface = Color(0xFF2A2A2A) +val lightGray = Color(0xFFD3D3D3) +val briarGray = Color(0xff222222) +val briarBlack = Color(0xff1E2228) +val briarSelBlack = Color(0xff495261) +val briarDarkGray = Color(0xFF3D4552) +val darkGray = Color(0xFF151515) +val divider = Color(0xff35383D) +val briarBlue = Color(0xFF2D3E50) +val briarLightBlue = Color(0xFFEBEFF2) +val briarGreen = Color(0xFF97D323) +val briarBlueMsg = Color(0xFF1b69b6) +val briarBlueSpecialMsg = Color(0xFF134a80) +val briarGrayMsg = Color(0xff3b4047) +val briarGraySpecialMsg = Color(0xFF212d3b) + +val DarkColorPallet = darkColors( + primary = Color.White, + secondary = graySurface, + background = briarLightBlue, + surface = briarBlue, + onPrimary = Color.White, + onSecondary = lightGray, + onBackground = Color.White, + onSurface = Color.White, + error = Color.Red, +) \ No newline at end of file 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 new file mode 100644 index 0000000000..85454f9c57 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarSidebar.kt @@ -0,0 +1,103 @@ +package org.briarproject.briar.desktop.paul.views + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Surface +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +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.imageFromResource +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import org.briarproject.briar.desktop.paul.theme.briarBlack +import org.briarproject.briar.desktop.paul.theme.briarBlue + +@Composable +fun briarSidebar(UIMode: String, onModeChange: (String) -> Unit) { + Surface(modifier = Modifier.width(66.dp).fillMaxHeight(), color = briarBlue) { + Column(verticalArrangement = Arrangement.Top) { + IconButton( + modifier = Modifier.align(Alignment.CenterHorizontally) + .padding(top = 9.dp, bottom = 10.dp), onClick = {}) { + Image( + bitmap = imageFromResource("images/profile_images/p0.png"), + "my_profile_image", + modifier = Modifier.size(48.dp).align(Alignment.CenterHorizontally).clip( + CircleShape + ).border(2.dp, color = Color.White, CircleShape) + ) + } + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Contacts", + Icons.Filled.Contacts + ) + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Private Groups", + Icons.Filled.Group + ) + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Forums", + Icons.Filled.Forum + ) + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Blogs", + Icons.Filled.ChromeReaderMode + ) + } + Column(verticalArrangement = Arrangement.Bottom) { + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Transports", + Icons.Filled.WifiTethering + ) + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Settings", + Icons.Filled.Settings + ) + briarSidebarButton( + UIMode = UIMode, + onModeChange = onModeChange, + "Sign Out", + Icons.Filled.Logout + ) + } + } +} + +@Composable +fun briarSidebarButton( + UIMode: String, + onModeChange: (String) -> Unit, + thisMode: String, + icon: ImageVector +) { + val bg = if (UIMode == thisMode) briarBlack else briarBlue; + Column() { + IconButton( + modifier = Modifier.align(Alignment.CenterHorizontally).background(color = bg) + .padding(vertical = 9.dp, horizontal = 12.dp), + onClick = { onModeChange(thisMode) }) { + Icon(icon, thisMode, tint = Color.White, modifier = Modifier.size(30.dp)) + } + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt new file mode 100644 index 0000000000..a58fa8f036 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt @@ -0,0 +1,51 @@ +package org.briarproject.briar.desktop.paul.views + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +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.Color +import org.briarproject.briar.desktop.paul.data.ContactList +import org.briarproject.briar.desktop.paul.theme.briarBlack + +/* + * This is the root of the tree, all state is held here and passed down to stateless composables, which render the UI + * Desktop specific kotlin files are found in briarComposeDesktop (possibly briar-compose-desktop project in the future) + * Multiplatform, stateless, composable are found in briarCompose (possible briar-compose project in the future) + */ +@Composable +fun briarUIStateManager() { + //current selected mode, changed using the sidebar buttons + val (UIMode, onModeChange) = remember { mutableStateOf("Contacts") } + //current selected contact + val (UIContact, onContactSelect) = remember { mutableStateOf(ContactList.contacts[0]) } + //current selected private message + val (UIPrivateMsg, onPMSelect) = remember { mutableStateOf(0) } + //current selected forum + val (UIForum, onForumSelect) = remember { mutableStateOf(0) } + //current blog state + val (UIBlog, onBlogSelect) = remember { mutableStateOf(0) } + //current transport state + val (UITransports, onTransportSelect) = remember { mutableStateOf(0) } + //current settings state + val (UISettings, onSettingSelect) = remember { mutableStateOf(0) } + //current profile + var Profile: String; + //Other global state that we need to track should go here also + Row() { + briarSidebar(UIMode, onModeChange) + when (UIMode) { + "Contacts" -> privateMessageView(UIContact, onContactSelect) + else -> Box(modifier = Modifier.fillMaxSize().background(briarBlack)) { + Text("TBD", modifier = Modifier.align(Alignment.Center), color = Color.White) + } + } + } + +} 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 new file mode 100644 index 0000000000..fc8544d5e9 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/PrivateMessageView.kt @@ -0,0 +1,319 @@ +package org.briarproject.briar.desktop.paul.views + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +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.imageFromResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.v1.DialogProperties +import org.briarproject.briar.desktop.paul.data.ContactList +import org.briarproject.briar.desktop.paul.model.Contact +import org.briarproject.briar.desktop.paul.model.Message +import org.briarproject.briar.desktop.paul.theme.* + + +val HEADER_SIZE = 66.dp; + +@Composable +fun privateMessageView(UIContact: Contact, onContactSelect: (Contact) -> Unit) { + //Local State for managing the Add Contact Popup + val (AddContactDialog, onCancelAdd) = remember { mutableStateOf(false) } + addContactDialog(AddContactDialog, onCancelAdd) + Column(modifier = Modifier.fillMaxHeight()) { + Row(modifier = Modifier.fillMaxWidth()) { + Divider(color = divider, modifier = Modifier.fillMaxHeight().width(1.dp)) + Column(modifier = Modifier.fillMaxHeight().background(color = briarBlack).width(275.dp)) { + Row( + modifier = Modifier.fillMaxWidth().height(HEADER_SIZE).padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + "Contacts", + fontSize = 24.sp, + color = Color.White, + modifier = Modifier.align(Alignment.CenterVertically) + ) + IconButton( + onClick = { onCancelAdd(true) }, + modifier = Modifier.align(Alignment.CenterVertically).background(color = briarDarkGray) + ) { + Icon(Icons.Filled.Add, "add contact", tint = Color.White, modifier = Modifier.size(24.dp)) + } + } + Divider(color = divider, thickness = 1.dp, modifier = Modifier.fillMaxWidth()) + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + for (c in ContactList.contacts) { + contactCard(c, UIContact, onSel = onContactSelect) + } + } + + } + Divider(color = divider, modifier = Modifier.fillMaxHeight().width(1.dp)) + Column(modifier = Modifier.weight(1f).fillMaxHeight().background(color = darkGray)) { + drawMessageRow(UIContact) + } + } + } +} + +@Composable +fun addContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) { + if (isVisible) { + AlertDialog( + onDismissRequest = { + onCancel(false) + }, + text = { + Column(modifier = Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth().padding(vertical = 16.dp)) { + Text( + text = "Add Contact at a Distance", + fontSize = 24.sp, + color = Color.White, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Text( + "Contact's Link", + Modifier.width(128.dp).align(Alignment.CenterVertically), + color = lightGray + ) + TextField("", onValueChange = {}, modifier = Modifier.fillMaxWidth()) + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Text( + "Contact's Name", + Modifier.width(128.dp).align(Alignment.CenterVertically), + color = lightGray + ) + TextField("", onValueChange = {}, modifier = Modifier.fillMaxWidth()) + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Text( + "Your Link", + modifier = Modifier.width(128.dp).align(Alignment.CenterVertically), + color = lightGray + ) + TextField( + "briar://ksdjlfgakslhjgaklsjdhglkasjdlk3j12h4lk2j3tkj4", + onValueChange = {}, + modifier = Modifier.fillMaxWidth() + ); + } + } + }, + confirmButton = { + TextButton( + onClick = { + onCancel(false) + }, + modifier = Modifier.background(briarGreen) + ) { + Text("Add") + } + }, + dismissButton = { + TextButton( + onClick = { + onCancel(false) + }, modifier = Modifier.background(briarBlack) + ) { + Text("Cancel") + } + }, + + backgroundColor = briarBlue, + contentColor = Color.White, + modifier = Modifier.border(1.dp, color = divider), + properties = DialogProperties(resizable = false, undecorated = true, size = IntSize(600, 300)) + ) + } +} + +@Composable +fun contactCard(contact: Contact, selContact: Contact, onSel: (Contact) -> Unit) { + var bgColor = briarBlack + if (selContact.name == contact.name) { + bgColor = darkGray + } + Row( + modifier = Modifier.fillMaxWidth().height(HEADER_SIZE).background(bgColor) + .clickable(onClick = { onSel(contact) }), horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)) { + Image( + bitmap = imageFromResource("images/profile_images/" + contact.profile_pic), + "image", + modifier = Modifier.size(40.dp).align(Alignment.CenterVertically).clip( + CircleShape + ).border(2.dp, color = Color.White, CircleShape) + ) + Column(modifier = Modifier.align(Alignment.CenterVertically).padding(start = 12.dp)) { + Text( + contact.name, + fontSize = 14.sp, + color = Color.White, + modifier = Modifier.align(Alignment.Start).padding(bottom = 2.dp) + ) + Text( + contact.last_heard, + fontSize = 10.sp, + color = Color.LightGray, + modifier = Modifier.align(Alignment.Start) + ) + } + } + androidx.compose.foundation.Canvas( + modifier = Modifier.padding(horizontal = 29.dp).size(22.dp).align(Alignment.CenterVertically), onDraw = { + val size = 16.dp.toPx() + drawCircle( + color = Color.White, + radius = size / 2f + ) + if (contact.online) { + drawCircle( + color = briarGreen, + radius = 14.dp.toPx() / 2f + ) + } else { + drawCircle( + color = briarBlack, + radius = 14.dp.toPx() / 2f + ) + } + }) + } + + Divider(color = divider, thickness = 1.dp, modifier = Modifier.fillMaxWidth()) +} + +@Composable +fun textBubble(m: Message) { + Column(Modifier.fillMaxWidth()) { + if (m.from == null) { + Column(Modifier.fillMaxWidth(fraction = 0.9f).align(Alignment.End)) { + Column(Modifier.background(briarBlueMsg).padding(8.dp).align(Alignment.End)) { + Text(m.message, fontSize = 14.sp, color = Color.White, modifier = Modifier.align(Alignment.Start)) + Row(modifier = Modifier.padding(top = 4.dp)) { + Text(m.time, Modifier.padding(end = 4.dp), fontSize = 10.sp, color = Color.LightGray) + if (m.delivered) { + Icon( + Icons.Filled.Check, + "sent", + tint = Color.LightGray, + modifier = Modifier.size(10.dp).align(Alignment.CenterVertically) + ) + } else { + Icon( + Icons.Filled.Send, + "sending", + tint = Color.LightGray, + modifier = Modifier.size(10.dp).align(Alignment.CenterVertically) + ) + } + } + } + } + } else { + Column(Modifier.fillMaxWidth(fraction = 0.9f).align(Alignment.Start)) { + Column(Modifier.background(briarGrayMsg).padding(8.dp).align(Alignment.Start)) { + Text(m.message, fontSize = 14.sp, color = Color.White, modifier = Modifier.align(Alignment.Start)) + Row(modifier = Modifier.padding(top = 4.dp)) { + Text(m.time, Modifier.padding(end = 4.dp), fontSize = 10.sp, color = Color.LightGray) + if (m.delivered) { + Icon( + Icons.Filled.Check, + "sent", + tint = Color.LightGray, + modifier = Modifier.size(10.dp).align(Alignment.CenterVertically) + ) + } else { + Icon( + Icons.Filled.Send, + "sending", + tint = Color.LightGray, + modifier = Modifier.size(10.dp).align(Alignment.CenterVertically) + ) + } + } + } + } + } + } +} + +@Composable +fun drawTextBubbles(msgList: List<Message>) { + LazyColumn( + Modifier.fillMaxWidth().padding(horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + reverseLayout = true, + contentPadding = PaddingValues(vertical = 8.dp) + ) { + items(msgList) { m -> + textBubble(m) + } + } +} + +@Composable +fun drawMessageRow(UIContact: Contact) { + Box(Modifier.fillMaxHeight()) { + Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { + Row(modifier = Modifier.align(Alignment.Center)) { + Image( + bitmap = imageFromResource("images/profile_images/" + UIContact.profile_pic), + "sel_contact_prof", + modifier = Modifier.size(36.dp).align( + Alignment.CenterVertically + ).clip( + CircleShape + ).border(2.dp, color = Color.White, CircleShape) + ) + Text( + UIContact.name, + color = Color.White, + modifier = Modifier.align(Alignment.CenterVertically).padding(start = 12.dp), + fontSize = 24.sp + ) + } + IconButton(onClick = {}, modifier = Modifier.align(Alignment.CenterEnd).padding(end = 16.dp)) { + Icon(Icons.Filled.MoreVert, "contact info", tint = Color.White, modifier = Modifier.size(24.dp)) + } + Divider(color = divider, thickness = 1.dp, modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) + } + Box(Modifier.padding(top = HEADER_SIZE + 1.dp, bottom = HEADER_SIZE)) { + drawTextBubbles(UIContact.privateMessages) + } + var text by remember { mutableStateOf(TextFieldValue("")) } + Box(Modifier.align(Alignment.BottomCenter).background(darkGray)) { + OutlinedTextField( + value = text, + trailingIcon = { Icon(Icons.Filled.Send, "send message", tint = briarGreen) }, + leadingIcon = { Icon(Icons.Filled.AddCircle, contentDescription = "add file") }, + modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp).fillMaxWidth(), + label = { Text(text = "Message") }, + textStyle = TextStyle(color = Color.White), + placeholder = { Text(text = "Your message to " + UIContact.name) }, + onValueChange = { + text = it + }, + ) + } + } +} \ No newline at end of file diff --git a/src/main/resources/images/profile_images/p0.png b/src/main/resources/images/profile_images/p0.png new file mode 100644 index 0000000000000000000000000000000000000000..8c67a3c592c8267f56a4c810cb2d561c88d05e18 GIT binary patch literal 45426 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/images/profile_images/p1.png b/src/main/resources/images/profile_images/p1.png new file mode 100644 index 0000000000000000000000000000000000000000..50155c8a1ebc3513e3b6e3cc2936881b594f92f4 GIT binary patch literal 2804 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/images/profile_images/p2.png b/src/main/resources/images/profile_images/p2.png new file mode 100644 index 0000000000000000000000000000000000000000..8ff9a6e10f151d8e9913a968a535cf7b3e542c0f GIT binary patch literal 45426 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 V^V|P;y}^qQUq1iC_kZ%6e*sZkT)O}O literal 0 HcmV?d00001 diff --git a/src/main/resources/images/profile_images/p3.png b/src/main/resources/images/profile_images/p3.png new file mode 100644 index 0000000000000000000000000000000000000000..4b8509e1e21afe1863ad0d35395ef11cdd9113b4 GIT binary patch literal 45426 zcmeI1F=~}T00#e%CIr%nMLHV`Z=oc_hY~`}0kkn<Ar>Omc7m1nh^0sH05*y@@D$Ia z+G&QoBFIW6pniNi-(%mb*!bz>_-Jo;e|K4yz18D`)5|}9T>aYFzWjaX=*`<@*<Mbb zo*k~&>(AeQU6%c1;1(LV^J@RgvTQByPLH3h*SAmrzVd_fpWm0|;@;}u;n|1ZTX!%1 zT>ZPzPX;oOfed6I0~yFb1~QO=3~XXx>-wJ!KIZxl+4^Unvdnauo4HG0$^Q1-jNhC2 zlwPS<;@>2*fu6Wz&>CgRTDRkXxa0wAk|k^1;()wl&=zIM+NS7<O9rh`rmS^44v0%0 zuqIiu)-4XmO9pLGmaJ`xp15St8fD5_x8s1g<N<4vC2QT{fV^bT7G=rWrs#=F2CY%1 ztaUpMh)W)@CRwu9Ee^;_25nK6tZj;(xMa{8Wy)H&<AAv20c(;aYu)02ykyW8Wy#v6 z=!r`Ptx=||bvq7-OCGQ$S+dqG4#-OeZBdr2ZHk__WY8LA%38PMfVkuVYmy~v-Qs|} zWY896$=asqiAx5pQKqbQI}V6T9<U}^veqpQ$V&!oQI@Q2ik`S+&>CgRTDRkXxa0wA zk|k^1;()wl&=zIM+NS7<O9rh`rmS^44v0%0uqIiu)-4XmO9pLGmaJ`xp15St8fD5_ zx8s1g<N<4vC2QT{fV^bT7G=rWrs#=F2CY%1taUpMh)W)@CRwu9Ee^;_25nK6tZj;( zxMa{8Wy)H&<AAv20c(;aYu)02ykyW8Wy#v6=!r`Ptx=||bvq7-OCGQ$S+dqG4#-Oe zZBdr2ZHk__WYEP7WFP|>$Up`%kbw+jAOji5Kn609fed6I0~yFb1~QO=3}he!|E~f0 zKhat1O+?Rncez(zNyJQ5_exVehg_yt5;0TNz0y?AA(!cuM9frmuQb(j$YpvZ5i?cY zD^2wra+zL9#7tH9N>e?DT&7nNF;msO(p1kOm+6&6%v5!+G}Uv+WqKtMGgaLyP4ygd znOpiwn|$q<3}he!8OT5eGLV4`WFP|>$Up`%kbw+jAOji5Kn609fed6I0~yFb1~QO= z3}he!8OT5eGLV4`WFP|>$Up`%kbw+jAOji5Kn609fed6I0~yFb1~QO=3}he!8OT5e cGBBrs^OyHm?_WH5eRaFl;qk%ON6$b01wq6_2mk;8 literal 0 HcmV?d00001 diff --git a/src/main/resources/images/profile_images/p4.png b/src/main/resources/images/profile_images/p4.png new file mode 100644 index 0000000000000000000000000000000000000000..96ed7fbc717af1d66c71fd5b9376b6a32dfc6e5b GIT binary patch literal 45426 zcmeI1v1*i26omgJO$em5u(Q-gUqMNT0SO_dvC=}Y3n?tD?F3&XHu?-cfQ_PsjZL0n z-E=mGd~?rm6*CJlaG5jb+wVXc-yI(v?C#v&S(at@@bTWs=J%JYpY5&9zuO0A=gYFS z96veTU$58izkS*~<`V-q(7@%hU(c6iwY)w#db(cUKmmVz<$JHbeqNS~JBND@Pv3lB z-MYBE`n!=&48%YT#6S$hKn%n{48%YT#K1fQtLs1WU_6_y|3)j%qIoU(O7mQgZ_1U% z-OZcKmFBq~-;^tjyPG$eE6sB~zA0B4cQ<b`SDNQ~d{eG8?rz>>t~Af}_@-QG+}*s% zTxp)`@lCnXxVw3ixzaq><C}7&ad-13bESE%$2a9l<L>57{!>>1Fdzctfff(}ixyA= zB486}0THk$U#ap4<W-~EJ6Mz}RZ4-pYE*j%i*lt(DUer<YVTlCu2d-n@~Tno9W2U~ zDy2YPHLAUXMY&R?6v(SawRf;6SE`f(dDW=)4i@D~l~N$D8r9yxqFkv`3glIz+B;a3 zD^*H?ylPZ?2a9s0N-2<6jcV^;QLa=e1@fv<?Hw%2l`5q`UNx${gGISgr4-1kMzwda zC|9bK0(sS__6`>1N|jO|uNu|f!J_~1O5kg29vf-EVu~~%wNVo<sL{9@X~1HNG$6H6 z6ECRIxEg7|Vu~~%wNVo<sL{9@X~1HNG$6H66ECRIxEg7|Vu~~%wNVo<sL{9@X~1HN zG$6H66ECRIxEg7|Vu~~%wNVo<sL{9@X~1HNG$6H66ECRIxEg7|Vu~~%wNVo<sL{9@ zX~1HNG$6H66ECRIxEg7|Vu~~%wNVo<sL{9@X~1HNG$6H66ECRIxEg7|Vu~~%wNVo< zsL{9@X~1HNG$6H66ECRIxEg7|Vu~~%wNVo<sL{9@X~1HNG$6H66ECRIxEg7|Vu~~% zwNVo<sL{9@X~2?548%YT#6S$hKn%n{48%YT#6S$hKn%n{48%YT#6S$hKn%n{48%YT z+!O=ge_w$LPYYN?A|4n|3*iMB4~(Y;;F5?3#?wN0LB<2)X#uz-;(_tB5MGe+z<62! zE{S+xJS~J5WIQmQ7Jy449vDvx;RP8FjHd<Ql86V!(?WPb#slMN0k|aMf$_8uUXby? zcv=81iFjZ<Erb_jJTRUXfJ-7C7*7k~1sM;Frv>1WhzG{gLU=*O1LJ7{xFq6%@w5<L zknzBHS^zGIcwjs&gcoEyFrF5GOClZ^PYdA%84rx71>lm12gcJvctOSk<7ol7B;tYb zv=Cm9@xXXm04|AmU_32^7i2szo)&;hA|4n|3*iMB4~(Y;;3g6SF%SbW5Cbs~12GT- iF%SbW5Cbvr7Y&@hy#M3p-H#8h5Dxc`_C7p%@%A@gC{OkP literal 0 HcmV?d00001 diff --git a/src/main/resources/images/profile_images/p5.png b/src/main/resources/images/profile_images/p5.png new file mode 100644 index 0000000000000000000000000000000000000000..72348d6d73b3a8cb15aa7f1269e5e980256c7388 GIT binary patch literal 45426 zcmeI4KWfwg6oh|4ix9B2un`LzZ=pmqAR)vYz)nFB3k$){PO!1|27>lpzysJQc9x!E zO~609@Fsi%Lj*GmNp@%7oB4KLNSRj$`*$wwT-jNc<<j2W-NWObpHIHFFC2g0zVqbi zvRqgW?j7A;tyXVee?1oaXanc4fh!;1y;zp5<=Nr>{nhFm7O?h_Z#?|+X<6Q1+uOZ) z^!(e_<@Y~M{%-Wq2HHRyXajAa4YYwa&<5H-8)yThfvv~`Bt>$zK+tk#NoN++kEE&w zzYl8F(?L+Lq$&mFsZ~!0LA{cy6qKh{Jskw~N~%&&o?7*E5Y#KFN<n#Q)zd*xucRsk z<*8Lq2SL4(suYx`Ry`dA^-8KzP@Y=#bP&`lsY*e4YSq(0P_Lva1?8z#PX|H0lByJx zr&c{31ocX)Qc#{+^>h%_E2&CBd1}?uK~S%xDh1`KRZj;&y^^XFl&4lb9R&4Cs!~v% zTJ>}g^gmt+d~DF;nYn-<GSZJETFMk7%w0JHlW}p`#e4=P<024UIRle%aoNRu1}5Vo z5M4O~lW}p`#e4=P<024UIRle%aoNRu1}5Vo5M4O~lW}p`#e4=P<024UIRle%aoNRu z1}5Vo5M4O~lW}p`#e4=P<024UIRle%aoNRu1}5Vo5M4O~lW}p`#e4=P<024UIRle% zaoNRu1}5Vo5M4O~lW}p`#e4=P<024UIRle%aoNRu1}5Vo5M4O~lW}p`#e4=P<024U zIRle%aoNRu1}5Vo5M4O~lW}p`#e4=P<024UIRle%aoNRu1}5Vo5I5wrOWKGXUxZjf zHY1`dhFC&2F8w$kVhP!dh^`o73E8;x<9vuEWHTbVVu&SV<I<1wA(oKMi0FzTmXM80 zKhB3(LN+6!D~4D?HZJ`*A7TmFjEJrnVhP!}^y7SpC1f)qx?+eWWaHA0^C6ay&4}oV zA(oJhOFzzsSVA@<qAP}2LN+e_I3HpO*^G#;7-9+8xb)+Eh$Un*BD!LTC1m5$kMkjx zkj;qbiXoPejY~hyhgd>3BcdyYSVA@~{Wu?D3E7N@t{7qo*|_xMe267vGa|ZTh$Up> z(vR~YmXOVe=!zkhkc~?}&WBh+HY1`dhFC&2F8w$kVgWW_WF*`afeRSf^qgM-8!$3^ zZ>YcpjBHqn7r+LL2=jFrxPXy$NA(=ofDz@srUMr+vSuis0vj+Q?f<?3T)@a*Km82Y zfD!Hg=N;ezM*i5H@&wp`ky8SGzXe>t$ZxmPegYdXa$3T#_kar+0YU%cKSuz~fDuq0 zZ~-GAs8>=wg7Va=r-PtgNmUBUQ>&g1f_f!YDJV~^dO8T|l~kpmJhkfSAgEVTm4for zs;7gXUP)C7%2TVJ4uX0mRVgS>t$I2L>XlTbpggte=^&_AQk8=8)T*b0pk7H;3d&Qf zo(_U~B~>XXPpx`72<nworJy{u>ggb;S5lRN^3<xQgP>kXRSL>etDX*mdL>mUC{L|= zItc2ORHdLiwd(00s8>>zg7Va=r-PtgNmUBUQ>&g1f_f!YDJV~^dO8T|l~kpmJhkfS zAgEVTm4fors;7gXUP)C7%2TVJ4uX0mRVgS>t$I2L>XlTbpggte=^&_AQk8=8)T*b0 zpk7H;3d&Qfo(_U~B~>XXPpx`72<nworJy{u>ggb;S5lRN^3<xQgP>kXRSL>etDX*m zI?@K(KpSWSZJ-Ubfi}<v+CUp<18txU{KE#WUcdL|(c|wIPZ0KQ@9%!N_2A`C<{xVo literal 0 HcmV?d00001 -- GitLab