diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt
index 5442901fd7eab790c64078204c0c3342950f06ae..43795303df5094fbec9d930f6cdc372fc21ce62a 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactDialog.kt
@@ -1,6 +1,6 @@
 /*
  * Briar Desktop
- * Copyright (C) 2021-2022 The Briar Project
+ * Copyright (C) 2021-2023 The Briar Project
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -113,7 +113,6 @@ const val link = "briar://ady23gvb2r76afe5zhxh5kvnh4b22zrcnxibn63tfknrdcwrw7zrs"
 
 @Suppress("HardCodedStringLiteral")
 fun main() = preview(
-    "visible" to true,
     "remote link" to "",
     "local link" to link,
     "alias" to "Alice",
@@ -132,9 +131,8 @@ fun main() = preview(
     ),
 ) {
     val localLink = getStringParameter("local link")
-    AddContactDialog(
-        onClose = { setBooleanParameter("visible", false) },
-        visible = getBooleanParameter("visible"),
+    AddContactDialogContent(
+        onClose = {},
         remoteHandshakeLink = getStringParameter("remote link"),
         setRemoteHandshakeLink = { link -> setStringParameter("remote link", link) },
         alias = getStringParameter("alias"),
@@ -166,33 +164,9 @@ private fun PreviewUtils.PreviewScope.mapErrors(name: String?): AddContactError?
 
 @Composable
 fun AddContactDialog(
-    viewModel: AddContactViewModel,
-) = AddContactDialog(
-    viewModel::dismissDialog,
-    viewModel.visible.value,
-    viewModel.remoteHandshakeLink.value,
-    viewModel::setRemoteHandshakeLink,
-    viewModel.alias.value,
-    viewModel::setAddContactAlias,
-    viewModel.handshakeLink.value,
-    viewModel::onSubmitAddContactDialog,
-    viewModel.error.value,
-    viewModel::clearError,
-)
-
-@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
-@Composable
-fun AddContactDialog(
-    onClose: () -> Unit,
     visible: Boolean,
-    remoteHandshakeLink: String,
-    setRemoteHandshakeLink: (String) -> Unit,
-    alias: String,
-    setAddContactAlias: (String) -> Unit,
-    handshakeLink: String,
-    onSubmitAddContactDialog: () -> Unit,
-    error: AddContactError?,
-    onErrorDialogDismissed: () -> Unit,
+    onClose: () -> Unit,
+    viewModel: AddContactViewModel = viewModel(),
 ) {
     if (!visible) {
         return
@@ -208,74 +182,99 @@ fun AddContactDialog(
         CompositionLocalProvider(LocalDensity provides density) {
             window.minimumSize = DensityDimension(360, 512)
             window.preferredSize = DensityDimension(520, 512)
-            val clipboardManager = LocalClipboardManager.current
-            val scaffoldState = rememberScaffoldState()
-            val coroutineScope = rememberCoroutineScope()
-            val aliasFocusRequester = remember { FocusRequester() }
-            Surface {
-                Scaffold(
-                    modifier = Modifier.padding(horizontal = 24.dp).padding(top = 24.dp, bottom = 12.dp),
-                    topBar = {
-                        Box(Modifier.fillMaxWidth()) {
-                            Text(
-                                i18n("contact.add.remote.title"),
-                                style = MaterialTheme.typography.h6,
-                                modifier = Modifier.padding(bottom = 12.dp)
-                            )
-                        }
-                    },
-                    scaffoldState = scaffoldState,
-                    content = {
-                        Column(Modifier.fillMaxSize()) {
-                            if (error != null) {
-                                AddContactErrorDialog(error, onErrorDialogDismissed)
-                            }
-                            OwnLink(
-                                handshakeLink,
-                                clipboardManager,
-                                coroutineScope,
-                                scaffoldState,
-                            )
-                            ContactLink(
-                                remoteHandshakeLink,
-                                setRemoteHandshakeLink,
-                                clipboardManager,
-                                coroutineScope,
-                                scaffoldState,
-                                aliasFocusRequester,
-                            )
-                            Alias(
-                                alias,
-                                setAddContactAlias,
-                                aliasFocusRequester,
-                                onSubmitAddContactDialog,
-                            )
+            AddContactDialogContent(
+                onClose,
+                viewModel.remoteHandshakeLink.value,
+                viewModel::setRemoteHandshakeLink,
+                viewModel.alias.value,
+                viewModel::setAddContactAlias,
+                viewModel.handshakeLink.value,
+                { viewModel.onSubmitAddContactDialog(onClose) },
+                viewModel.error.value,
+                viewModel::clearError,
+            )
+        }
+    }
+}
+
+@Composable
+private fun AddContactDialogContent(
+    onClose: () -> Unit,
+    remoteHandshakeLink: String,
+    setRemoteHandshakeLink: (String) -> Unit,
+    alias: String,
+    setAddContactAlias: (String) -> Unit,
+    handshakeLink: String,
+    onSubmitAddContactDialog: () -> Unit,
+    error: AddContactError?,
+    onErrorDialogDismissed: () -> Unit,
+) {
+    val clipboardManager = LocalClipboardManager.current
+    val scaffoldState = rememberScaffoldState()
+    val coroutineScope = rememberCoroutineScope()
+    val aliasFocusRequester = remember { FocusRequester() }
+    Surface {
+        Scaffold(
+            modifier = Modifier.padding(horizontal = 24.dp).padding(top = 24.dp, bottom = 12.dp),
+            topBar = {
+                Box(Modifier.fillMaxWidth()) {
+                    Text(
+                        i18n("contact.add.remote.title"),
+                        style = MaterialTheme.typography.h6,
+                        modifier = Modifier.padding(bottom = 12.dp)
+                    )
+                }
+            },
+            scaffoldState = scaffoldState,
+            content = {
+                Column(Modifier.fillMaxSize()) {
+                    if (error != null) {
+                        AddContactErrorDialog(error, onErrorDialogDismissed)
+                    }
+                    OwnLink(
+                        handshakeLink,
+                        clipboardManager,
+                        coroutineScope,
+                        scaffoldState,
+                    )
+                    ContactLink(
+                        remoteHandshakeLink,
+                        setRemoteHandshakeLink,
+                        clipboardManager,
+                        coroutineScope,
+                        scaffoldState,
+                        aliasFocusRequester,
+                    )
+                    Alias(
+                        alias,
+                        setAddContactAlias,
+                        aliasFocusRequester,
+                        onSubmitAddContactDialog,
+                    )
+                }
+            },
+            bottomBar = {
+                Box(Modifier.fillMaxWidth()) {
+                    Row(Modifier.align(Alignment.CenterEnd)) {
+                        TextButton(
+                            onClose,
+                            colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colors.error)
+                        ) {
+                            Text(i18n("cancel"))
                         }
-                    },
-                    bottomBar = {
-                        Box(Modifier.fillMaxWidth()) {
-                            Row(Modifier.align(Alignment.CenterEnd)) {
-                                TextButton(
-                                    onClose,
-                                    colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colors.error)
-                                ) {
-                                    Text(i18n("cancel"))
-                                }
-                                Button(onSubmitAddContactDialog, modifier = Modifier.padding(start = 8.dp)) {
-                                    Text(i18n("add"))
-                                }
-                            }
+                        Button(onSubmitAddContactDialog, modifier = Modifier.padding(start = 8.dp)) {
+                            Text(i18n("add"))
                         }
-                    },
-                )
-            }
-        }
+                    }
+                }
+            },
+        )
     }
 }
 
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
-fun AddContactErrorDialog(error: AddContactError, onErrorDialogDismissed: () -> Unit) {
+private fun AddContactErrorDialog(error: AddContactError, onErrorDialogDismissed: () -> Unit) {
     val (type, title, message) = errorMessage(error)
     val (icon, color) = when (type) {
         WARNING -> Icons.Filled.Warning to Orange500
@@ -308,11 +307,11 @@ fun AddContactErrorDialog(error: AddContactError, onErrorDialogDismissed: () ->
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-fun OwnLink(
+private fun OwnLink(
     handshakeLink: String,
     clipboardManager: ClipboardManager,
     coroutineScope: CoroutineScope,
-    scaffoldState: ScaffoldState
+    scaffoldState: ScaffoldState,
 ) {
     Row(verticalAlignment = Alignment.CenterVertically) {
         Icon(Icons.Filled.NorthEast, "contact.add.remote.outgoing_arrow")
@@ -367,7 +366,7 @@ fun OwnLink(
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Composable
-fun ContactLink(
+private fun ContactLink(
     remoteHandshakeLink: String,
     setRemoteHandshakeLink: (String) -> Unit,
     clipboardManager: ClipboardManager,
@@ -428,11 +427,11 @@ fun ContactLink(
 }
 
 @Composable
-fun Alias(
+private fun Alias(
     alias: String,
     setAddContactAlias: (String) -> Unit,
     aliasFocusRequester: FocusRequester,
-    onSubmitAddContactDialog: () -> Unit
+    onSubmitAddContactDialog: () -> Unit,
 ) {
     Row(verticalAlignment = Alignment.CenterVertically) {
         Icon(Icons.Filled.Person, "contact.add.remote.choose_nickname")
@@ -454,7 +453,7 @@ fun Alias(
     )
 }
 
-fun errorMessage(error: AddContactError) = when (error) {
+private fun errorMessage(error: AddContactError) = when (error) {
     is OwnLinkError -> Triple(ERROR, i18n("error"), i18n("contact.add.error.own_link"))
     is RemoteInvalidError -> Triple(ERROR, i18n("error"), i18n("contact.add.error.remote_invalid"))
     is AliasInvalidError -> Triple(ERROR, i18n("error"), i18n("contact.add.error.alias_invalid"))
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt
index 486e39c1975a08073ffb067aecb665264b7f2751..0da354c91ba4f195da1541c49ebab2a3efe15a20 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/contact/add/remote/AddContactViewModel.kt
@@ -1,6 +1,6 @@
 /*
  * Briar Desktop
- * Copyright (C) 2021-2022 The Briar Project
+ * Copyright (C) 2021-2023 The Briar Project
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -68,26 +68,16 @@ constructor(
         fetchHandshakeLink()
     }
 
-    private val _visible = mutableStateOf(false)
     private val _alias = mutableStateOf("")
     private val _remoteHandshakeLink = mutableStateOf("")
     private val _handshakeLink = mutableStateOf("")
     private val _error = mutableStateOf<AddContactError?>(null)
 
-    val visible = _visible.asState()
     val alias = _alias.asState()
     val remoteHandshakeLink = _remoteHandshakeLink.asState()
     val handshakeLink = _handshakeLink.asState()
     val error = _error.asState()
 
-    fun showDialog() {
-        _visible.value = true
-    }
-
-    fun dismissDialog() {
-        _visible.value = false
-    }
-
     fun setAddContactAlias(alias: String) {
         _alias.value = alias
     }
@@ -101,17 +91,17 @@ constructor(
         txn.attach { _handshakeLink.value = link }
     }
 
-    fun onSubmitAddContactDialog() {
+    fun onSubmitAddContactDialog(onSuccess: () -> Unit) {
         val link = _remoteHandshakeLink.value
         val alias = _alias.value
-        addPendingContact(link, alias)
+        addPendingContact(link, alias, onSuccess)
     }
 
     fun clearError() {
         _error.value = null
     }
 
-    private fun addPendingContact(link: String, alias: String) {
+    private fun addPendingContact(link: String, alias: String, onSuccess: () -> Unit) {
         // ignore preceding and trailing whitespace
         val matcher = HandshakeLinkConstants.LINK_REGEX.matcher(link.trim())
         // check if the link is well-formed
@@ -139,7 +129,7 @@ constructor(
             try {
                 contactManager.addPendingContact(txn, link, alias)
                 txn.attach {
-                    _visible.value = false
+                    onSuccess()
                     _alias.value = ""
                     _remoteHandshakeLink.value = ""
                 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageScreen.kt
index cafa8ef3eb0e6381dbfff46023eba7cca4212a0c..83b3955a31121bed25416e8db8e678aaeb28cb6f 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/conversation/PrivateMessageScreen.kt
@@ -1,6 +1,6 @@
 /*
  * Briar Desktop
- * Copyright (C) 2021-2022 The Briar Project
+ * Copyright (C) 2021-2023 The Briar Project
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -32,6 +32,10 @@ import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.PersonAdd
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextAlign
@@ -41,7 +45,6 @@ import org.briarproject.briar.desktop.contact.ContactItem
 import org.briarproject.briar.desktop.contact.ContactList
 import org.briarproject.briar.desktop.contact.ContactListViewModel
 import org.briarproject.briar.desktop.contact.add.remote.AddContactDialog
-import org.briarproject.briar.desktop.contact.add.remote.AddContactViewModel
 import org.briarproject.briar.desktop.contact.add.remote.PendingContactItem
 import org.briarproject.briar.desktop.ui.BriarLogo
 import org.briarproject.briar.desktop.ui.ColoredIconButton
@@ -53,9 +56,12 @@ import org.briarproject.briar.desktop.viewmodel.viewModel
 @Composable
 fun PrivateMessageScreen(
     viewModel: ContactListViewModel = viewModel(),
-    addContactViewModel: AddContactViewModel = viewModel(),
 ) {
-    AddContactDialog(addContactViewModel)
+    var addDialogVisible by remember { mutableStateOf(false) }
+    AddContactDialog(
+        visible = addDialogVisible,
+        onClose = { addDialogVisible = false }
+    )
 
     ConfirmRemovePendingContactDialog(
         viewModel.removePendingContactDialogVisible.value,
@@ -64,7 +70,7 @@ fun PrivateMessageScreen(
     )
 
     if (viewModel.noContactsYet.value) {
-        NoContactsYet(onContactAdd = { addContactViewModel.showDialog() })
+        NoContactsYet(onContactAdd = { addDialogVisible = true })
         return
     }
 
@@ -76,7 +82,7 @@ fun PrivateMessageScreen(
             viewModel::removePendingContact,
             viewModel.filterBy.value,
             viewModel::setFilterBy,
-            onContactAdd = { addContactViewModel.showDialog() }
+            onContactAdd = { addDialogVisible = true }
         )
         VerticalDivider()
         Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
@@ -87,6 +93,7 @@ fun PrivateMessageScreen(
                 is ContactItem -> {
                     ConversationScreen(item.id)
                 }
+
                 is PendingContactItem -> {
                     PendingContactSelected()
                 }