mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-04 03:51:48 +00:00
Merge branch 'master' into master-ghc8107
This commit is contained in:
+14
-3
@@ -53,6 +53,7 @@ object ChatModel {
|
||||
// current chat
|
||||
val chatId = mutableStateOf<String?>(null)
|
||||
val chatItems = mutableStateListOf<ChatItem>()
|
||||
val chatItemStatuses = mutableMapOf<Long, CIStatus>()
|
||||
val groupMembers = mutableStateListOf<GroupMember>()
|
||||
|
||||
val terminalItems = mutableStateListOf<TerminalItem>()
|
||||
@@ -135,6 +136,7 @@ object ChatModel {
|
||||
fun hasChat(id: String): Boolean = chats.toList().firstOrNull { it.id == id } != null
|
||||
fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id }
|
||||
fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
|
||||
fun getGroupChat(groupId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
|
||||
private fun getChatIndex(id: String): Int = chats.toList().indexOfFirst { it.id == id }
|
||||
fun addChat(chat: Chat) = chats.add(index = 0, chat)
|
||||
|
||||
@@ -272,7 +274,13 @@ object ChatModel {
|
||||
Log.d(TAG, "TODOCHAT: upsertChatItem: updated in chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
false
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
val status = chatItemStatuses.remove(cItem.id)
|
||||
val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) {
|
||||
cItem.copy(meta = cItem.meta.copy(itemStatus = status))
|
||||
} else {
|
||||
cItem
|
||||
}
|
||||
chatItems.add(ci)
|
||||
Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
true
|
||||
}
|
||||
@@ -282,13 +290,15 @@ object ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (chatId.value == cInfo.id) {
|
||||
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
chatItems[itemIndex] = cItem
|
||||
}
|
||||
} else if (status != null) {
|
||||
chatItemStatuses[cItem.id] = status
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,6 +336,7 @@ object ChatModel {
|
||||
}
|
||||
// clear current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItemStatuses.clear()
|
||||
chatItems.clear()
|
||||
}
|
||||
}
|
||||
@@ -2416,7 +2427,7 @@ sealed class Format {
|
||||
@Serializable @SerialName("secret") class Secret: Format()
|
||||
@Serializable @SerialName("colored") class Colored(val color: FormatColor): Format()
|
||||
@Serializable @SerialName("uri") class Uri: Format()
|
||||
@Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val trustedUri: Boolean, val smpHosts: List<String>): Format()
|
||||
@Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val smpHosts: List<String>): Format()
|
||||
@Serializable @SerialName("email") class Email: Format()
|
||||
@Serializable @SerialName("phone") class Phone: Format()
|
||||
|
||||
|
||||
+51
-5
@@ -855,6 +855,14 @@ object ChatController {
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnectPlan(connReq: String): ConnectionPlan? {
|
||||
val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null }
|
||||
val r = sendCmd(CC.APIConnectPlan(userId, connReq))
|
||||
if (r is CR.CRConnectionPlan) return r.connectionPlan
|
||||
Log.e(TAG, "apiConnectPlan bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnect(incognito: Boolean, connReq: String): Boolean {
|
||||
val userId = chatModel.currentUser.value?.userId ?: run {
|
||||
Log.e(TAG, "apiConnect: no current user")
|
||||
@@ -1489,11 +1497,8 @@ object ChatController {
|
||||
is CR.ChatItemStatusUpdated -> {
|
||||
val cInfo = r.chatItem.chatInfo
|
||||
val cItem = r.chatItem.chatItem
|
||||
if (!cItem.isDeletedContent) {
|
||||
val added = if (active(r.user)) chatModel.upsertChatItem(cInfo, cItem) else true
|
||||
if (added && cItem.showNotification) {
|
||||
ntfManager.notifyMessageReceived(r.user, cInfo, cItem)
|
||||
}
|
||||
if (!cItem.isDeletedContent && active(r.user)) {
|
||||
chatModel.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
|
||||
}
|
||||
}
|
||||
is CR.ChatItemUpdated ->
|
||||
@@ -1917,6 +1922,7 @@ sealed class CC {
|
||||
class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC()
|
||||
class APIAddContact(val userId: Long, val incognito: Boolean): CC()
|
||||
class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC()
|
||||
class APIConnectPlan(val userId: Long, val connReq: String): CC()
|
||||
class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC()
|
||||
class ApiDeleteChat(val type: ChatType, val id: Long): CC()
|
||||
class ApiClearChat(val type: ChatType, val id: Long): CC()
|
||||
@@ -2026,6 +2032,7 @@ sealed class CC {
|
||||
is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else ""
|
||||
is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}"
|
||||
is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}"
|
||||
is APIConnectPlan -> "/_connect plan $userId $connReq"
|
||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq"
|
||||
is ApiDeleteChat -> "/_delete ${chatRef(type, id)}"
|
||||
is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
|
||||
@@ -2127,6 +2134,7 @@ sealed class CC {
|
||||
is APIVerifyGroupMember -> "apiVerifyGroupMember"
|
||||
is APIAddContact -> "apiAddContact"
|
||||
is ApiSetConnectionIncognito -> "apiSetConnectionIncognito"
|
||||
is APIConnectPlan -> "apiConnectPlan"
|
||||
is APIConnect -> "apiConnect"
|
||||
is ApiDeleteChat -> "apiDeleteChat"
|
||||
is ApiClearChat -> "apiClearChat"
|
||||
@@ -3340,6 +3348,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR()
|
||||
@Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef): CR()
|
||||
@Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef): CR()
|
||||
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
|
||||
@@ -3475,6 +3484,7 @@ sealed class CR {
|
||||
is ConnectionVerified -> "connectionVerified"
|
||||
is Invitation -> "invitation"
|
||||
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
|
||||
is CRConnectionPlan -> "connectionPlan"
|
||||
is SentConfirmation -> "sentConfirmation"
|
||||
is SentInvitation -> "sentInvitation"
|
||||
is ContactAlreadyExists -> "contactAlreadyExists"
|
||||
@@ -3605,6 +3615,7 @@ sealed class CR {
|
||||
is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode")
|
||||
is Invitation -> withUser(user, connReqInvitation)
|
||||
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||
is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan))
|
||||
is SentConfirmation -> withUser(user, noDetails())
|
||||
is SentInvitation -> withUser(user, noDetails())
|
||||
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
|
||||
@@ -3718,6 +3729,39 @@ fun chatError(r: CR): ChatErrorType? {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ConnectionPlan {
|
||||
@Serializable @SerialName("invitationLink") class InvitationLink(val invitationLinkPlan: InvitationLinkPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("contactAddress") class ContactAddress(val contactAddressPlan: ContactAddressPlan): ConnectionPlan()
|
||||
@Serializable @SerialName("groupLink") class GroupLink(val groupLinkPlan: GroupLinkPlan): ConnectionPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class InvitationLinkPlan {
|
||||
@Serializable @SerialName("ok") object Ok: InvitationLinkPlan()
|
||||
@Serializable @SerialName("ownLink") object OwnLink: InvitationLinkPlan()
|
||||
@Serializable @SerialName("connecting") class Connecting(val contact_: Contact? = null): InvitationLinkPlan()
|
||||
@Serializable @SerialName("known") class Known(val contact: Contact): InvitationLinkPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ContactAddressPlan {
|
||||
@Serializable @SerialName("ok") object Ok: ContactAddressPlan()
|
||||
@Serializable @SerialName("ownLink") object OwnLink: ContactAddressPlan()
|
||||
@Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: ContactAddressPlan()
|
||||
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan()
|
||||
@Serializable @SerialName("known") class Known(val contact: Contact): ContactAddressPlan()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class GroupLinkPlan {
|
||||
@Serializable @SerialName("ok") object Ok: 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()
|
||||
@Serializable @SerialName("known") class Known(val groupInfo: GroupInfo): GroupLinkPlan()
|
||||
}
|
||||
|
||||
abstract class TerminalItem {
|
||||
abstract val id: Long
|
||||
val date: Instant = Clock.System.now()
|
||||
@@ -3877,6 +3921,7 @@ sealed class ChatErrorType {
|
||||
is ChatNotStarted -> "chatNotStarted"
|
||||
is ChatNotStopped -> "chatNotStopped"
|
||||
is ChatStoreChanged -> "chatStoreChanged"
|
||||
is ConnectionPlanChatError -> "connectionPlan"
|
||||
is InvalidConnReq -> "invalidConnReq"
|
||||
is InvalidChatMessage -> "invalidChatMessage"
|
||||
is ContactNotReady -> "contactNotReady"
|
||||
@@ -3953,6 +3998,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("chatNotStarted") object ChatNotStarted: ChatErrorType()
|
||||
@Serializable @SerialName("chatNotStopped") object ChatNotStopped: ChatErrorType()
|
||||
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
|
||||
@Serializable @SerialName("connectionPlan") class ConnectionPlanChatError(val connectionPlan: ConnectionPlan): ChatErrorType()
|
||||
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType()
|
||||
|
||||
+3
-3
@@ -31,10 +31,10 @@ import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.chatlist.updateChatSettings
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -309,9 +309,9 @@ fun ChatInfoLayout(
|
||||
|
||||
if (contact.contactLink != null) {
|
||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||
QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
SimpleXLinkQRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
val clipboard = LocalClipboardManager.current
|
||||
ShareAddressButton { clipboard.shareText(contact.contactLink) }
|
||||
ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
|
||||
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
|
||||
+4
-6
@@ -19,7 +19,7 @@ import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.shareText
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
@@ -44,14 +44,12 @@ fun GroupLinkView(chatModel: ChatModel, groupInfo: GroupInfo, connReqContact: St
|
||||
createLink()
|
||||
}
|
||||
}
|
||||
val clipboard = LocalClipboardManager.current
|
||||
GroupLinkLayout(
|
||||
groupLink = groupLink,
|
||||
groupInfo,
|
||||
groupLinkMemberRole,
|
||||
creatingLink,
|
||||
createLink = ::createLink,
|
||||
share = { clipboard.shareText(groupLink ?: return@GroupLinkLayout) },
|
||||
updateLink = {
|
||||
val role = groupLinkMemberRole.value
|
||||
if (role != null) {
|
||||
@@ -95,7 +93,6 @@ fun GroupLinkLayout(
|
||||
groupLinkMemberRole: MutableState<GroupMemberRole?>,
|
||||
creatingLink: Boolean,
|
||||
createLink: () -> Unit,
|
||||
share: () -> Unit,
|
||||
updateLink: () -> Unit,
|
||||
deleteLink: () -> Unit
|
||||
) {
|
||||
@@ -125,16 +122,17 @@ fun GroupLinkLayout(
|
||||
}
|
||||
initialLaunch = false
|
||||
}
|
||||
QRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
|
||||
SimpleXLinkQRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = DEFAULT_PADDING, vertical = 10.dp)
|
||||
) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SimpleButton(
|
||||
stringResource(MR.strings.share_link),
|
||||
icon = painterResource(MR.images.ic_share),
|
||||
click = share
|
||||
click = { clipboard.shareText(simplexChatLink(groupLink)) }
|
||||
)
|
||||
SimpleButton(
|
||||
stringResource(MR.strings.delete_link),
|
||||
|
||||
+13
-38
@@ -73,6 +73,7 @@ fun GroupMemberInfoView(
|
||||
chatModel.addChat(c)
|
||||
}
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.addAll(c.chatItems)
|
||||
chatModel.chatId.value = c.id
|
||||
closeAll()
|
||||
@@ -283,9 +284,9 @@ fun GroupMemberInfoLayout(
|
||||
|
||||
if (member.contactLink != null) {
|
||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||
QRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
SimpleXLinkQRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
val clipboard = LocalClipboardManager.current
|
||||
ShareAddressButton { clipboard.shareText(member.contactLink) }
|
||||
ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) }
|
||||
if (contactId != null) {
|
||||
if (knownDirectChat(contactId) == null && !groupInfo.fullGroupPreferences.directMessages.on) {
|
||||
ConnectViaAddressButton(onClick = { connectViaAddress(member.contactLink) })
|
||||
@@ -472,43 +473,17 @@ private fun updateMemberRoleDialog(
|
||||
}
|
||||
|
||||
fun connectViaMemberAddressAlert(connReqUri: String) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(MR.strings.connect_via_member_address_alert_title),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_via_member_address_alert_desc)),
|
||||
buttons = {
|
||||
Column {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
withApi {
|
||||
Log.d(TAG, "connectViaUri: connecting")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = false)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
withApi {
|
||||
Log.d(TAG, "connectViaUri: connecting incognito")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = true)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
try {
|
||||
val uri = URI(connReqUri)
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
|
||||
}
|
||||
)
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_connection_link),
|
||||
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
+5
-6
@@ -103,12 +103,11 @@ private fun GroupPreferencesLayout(
|
||||
FeatureSection(GroupFeature.Voice, allowVoice, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(voice = GroupPreference(enable = it)))
|
||||
}
|
||||
// TODO uncomment in 5.3
|
||||
// SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
// val allowFiles = remember(preferences) { mutableStateOf(preferences.files.enable) }
|
||||
// FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) {
|
||||
// applyPrefs(preferences.copy(files = GroupPreference(enable = it)))
|
||||
// }
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
val allowFiles = remember(preferences) { mutableStateOf(preferences.files.enable) }
|
||||
FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) {
|
||||
applyPrefs(preferences.copy(files = GroupPreference(enable = it)))
|
||||
}
|
||||
if (groupInfo.canEdit) {
|
||||
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
||||
ResetSaveButtons(
|
||||
|
||||
+2
-6
@@ -136,12 +136,8 @@ fun MarkdownText (
|
||||
val link = ft.link(linkMode)
|
||||
if (link != null) {
|
||||
hasLinks = true
|
||||
val ftStyle = if (ft.format is Format.SimplexLink && !ft.format.trustedUri && linkMode == SimplexLinkMode.BROWSER) {
|
||||
SpanStyle(color = Color.Red, textDecoration = TextDecoration.Underline)
|
||||
} else {
|
||||
ft.format.style
|
||||
}
|
||||
withAnnotation(tag = if (ft.format is Format.SimplexLink && linkMode != SimplexLinkMode.BROWSER) "SIMPLEX_URL" else "URL", annotation = link) {
|
||||
val ftStyle = ft.format.style
|
||||
withAnnotation(tag = if (ft.format is Format.SimplexLink) "SIMPLEX_URL" else "URL", annotation = link) {
|
||||
withStyle(ftStyle) { append(ft.viewText(linkMode)) }
|
||||
}
|
||||
} else {
|
||||
|
||||
+8
@@ -130,6 +130,13 @@ suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun openGroupChat(groupId: Long, chatModel: ChatModel) {
|
||||
val chat = chatModel.controller.apiGetChat(ChatType.Group, groupId)
|
||||
if (chat != null) {
|
||||
openChat(chat, chatModel)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
||||
Log.d(TAG, "TODOCHAT: openChat: opening ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}")
|
||||
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
|
||||
@@ -141,6 +148,7 @@ suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
||||
|
||||
suspend fun openChat(chat: Chat, chatModel: ChatModel) {
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.addAll(chat.chatItems)
|
||||
chatModel.chatId.value = chat.chatInfo.id
|
||||
}
|
||||
|
||||
+2
-40
@@ -316,46 +316,8 @@ fun connectIfOpenedViaUri(uri: URI, chatModel: ChatModel) {
|
||||
if (chatModel.currentUser.value == null) {
|
||||
chatModel.appOpenUrl.value = uri
|
||||
} else {
|
||||
withUriAction(uri) { linkType ->
|
||||
val title = when (linkType) {
|
||||
ConnectionLinkType.CONTACT -> generalGetString(MR.strings.connect_via_contact_link)
|
||||
ConnectionLinkType.INVITATION -> generalGetString(MR.strings.connect_via_invitation_link)
|
||||
ConnectionLinkType.GROUP -> generalGetString(MR.strings.connect_via_group_link)
|
||||
}
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = title,
|
||||
text = if (linkType == ConnectionLinkType.GROUP)
|
||||
AnnotatedString(generalGetString(MR.strings.you_will_join_group))
|
||||
else
|
||||
AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
|
||||
buttons = {
|
||||
Column {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
Log.d(TAG, "connectIfOpenedViaUri: connecting")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = false)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
Log.d(TAG, "connectIfOpenedViaUri: connecting incognito")
|
||||
connectViaUri(chatModel, linkType, uri, incognito = true)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = null, close = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-7
@@ -34,7 +34,6 @@ fun AddContactView(
|
||||
incognitoPref = chatModel.controller.appPrefs.incognito,
|
||||
connReq = connReqInvitation,
|
||||
contactConnection = contactConnection,
|
||||
share = { clipboard.shareText(connReqInvitation) },
|
||||
learnMore = {
|
||||
ModalManager.center.showModal {
|
||||
Column(
|
||||
@@ -56,7 +55,6 @@ fun AddContactLayout(
|
||||
incognitoPref: SharedPreference<Boolean>,
|
||||
connReq: String,
|
||||
contactConnection: MutableState<PendingContactConnection?>,
|
||||
share: () -> Unit,
|
||||
learnMore: () -> Unit
|
||||
) {
|
||||
val incognito = remember { mutableStateOf(incognitoPref.get()) }
|
||||
@@ -82,7 +80,7 @@ fun AddContactLayout(
|
||||
|
||||
SectionView(stringResource(MR.strings.one_time_link_short).uppercase()) {
|
||||
if (connReq.isNotEmpty()) {
|
||||
QRCode(
|
||||
SimpleXLinkQRCode(
|
||||
connReq, Modifier
|
||||
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
|
||||
.aspectRatio(1f)
|
||||
@@ -99,7 +97,7 @@ fun AddContactLayout(
|
||||
}
|
||||
|
||||
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
|
||||
ShareLinkButton(share)
|
||||
ShareLinkButton(connReq)
|
||||
OneTimeLinkLearnMoreButton(learnMore)
|
||||
}
|
||||
SectionTextFooter(sharedProfileInfo(chatModel, incognito.value))
|
||||
@@ -109,11 +107,12 @@ fun AddContactLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShareLinkButton(onClick: () -> Unit) {
|
||||
fun ShareLinkButton(connReqInvitation: String) {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_share),
|
||||
stringResource(MR.strings.share_invitation_link),
|
||||
onClick,
|
||||
click = { clipboard.shareText(simplexChatLink(connReqInvitation)) },
|
||||
iconColor = MaterialTheme.colors.primary,
|
||||
textColor = MaterialTheme.colors.primary,
|
||||
)
|
||||
@@ -177,7 +176,6 @@ fun PreviewAddContactView() {
|
||||
incognitoPref = SharedPreference({ false }, {}),
|
||||
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
|
||||
contactConnection = mutableStateOf(PendingContactConnection.getSampleData()),
|
||||
share = {},
|
||||
learnMore = {},
|
||||
)
|
||||
}
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) {
|
||||
if (groupInfo != null) {
|
||||
chatModel.addChat(Chat(chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf()))
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatId.value = groupInfo.id
|
||||
setGroupMembers(groupInfo, chatModel)
|
||||
close.invoke()
|
||||
|
||||
+2
-2
@@ -131,13 +131,13 @@ private fun ContactConnectionInfoLayout(
|
||||
|
||||
SectionView {
|
||||
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
|
||||
QRCode(
|
||||
SimpleXLinkQRCode(
|
||||
connReq, Modifier
|
||||
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
incognitoEnabled()
|
||||
ShareLinkButton(share)
|
||||
ShareLinkButton(connReq)
|
||||
OneTimeLinkLearnMoreButton(learnMore)
|
||||
} else {
|
||||
incognitoEnabled()
|
||||
|
||||
+3
-20
@@ -53,32 +53,14 @@ fun PasteToConnectLayout(
|
||||
fun connectViaLink(connReqUri: String) {
|
||||
try {
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
val action = suspend {
|
||||
Log.d(TAG, "connectViaUri: connecting")
|
||||
if (connectViaUri(chatModel, linkType, uri, incognito = incognito.value)) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
if (linkType == ConnectionLinkType.GROUP) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group),
|
||||
confirmText = if (incognito.value) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { action() } }
|
||||
)
|
||||
} else action()
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = incognito.value, close)
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_connection_link),
|
||||
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
|
||||
)
|
||||
} catch (e: URISyntaxException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_connection_link),
|
||||
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +97,7 @@ fun PasteToConnectLayout(
|
||||
painterResource(MR.images.ic_link),
|
||||
stringResource(MR.strings.connect_button),
|
||||
click = { connectViaLink(connectionLink.value) },
|
||||
disabled = connectionLink.value.isEmpty() || connectionLink.value.trim().contains(" ")
|
||||
)
|
||||
|
||||
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
|
||||
|
||||
+23
@@ -19,6 +19,29 @@ import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SimpleXLinkQRCode(
|
||||
connReq: String,
|
||||
modifier: Modifier = Modifier,
|
||||
tintColor: Color = Color(0xff062d56),
|
||||
withLogo: Boolean = true
|
||||
) {
|
||||
QRCode(
|
||||
simplexChatLink(connReq),
|
||||
modifier,
|
||||
tintColor,
|
||||
withLogo
|
||||
)
|
||||
}
|
||||
|
||||
fun simplexChatLink(uri: String): String {
|
||||
return if (uri.startsWith("simplex:/")) {
|
||||
uri.replace("simplex:/", "https://simplex.chat/")
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QRCode(
|
||||
connReq: String,
|
||||
|
||||
+366
-46
@@ -1,6 +1,7 @@
|
||||
package chat.simplex.common.views.newchat
|
||||
|
||||
import SectionBottomSpacer
|
||||
import SectionItemView
|
||||
import SectionTextFooter
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import chat.simplex.common.platform.Log
|
||||
@@ -10,66 +11,265 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.TAG
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chatlist.openDirectChat
|
||||
import chat.simplex.common.views.chatlist.openGroupChat
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Composable
|
||||
expect fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit)
|
||||
|
||||
enum class ConnectionLinkType {
|
||||
CONTACT, INVITATION, GROUP
|
||||
INVITATION, CONTACT, GROUP
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class CReqClientData {
|
||||
@Serializable @SerialName("group") data class Group(val groupLinkId: String): CReqClientData()
|
||||
}
|
||||
|
||||
fun withUriAction(uri: URI, run: suspend (ConnectionLinkType) -> Unit) {
|
||||
val action = uri.path?.drop(1)?.replace("/", "")
|
||||
val data = URI(uri.toString().replaceFirst("#/", "/")).getQueryParameter("data")
|
||||
val type = when {
|
||||
data != null -> {
|
||||
val parsed = runCatching {
|
||||
json.decodeFromString(CReqClientData.serializer(), data)
|
||||
suspend fun planAndConnect(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
incognito: Boolean?,
|
||||
close: (() -> Unit)?
|
||||
) {
|
||||
val connectionPlan = chatModel.controller.apiConnectPlan(uri.toString())
|
||||
if (connectionPlan != null) {
|
||||
when (connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) {
|
||||
InvitationLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan, close)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_invitation_link),
|
||||
text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
InvitationLinkPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
is InvitationLinkPlan.Connecting -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito")
|
||||
val contact = connectionPlan.invitationLinkPlan.contact_
|
||||
if (contact != null) {
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName)
|
||||
)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_already_connecting),
|
||||
generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
is InvitationLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito")
|
||||
val contact = connectionPlan.invitationLinkPlan.contact
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName)
|
||||
)
|
||||
}
|
||||
}
|
||||
when {
|
||||
parsed.getOrNull() is CReqClientData.Group -> ConnectionLinkType.GROUP
|
||||
else -> null
|
||||
is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) {
|
||||
ContactAddressPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan, close)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_contact_link),
|
||||
text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
ContactAddressPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
ContactAddressPlan.ConnectingConfirmReconnect -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
is ContactAddressPlan.ConnectingProhibit -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName)
|
||||
)
|
||||
}
|
||||
is ContactAddressPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
openKnownContact(chatModel, close, contact)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.contact_already_exists),
|
||||
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName)
|
||||
)
|
||||
}
|
||||
}
|
||||
is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) {
|
||||
GroupLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = AnnotatedString(generalGetString(MR.strings.you_will_join_group)),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
ownGroupLinkConfirmConnect(chatModel, uri, incognito, connectionPlan, groupInfo, close)
|
||||
}
|
||||
GroupLinkPlan.ConnectingConfirmReconnect -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } },
|
||||
destructive = true,
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)),
|
||||
connectDestructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.ConnectingProhibit -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo_
|
||||
if (groupInfo != null) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_group_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName)
|
||||
)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_already_joining_the_group),
|
||||
generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
openKnownGroup(chatModel, close, groupInfo)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.connect_plan_group_already_exists),
|
||||
String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action == "contact" -> ConnectionLinkType.CONTACT
|
||||
action == "invitation" -> ConnectionLinkType.INVITATION
|
||||
else -> null
|
||||
}
|
||||
if (type != null) {
|
||||
withApi { run(type) }
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.invalid_contact_link),
|
||||
text = generalGetString(MR.strings.this_link_is_not_a_valid_connection_link)
|
||||
)
|
||||
Log.d(TAG, "planAndConnect, plan error")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan = null, close)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, uri, connectionPlan = null, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_via_link),
|
||||
connectDestructive = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri: URI, incognito: Boolean): Boolean {
|
||||
suspend fun connectViaUri(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
incognito: Boolean,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
close: (() -> Unit)?
|
||||
): Boolean {
|
||||
val r = chatModel.controller.apiConnect(incognito, uri.toString())
|
||||
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
|
||||
if (r) {
|
||||
close?.invoke()
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.connection_request_sent),
|
||||
text =
|
||||
when (action) {
|
||||
when (connLinkType) {
|
||||
ConnectionLinkType.CONTACT -> generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted)
|
||||
ConnectionLinkType.INVITATION -> generalGetString(MR.strings.you_will_be_connected_when_your_contacts_device_is_online)
|
||||
ConnectionLinkType.GROUP -> generalGetString(MR.strings.you_will_be_connected_when_group_host_device_is_online)
|
||||
@@ -79,6 +279,139 @@ suspend fun connectViaUri(chatModel: ChatModel, action: ConnectionLinkType, uri:
|
||||
return r
|
||||
}
|
||||
|
||||
fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType {
|
||||
return when(connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> ConnectionLinkType.INVITATION
|
||||
is ConnectionPlan.ContactAddress -> ConnectionLinkType.CONTACT
|
||||
is ConnectionPlan.GroupLink -> ConnectionLinkType.GROUP
|
||||
}
|
||||
}
|
||||
|
||||
fun askCurrentOrIncognitoProfileAlert(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
close: (() -> Unit)?,
|
||||
title: String,
|
||||
text: AnnotatedString? = null,
|
||||
connectDestructive: Boolean,
|
||||
) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = title,
|
||||
text = text,
|
||||
buttons = {
|
||||
Column {
|
||||
val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = false, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = true, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun openKnownContact(chatModel: ChatModel, close: (() -> Unit)?, contact: Contact) {
|
||||
withApi {
|
||||
val c = chatModel.getContactChat(contact.contactId)
|
||||
if (c != null) {
|
||||
close?.invoke()
|
||||
openDirectChat(contact.contactId, chatModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ownGroupLinkConfirmConnect(
|
||||
chatModel: ChatModel,
|
||||
uri: URI,
|
||||
incognito: Boolean?,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
groupInfo: GroupInfo,
|
||||
close: (() -> Unit)?,
|
||||
) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(MR.strings.connect_plan_join_your_group),
|
||||
text = AnnotatedString(String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName)),
|
||||
buttons = {
|
||||
Column {
|
||||
// Open group
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
openKnownGroup(chatModel, close, groupInfo)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
if (incognito != null) {
|
||||
// Join incognito / Join with current profile
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(
|
||||
if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Use current profile
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = false, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
// Use new incognito profile
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
withApi {
|
||||
connectViaUri(chatModel, uri, incognito = true, connectionPlan, close)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
}
|
||||
// Cancel
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun openKnownGroup(chatModel: ChatModel, close: (() -> Unit)?, groupInfo: GroupInfo) {
|
||||
withApi {
|
||||
val g = chatModel.getGroupChat(groupInfo.groupId)
|
||||
if (g != null) {
|
||||
close?.invoke()
|
||||
openGroupChat(groupInfo.groupId, chatModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConnectContactLayout(
|
||||
chatModel: ChatModel,
|
||||
@@ -92,21 +425,8 @@ fun ConnectContactLayout(
|
||||
QRCodeScanner { connReqUri ->
|
||||
try {
|
||||
val uri = URI(connReqUri)
|
||||
withUriAction(uri) { linkType ->
|
||||
val action = suspend {
|
||||
Log.d(TAG, "connectViaUri: connecting")
|
||||
if (connectViaUri(ChatModel, linkType, uri, incognito = incognito.value)) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
if (linkType == ConnectionLinkType.GROUP) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group),
|
||||
confirmText = if (incognito.value) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withApi { action() } }
|
||||
)
|
||||
} else action()
|
||||
withApi {
|
||||
planAndConnect(chatModel, uri, incognito = incognito.value, close)
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
|
||||
+5
-4
@@ -18,7 +18,8 @@ import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.views.newchat.SimpleXLinkQRCode
|
||||
import chat.simplex.common.views.newchat.simplexChatLink
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
@@ -38,7 +39,7 @@ fun CreateSimpleXAddress(m: ChatModel) {
|
||||
sendEmail = { address ->
|
||||
uriHandler.sendEmail(
|
||||
generalGetString(MR.strings.email_invite_subject),
|
||||
generalGetString(MR.strings.email_invite_body).format(address.connReqContact)
|
||||
generalGetString(MR.strings.email_invite_body).format(simplexChatLink(address.connReqContact))
|
||||
)
|
||||
},
|
||||
createAddress = {
|
||||
@@ -91,8 +92,8 @@ private fun CreateSimpleXAddressLayout(
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
if (userAddress != null) {
|
||||
QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(userAddress.connReqContact) }
|
||||
SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
ShareViaEmailButton { sendEmail(userAddress) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
+3
-4
@@ -92,9 +92,6 @@ fun PrivacySettingsView(
|
||||
chatModel.simplexLinkMode.value = it
|
||||
})
|
||||
}
|
||||
if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) {
|
||||
SectionTextFooter(stringResource(MR.strings.simplex_link_mode_browser_warning))
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
|
||||
val currentUser = chatModel.currentUser.value
|
||||
@@ -185,8 +182,10 @@ fun PrivacySettingsView(
|
||||
|
||||
@Composable
|
||||
private fun SimpleXLinkOptions(simplexLinkModeState: State<SimplexLinkMode>, onSelected: (SimplexLinkMode) -> Unit) {
|
||||
val modeValues = listOf(SimplexLinkMode.DESCRIPTION, SimplexLinkMode.FULL)
|
||||
val pickerValues = modeValues + if (modeValues.contains(simplexLinkModeState.value)) emptyList() else listOf(simplexLinkModeState.value)
|
||||
val values = remember {
|
||||
SimplexLinkMode.values().map {
|
||||
pickerValues.map {
|
||||
when (it) {
|
||||
SimplexLinkMode.DESCRIPTION -> it to generalGetString(MR.strings.simplex_link_mode_description)
|
||||
SimplexLinkMode.FULL -> it to generalGetString(MR.strings.simplex_link_mode_full)
|
||||
|
||||
+4
-4
@@ -24,10 +24,10 @@ import chat.simplex.common.model.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.ShareAddressButton
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.newchat.QRCode
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.MsgContent
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
@@ -100,7 +100,7 @@ fun UserAddressView(
|
||||
sendEmail = { userAddress ->
|
||||
uriHandler.sendEmail(
|
||||
generalGetString(MR.strings.email_invite_subject),
|
||||
generalGetString(MR.strings.email_invite_body).format(userAddress.connReqContact)
|
||||
generalGetString(MR.strings.email_invite_body).format(simplexChatLink( userAddress.connReqContact))
|
||||
)
|
||||
},
|
||||
setProfileAddress = ::setProfileAddress,
|
||||
@@ -201,8 +201,8 @@ private fun UserAddressLayout(
|
||||
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
|
||||
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
|
||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||
QRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(userAddress.connReqContact) }
|
||||
SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
|
||||
ShareViaEmailButton { sendEmail(userAddress) }
|
||||
ShareWithContactsButton(shareViaProfile, setProfileAddress)
|
||||
AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<string name="thousand_abbreviation">k</string>
|
||||
|
||||
<!-- Connect via Link - MainActivity.kt -->
|
||||
<string name="connect_via_contact_link">Connect via contact link?</string>
|
||||
<string name="connect_via_invitation_link">Connect via invitation link?</string>
|
||||
<string name="connect_via_group_link">Connect via group link?</string>
|
||||
<string name="connect_via_contact_link">Connect via contact address?</string>
|
||||
<string name="connect_via_invitation_link">Connect via one-time link?</string>
|
||||
<string name="connect_via_group_link">Join group?</string>
|
||||
<string name="connect_use_current_profile">Use current profile</string>
|
||||
<string name="connect_use_new_incognito_profile">Use new incognito profile</string>
|
||||
<string name="profile_will_be_sent_to_contact_sending_link">Your profile will be sent to the contact that you received this link from.</string>
|
||||
<string name="you_will_join_group">You will join a group this link refers to and connect to its group members.</string>
|
||||
<string name="you_will_join_group">You will connect to all group members.</string>
|
||||
<string name="connect_via_link_verb">Connect</string>
|
||||
<string name="connect_via_link_incognito">Connect incognito</string>
|
||||
|
||||
@@ -1600,4 +1600,25 @@
|
||||
<!-- Under development -->
|
||||
<string name="in_developing_title">Coming soon!</string>
|
||||
<string name="in_developing_desc">This feature is not yet supported. Try the next release.</string>
|
||||
|
||||
<!-- Connection plan -->
|
||||
<string name="connect_plan_connect_to_yourself">Connect to yourself?</string>
|
||||
<string name="connect_plan_this_is_your_own_one_time_link">This is your own one-time link!</string>
|
||||
<string name="connect_plan_you_are_already_connecting_to_vName">You are already connecting to %1$s.</string>
|
||||
<string name="connect_plan_already_connecting">Already connecting!</string>
|
||||
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">You are already connecting via this one-time link!</string>
|
||||
<string name="connect_plan_this_is_your_own_simplex_address">This is your own SimpleX address!</string>
|
||||
<string name="connect_plan_repeat_connection_request">Repeat connection request?</string>
|
||||
<string name="connect_plan_you_have_already_requested_connection_via_this_address">You have already requested connection via this address!</string>
|
||||
<string name="connect_plan_join_your_group">Join your group?</string>
|
||||
<string name="connect_plan_this_is_your_link_for_group_vName">This is your link for group %1$s!</string>
|
||||
<string name="connect_plan_open_group">Open group</string>
|
||||
<string name="connect_plan_repeat_join_request">Repeat join request?</string>
|
||||
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link!</string>
|
||||
<string name="connect_plan_group_already_exists">Group already exists!</string>
|
||||
<string name="connect_plan_you_are_already_joining_the_group_vName">You are already joining the group %1$s.</string>
|
||||
<string name="connect_plan_already_joining_the_group">Already joining the group!</string>
|
||||
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link.</string>
|
||||
<string name="connect_plan_you_are_already_in_group_vName">You are already in group %1$s.</string>
|
||||
<string name="connect_plan_connect_via_link">Connect via link?</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user