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