align and simplify core/UI/UXcanvas

This commit is contained in:
Alain Brenzikofer
2026-09-30 20:11:51 +02:00
parent 10c456b057
commit 6bc2f4450e
26 changed files with 634 additions and 460 deletions
@@ -1633,7 +1633,7 @@ object ChatController {
suspend fun apiConnectPlan(rh: Long?, connLink: String, resolveMode: PlanResolveMode = PlanResolveMode.PRMUnknown, linkOwnerSig: LinkOwnerSig? = null, inProgress: MutableState<Boolean>): ConnectionPlanResult? {
val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null }
val r = sendCmdWithRetry(rh, CC.APIConnectPlan(userId, connLink, resolveMode, linkOwnerSig), inProgress = inProgress)
if (r is API.Result && r.res is CR.CRConnectionPlan) return ConnectionPlanResult(r.res.connLink, r.res.planSimplexName, r.res.otherSimplexName, r.res.connectionPlan)
if (r is API.Result && r.res is CR.CRConnectionPlan) return ConnectionPlanResult(r.res.connLink, r.res.planSimplexName, r.res.otherSimplexName, r.res.connectionPlan, r.res.localChats, r.res.offerLookup)
// a PRMNever (typing) search that matches nothing locally is not an error to surface
if (r is API.Error && r.err is ChatError.ChatErrorChat && r.err.errorType is ChatErrorType.NotResolvedLocally) return null
if (inProgress.value && r != null) apiConnectResponseAlert(r)
@@ -6712,7 +6712,7 @@ sealed class CR {
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connLinkInvitation: CreatedConnLink, val connection: PendingContactConnection): CR()
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
@Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR()
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connLink: CreatedConnLink? = null, val planSimplexName: SimplexNameInfo? = null, val otherSimplexName: SimplexNameInfo? = null, val connectionPlan: ConnectionPlan): CR()
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connLink: CreatedConnLink? = null, val planSimplexName: SimplexNameInfo? = null, val otherSimplexName: SimplexNameInfo? = null, val connectionPlan: ConnectionPlan, val localChats: List<ChatInfo> = emptyList(), val offerLookup: Boolean = false): CR()
@Serializable @SerialName("newPreparedChat") class NewPreparedChat(val user: UserRef, val chat: Chat): CR()
@Serializable @SerialName("contactUserChanged") class ContactUserChanged(val user: UserRef, val fromContact: Contact, val newUser: UserRef, val toContact: Contact): CR()
@Serializable @SerialName("groupUserChanged") class GroupUserChanged(val user: UserRef, val fromGroup: GroupInfo, val newUser: UserRef, val toGroup: GroupInfo): CR()
@@ -7426,6 +7426,8 @@ data class ConnectionPlanResult(
val planSimplexName: SimplexNameInfo?,
val otherSimplexName: SimplexNameInfo?,
val connectionPlan: ConnectionPlan,
val localChats: List<ChatInfo>,
val offerLookup: Boolean,
)
// APIConnectPlan resolution scope; PRMNever is local-store-only (no network), used for per-keystroke name search
@@ -7472,7 +7474,7 @@ sealed class InvitationLinkPlan {
@Serializable
sealed class ContactAddressPlan {
@Serializable @SerialName("ok") class Ok(val contactSLinkData_: ContactShortLinkData? = null, val ownerVerification: OwnerVerification? = null, val addressChanged: Boolean = false, val existingChat_: ChatInfo? = null): ContactAddressPlan()
@Serializable @SerialName("ok") class Ok(val contactSLinkData_: ContactShortLinkData? = null, val ownerVerification: OwnerVerification? = null, val addressChanged: Boolean = false): ContactAddressPlan()
@Serializable @SerialName("ownLink") object OwnLink: ContactAddressPlan()
@Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: ContactAddressPlan()
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan()
@@ -7482,7 +7484,7 @@ sealed class ContactAddressPlan {
@Serializable
sealed class GroupLinkPlan {
@Serializable @SerialName("ok") class Ok(val groupSLinkInfo_: GroupShortLinkInfo? = null, val groupSLinkData_: GroupShortLinkData? = null, val ownerVerification: OwnerVerification? = null, val addressChanged: Boolean = false, val existingChat_: ChatInfo? = null): GroupLinkPlan()
@Serializable @SerialName("ok") class Ok(val groupSLinkInfo_: GroupShortLinkInfo? = null, val groupSLinkData_: GroupShortLinkData? = null, val ownerVerification: OwnerVerification? = null, val addressChanged: Boolean = false): GroupLinkPlan()
@Serializable @SerialName("ownLink") class OwnLink(val groupInfo: GroupInfo): GroupLinkPlan()
@Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: GroupLinkPlan()
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val groupInfo_: GroupInfo? = null): GroupLinkPlan()
@@ -850,17 +850,16 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
searchChatFilteredBySimplexLink.value = emptySet()
if (candidate != null) {
// resolve the name locally on each keystroke, debounced; collectLatest cancels the in-flight
// search when the next keystroke arrives. A bare name can be a contact or a channel, so search
// both and filter every known chat found; drop the row only when both types are already known.
// search when the next keystroke arrives.
delay(NAME_SEARCH_DEBOUNCE_MS)
val rhId = chatModel.remoteHostId()
val inProgress = mutableStateOf(false) // background search: no spinner, no error alerts
val targets = if (candidate.startsWith("@") || candidate.startsWith("#")) listOf(candidate) else listOf("@$candidate", "#$candidate")
val ids = targets.mapNotNull { name ->
knownChatId(rhId, chatModel.controller.apiConnectPlan(rhId, name, PlanResolveMode.PRMNever, inProgress = inProgress))
val result = chatModel.controller.apiConnectPlan(rhId, candidate, PlanResolveMode.PRMNever, inProgress = inProgress)
if (result != null) {
addMissingChats(rhId, result.localChats)
searchChatFilteredBySimplexLink.value = result.localChats.map { it.id }.toSet()
if (!result.offerLookup) connectNameCandidate.value = null
}
searchChatFilteredBySimplexLink.value = ids.toSet()
if (ids.size == targets.size) connectNameCandidate.value = null
} else if (!searchShowingSimplexLink.value || it.isEmpty()) {
if (it.isNotEmpty()) {
focusRequester.requestFocus()
@@ -889,8 +888,10 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<
planAndConnect(
chatModel.remoteHostId(),
link,
filterKnownContact = { searchChatFilteredBySimplexLink.value = setOf(it.id) },
filterKnownGroup = { searchChatFilteredBySimplexLink.value = setOf(it.id) },
filterChats = { chats ->
searchChatFilteredBySimplexLink.value = chats.map { it.id }.toSet()
true
},
close = null,
cleanup = cleanup,
)
@@ -1162,30 +1163,6 @@ internal fun nameSearchCandidate(str: String): String? {
}
}
// The chat id a local (PRMNever) search resolved to — a contact, a business, or a channel — or null on a miss.
// The core returns the correct type for @ vs # (getContactToConnect / type-filtered getGroupToConnect), so no
// client-side type check is needed.
internal suspend fun knownChatId(rhId: Long?, result: ConnectionPlanResult?): String? = when (val plan = result?.connectionPlan) {
is ConnectionPlan.ContactAddress -> (plan.contactAddressPlan as? ContactAddressPlan.Known)?.contact?.let { contact ->
// a name-resolved chat may be prepared in the store but not yet listed, so add it (as the tap path does)
if (chatModel.getContactChat(contact.contactId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Direct(contact), chatItems = emptyList()))
}
contact.id
}
is ConnectionPlan.GroupLink -> (when (val g = plan.groupLinkPlan) {
is GroupLinkPlan.Known -> g.groupInfo
is GroupLinkPlan.OwnLink -> g.groupInfo
else -> null
})?.let { gInfo ->
if (chatModel.getGroupChat(gInfo.groupId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Group(gInfo, groupChatScope = null), chatItems = emptyList()))
}
gInfo.id
}
else -> null
}
// The list tags and the connect-by-name row share one slot. When there is no name, the tags show; on
// mobile the row replaces the tags while shown. On desktop both show, arranged by the caller (which
// knows whether the search bar is above or below), passed as desktopView.
@@ -34,8 +34,7 @@ suspend fun planAndConnect(
linkOwnerSig: LinkOwnerSig? = null,
close: (() -> Unit)?,
cleanup: (() -> Unit)? = null,
filterKnownContact: ((Contact) -> Unit)? = null,
filterKnownGroup: ((GroupInfo) -> Unit)? = null,
filterChats: ((List<ChatInfo>) -> Boolean)? = null,
): CompletableDeferred<Boolean> {
when (val target = strConnectTarget(shortOrFullLink.trim())) {
is ConnectTarget.Link -> {
@@ -58,7 +57,7 @@ suspend fun planAndConnect(
inProgress.value = false
cleanup?.invoke()
}
return planAndConnectTask(rhId, shortOrFullLink, linkOwnerSig, close, cleanup, filterKnownContact, filterKnownGroup, inProgress)
return planAndConnectTask(rhId, shortOrFullLink, linkOwnerSig, close, cleanup, filterChats, inProgress)
}
private fun nameDate(t: Instant): String =
@@ -156,8 +155,7 @@ private suspend fun planAndConnectTask(
linkOwnerSig: LinkOwnerSig? = null,
close: (() -> Unit)?,
cleanup: (() -> Unit)? = null,
filterKnownContact: ((Contact) -> Unit)? = null,
filterKnownGroup: ((GroupInfo) -> Unit)? = null,
filterChats: ((List<ChatInfo>) -> Boolean)? = null,
inProgress: MutableState<Boolean>
): CompletableDeferred<Boolean> {
val completable = CompletableDeferred<Boolean>()
@@ -174,7 +172,8 @@ private suspend fun planAndConnectTask(
connectProgressManager.stopConnectProgress()
if (!inProgress.value) { return completable }
if (result != null) {
val (connectionLink, planSimplexName, otherSimplexName, connectionPlan) = result
val (connectionLink, planSimplexName, otherSimplexName, connectionPlan, localChats) = result
addMissingChats(rhId, localChats)
val target = strConnectTarget(shortOrFullLink.trim())
val linkText = if (target is ConnectTarget.Link) "<br><br><u>${target.linkText}</u>" else ""
// the name can also resolve to the other kind; its type picks the verb, its short form the label and target
@@ -190,22 +189,7 @@ private suspend fun planAndConnectTask(
else -> null to null
}
if (nameWarning != null && nameDomain != null) {
val knownContact = ((connectionPlan as? ConnectionPlan.ContactAddress)?.contactAddressPlan as? ContactAddressPlan.Known)?.contact
val knownGroup = ((connectionPlan as? ConnectionPlan.GroupLink)?.groupLinkPlan as? GroupLinkPlan.Known)?.groupInfo
var openExisting: (() -> Unit)? = null
if (knownContact != null) {
if (chatModel.getContactChat(knownContact.contactId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Direct(knownContact), chatItems = emptyList()))
}
if (filterKnownContact != null) filterKnownContact(knownContact)
else openExisting = { openKnownContact(chatModel, rhId, close, knownContact) }
} else if (knownGroup != null) {
if (chatModel.getGroupChat(knownGroup.groupId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Group(knownGroup, groupChatScope = null), chatItems = emptyList()))
}
if (filterKnownGroup != null) filterKnownGroup(knownGroup)
else openExisting = { openKnownGroup(chatModel, rhId, close, knownGroup) }
}
val openExisting: (() -> Unit)? = localChats.firstOrNull()?.takeIf { filterChats?.invoke(localChats) != true }?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())) } }
showNameWarningAlert(rhId, nameDomain, nameWarning, openExisting, cleanup)
return completable
}
@@ -251,9 +235,7 @@ private suspend fun planAndConnectTask(
Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting")
val contact = connectionPlan.invitationLinkPlan.contact_
if (contact != null) {
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
if (filterChats?.invoke(localChats) != true) {
showOpenKnownContactAlert(chatModel, rhId, close, contact)
cleanup()
}
@@ -269,9 +251,7 @@ private suspend fun planAndConnectTask(
is InvitationLinkPlan.Known -> {
Log.d(TAG, "planAndConnect, .InvitationLink, .Known")
val contact = connectionPlan.invitationLinkPlan.contact
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
if (filterChats?.invoke(localChats) != true) {
showOpenKnownContactAlert(chatModel, rhId, close, contact)
cleanup()
}
@@ -290,9 +270,10 @@ private suspend fun planAndConnectTask(
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
addressChanged = connectionPlan.contactAddressPlan.addressChanged,
openExistingChat = if (filterKnownContact == null) connectionPlan.contactAddressPlan.existingChat_?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())); cleanup() } } else null,
openExistingChat = localChats.firstOrNull()?.takeIf { filterChats?.invoke(localChats) != true }?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())); cleanup() } },
close,
cleanup
cleanup,
filterChats
)
} else {
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, no short link data")
@@ -305,6 +286,7 @@ private suspend fun planAndConnectTask(
ownerVerification = connectionPlan.contactAddressPlan.ownerVerification,
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
filterChats = filterChats,
)
}
ContactAddressPlan.OwnLink -> {
@@ -317,6 +299,7 @@ private suspend fun planAndConnectTask(
cleanup = cleanup,
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
filterChats = filterChats,
)
}
ContactAddressPlan.ConnectingConfirmReconnect -> {
@@ -329,32 +312,24 @@ private suspend fun planAndConnectTask(
cleanup = cleanup,
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
filterChats = filterChats,
)
}
is ContactAddressPlan.ConnectingProhibit -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit")
val contact = connectionPlan.contactAddressPlan.contact
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
showOpenKnownContactAlert(chatModel, rhId, close, contact, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink)
if (filterChats?.invoke(localChats) != true) {
showOpenKnownContactAlert(chatModel, rhId, close, contact, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink, filterChats = filterChats)
cleanup()
}
}
is ContactAddressPlan.Known -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .Known")
val contact = connectionPlan.contactAddressPlan.contact
// A name-resolved contact is prepared in the store but not yet in the
// chat list (link-prepared chats arrive via NewPreparedChat). Surface it
// so it's visible and openable; no-op if already present.
if (chatModel.getContactChat(contact.contactId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Direct(contact), chatItems = emptyList()))
}
if (filterKnownContact != null) {
filterKnownContact(contact)
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup)
if (filterChats?.invoke(localChats) == true) {
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup, filterChats)
} else {
showOpenKnownContactAlert(chatModel, rhId, close, contact, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink)
showOpenKnownContactAlert(chatModel, rhId, close, contact, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink, filterChats = filterChats)
cleanup()
}
}
@@ -362,14 +337,9 @@ private suspend fun planAndConnectTask(
Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress")
val contact = connectionPlan.contactAddressPlan.contact
// the contact is already prepared in the store, so open the existing chat instead of sending a new
// connection request; surface it in the chat list first if it is not there yet (as for Known above)
if (chatModel.getContactChat(contact.contactId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Direct(contact), chatItems = emptyList()))
}
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
showOpenKnownContactAlert(chatModel, rhId, close, contact, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink)
// connection request
if (filterChats?.invoke(localChats) != true) {
showOpenKnownContactAlert(chatModel, rhId, close, contact, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink, filterChats = filterChats)
cleanup()
}
}
@@ -388,9 +358,10 @@ private suspend fun planAndConnectTask(
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
addressChanged = connectionPlan.groupLinkPlan.addressChanged,
openExistingChat = if (filterKnownGroup == null) connectionPlan.groupLinkPlan.existingChat_?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())); cleanup() } } else null,
openExistingChat = localChats.firstOrNull()?.takeIf { filterChats?.invoke(localChats) != true }?.let { chatInfo -> { openChat_(chatModel, rhId, close, Chat(remoteHostId = rhId, chatInfo = chatInfo, chatItems = emptyList())); cleanup() } },
close,
cleanup
cleanup,
filterChats
)
} else {
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, no short link data")
@@ -403,16 +374,16 @@ private suspend fun planAndConnectTask(
ownerVerification = connectionPlan.groupLinkPlan.ownerVerification,
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
filterChats = filterChats,
)
}
is GroupLinkPlan.OwnLink -> {
Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink")
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
if (filterKnownGroup != null) {
filterKnownGroup(groupInfo)
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup)
if (filterChats?.invoke(localChats) == true) {
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup, filterChats)
} else {
ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, connectionPlan, groupInfo, close, cleanup, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink)
ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, connectionPlan, groupInfo, close, cleanup, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink, filterChats = filterChats)
}
}
GroupLinkPlan.ConnectingConfirmReconnect -> {
@@ -425,6 +396,7 @@ private suspend fun planAndConnectTask(
cleanup = cleanup,
connectOtherButton = connectOtherButton,
connectOtherLink = connectOtherLink,
filterChats = filterChats,
)
}
is GroupLinkPlan.ConnectingProhibit -> {
@@ -454,16 +426,10 @@ private suspend fun planAndConnectTask(
is GroupLinkPlan.Known -> {
Log.d(TAG, "planAndConnect, .GroupLink, .Known")
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
// Same as ContactAddress.Known: surface a name-resolved (prepared)
// group in the chat list so it's visible and openable.
if (chatModel.getGroupChat(groupInfo.groupId) == null) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Group(groupInfo, groupChatScope = null), chatItems = emptyList()))
}
if (filterKnownGroup != null) {
filterKnownGroup(groupInfo)
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup)
if (filterChats?.invoke(localChats) == true) {
if (otherSimplexName != null && connectOtherButton != null) showOtherNameAlert(rhId, otherSimplexName, connectOtherButton, close, cleanup, filterChats)
} else {
showOpenKnownGroupAlert(chatModel, rhId, close, groupInfo, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink)
showOpenKnownGroupAlert(chatModel, rhId, close, groupInfo, planSimplexName = planSimplexName, connectOtherButton = connectOtherButton, connectOtherLink = connectOtherLink, filterChats = filterChats)
cleanup()
}
}
@@ -593,6 +559,7 @@ fun askCurrentOrIncognitoProfileAlert(
ownerVerification: OwnerVerification? = null,
connectOtherButton: String? = null,
connectOtherLink: String? = null,
filterChats: ((List<ChatInfo>) -> Boolean)? = null,
) {
val fullText = listOfNotNull(text, ownerVerificationMessage(ownerVerification)).joinToString("\n\n").ifEmpty { null }
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
@@ -620,7 +587,7 @@ fun askCurrentOrIncognitoProfileAlert(
if (connectOtherButton != null && connectOtherLink != null) {
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withBGApi { planAndConnect(rhId, connectOtherLink, close = close, cleanup = cleanup) }
withBGApi { planAndConnect(rhId, connectOtherLink, close = close, cleanup = cleanup, filterChats = filterChats) }
}) {
Text(connectOtherButton, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -638,6 +605,14 @@ fun askCurrentOrIncognitoProfileAlert(
)
}
suspend fun addMissingChats(rhId: Long?, chats: List<ChatInfo>) {
chats.forEach {
if (!chatModel.chatsContext.hasChat(rhId, it.id)) {
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = it, chatItems = emptyList()))
}
}
}
fun openChat_(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, chat: Chat) {
withBGApi {
close?.invoke()
@@ -651,7 +626,7 @@ val alertProfileImageSize = 138.dp
private fun nameWithDomain(name: String, planSimplexName: SimplexNameInfo?): String =
name + (planSimplexName?.let { " (${it.shortStr})" } ?: "")
private fun showOpenKnownContactAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact, planSimplexName: SimplexNameInfo? = null, connectOtherButton: String? = null, connectOtherLink: String? = null) {
private fun showOpenKnownContactAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact, planSimplexName: SimplexNameInfo? = null, connectOtherButton: String? = null, connectOtherLink: String? = null, filterChats: ((List<ChatInfo>) -> Boolean)? = null) {
AlertManager.privacySensitive.showOpenChatAlert(
profileName = contact.profile.displayName,
profileFullName = contact.profile.fullName,
@@ -670,7 +645,7 @@ private fun showOpenKnownContactAlert(chatModel: ChatModel, rhId: Long?, close:
openKnownContact(chatModel, rhId, close, contact)
},
connectOtherButton = connectOtherButton,
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close) } } },
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close, filterChats = filterChats) } } },
onDismiss = null
)
}
@@ -697,6 +672,7 @@ fun ownGroupLinkConfirmConnect(
planSimplexName: SimplexNameInfo? = null,
connectOtherButton: String? = null,
connectOtherLink: String? = null,
filterChats: ((List<ChatInfo>) -> Boolean)? = null,
) {
if (groupInfo.useRelays) {
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
@@ -714,7 +690,7 @@ fun ownGroupLinkConfirmConnect(
if (connectOtherButton != null && connectOtherLink != null) {
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withBGApi { planAndConnect(rhId, connectOtherLink, close = close, cleanup = cleanup) }
withBGApi { planAndConnect(rhId, connectOtherLink, close = close, cleanup = cleanup, filterChats = filterChats) }
}) {
Text(connectOtherButton, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -777,7 +753,7 @@ fun ownGroupLinkConfirmConnect(
}
}
private fun showOpenKnownGroupAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo, planSimplexName: SimplexNameInfo? = null, connectOtherButton: String? = null, connectOtherLink: String? = null) {
private fun showOpenKnownGroupAlert(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo, planSimplexName: SimplexNameInfo? = null, connectOtherButton: String? = null, connectOtherLink: String? = null, filterChats: ((List<ChatInfo>) -> Boolean)? = null) {
val subscriberCount = if (groupInfo.useRelays) groupInfo.groupSummary.publicMemberCount?.let { subscriberCountStr(it) } else null
AlertManager.privacySensitive.showOpenChatAlert(
profileName = groupInfo.groupProfile.displayName,
@@ -817,7 +793,7 @@ private fun showOpenKnownGroupAlert(chatModel: ChatModel, rhId: Long?, close: ((
openKnownGroup(chatModel, rhId, close, groupInfo)
},
connectOtherButton = connectOtherButton,
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close) } } },
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close, filterChats = filterChats) } } },
onDismiss = null
)
}
@@ -843,7 +819,8 @@ fun showPrepareContactAlert(
addressChanged: Boolean = false,
openExistingChat: (() -> Unit)? = null,
close: (() -> Unit)?,
cleanup: (() -> Unit)?
cleanup: (() -> Unit)?,
filterChats: ((List<ChatInfo>) -> Boolean)? = null
) {
AlertManager.privacySensitive.showOpenChatAlert(
profileName = contactShortLinkData.profile.displayName,
@@ -880,7 +857,7 @@ fun showPrepareContactAlert(
}
},
connectOtherButton = connectOtherButton,
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close, cleanup = cleanup) } } },
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close, cleanup = cleanup, filterChats = filterChats) } } },
dismissText = generalGetString(if (openExistingChat != null) MR.strings.connect_plan_open_existing_chat else MR.strings.cancel_verb),
onDismissButton = openExistingChat,
onDismiss = {
@@ -889,7 +866,7 @@ fun showPrepareContactAlert(
)
}
private fun showOtherNameAlert(rhId: Long?, otherSimplexName: SimplexNameInfo, connectOtherButton: String, close: (() -> Unit)?, cleanup: (() -> Unit)?) {
private fun showOtherNameAlert(rhId: Long?, otherSimplexName: SimplexNameInfo, connectOtherButton: String, close: (() -> Unit)?, cleanup: (() -> Unit)?, filterChats: ((List<ChatInfo>) -> Boolean)?) {
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
title = String.format(
generalGetString(if (otherSimplexName.nameType == SimplexNameType.publicGroup) MR.strings.simplex_name_also_leads_to_channel else MR.strings.simplex_name_also_leads_to_contact),
@@ -901,7 +878,7 @@ private fun showOtherNameAlert(rhId: Long?, otherSimplexName: SimplexNameInfo, c
Column {
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withBGApi { planAndConnect(rhId, otherSimplexName.shortStr, close = close, cleanup = cleanup) }
withBGApi { planAndConnect(rhId, otherSimplexName.shortStr, close = close, cleanup = cleanup, filterChats = filterChats) }
}) {
Text(connectOtherButton, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -925,7 +902,8 @@ fun showPrepareGroupAlert(
addressChanged: Boolean = false,
openExistingChat: (() -> Unit)? = null,
close: (() -> Unit)?,
cleanup: (() -> Unit)?
cleanup: (() -> Unit)?,
filterChats: ((List<ChatInfo>) -> Boolean)? = null
) {
val isChannel = !(groupShortLinkInfo?.direct ?: true)
val subscriberCount = if (isChannel) groupShortLinkData.publicGroupData?.publicMemberCount?.let { subscriberCountStr(it) } else null
@@ -971,7 +949,7 @@ fun showPrepareGroupAlert(
}
},
connectOtherButton = connectOtherButton,
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close, cleanup = cleanup) } } },
onConnectOther = connectOtherLink?.let { link -> { withBGApi { planAndConnect(rhId, link, close = close, cleanup = cleanup, filterChats = filterChats) } } },
dismissText = generalGetString(if (openExistingChat != null) MR.strings.connect_plan_open_existing_chat else MR.strings.cancel_verb),
onDismissButton = openExistingChat,
onDismiss = {
@@ -598,7 +598,11 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<
planAndConnect(
chatModel.remoteHostId(),
link,
filterKnownContact = { searchChatFilteredBySimplexLink.value = setOf(it.id) },
filterChats = { chats ->
val contacts = chats.all { it is ChatInfo.Direct }
if (contacts) searchChatFilteredBySimplexLink.value = chats.map { it.id }.toSet()
contacts
},
close = close,
cleanup = cleanup,
)