diff --git a/src/main/java/org/briarproject/briar/desktop/chat/ChatHistoryConversationVisitor.java b/src/main/java/org/briarproject/briar/desktop/chat/ChatHistoryConversationVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..def7853ad30e88f39f14f623d869ffcfe06c8aae --- /dev/null +++ b/src/main/java/org/briarproject/briar/desktop/chat/ChatHistoryConversationVisitor.java @@ -0,0 +1,104 @@ +package org.briarproject.briar.desktop.chat; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.briar.api.blog.BlogInvitationRequest; +import org.briarproject.briar.api.blog.BlogInvitationResponse; +import org.briarproject.briar.api.conversation.ConversationMessageHeader; +import org.briarproject.briar.api.conversation.ConversationMessageVisitor; +import org.briarproject.briar.api.forum.ForumInvitationRequest; +import org.briarproject.briar.api.forum.ForumInvitationResponse; +import org.briarproject.briar.api.introduction.IntroductionRequest; +import org.briarproject.briar.api.introduction.IntroductionResponse; +import org.briarproject.briar.api.messaging.MessagingManager; +import org.briarproject.briar.api.messaging.PrivateMessageHeader; +import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest; +import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChatHistoryConversationVisitor + implements ConversationMessageVisitor<Void> { + + final static Logger logger = LoggerFactory.getLogger(ChatHistoryConversationVisitor.class); + + private Chat chat; + private MessagingManager messagingManager; + + public ChatHistoryConversationVisitor(Chat chat, MessagingManager messagingManager) { + this.chat = chat; + this.messagingManager = messagingManager; + } + + void appendMessage(ConversationMessageHeader header) { + try { + String messageText = messagingManager.getMessageText(header.getId()); + chat.appendMessage(header.isLocal(), header.getTimestamp(), messageText); + } catch (DbException e) { + logger.warn("Error while getting message text", e); + } + } + + @Override + public Void visitPrivateMessageHeader(PrivateMessageHeader h) { + appendMessage(h); + return null; + } + + @Override + public Void visitBlogInvitationRequest(BlogInvitationRequest r) { + return null; + } + + @Override + public Void visitBlogInvitationResponse(BlogInvitationResponse r) { + return null; + } + + @Override + public Void visitForumInvitationRequest(ForumInvitationRequest r) { + return null; + } + + @Override + public Void visitForumInvitationResponse(ForumInvitationResponse r) { + return null; + } + + @Override + public Void visitGroupInvitationRequest(GroupInvitationRequest r) { + return null; + } + + @Override + public Void visitGroupInvitationResponse(GroupInvitationResponse r) { + return null; + } + + @Override + public Void visitIntroductionRequest(IntroductionRequest r) { + Author nameable = r.getNameable(); + chat.appendMessage(r.isLocal(), r.getTimestamp(), String.format( + "You received an introduction request! Username: %s, Message: %s", + r.getName(), r.getText())); + if (!r.wasAnswered()) { + chat.appendMessage(r.isLocal(), r.getTimestamp(), + "Do you accept the invitation?"); + // TODO chat.appendYesNoButtons(r); + } + return null; + } + + @Override + public Void visitIntroductionResponse(IntroductionResponse r) { + if (r.wasAccepted()) { + chat.appendMessage(r.isLocal(), r.getTimestamp(), + "You accepted the request"); + } else { + chat.appendMessage(r.isLocal(), r.getTimestamp(), + "You declined the request"); + } + return null; + } + +} diff --git a/src/main/java/org/briarproject/briar/desktop/chat/ConversationMessageHeaderComparator.java b/src/main/java/org/briarproject/briar/desktop/chat/ConversationMessageHeaderComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..beace82b0d24eaeb39dfc97daa09148ca632efb3 --- /dev/null +++ b/src/main/java/org/briarproject/briar/desktop/chat/ConversationMessageHeaderComparator.java @@ -0,0 +1,16 @@ +package org.briarproject.briar.desktop.chat; + +import org.briarproject.briar.api.conversation.ConversationMessageHeader; + +import java.util.Comparator; + +public class ConversationMessageHeaderComparator + implements Comparator<ConversationMessageHeader> { + + @Override + public int compare(ConversationMessageHeader h1, + ConversationMessageHeader h2) { + return Long.compare(h1.getTimestamp(), h2.getTimestamp()); + } + +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt index 2241913c7173e039b7beea7ac245e837f38d0ff3..721c6fb7e49158766a86fd0f442556df458c60f2 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/BriarService.kt @@ -12,6 +12,8 @@ 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.lifecycle.LifecycleManager +import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.messaging.MessagingManager import org.briarproject.briar.desktop.dialogs.Login import org.briarproject.briar.desktop.dialogs.Registration import org.briarproject.briar.desktop.paul.views.BriarUIStateManager @@ -21,7 +23,11 @@ import javax.inject.Singleton interface BriarService { @Composable - fun start() + fun start( + conversationManager: ConversationManager, + messagingManager: MessagingManager + ) + fun stop() } @@ -32,6 +38,7 @@ internal class BriarServiceImpl constructor( private val accountManager: AccountManager, private val contactManager: ContactManager, + private val messagingManager: MessagingManager, private val lifecycleManager: LifecycleManager, private val passwordStrengthEstimator: PasswordStrengthEstimator ) : BriarService { @@ -39,11 +46,14 @@ constructor( private val contacts: MutableList<Contact> = ArrayList() @Composable - override fun start() { + override fun start( + conversationManager: ConversationManager, + messagingManager: MessagingManager + ) { if (!accountManager.accountExists()) { createAccount() } else { - login() + login(conversationManager, messagingManager) } } @@ -58,7 +68,10 @@ constructor( } @Composable - private fun login() { + private fun login( + conversationManager: ConversationManager, + messagingManager: MessagingManager + ) { val title = "Briar Desktop" var screenState by remember { mutableStateOf<Screen>(Screen.Login) } Window(title = title) { @@ -78,7 +91,7 @@ constructor( ) is Screen.Main -> - BriarUIStateManager(contacts) + BriarUIStateManager(contacts, conversationManager, messagingManager) } } } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/UI.kt b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt index a1e77ff829325a394bebe4760af7571e2801243a..a95c4f856a5f7f615639f518a3b69a364dbd8d02 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/UI.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/UI.kt @@ -34,7 +34,7 @@ constructor( @Composable internal fun startBriar() { - briarService.start() + briarService.start(conversationManager, messagingManager) } internal fun getContactManager(): ContactManager { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/chat/Chat.kt b/src/main/kotlin/org/briarproject/briar/desktop/chat/Chat.kt new file mode 100644 index 0000000000000000000000000000000000000000..d687df5a2b7a1cf8df5a780444f8ab5835f5ed4f --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/chat/Chat.kt @@ -0,0 +1,29 @@ +package org.briarproject.briar.desktop.chat + +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +class Chat { + + companion object { + private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + } + + val messages: MutableList<SimpleMessage> = ArrayList() + + fun add(message: SimpleMessage) { + messages.add(message) + } + + fun appendMessage(local: Boolean, timestamp: Long, messageText: String?) { + val name = if (local) "You" else "Other" + val dateTime = LocalDateTime.ofInstant( + Instant.ofEpochMilli(timestamp), ZoneId.systemDefault() + ) + val author = String.format("%s (%s): ", name, formatter.format(dateTime)) + messages.add(SimpleMessage(local, author, messageText!!, "time", true)) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/desktop/chat/SimpleMessage.kt b/src/main/kotlin/org/briarproject/briar/desktop/chat/SimpleMessage.kt new file mode 100644 index 0000000000000000000000000000000000000000..cf1f56a4916ba82a5a2f64d7c7c0abfc6788776d --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/chat/SimpleMessage.kt @@ -0,0 +1,10 @@ +package org.briarproject.briar.desktop.chat + +data class SimpleMessage( + val local: Boolean, + val from: String?, + val message: String, + val time: String, + val delivered: Boolean, + // val read: Boolean, +) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/chat/UiState.kt b/src/main/kotlin/org/briarproject/briar/desktop/chat/UiState.kt new file mode 100644 index 0000000000000000000000000000000000000000..e787fc238c01ae88a7fbf613fa85a9875810ab07 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/chat/UiState.kt @@ -0,0 +1,7 @@ +package org.briarproject.briar.desktop.chat + +sealed class UiState<out T> { + object Loading : UiState<Nothing>() + data class Success<out T>(val data: T) : UiState<T>() + data class Error(val exception: Exception) : UiState<Nothing>() +} \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContactList.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContactList.kt deleted file mode 100644 index f01578cb024a96eebd02a80a0cfc982e45a34c85..0000000000000000000000000000000000000000 --- a/src/main/kotlin/org/briarproject/briar/desktop/paul/data/ContactList.kt +++ /dev/null @@ -1,228 +0,0 @@ -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/views/BriarUIStateManager.kt b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt index e1a46d5396283c178af3bdcea5839f5148a5b82b..fd973e48e1013a48f44c206c50948bc9ca7924a8 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/paul/views/BriarUIStateManager.kt @@ -6,12 +6,15 @@ 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.MutableState 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.bramble.api.contact.Contact +import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.messaging.MessagingManager import org.briarproject.briar.desktop.paul.theme.briarBlack /* @@ -20,11 +23,15 @@ import org.briarproject.briar.desktop.paul.theme.briarBlack * Multiplatform, stateless, composable are found in briarCompose (possible briar-compose project in the future) */ @Composable -fun BriarUIStateManager(contacts: List<Contact>) { +fun BriarUIStateManager( + contacts: List<Contact>, + conversationManager: ConversationManager, + messagingManager: MessagingManager +) { // current selected mode, changed using the sidebar buttons val (uiMode, onModeChange) = remember { mutableStateOf("Contacts") } // current selected contact - val (uiContact, onContactSelect) = remember { mutableStateOf(contacts[0]) } + val uiContact: MutableState<Contact> = remember { mutableStateOf(contacts[0]) } // current selected private message val (uiPrivateMsg, onPMSelect) = remember { mutableStateOf(0) } // current selected forum @@ -41,7 +48,12 @@ fun BriarUIStateManager(contacts: List<Contact>) { Row() { BriarSidebar(uiMode, onModeChange) when (uiMode) { - "Contacts" -> PrivateMessageView(contacts, uiContact, onContactSelect) + "Contacts" -> PrivateMessageView( + contacts, + uiContact, + conversationManager, + messagingManager + ) 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 index 172b3b657c065f46225aa5a55fd02718ab4a36a1..ded78f22449845a3a56e4aeea09fd64048cda007 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 @@ -21,6 +21,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.AlertDialog +import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.IconButton @@ -35,6 +36,8 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -51,7 +54,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.v1.DialogProperties import org.briarproject.bramble.api.contact.Contact -import org.briarproject.briar.desktop.paul.model.Message +import org.briarproject.bramble.api.contact.ContactId +import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.conversation.ConversationMessageHeader +import org.briarproject.briar.api.messaging.MessagingManager +import org.briarproject.briar.desktop.chat.Chat +import org.briarproject.briar.desktop.chat.ChatHistoryConversationVisitor +import org.briarproject.briar.desktop.chat.ConversationMessageHeaderComparator +import org.briarproject.briar.desktop.chat.SimpleMessage +import org.briarproject.briar.desktop.chat.UiState import org.briarproject.briar.desktop.paul.theme.briarBlack import org.briarproject.briar.desktop.paul.theme.briarBlue import org.briarproject.briar.desktop.paul.theme.briarBlueMsg @@ -61,11 +72,18 @@ import org.briarproject.briar.desktop.paul.theme.briarGreen import org.briarproject.briar.desktop.paul.theme.darkGray import org.briarproject.briar.desktop.paul.theme.divider import org.briarproject.briar.desktop.paul.theme.lightGray +import java.util.* + val HEADER_SIZE = 66.dp @Composable -fun PrivateMessageView(contacts: List<Contact>, uiContact: Contact, onContactSelect: (Contact) -> Unit) { +fun PrivateMessageView( + contacts: List<Contact>, + uiContact: MutableState<Contact>, + conversationManager: ConversationManager, + messagingManager: MessagingManager +) { // Local State for managing the Add Contact Popup val (AddContactDialog, onCancelAdd) = remember { mutableStateOf(false) } AddContactDialog(AddContactDialog, onCancelAdd) @@ -93,13 +111,13 @@ fun PrivateMessageView(contacts: List<Contact>, uiContact: Contact, onContactSel Divider(color = divider, thickness = 1.dp, modifier = Modifier.fillMaxWidth()) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { for (c in contacts) { - ContactCard(c, uiContact, onSel = onContactSelect) + ContactCard(uiContact, c) } } } Divider(color = divider, modifier = Modifier.fillMaxHeight().width(1.dp)) Column(modifier = Modifier.weight(1f).fillMaxHeight().background(color = darkGray)) { - DrawMessageRow(uiContact) + DrawMessageRow(uiContact.value, conversationManager, messagingManager) } } } @@ -183,14 +201,16 @@ fun AddContactDialog(isVisible: Boolean, onCancel: (Boolean) -> Unit) { } @Composable -fun ContactCard(contact: Contact, selContact: Contact, onSel: (Contact) -> Unit) { +fun ContactCard(selContact: MutableState<Contact>, contact: Contact) { var bgColor = briarBlack - if (selContact.id == contact.id) { + if (selContact.value.id == contact.id) { bgColor = darkGray } Row( modifier = Modifier.fillMaxWidth().height(HEADER_SIZE).background(bgColor) - .clickable(onClick = { onSel(contact) }), + .clickable(onClick = { + selContact.value = contact + }), horizontalArrangement = Arrangement.SpaceBetween ) { Row(modifier = Modifier.align(Alignment.CenterVertically).padding(horizontal = 16.dp)) { @@ -246,9 +266,9 @@ fun ContactCard(contact: Contact, selContact: Contact, onSel: (Contact) -> Unit) } @Composable -fun TextBubble(m: Message) { +fun TextBubble(m: SimpleMessage) { Column(Modifier.fillMaxWidth()) { - if (m.from == null) { + if (m.local) { 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)) @@ -303,21 +323,40 @@ fun TextBubble(m: Message) { } @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) +fun DrawTextBubbles(chat: UiState<Chat>) { + when (chat) { + is UiState.Loading -> Loader() + is UiState.Error -> Loader() + is UiState.Success -> + LazyColumn( + Modifier.fillMaxWidth().padding(horizontal = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + reverseLayout = true, + contentPadding = PaddingValues(vertical = 8.dp) + ) { + items(chat.data.messages) { m -> + TextBubble(m) + } + } + } +} + +@Composable +fun Loader() { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth().padding(20.dp) ) { - items(msgList) { m -> - TextBubble(m) - } + CircularProgressIndicator() } } @Composable -fun DrawMessageRow(uiContact: Contact) { +fun DrawMessageRow( + uiContact: Contact, + conversationManager: ConversationManager, + messagingManager: MessagingManager +) { Box(Modifier.fillMaxHeight()) { Box(modifier = Modifier.fillMaxWidth().height(HEADER_SIZE + 1.dp)) { Row(modifier = Modifier.align(Alignment.Center)) { @@ -342,12 +381,15 @@ fun DrawMessageRow(uiContact: Contact) { 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)) + Divider( + color = divider, + thickness = 1.dp, + modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter) + ) } Box(Modifier.padding(top = HEADER_SIZE + 1.dp, bottom = HEADER_SIZE)) { - // TODO: use real messages - //DrawTextBubbles(UIContact.privateMessages) - DrawTextBubbles(ArrayList()) + val chat = ChatState(uiContact.id, conversationManager, messagingManager) + DrawTextBubbles(chat.value) } var text by remember { mutableStateOf(TextFieldValue("")) } Box(Modifier.align(Alignment.BottomCenter).background(darkGray)) { @@ -366,3 +408,29 @@ fun DrawMessageRow(uiContact: Contact) { } } } + +@Composable +fun ChatState( + id: ContactId, + conversationManager: ConversationManager, + messagingManager: MessagingManager +): MutableState<UiState<Chat>> { + val state: MutableState<UiState<Chat>> = remember { mutableStateOf(UiState.Loading) } + + DisposableEffect(id) { + state.value = UiState.Loading + val chat = Chat() + val visitor = ChatHistoryConversationVisitor(chat, messagingManager) + val messageHeaders: List<ConversationMessageHeader> = ArrayList(conversationManager.getMessageHeaders(id)) + Collections.sort(messageHeaders, ConversationMessageHeaderComparator()) + // FIXME: for some reason messages are displayed in reverse order + Collections.reverse(messageHeaders) + for (header in messageHeaders) { + header.accept(visitor) + } + state.value = UiState.Success(chat) + onDispose { } + } + + return state +} \ No newline at end of file