mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-26 19:35:48 +00:00
android, desktop: short links UX (main functionality) (#5999)
This commit is contained in:
@@ -136,7 +136,6 @@ object NtfManager {
|
||||
val actionPendingIntent: PendingIntent = PendingIntent.getBroadcast(SimplexApp.context, 0, actionIntent, flags)
|
||||
val actionButton = when (action) {
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST -> generalGetString(MR.strings.accept)
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST_INCOGNITO -> generalGetString(MR.strings.accept_contact_incognito_button)
|
||||
}
|
||||
builder.addAction(0, actionButton, actionPendingIntent)
|
||||
}
|
||||
@@ -316,7 +315,6 @@ object NtfManager {
|
||||
val m = SimplexApp.context.chatModel
|
||||
when (intent.action) {
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST.name -> ntfManager.acceptContactRequestAction(userId, incognito = false, chatId)
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST_INCOGNITO.name -> ntfManager.acceptContactRequestAction(userId, incognito = true, chatId)
|
||||
RejectCallAction -> {
|
||||
val invitation = m.callInvitations[chatId]
|
||||
if (invitation != null) {
|
||||
|
||||
+80
-5
@@ -171,6 +171,8 @@ object ChatModel {
|
||||
// return true if you handled the click
|
||||
var centerPanelBackgroundClickHandler: (() -> Boolean)? = null
|
||||
|
||||
var addressShortLinkDataSet: Boolean = userAddress.value?.shortLinkDataSet ?: true
|
||||
|
||||
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
|
||||
currentUser.value
|
||||
} else {
|
||||
@@ -1272,6 +1274,7 @@ interface SomeChat {
|
||||
val apiId: Long
|
||||
val ready: Boolean
|
||||
val chatDeleted: Boolean
|
||||
val nextConnect: Boolean
|
||||
val incognito: Boolean
|
||||
fun featureEnabled(feature: ChatFeature): Boolean
|
||||
val timedMessagesTTL: Int?
|
||||
@@ -1351,6 +1354,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val apiId get() = contact.apiId
|
||||
override val ready get() = contact.ready
|
||||
override val chatDeleted get() = contact.chatDeleted
|
||||
override val nextConnect get() = contact.nextConnect
|
||||
override val incognito get() = contact.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL
|
||||
@@ -1375,6 +1379,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val apiId get() = groupInfo.apiId
|
||||
override val ready get() = groupInfo.ready
|
||||
override val chatDeleted get() = groupInfo.chatDeleted
|
||||
override val nextConnect get() = groupInfo.nextConnect
|
||||
override val incognito get() = groupInfo.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL
|
||||
@@ -1398,6 +1403,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val apiId get() = noteFolder.apiId
|
||||
override val ready get() = noteFolder.ready
|
||||
override val chatDeleted get() = noteFolder.chatDeleted
|
||||
override val nextConnect get() = noteFolder.nextConnect
|
||||
override val incognito get() = noteFolder.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL
|
||||
@@ -1421,6 +1427,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val apiId get() = contactRequest.apiId
|
||||
override val ready get() = contactRequest.ready
|
||||
override val chatDeleted get() = contactRequest.chatDeleted
|
||||
override val nextConnect get() = contactRequest.nextConnect
|
||||
override val incognito get() = contactRequest.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL
|
||||
@@ -1444,6 +1451,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val apiId get() = contactConnection.apiId
|
||||
override val ready get() = contactConnection.ready
|
||||
override val chatDeleted get() = contactConnection.chatDeleted
|
||||
override val nextConnect get() = contactConnection.nextConnect
|
||||
override val incognito get() = contactConnection.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL
|
||||
@@ -1472,6 +1480,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val id get() = "?$apiId"
|
||||
override val ready get() = false
|
||||
override val chatDeleted get() = false
|
||||
override val nextConnect get() = false
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -1490,8 +1499,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
get() {
|
||||
when (this) {
|
||||
is Direct -> {
|
||||
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
|
||||
if (contact.nextSendGrpInv) return null
|
||||
if (contact.sendMsgToConnect) return null
|
||||
if (contact.nextAcceptContactRequest) { return generalGetString(MR.strings.cant_send_message_generic) to null }
|
||||
if (!contact.active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null
|
||||
if (!contact.sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null
|
||||
if (contact.activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null
|
||||
@@ -1641,6 +1650,8 @@ data class Contact(
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
val chatTs: Instant?,
|
||||
val preparedContact: PreparedContact?,
|
||||
val contactRequestId: Long?,
|
||||
val contactGroupMemberId: Long? = null,
|
||||
val contactGrpInvSent: Boolean,
|
||||
val chatTags: List<Long>,
|
||||
@@ -1654,7 +1665,11 @@ data class Contact(
|
||||
override val ready get() = activeConn?.connStatus == ConnStatus.Ready
|
||||
val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady
|
||||
val active get() = contactStatus == ContactStatus.Active
|
||||
override val nextConnect get() = sendMsgToConnect
|
||||
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
|
||||
val nextConnectPrepared get() = preparedContact != null && activeConn == null
|
||||
val nextAcceptContactRequest get() = contactRequestId != null && activeConn == null
|
||||
val sendMsgToConnect get() = nextSendGrpInv || nextConnectPrepared
|
||||
override val incognito get() = contactConnIncognito
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
ChatFeature.TimedMessages -> mergedPreferences.timedMessages.enabled.forUser
|
||||
@@ -1717,6 +1732,8 @@ data class Contact(
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
chatTs = Clock.System.now(),
|
||||
preparedContact = null,
|
||||
contactRequestId = null,
|
||||
contactGrpInvSent = false,
|
||||
chatDeleted = false,
|
||||
uiThemes = null,
|
||||
@@ -1732,6 +1749,18 @@ data class NavigationInfo(
|
||||
val afterTotal: Int = 0
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PreparedContact (
|
||||
val connLinkToConnect: CreatedConnLink,
|
||||
val uiConnLinkType: ConnectionMode
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class ConnectionMode {
|
||||
@SerialName("inv") Inv,
|
||||
@SerialName("con") Con
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ContactStatus {
|
||||
@SerialName("active") Active,
|
||||
@@ -1877,6 +1906,8 @@ data class GroupInfo (
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant,
|
||||
val chatTs: Instant?,
|
||||
val connLinkToConnect: CreatedConnLink?,
|
||||
val connLinkStartedConnection: Boolean,
|
||||
val uiThemes: ThemeModeOverrides? = null,
|
||||
val membersRequireAttention: Int,
|
||||
val chatTags: List<Long>,
|
||||
@@ -1887,6 +1918,8 @@ data class GroupInfo (
|
||||
override val id get() = "#$groupId"
|
||||
override val apiId get() = groupId
|
||||
override val ready get() = membership.memberActive
|
||||
override val nextConnect get() = nextConnectPrepared
|
||||
val nextConnectPrepared = connLinkToConnect != null && !connLinkStartedConnection
|
||||
override val chatDeleted get() = false
|
||||
override val incognito get() = membership.memberIncognito
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
@@ -1939,6 +1972,8 @@ data class GroupInfo (
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now(),
|
||||
chatTs = Clock.System.now(),
|
||||
connLinkToConnect = null,
|
||||
connLinkStartedConnection = false,
|
||||
uiThemes = null,
|
||||
membersRequireAttention = 0,
|
||||
chatTags = emptyList(),
|
||||
@@ -1990,6 +2025,18 @@ enum class MemberCriteria {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ContactShortLinkData (
|
||||
val profile: Profile,
|
||||
val message: String?,
|
||||
val business: Boolean
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GroupShortLinkData (
|
||||
val groupProfile: GroupProfile
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BusinessChatInfo (
|
||||
val chatType: BusinessChatType,
|
||||
@@ -2313,6 +2360,7 @@ class NoteFolder(
|
||||
override val apiId get() = noteFolderId
|
||||
override val chatDeleted get() = false
|
||||
override val ready get() = true
|
||||
override val nextConnect get() = false
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -2344,10 +2392,11 @@ class UserContactRequest (
|
||||
override val updatedAt: Instant
|
||||
): SomeChat, NamedChat {
|
||||
override val chatType get() = ChatType.ContactRequest
|
||||
override val id get() = "<@$contactRequestId"
|
||||
override val id get() = contactRequestChatId(contactRequestId)
|
||||
override val apiId get() = contactRequestId
|
||||
override val chatDeleted get() = false
|
||||
override val ready get() = true
|
||||
override val nextConnect get() = false
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -2368,6 +2417,8 @@ class UserContactRequest (
|
||||
}
|
||||
}
|
||||
|
||||
fun contactRequestChatId(contactRequestId: Long): String = "<@$contactRequestId"
|
||||
|
||||
@Serializable
|
||||
class PendingContactConnection(
|
||||
val pccConnId: Long,
|
||||
@@ -2386,6 +2437,7 @@ class PendingContactConnection(
|
||||
override val apiId get() = pccConnId
|
||||
override val chatDeleted get() = false
|
||||
override val ready get() = false
|
||||
override val nextConnect get() = false
|
||||
override val incognito get() = customUserProfileId != null
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -3433,7 +3485,7 @@ sealed class CIContent: ItemContent {
|
||||
|
||||
companion object {
|
||||
fun directE2EEInfoStr(e2EEInfo: E2EEInfo): String =
|
||||
if (e2EEInfo.pqEnabled) {
|
||||
if (e2EEInfo.pqEnabled == true) {
|
||||
generalGetString(MR.strings.e2ee_info_pq_short)
|
||||
} else {
|
||||
e2eeInfoNoPQStr
|
||||
@@ -3912,6 +3964,7 @@ sealed class MsgContent {
|
||||
@Serializable(with = MsgContentSerializer::class) class MCVoice(override val text: String, val duration: Int): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCFile(override val text: String): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCReport(override val text: String, val reason: ReportReason): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCChat(override val text: String, val chatLink: MsgChatLink): MsgContent()
|
||||
@Serializable(with = MsgContentSerializer::class) class MCUnknown(val type: String? = null, override val text: String, val json: JsonElement): MsgContent()
|
||||
|
||||
val isVoice: Boolean get() =
|
||||
@@ -3965,7 +4018,7 @@ enum class CIGroupInvitationStatus {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class E2EEInfo (val pqEnabled: Boolean) {}
|
||||
class E2EEInfo (val pqEnabled: Boolean?) {}
|
||||
|
||||
object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) {
|
||||
@@ -3992,6 +4045,10 @@ object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
element<String>("text")
|
||||
element<ReportReason>("reason")
|
||||
})
|
||||
element("MCChat", buildClassSerialDescriptor("MCChat") {
|
||||
element<String>("text")
|
||||
element<MsgChatLink>("chatLink")
|
||||
})
|
||||
element("MCUnknown", buildClassSerialDescriptor("MCUnknown"))
|
||||
}
|
||||
|
||||
@@ -4026,6 +4083,10 @@ object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
val reason = Json.decodeFromString<ReportReason>(json["reason"].toString())
|
||||
MsgContent.MCReport(text, reason)
|
||||
}
|
||||
"chat" -> {
|
||||
val chatLink = Json.decodeFromString<MsgChatLink>(json["chatLink"].toString())
|
||||
MsgContent.MCChat(text, chatLink)
|
||||
}
|
||||
else -> MsgContent.MCUnknown(t, text, json)
|
||||
}
|
||||
} else {
|
||||
@@ -4080,6 +4141,12 @@ object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
put("text", value.text)
|
||||
put("reason", json.encodeToJsonElement(value.reason))
|
||||
}
|
||||
is MsgContent.MCChat ->
|
||||
buildJsonObject {
|
||||
put("type", "chat")
|
||||
put("text", value.text)
|
||||
put("chatLink", json.encodeToJsonElement(value.chatLink))
|
||||
}
|
||||
is MsgContent.MCUnknown -> value.json
|
||||
}
|
||||
encoder.encodeJsonElement(json)
|
||||
@@ -4095,6 +4162,14 @@ enum class MsgContentTag {
|
||||
@SerialName("voice") Voice,
|
||||
@SerialName("file") File,
|
||||
@SerialName("report") Report,
|
||||
@SerialName("chat") Chat,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class MsgChatLink {
|
||||
@Serializable @SerialName("contact") data class Contact(val connLink: String, val profile: Profile, val business: Boolean) : MsgChatLink()
|
||||
@Serializable @SerialName("invitation") data class Invitation(val invLink: String, val profile: Profile) : MsgChatLink()
|
||||
@Serializable @SerialName("group") data class Group(val connLink: String, val groupProfile: GroupProfile) : MsgChatLink()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
+150
-33
@@ -1391,6 +1391,60 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiPrepareContact(rh: Long?, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData): Contact? {
|
||||
val userId = try { currentUserId("apiPrepareContact") } catch (e: Exception) { return null }
|
||||
val r = sendCmd(rh, CC.APIPrepareContact(userId, connLink, contactShortLinkData))
|
||||
if (r is API.Result && r.res is CR.NewPreparedContact) return r.res.contact
|
||||
Log.e(TAG, "apiPrepareContact bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_contact), "${r.responseType}: ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiPrepareGroup(rh: Long?, connLink: CreatedConnLink, groupShortLinkData: GroupShortLinkData): GroupInfo? {
|
||||
val userId = try { currentUserId("apiPrepareGroup") } catch (e: Exception) { return null }
|
||||
val r = sendCmd(rh, CC.APIPrepareGroup(userId, connLink, groupShortLinkData))
|
||||
if (r is API.Result && r.res is CR.NewPreparedGroup) return r.res.groupInfo
|
||||
Log.e(TAG, "apiPrepareGroup bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_group), "${r.responseType}: ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiChangePreparedContactUser(rh: Long?, contactId: Long, newUserId: Long): Contact? {
|
||||
val r = sendCmd(rh, CC.APIChangePreparedContactUser(contactId, newUserId))
|
||||
if (r is API.Result && r.res is CR.ContactUserChanged) return r.res.toContact
|
||||
Log.e(TAG, "apiChangePreparedContactUser bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_contact_user), "${r.responseType}: ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiChangePreparedGroupUser(rh: Long?, groupId: Long, newUserId: Long): GroupInfo? {
|
||||
val r = sendCmd(rh, CC.APIChangePreparedGroupUser(groupId, newUserId))
|
||||
if (r is API.Result && r.res is CR.GroupUserChanged) return r.res.toGroup
|
||||
Log.e(TAG, "apiChangePreparedGroupUser bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_group_user), "${r.responseType}: ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnectPreparedContact(rh: Long?, contactId: Long, incognito: Boolean, msg: MsgContent): Contact? {
|
||||
val r = sendCmd(rh, CC.APIConnectPreparedContact(contactId, incognito, msg))
|
||||
if (r is API.Result && r.res is CR.StartedConnectionToContact) return r.res.contact
|
||||
Log.e(TAG, "apiConnectPreparedContact bad response: ${r.responseType} ${r.details}")
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiConnectPreparedContact", generalGetString(MR.strings.connection_error), r)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean): GroupInfo? {
|
||||
val r = sendCmd(rh, CC.APIConnectPreparedGroup(groupId, incognito))
|
||||
if (r is API.Result && r.res is CR.StartedConnectionToGroup) return r.res.groupInfo
|
||||
Log.e(TAG, "apiConnectPreparedGroup bad response: ${r.responseType} ${r.details}")
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiConnectPreparedGroup", generalGetString(MR.strings.connection_error), r)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiConnectContactViaAddress(rh: Long?, incognito: Boolean, contactId: Long): Contact? {
|
||||
val userId = try { currentUserId("apiConnectContactViaAddress") } catch (e: Exception) { return null }
|
||||
val r = sendCmd(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId))
|
||||
@@ -1612,11 +1666,14 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Boolean {
|
||||
suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Contact? {
|
||||
val r = sendCmd(rh, CC.ApiRejectContact(contactReqId))
|
||||
if (r is API.Result && r.res is CR.ContactRequestRejected) return true
|
||||
if (r is API.Result && r.res is CR.ContactRequestRejected) return r.res.contact_
|
||||
Log.e(TAG, "apiRejectContactRequest bad response: ${r.responseType} ${r.details}")
|
||||
return false
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiRejectContactRequest", generalGetString(MR.strings.error_rejecting_contact_request), r)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiGetCallInvitations(rh: Long?): List<RcvCallInvitation> {
|
||||
@@ -1988,18 +2045,18 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? {
|
||||
val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole))
|
||||
if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.connLinkContact to r.res.memberRole
|
||||
if (r is API.Result && r.res is CR.GroupLinkCreated) return r.res.groupLink
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiCreateGroupLink", generalGetString(MR.strings.error_creating_link_for_group), r)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): GroupLink? {
|
||||
val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole))
|
||||
if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole
|
||||
if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiGroupLinkMemberRole", generalGetString(MR.strings.error_updating_link_for_group), r)
|
||||
}
|
||||
@@ -2015,16 +2072,16 @@ object ChatController {
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
suspend fun apiGetGroupLink(rh: Long?, groupId: Long): GroupLink? {
|
||||
val r = sendCmd(rh, CC.APIGetGroupLink(groupId))
|
||||
if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole
|
||||
if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink
|
||||
Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiAddGroupShortLink(rh: Long?, groupId: Long): Pair<CreatedConnLink, GroupMemberRole>? {
|
||||
suspend fun apiAddGroupShortLink(rh: Long?, groupId: Long): GroupLink? {
|
||||
val r = sendCmd(rh, CC.ApiAddGroupShortLink(groupId))
|
||||
if (r is API.Result && r.res is CR.GroupLink) return r.res.connLinkContact to r.res.memberRole
|
||||
if (r is API.Result && r.res is CR.CRGroupLink) return r.res.groupLink
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiAddGroupShortLink", generalGetString(MR.strings.error_creating_link_for_group), r)
|
||||
}
|
||||
@@ -2376,17 +2433,26 @@ object ChatController {
|
||||
}
|
||||
is CR.ReceivedContactRequest -> {
|
||||
val contactRequest = r.contactRequest
|
||||
val cInfo = ChatInfo.ContactRequest(contactRequest)
|
||||
if (active(r.user)) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (chatModel.chatsContext.hasChat(rhId, contactRequest.id)) {
|
||||
chatModel.chatsContext.updateChatInfo(rhId, cInfo)
|
||||
if (r.contact_ != null) { // means contact request was created with contact, so we need to add/update contact chat
|
||||
val contact = r.contact_
|
||||
if (chatModel.chatsContext.hasChat(rhId, contact.id)) {
|
||||
chatModel.chatsContext.updateContact(rhId, contact)
|
||||
} else {
|
||||
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Direct(contact), chatItems = listOf()))
|
||||
}
|
||||
} else {
|
||||
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf()))
|
||||
val cInfo = ChatInfo.ContactRequest(contactRequest)
|
||||
if (chatModel.chatsContext.hasChat(rhId, contactRequest.id)) {
|
||||
chatModel.chatsContext.updateChatInfo(rhId, cInfo)
|
||||
} else {
|
||||
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ntfManager.notifyContactRequestReceived(r.user, cInfo)
|
||||
ntfManager.notifyContactRequestReceived(r.user, ChatInfo.ContactRequest(contactRequest))
|
||||
}
|
||||
is CR.ContactUpdated -> {
|
||||
if (active(r.user) && chatModel.chatsContext.hasChat(rhId, r.toContact.id)) {
|
||||
@@ -3423,6 +3489,12 @@ sealed class CC {
|
||||
class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC()
|
||||
class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC()
|
||||
class APIConnectPlan(val userId: Long, val connLink: String): CC()
|
||||
class APIPrepareContact(val userId: Long, val connLink: CreatedConnLink, val contactShortLinkData: ContactShortLinkData): CC()
|
||||
class APIPrepareGroup(val userId: Long, val connLink: CreatedConnLink, val groupShortLinkData: GroupShortLinkData): CC()
|
||||
class APIChangePreparedContactUser(val contactId: Long, val newUserId: Long): CC()
|
||||
class APIChangePreparedGroupUser(val groupId: Long, val newUserId: Long): CC()
|
||||
class APIConnectPreparedContact(val contactId: Long, val incognito: Boolean, val msg: MsgContent): CC()
|
||||
class APIConnectPreparedGroup(val groupId: Long, val incognito: Boolean): CC()
|
||||
class APIConnect(val userId: Long, val incognito: Boolean, val connLink: CreatedConnLink): CC()
|
||||
class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC()
|
||||
class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC()
|
||||
@@ -3612,6 +3684,12 @@ sealed class CC {
|
||||
is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}"
|
||||
is ApiChangeConnectionUser -> "/_set conn user :$connId $userId"
|
||||
is APIConnectPlan -> "/_connect plan $userId $connLink"
|
||||
is APIPrepareContact -> "/_prepare contact $userId ${connLink.connFullLink} ${connLink.connShortLink ?: ""} ${json.encodeToString(contactShortLinkData)}"
|
||||
is APIPrepareGroup -> "/_prepare group $userId ${connLink.connFullLink} ${connLink.connShortLink ?: ""} ${json.encodeToString(groupShortLinkData)}"
|
||||
is APIChangePreparedContactUser -> "/_set contact user @$contactId $newUserId"
|
||||
is APIChangePreparedGroupUser -> "/_set group user #$groupId $newUserId"
|
||||
is APIConnectPreparedContact -> "/_connect contact @$contactId incognito=${onOff(incognito)} ${msg.cmdString}"
|
||||
is APIConnectPreparedGroup -> "/_connect group #$groupId incognito=${onOff(incognito)}"
|
||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}"
|
||||
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
|
||||
is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}"
|
||||
@@ -3779,6 +3857,12 @@ sealed class CC {
|
||||
is ApiSetConnectionIncognito -> "apiSetConnectionIncognito"
|
||||
is ApiChangeConnectionUser -> "apiChangeConnectionUser"
|
||||
is APIConnectPlan -> "apiConnectPlan"
|
||||
is APIPrepareContact -> "apiPrepareContact"
|
||||
is APIPrepareGroup -> "apiPrepareGroup"
|
||||
is APIChangePreparedContactUser -> "apiChangePreparedContactUser"
|
||||
is APIChangePreparedGroupUser -> "apiChangePreparedGroupUser"
|
||||
is APIConnectPreparedContact -> "apiConnectPreparedContact"
|
||||
is APIConnectPreparedGroup -> "apiConnectPreparedGroup"
|
||||
is APIConnect -> "apiConnect"
|
||||
is ApiConnectContactViaAddress -> "apiConnectContactViaAddress"
|
||||
is ApiDeleteChat -> "apiDeleteChat"
|
||||
@@ -5829,8 +5913,14 @@ sealed class 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, val connectionPlan: ConnectionPlan): CR()
|
||||
@Serializable @SerialName("newPreparedContact") class NewPreparedContact(val user: UserRef, val contact: Contact): CR()
|
||||
@Serializable @SerialName("newPreparedGroup") class NewPreparedGroup(val user: UserRef, val groupInfo: GroupInfo): 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()
|
||||
@Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("startedConnectionToContact") class StartedConnectionToContact(val user: UserRef, val contact: Contact): CR()
|
||||
@Serializable @SerialName("startedConnectionToGroup") class StartedConnectionToGroup(val user: UserRef, val groupInfo: GroupInfo): CR()
|
||||
@Serializable @SerialName("sentInvitationToContact") class SentInvitationToContact(val user: UserRef, val contact: Contact, val customUserProfile: Profile?): CR()
|
||||
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
|
||||
@Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR()
|
||||
@@ -5851,9 +5941,9 @@ sealed class CR {
|
||||
@Serializable @SerialName("contactConnected") class ContactConnected(val user: UserRef, val contact: Contact, val userCustomProfile: Profile? = null): CR()
|
||||
@Serializable @SerialName("contactConnecting") class ContactConnecting(val user: UserRef, val contact: Contact): CR()
|
||||
@Serializable @SerialName("contactSndReady") class ContactSndReady(val user: UserRef, val contact: Contact): CR()
|
||||
@Serializable @SerialName("receivedContactRequest") class ReceivedContactRequest(val user: UserRef, val contactRequest: UserContactRequest): CR()
|
||||
@Serializable @SerialName("receivedContactRequest") class ReceivedContactRequest(val user: UserRef, val contactRequest: UserContactRequest, val contact_: Contact?): CR()
|
||||
@Serializable @SerialName("acceptingContactRequest") class AcceptingContactRequest(val user: UserRef, val contact: Contact): CR()
|
||||
@Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef): CR()
|
||||
@Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef, val contactRequest: UserContactRequest, val contact_: Contact?): CR()
|
||||
@Serializable @SerialName("contactUpdated") class ContactUpdated(val user: UserRef, val toContact: Contact): CR()
|
||||
@Serializable @SerialName("groupMemberUpdated") class GroupMemberUpdated(val user: UserRef, val groupInfo: GroupInfo, val fromMember: GroupMember, val toMember: GroupMember): CR()
|
||||
// TODO remove below
|
||||
@@ -5900,8 +5990,8 @@ sealed class CR {
|
||||
@Serializable @SerialName("joinedGroupMember") class JoinedGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("connectedToGroupMember") class ConnectedToGroupMember(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val memberContact: Contact? = null): CR()
|
||||
@Serializable @SerialName("groupUpdated") class GroupUpdated(val user: UserRef, val toGroup: GroupInfo): CR()
|
||||
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR()
|
||||
@Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connLinkContact: CreatedConnLink, val memberRole: GroupMemberRole): CR()
|
||||
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink): CR()
|
||||
@Serializable @SerialName("groupLink") class CRGroupLink(val user: UserRef, val groupInfo: GroupInfo, val groupLink: GroupLink): CR()
|
||||
@Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR()
|
||||
@Serializable @SerialName("newMemberContact") class NewMemberContact(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@Serializable @SerialName("newMemberContactSentInv") class NewMemberContactSentInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||
@@ -6011,8 +6101,14 @@ sealed class CR {
|
||||
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
|
||||
is ConnectionUserChanged -> "ConnectionUserChanged"
|
||||
is CRConnectionPlan -> "connectionPlan"
|
||||
is NewPreparedContact -> "newPreparedContact"
|
||||
is NewPreparedGroup -> "newPreparedGroup"
|
||||
is ContactUserChanged -> "contactUserChanged"
|
||||
is GroupUserChanged -> "groupUserChanged"
|
||||
is SentConfirmation -> "sentConfirmation"
|
||||
is SentInvitation -> "sentInvitation"
|
||||
is StartedConnectionToContact -> "startedConnectionToContact"
|
||||
is StartedConnectionToGroup -> "startedConnectionToGroup"
|
||||
is SentInvitationToContact -> "sentInvitationToContact"
|
||||
is ContactAlreadyExists -> "contactAlreadyExists"
|
||||
is ContactDeleted -> "contactDeleted"
|
||||
@@ -6080,7 +6176,7 @@ sealed class CR {
|
||||
is ConnectedToGroupMember -> "connectedToGroupMember"
|
||||
is GroupUpdated -> "groupUpdated"
|
||||
is GroupLinkCreated -> "groupLinkCreated"
|
||||
is GroupLink -> "groupLink"
|
||||
is CRGroupLink -> "groupLink"
|
||||
is GroupLinkDeleted -> "groupLinkDeleted"
|
||||
is NewMemberContact -> "newMemberContact"
|
||||
is NewMemberContactSentInv -> "newMemberContactSentInv"
|
||||
@@ -6183,8 +6279,14 @@ sealed class CR {
|
||||
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||
is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" )
|
||||
is CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nconnectionPlan: ${json.encodeToString(connectionPlan)}")
|
||||
is NewPreparedContact -> withUser(user, json.encodeToString(contact))
|
||||
is NewPreparedGroup -> withUser(user, json.encodeToString(groupInfo))
|
||||
is ContactUserChanged -> withUser(user, "fromContact: ${json.encodeToString(fromContact)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoContact: ${json.encodeToString(toContact)}")
|
||||
is GroupUserChanged -> withUser(user, "fromGroup: ${json.encodeToString(fromGroup)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoGroup: ${json.encodeToString(toGroup)}")
|
||||
is SentConfirmation -> withUser(user, json.encodeToString(connection))
|
||||
is SentInvitation -> withUser(user, json.encodeToString(connection))
|
||||
is StartedConnectionToContact -> withUser(user, json.encodeToString(contact))
|
||||
is StartedConnectionToGroup -> withUser(user, json.encodeToString(groupInfo))
|
||||
is SentInvitationToContact -> withUser(user, json.encodeToString(contact))
|
||||
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
|
||||
is ContactDeleted -> withUser(user, json.encodeToString(contact))
|
||||
@@ -6198,16 +6300,16 @@ sealed class CR {
|
||||
is GroupAliasUpdated -> withUser(user, json.encodeToString(toGroup))
|
||||
is ConnectionAliasUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||
is ContactPrefsUpdated -> withUser(user, "fromContact: $fromContact\ntoContact: \n${json.encodeToString(toContact)}")
|
||||
is UserContactLink -> withUser(user, contactLink.responseDetails)
|
||||
is UserContactLinkUpdated -> withUser(user, contactLink.responseDetails)
|
||||
is UserContactLink -> withUser(user, json.encodeToString(contactLink))
|
||||
is UserContactLinkUpdated -> withUser(user, json.encodeToString(contactLink))
|
||||
is UserContactLinkCreated -> withUser(user, json.encodeToString(connLinkContact))
|
||||
is UserContactLinkDeleted -> withUser(user, noDetails())
|
||||
is ContactConnected -> withUser(user, json.encodeToString(contact))
|
||||
is ContactConnecting -> withUser(user, json.encodeToString(contact))
|
||||
is ContactSndReady -> withUser(user, json.encodeToString(contact))
|
||||
is ReceivedContactRequest -> withUser(user, json.encodeToString(contactRequest))
|
||||
is ReceivedContactRequest -> withUser(user, "contactRequest: ${json.encodeToString(contactRequest)}\ncontact_: ${json.encodeToString(contact_)}")
|
||||
is AcceptingContactRequest -> withUser(user, json.encodeToString(contact))
|
||||
is ContactRequestRejected -> withUser(user, noDetails())
|
||||
is ContactRequestRejected -> withUser(user, "contactRequest: ${json.encodeToString(contactRequest)}\ncontact_: ${json.encodeToString(contact_)}")
|
||||
is ContactUpdated -> withUser(user, json.encodeToString(toContact))
|
||||
is GroupMemberUpdated -> withUser(user, "groupInfo: $groupInfo\nfromMember: $fromMember\ntoMember: $toMember")
|
||||
is ContactsSubscribed -> "server: $server\ncontacts:\n${json.encodeToString(contactRefs)}"
|
||||
@@ -6251,8 +6353,8 @@ sealed class CR {
|
||||
is JoinedGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
|
||||
is ConnectedToGroupMember -> withUser(user, "groupInfo: $groupInfo\nmember: $member\nmemberContact: $memberContact")
|
||||
is GroupUpdated -> withUser(user, json.encodeToString(toGroup))
|
||||
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole")
|
||||
is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnLinkContact: $connLinkContact\nmemberRole: $memberRole")
|
||||
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink")
|
||||
is CRGroupLink -> withUser(user, "groupInfo: $groupInfo\ngroupLink: $groupLink")
|
||||
is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo))
|
||||
is NewMemberContact -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||
is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||
@@ -6388,7 +6490,7 @@ sealed class ConnectionPlan {
|
||||
|
||||
@Serializable
|
||||
sealed class InvitationLinkPlan {
|
||||
@Serializable @SerialName("ok") object Ok: InvitationLinkPlan()
|
||||
@Serializable @SerialName("ok") class Ok(val contactSLinkData_: ContactShortLinkData? = null): 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()
|
||||
@@ -6396,7 +6498,7 @@ sealed class InvitationLinkPlan {
|
||||
|
||||
@Serializable
|
||||
sealed class ContactAddressPlan {
|
||||
@Serializable @SerialName("ok") object Ok: ContactAddressPlan()
|
||||
@Serializable @SerialName("ok") class Ok(val contactSLinkData_: ContactShortLinkData? = null): ContactAddressPlan()
|
||||
@Serializable @SerialName("ownLink") object OwnLink: ContactAddressPlan()
|
||||
@Serializable @SerialName("connectingConfirmReconnect") object ConnectingConfirmReconnect: ContactAddressPlan()
|
||||
@Serializable @SerialName("connectingProhibit") class ConnectingProhibit(val contact: Contact): ContactAddressPlan()
|
||||
@@ -6406,7 +6508,7 @@ sealed class ContactAddressPlan {
|
||||
|
||||
@Serializable
|
||||
sealed class GroupLinkPlan {
|
||||
@Serializable @SerialName("ok") object Ok: GroupLinkPlan()
|
||||
@Serializable @SerialName("ok") class Ok(val groupSLinkData_: GroupShortLinkData? = null): 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()
|
||||
@@ -6516,9 +6618,11 @@ enum class RatchetSyncState {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class UserContactLinkRec(val connLinkContact: CreatedConnLink, val autoAccept: AutoAccept? = null) {
|
||||
val responseDetails: String get() = "connLinkContact: ${connLinkContact}\nautoAccept: ${AutoAccept.cmdString(autoAccept)}"
|
||||
}
|
||||
data class UserContactLinkRec(
|
||||
val connLinkContact: CreatedConnLink,
|
||||
val shortLinkDataSet: Boolean,
|
||||
val autoAccept: AutoAccept? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class AutoAccept(val businessAddress: Boolean, val acceptIncognito: Boolean, val autoReply: MsgContent?) {
|
||||
@@ -6537,6 +6641,15 @@ class AutoAccept(val businessAddress: Boolean, val acceptIncognito: Boolean, val
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class GroupLink(
|
||||
val userContactLinkId: Long,
|
||||
val connLinkContact: CreatedConnLink,
|
||||
val shortLinkDataSet: Boolean,
|
||||
val groupLinkId: String,
|
||||
val acceptMemberRole: GroupMemberRole
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CoreVersionInfo(
|
||||
val version: String,
|
||||
@@ -6621,6 +6734,7 @@ sealed class ChatErrorType {
|
||||
is InvalidConnReq -> "invalidConnReq"
|
||||
is UnsupportedConnReq -> "unsupportedConnReq"
|
||||
is InvalidChatMessage -> "invalidChatMessage"
|
||||
is ConnReqMessageProhibited -> "connReqMessageProhibited"
|
||||
is ContactNotReady -> "contactNotReady"
|
||||
is ContactNotActive -> "contactNotActive"
|
||||
is ContactDisabled -> "contactDisabled"
|
||||
@@ -6636,6 +6750,7 @@ sealed class ChatErrorType {
|
||||
is GroupMemberNotActive -> "groupMemberNotActive"
|
||||
is GroupMemberUserRemoved -> "groupMemberUserRemoved"
|
||||
is GroupMemberNotFound -> "groupMemberNotFound"
|
||||
is GroupHostMemberNotFound -> "groupHostMemberNotFound"
|
||||
is GroupCantResendInvitation -> "groupCantResendInvitation"
|
||||
is GroupInternal -> "groupInternal"
|
||||
is FileNotFound -> "fileNotFound"
|
||||
@@ -6700,6 +6815,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("invalidConnReq") object InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("unsupportedConnReq") object UnsupportedConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("invalidChatMessage") class InvalidChatMessage(val connection: Connection, val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("connReqMessageProhibited") object ConnReqMessageProhibited: ChatErrorType()
|
||||
@Serializable @SerialName("contactNotReady") class ContactNotReady(val contact: Contact): ChatErrorType()
|
||||
@Serializable @SerialName("contactNotActive") class ContactNotActive(val contact: Contact): ChatErrorType()
|
||||
@Serializable @SerialName("contactDisabled") class ContactDisabled(val contact: Contact): ChatErrorType()
|
||||
@@ -6715,6 +6831,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("groupMemberNotActive") object GroupMemberNotActive: ChatErrorType()
|
||||
@Serializable @SerialName("groupMemberUserRemoved") object GroupMemberUserRemoved: ChatErrorType()
|
||||
@Serializable @SerialName("groupMemberNotFound") object GroupMemberNotFound: ChatErrorType()
|
||||
@Serializable @SerialName("groupHostMemberNotFound") class GroupHostMemberNotFound(val groupId: Long): ChatErrorType()
|
||||
@Serializable @SerialName("groupCantResendInvitation") class GroupCantResendInvitation(val groupInfo: GroupInfo, val contactName: String): ChatErrorType()
|
||||
@Serializable @SerialName("groupInternal") class GroupInternal(val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("fileNotFound") class FileNotFound(val message: String): ChatErrorType()
|
||||
|
||||
+3
-10
@@ -10,8 +10,7 @@ import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
enum class NotificationAction {
|
||||
ACCEPT_CONTACT_REQUEST,
|
||||
ACCEPT_CONTACT_REQUEST_INCOGNITO
|
||||
ACCEPT_CONTACT_REQUEST
|
||||
}
|
||||
|
||||
lateinit var ntfManager: NtfManager
|
||||
@@ -31,8 +30,7 @@ abstract class NtfManager {
|
||||
msgText = generalGetString(MR.strings.notification_new_contact_request),
|
||||
image = cInfo.image,
|
||||
listOf(
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST to { acceptContactRequestAction(user.userId, incognito = false, cInfo.id) },
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST_INCOGNITO to { acceptContactRequestAction(user.userId, incognito = true, cInfo.id) }
|
||||
NotificationAction.ACCEPT_CONTACT_REQUEST to { acceptContactRequestAction(user.userId, incognito = false, cInfo.id) }
|
||||
)
|
||||
)
|
||||
|
||||
@@ -51,14 +49,9 @@ abstract class NtfManager {
|
||||
|
||||
fun acceptContactRequestAction(userId: Long?, incognito: Boolean, chatId: ChatId) {
|
||||
val isCurrentUser = ChatModel.currentUser.value?.userId == userId
|
||||
val cInfo: ChatInfo.ContactRequest? = if (isCurrentUser) {
|
||||
(ChatModel.getChat(chatId)?.chatInfo as? ChatInfo.ContactRequest) ?: return
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val apiId = chatId.replace("<@", "").toLongOrNull() ?: return
|
||||
// TODO include remote host in notification
|
||||
acceptContactRequest(null, incognito, apiId, cInfo, isCurrentUser, ChatModel)
|
||||
acceptContactRequest(null, incognito, apiId, isCurrentUser, ChatModel)
|
||||
cancelNotificationsForChat(chatId)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -934,7 +934,7 @@ fun CallButton(
|
||||
}
|
||||
}
|
||||
} }
|
||||
contact.nextSendGrpInv -> { { showCantCallContactSendMessageAlert() } }
|
||||
contact.sendMsgToConnect -> { { showCantCallContactSendMessageAlert() } }
|
||||
!contact.active -> { { showCantCallContactDeletedAlert() } }
|
||||
!contact.ready -> { { showCantCallContactConnectingAlert() } }
|
||||
needToAllowCallsToContact -> { { showNeedToAllowCallsAlert(onConfirm = { allowCallsToContact(chat) }) } }
|
||||
|
||||
+6
-5
@@ -181,7 +181,8 @@ fun ChatView(
|
||||
chatInfo is ChatInfo.Direct
|
||||
&& !chatInfo.contact.sndReady
|
||||
&& chatInfo.contact.active
|
||||
&& !chatInfo.contact.nextSendGrpInv
|
||||
&& !chatInfo.contact.sendMsgToConnect
|
||||
&& !chatInfo.contact.nextAcceptContactRequest
|
||||
) {
|
||||
Text(
|
||||
generalGetString(MR.strings.contact_connection_pending),
|
||||
@@ -287,7 +288,7 @@ fun ChatView(
|
||||
// The idea is to preload information before showing a modal because large groups can take time to load all members
|
||||
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
|
||||
var preloadedCode: String? = null
|
||||
var preloadedLink: Pair<CreatedConnLink, GroupMemberRole>? = null
|
||||
var preloadedLink: GroupLink? = null
|
||||
if (chatInfo is ChatInfo.Direct) {
|
||||
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
|
||||
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
|
||||
@@ -315,13 +316,13 @@ fun ChatView(
|
||||
showSearch.value = true
|
||||
}
|
||||
} else if (chatInfo is ChatInfo.Group) {
|
||||
var link: Pair<CreatedConnLink, GroupMemberRole>? by remember(chatInfo.id) { mutableStateOf(preloadedLink) }
|
||||
var link: GroupLink? by remember(chatInfo.id) { mutableStateOf(preloadedLink) }
|
||||
KeyChangeEffect(chatInfo.id) {
|
||||
setGroupMembers(chatRh, chatInfo.groupInfo, chatModel)
|
||||
link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId)
|
||||
preloadedLink = link
|
||||
}
|
||||
GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link?.first, link?.second, selectedItems, appBar, scrollToItemId, {
|
||||
GroupChatInfoView(chatsCtx, chatRh, chatInfo.id, link, selectedItems, appBar, scrollToItemId, {
|
||||
link = it
|
||||
preloadedLink = it
|
||||
}, close, { showSearch.value = true })
|
||||
@@ -2509,7 +2510,7 @@ fun openGroupLink(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (
|
||||
val link = chatModel.controller.apiGetGroupLink(rhId, groupInfo.groupId)
|
||||
close?.invoke()
|
||||
ModalManager.end.showModalCloseable(true) {
|
||||
GroupLinkView(chatModel, rhId, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null)
|
||||
GroupLinkView(chatModel, rhId, groupInfo, link, onGroupLinkUpdated = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package chat.simplex.common.views.chat
|
||||
|
||||
import SectionItemView
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.common.views.chatlist.acceptContactRequest
|
||||
import chat.simplex.common.views.chatlist.rejectContactRequest
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
fun ComposeContextContactRequestActionsView(
|
||||
rhId: Long?,
|
||||
contactRequestId: Long
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.height(60.dp)
|
||||
.background(MaterialTheme.colors.surface)
|
||||
) {
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.weight(1F)
|
||||
.clickable {
|
||||
showRejectRequestAlert(rhId, contactRequestId)
|
||||
},
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(MR.strings.reject_contact_button), color = Color.Red)
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.weight(1F)
|
||||
.clickable {
|
||||
showAcceptRequestAlert(rhId, contactRequestId)
|
||||
},
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(MR.strings.accept_contact_button), color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showRejectRequestAlert(rhId: Long?, contactRequestId: Long) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.reject_contact_request),
|
||||
text = generalGetString(MR.strings.the_sender_will_not_be_notified),
|
||||
confirmText = generalGetString(MR.strings.reject_contact_button),
|
||||
onConfirm = {
|
||||
AlertManager.shared.hideAlert()
|
||||
rejectContactRequest(rhId, contactRequestId, chatModel, dismissToChatList = true)
|
||||
},
|
||||
destructive = true,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
}
|
||||
|
||||
fun showAcceptRequestAlert(rhId: Long?, contactRequestId: Long) {
|
||||
// Show 2 buttons in a row
|
||||
if (chatModel.addressShortLinkDataSet) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.accept_contact_request),
|
||||
confirmText = generalGetString(MR.strings.accept_contact_button),
|
||||
onConfirm = {
|
||||
AlertManager.shared.hideAlert()
|
||||
acceptContactRequest(rhId, incognito = false, contactRequestId, isCurrentUser = true, chatModel)
|
||||
},
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
// Show 3 buttons in a column
|
||||
} else {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(MR.strings.accept_contact_request),
|
||||
buttons = {
|
||||
Column {
|
||||
// Accept
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
acceptContactRequest(rhId, incognito = false, contactRequestId, isCurrentUser = true, chatModel)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.accept_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
// Accept incognito
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
acceptContactRequest(rhId, incognito = true, contactRequestId, isCurrentUser = true, chatModel)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
// Cancel
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
},
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
}
|
||||
}
|
||||
+97
-27
@@ -245,6 +245,7 @@ fun chatItemPreview(chatItem: ChatItem): ComposePreview {
|
||||
is MsgContent.MCVoice -> ComposePreview.VoicePreview(voice = fileName, mc.duration / 1000, true)
|
||||
is MsgContent.MCFile -> ComposePreview.FilePreview(fileName, getAppFileUri(fileName))
|
||||
is MsgContent.MCReport -> ComposePreview.NoPreview
|
||||
is MsgContent.MCChat -> ComposePreview.NoPreview
|
||||
is MsgContent.MCUnknown, null -> ComposePreview.NoPreview
|
||||
}
|
||||
}
|
||||
@@ -485,6 +486,80 @@ fun ComposeView(
|
||||
return null
|
||||
}
|
||||
|
||||
fun checkLinkPreview(): MsgContent {
|
||||
val msgText = composeState.value.message.text
|
||||
return when (val composePreview = composeState.value.preview) {
|
||||
is ComposePreview.CLinkPreview -> {
|
||||
val parsedMsg = parseToMarkdown(msgText)
|
||||
val url = getSimplexLink(parsedMsg).first
|
||||
val lp = composePreview.linkPreview
|
||||
if (lp != null && url == lp.uri) {
|
||||
MsgContent.MCLink(msgText, preview = lp)
|
||||
} else {
|
||||
MsgContent.MCText(msgText)
|
||||
}
|
||||
}
|
||||
|
||||
else -> MsgContent.MCText(msgText)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendMemberContactInvitation() {
|
||||
val mc = checkLinkPreview()
|
||||
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc)
|
||||
if (contact != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||
clearState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendConnectPreparedContact() {
|
||||
val mc = checkLinkPreview()
|
||||
// TODO [short links] use incognito default (incognito choice will be available via context profile picker)
|
||||
val contact = chatModel.controller.apiConnectPreparedContact(chat.remoteHostId, chat.chatInfo.apiId, incognito = false, msg = mc)
|
||||
if (contact != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||
clearState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun connectPreparedGroup() {
|
||||
// TODO [short links] use incognito default (incognito choice will be available via context profile picker)
|
||||
val groupInfo = chatModel.controller.apiConnectPreparedGroup(chat.remoteHostId, chat.chatInfo.apiId, incognito = false)
|
||||
if (groupInfo != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatsCtx.updateGroup(chat.remoteHostId, groupInfo)
|
||||
clearState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [short links] next connect button design, rework compose to not show send button, align with Swift
|
||||
@Composable
|
||||
fun NextConnectPreparedButton() {
|
||||
TextButton(onClick = {
|
||||
withBGApi {
|
||||
if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextSendGrpInv) {
|
||||
sendMemberContactInvitation()
|
||||
} else if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextConnectPrepared) {
|
||||
sendConnectPreparedContact()
|
||||
} else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared) {
|
||||
connectPreparedGroup()
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if (chat.chatInfo is ChatInfo.Group) {
|
||||
Text("Join")
|
||||
} else {
|
||||
Text("Connect")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): List<ChatItem>? {
|
||||
val cInfo = chat.chatInfo
|
||||
val cs = composeState.value
|
||||
@@ -525,23 +600,6 @@ fun ComposeView(
|
||||
return chatItems
|
||||
}
|
||||
|
||||
fun checkLinkPreview(): MsgContent {
|
||||
return when (val composePreview = cs.preview) {
|
||||
is ComposePreview.CLinkPreview -> {
|
||||
val parsedMsg = parseToMarkdown(msgText)
|
||||
val url = getSimplexLink(parsedMsg).first
|
||||
val lp = composePreview.linkPreview
|
||||
if (lp != null && url == lp.uri) {
|
||||
MsgContent.MCLink(msgText, preview = lp)
|
||||
} else {
|
||||
MsgContent.MCText(msgText)
|
||||
}
|
||||
}
|
||||
|
||||
else -> MsgContent.MCText(msgText)
|
||||
}
|
||||
}
|
||||
|
||||
fun constructFailedMessage(cs: ComposeState): ComposeState {
|
||||
val preview = when (cs.preview) {
|
||||
is ComposePreview.MediaPreview -> {
|
||||
@@ -564,6 +622,8 @@ fun ComposeView(
|
||||
is MsgContent.MCVoice -> MsgContent.MCVoice(msgText, duration = msgContent.duration)
|
||||
is MsgContent.MCFile -> MsgContent.MCFile(msgText)
|
||||
is MsgContent.MCReport -> MsgContent.MCReport(msgText, reason = msgContent.reason)
|
||||
// TODO [short links] update chat link
|
||||
is MsgContent.MCChat -> MsgContent.MCChat(msgText, chatLink = msgContent.chatLink)
|
||||
is MsgContent.MCUnknown -> MsgContent.MCUnknown(type = msgContent.type, text = msgText, json = msgContent.json)
|
||||
}
|
||||
}
|
||||
@@ -586,16 +646,6 @@ fun ComposeView(
|
||||
return cItems?.map { it.chatItem }
|
||||
}
|
||||
|
||||
suspend fun sendMemberContactInvitation() {
|
||||
val mc = checkLinkPreview()
|
||||
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc)
|
||||
if (contact != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateMessage(ei: ChatItem, chat: Chat, live: Boolean): ChatItem? {
|
||||
val cInfo = chat.chatInfo
|
||||
val oldMsgContent = ei.content.msgContent
|
||||
@@ -1015,6 +1065,23 @@ fun ComposeView(
|
||||
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
|
||||
|
||||
Column {
|
||||
// TODO [short links] move button to the right of send field, rework SendMsgView to not show send button, align with Swift
|
||||
if (chat.chatInfo.nextConnect) {
|
||||
NextConnectPreparedButton()
|
||||
}
|
||||
// TODO ^^^ (this shouldn't be here)
|
||||
|
||||
if (
|
||||
chat.chatInfo is ChatInfo.Direct
|
||||
&& chat.chatInfo.contact.nextAcceptContactRequest
|
||||
&& chat.chatInfo.contact.contactRequestId != null
|
||||
) {
|
||||
ComposeContextContactRequestActionsView(
|
||||
rhId = rhId,
|
||||
contactRequestId = chat.chatInfo.contact.contactRequestId
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
chat.chatInfo is ChatInfo.Group
|
||||
&& chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext
|
||||
@@ -1030,13 +1097,16 @@ fun ComposeView(
|
||||
member = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_
|
||||
)
|
||||
}
|
||||
|
||||
if (nextSendGrpInv.value) {
|
||||
ComposeContextInvitingContactMemberView()
|
||||
}
|
||||
|
||||
val ctx = composeState.value.contextItem
|
||||
if (ctx is ComposeContextItem.ReportedItem) {
|
||||
ReportReasonView(ctx.reason)
|
||||
}
|
||||
|
||||
val simplexLinkProhibited = chatsCtx.secondaryContextFilter == null && hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
|
||||
val fileProhibited = chatsCtx.secondaryContextFilter == null && composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files)
|
||||
val voiceProhibited = composeState.value.preview is ComposePreview.VoicePreview && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List<Contact> {
|
||||
.filterIsInstance<ChatInfo.Direct>()
|
||||
.filter { it.sendMsgEnabled }
|
||||
.map { it.contact }
|
||||
.filter { c -> !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
|
||||
.filter { c -> !c.sendMsgToConnect && c.contactId !in memberContactIds && c.anyNameContains(s)
|
||||
}
|
||||
.sortedBy { it.displayName.lowercase() }
|
||||
.toList()
|
||||
|
||||
+49
-46
@@ -54,12 +54,11 @@ fun ModalData.GroupChatInfoView(
|
||||
chatsCtx: ChatModel.ChatsContext,
|
||||
rhId: Long?,
|
||||
chatId: String,
|
||||
groupLink: CreatedConnLink?,
|
||||
groupLinkMemberRole: GroupMemberRole?,
|
||||
groupLink: GroupLink?,
|
||||
selectedItems: MutableState<Set<Long>?>,
|
||||
appBar: MutableState<@Composable (BoxScope.() -> Unit)?>,
|
||||
scrollToItemId: MutableState<Long?>,
|
||||
onGroupLinkUpdated: (Pair<CreatedConnLink, GroupMemberRole>?) -> Unit,
|
||||
onGroupLinkUpdated: (GroupLink?) -> Unit,
|
||||
close: () -> Unit,
|
||||
onSearchClicked: () -> Unit
|
||||
) {
|
||||
@@ -166,7 +165,7 @@ fun ModalData.GroupChatInfoView(
|
||||
clearChat = { clearChatDialog(chat, close) },
|
||||
leaveGroup = { leaveGroupDialog(rhId, groupInfo, chatModel, close) },
|
||||
manageGroupLink = {
|
||||
ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) }
|
||||
ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, onGroupLinkUpdated) }
|
||||
},
|
||||
onSearchClicked = onSearchClicked,
|
||||
deletingItems = deletingItems
|
||||
@@ -375,7 +374,7 @@ fun ModalData.GroupChatInfoLayout(
|
||||
activeSortedMembers: List<GroupMember>,
|
||||
developerTools: Boolean,
|
||||
onLocalAliasChanged: (String) -> Unit,
|
||||
groupLink: CreatedConnLink?,
|
||||
groupLink: GroupLink?,
|
||||
selectedItems: MutableState<Set<Long>?>,
|
||||
appBar: MutableState<@Composable (BoxScope.() -> Unit)?>,
|
||||
scrollToItemId: MutableState<Long?>,
|
||||
@@ -537,53 +536,57 @@ fun ModalData.GroupChatInfoLayout(
|
||||
}
|
||||
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = true)
|
||||
|
||||
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) {
|
||||
if (groupInfo.canAddMembers) {
|
||||
val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers
|
||||
val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
|
||||
val addMembersTitleId = when (groupInfo.businessChat?.chatType) {
|
||||
BusinessChatType.Customer -> MR.strings.button_add_team_members
|
||||
BusinessChatType.Business -> MR.strings.button_add_friends
|
||||
null -> MR.strings.button_add_members
|
||||
if (!groupInfo.nextConnectPrepared) {
|
||||
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) {
|
||||
if (groupInfo.canAddMembers) {
|
||||
val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers
|
||||
val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
|
||||
val addMembersTitleId = when (groupInfo.businessChat?.chatType) {
|
||||
BusinessChatType.Customer -> MR.strings.button_add_team_members
|
||||
BusinessChatType.Business -> MR.strings.button_add_friends
|
||||
null -> MR.strings.button_add_members
|
||||
}
|
||||
AddMembersButton(addMembersTitleId, tint, onAddMembersClick)
|
||||
}
|
||||
AddMembersButton(addMembersTitleId, tint, onAddMembersClick)
|
||||
}
|
||||
if (activeSortedMembers.size > 8) {
|
||||
SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) {
|
||||
MemberListSearchRowView(searchText)
|
||||
if (activeSortedMembers.size > 8) {
|
||||
SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) {
|
||||
MemberListSearchRowView(searchText)
|
||||
}
|
||||
}
|
||||
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
||||
MemberRow(groupInfo.membership, user = true)
|
||||
}
|
||||
}
|
||||
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
|
||||
MemberRow(groupInfo.membership, user = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
items(filteredMembers.value, key = { it.groupMemberId }) { member ->
|
||||
Divider()
|
||||
val showMenu = remember { mutableStateOf(false) }
|
||||
val canBeSelected = groupInfo.membership.memberRole >= member.memberRole && member.memberRole < GroupMemberRole.Moderator
|
||||
SectionItemViewLongClickable(
|
||||
click = {
|
||||
if (selectedItems.value != null) {
|
||||
if (canBeSelected) {
|
||||
toggleItemSelection(member.groupMemberId, selectedItems)
|
||||
if (!groupInfo.nextConnectPrepared) {
|
||||
items(filteredMembers.value, key = { it.groupMemberId }) { member ->
|
||||
Divider()
|
||||
val showMenu = remember { mutableStateOf(false) }
|
||||
val canBeSelected = groupInfo.membership.memberRole >= member.memberRole && member.memberRole < GroupMemberRole.Moderator
|
||||
SectionItemViewLongClickable(
|
||||
click = {
|
||||
if (selectedItems.value != null) {
|
||||
if (canBeSelected) {
|
||||
toggleItemSelection(member.groupMemberId, selectedItems)
|
||||
}
|
||||
} else {
|
||||
showMemberInfo(member)
|
||||
}
|
||||
},
|
||||
longClick = { showMenu.value = true },
|
||||
minHeight = 54.dp,
|
||||
padding = PaddingValues(horizontal = DEFAULT_PADDING)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.CenterStart) {
|
||||
androidx.compose.animation.AnimatedVisibility(selectedItems.value != null, enter = fadeIn(), exit = fadeOut()) {
|
||||
SelectedListItem(Modifier.alpha(if (canBeSelected) 1f else 0f).padding(start = 2.dp), member.groupMemberId, selectedItems)
|
||||
}
|
||||
val selectionOffset by animateDpAsState(if (selectedItems.value != null) 20.dp + 22.dp * fontSizeMultiplier else 0.dp)
|
||||
DropDownMenuForMember(chat.remoteHostId, member, groupInfo, selectedItems, showMenu)
|
||||
Box(Modifier.padding(start = selectionOffset)) {
|
||||
MemberRow(member)
|
||||
}
|
||||
} else {
|
||||
showMemberInfo(member)
|
||||
}
|
||||
},
|
||||
longClick = { showMenu.value = true },
|
||||
minHeight = 54.dp,
|
||||
padding = PaddingValues(horizontal = DEFAULT_PADDING)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.CenterStart) {
|
||||
androidx.compose.animation.AnimatedVisibility(selectedItems.value != null, enter = fadeIn(), exit = fadeOut()) {
|
||||
SelectedListItem(Modifier.alpha(if (canBeSelected) 1f else 0f).padding(start = 2.dp), member.groupMemberId, selectedItems)
|
||||
}
|
||||
val selectionOffset by animateDpAsState(if (selectedItems.value != null) 20.dp + 22.dp * fontSizeMultiplier else 0.dp)
|
||||
DropDownMenuForMember(chat.remoteHostId, member, groupInfo, selectedItems, showMenu)
|
||||
Box(Modifier.padding(start = selectionOffset)) {
|
||||
MemberRow(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-22
@@ -28,22 +28,21 @@ fun GroupLinkView(
|
||||
chatModel: ChatModel,
|
||||
rhId: Long?,
|
||||
groupInfo: GroupInfo,
|
||||
connLinkContact: CreatedConnLink?,
|
||||
memberRole: GroupMemberRole?,
|
||||
onGroupLinkUpdated: ((Pair<CreatedConnLink, GroupMemberRole>?) -> Unit)?,
|
||||
groupLink: GroupLink?,
|
||||
onGroupLinkUpdated: ((GroupLink?) -> Unit)?,
|
||||
creatingGroup: Boolean = false,
|
||||
close: (() -> Unit)? = null
|
||||
) {
|
||||
var groupLink by rememberSaveable(stateSaver = CreatedConnLink.nullableStateSaver) { mutableStateOf(connLinkContact) }
|
||||
val groupLinkMemberRole = rememberSaveable { mutableStateOf(memberRole) }
|
||||
var groupLinkVar by rememberSaveable { mutableStateOf(groupLink) }
|
||||
val groupLinkMemberRole = rememberSaveable { mutableStateOf(groupLink?.acceptMemberRole) }
|
||||
var creatingLink by rememberSaveable { mutableStateOf(false) }
|
||||
fun createLink() {
|
||||
creatingLink = true
|
||||
withBGApi {
|
||||
val link = chatModel.controller.apiCreateGroupLink(rhId, groupInfo.groupId)
|
||||
if (link != null) {
|
||||
groupLink = link.first
|
||||
groupLinkMemberRole.value = link.second
|
||||
groupLinkVar = link
|
||||
groupLinkMemberRole.value = link.acceptMemberRole
|
||||
onGroupLinkUpdated?.invoke(link)
|
||||
}
|
||||
creatingLink = false
|
||||
@@ -54,8 +53,8 @@ fun GroupLinkView(
|
||||
withBGApi {
|
||||
val link = chatModel.controller.apiAddGroupShortLink(rhId, groupInfo.groupId)
|
||||
if (link != null) {
|
||||
groupLink = link.first
|
||||
groupLinkMemberRole.value = link.second
|
||||
groupLinkVar = link
|
||||
groupLinkMemberRole.value = link.acceptMemberRole
|
||||
onGroupLinkUpdated?.invoke(link)
|
||||
}
|
||||
creatingLink = false
|
||||
@@ -67,7 +66,7 @@ fun GroupLinkView(
|
||||
}
|
||||
}
|
||||
GroupLinkLayout(
|
||||
groupLink = groupLink,
|
||||
groupLink = groupLinkVar,
|
||||
groupInfo,
|
||||
groupLinkMemberRole,
|
||||
creatingLink,
|
||||
@@ -79,8 +78,8 @@ fun GroupLinkView(
|
||||
withBGApi {
|
||||
val link = chatModel.controller.apiGroupLinkMemberRole(rhId, groupInfo.groupId, role)
|
||||
if (link != null) {
|
||||
groupLink = link.first
|
||||
groupLinkMemberRole.value = link.second
|
||||
groupLinkVar = link
|
||||
groupLinkMemberRole.value = link.acceptMemberRole
|
||||
onGroupLinkUpdated?.invoke(link)
|
||||
}
|
||||
}
|
||||
@@ -95,7 +94,7 @@ fun GroupLinkView(
|
||||
withBGApi {
|
||||
val r = chatModel.controller.apiDeleteGroupLink(rhId, groupInfo.groupId)
|
||||
if (r) {
|
||||
groupLink = null
|
||||
groupLinkVar = null
|
||||
onGroupLinkUpdated?.invoke(null)
|
||||
}
|
||||
}
|
||||
@@ -113,7 +112,7 @@ fun GroupLinkView(
|
||||
|
||||
@Composable
|
||||
fun GroupLinkLayout(
|
||||
groupLink: CreatedConnLink?,
|
||||
groupLink: GroupLink?,
|
||||
groupInfo: GroupInfo,
|
||||
groupLinkMemberRole: MutableState<GroupMemberRole?>,
|
||||
creatingLink: Boolean,
|
||||
@@ -167,11 +166,11 @@ fun GroupLinkLayout(
|
||||
}
|
||||
val showShortLink = remember { mutableStateOf(true) }
|
||||
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
||||
if (groupLink.connShortLink == null) {
|
||||
SimpleXCreatedLinkQRCode(groupLink, short = false)
|
||||
if (groupLink.connLinkContact.connShortLink == null) {
|
||||
SimpleXCreatedLinkQRCode(groupLink.connLinkContact, short = false)
|
||||
} else {
|
||||
SectionViewWithButton(titleButton = { ToggleShortLinkButton(showShortLink) }) {
|
||||
SimpleXCreatedLinkQRCode(groupLink, short = showShortLink.value)
|
||||
SimpleXCreatedLinkQRCode(groupLink.connLinkContact, short = showShortLink.value)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
@@ -183,7 +182,7 @@ fun GroupLinkLayout(
|
||||
SimpleButton(
|
||||
stringResource(MR.strings.share_link),
|
||||
icon = painterResource(MR.images.ic_share),
|
||||
click = { clipboard.shareText(groupLink.simplexChatUri(short = showShortLink.value)) }
|
||||
click = { clipboard.shareText(groupLink.connLinkContact.simplexChatUri(short = showShortLink.value)) }
|
||||
)
|
||||
if (creatingGroup && close != null) {
|
||||
ContinueButton(close)
|
||||
@@ -196,8 +195,10 @@ fun GroupLinkLayout(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (groupLink.connShortLink == null) {
|
||||
AddShortLinkButton(addShortLink)
|
||||
if (groupLink.connLinkContact.connShortLink == null) {
|
||||
AddShortLinkButton(text = stringResource(MR.strings.add_short_link), addShortLink)
|
||||
} else if (!groupLink.shortLinkDataSet) {
|
||||
AddShortLinkButton(text = stringResource(MR.strings.share_group_profile_via_link), addShortLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,10 +207,10 @@ fun GroupLinkLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddShortLinkButton(onClick: () -> Unit) {
|
||||
private fun AddShortLinkButton(text: String, onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_add),
|
||||
stringResource(MR.strings.add_short_link),
|
||||
text,
|
||||
onClick,
|
||||
iconColor = MaterialTheme.colors.primary,
|
||||
textColor = MaterialTheme.colors.primary,
|
||||
|
||||
+1
-1
@@ -788,7 +788,7 @@ fun updateMembersRoleDialog(
|
||||
fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) {
|
||||
try {
|
||||
withBGApi {
|
||||
planAndConnect(rhId, connReqUri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
|
||||
planAndConnect(rhId, connReqUri, close = { ModalManager.closeAllModalsEverywhere() })
|
||||
}
|
||||
} catch (e: RuntimeException) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
|
||||
+15
-10
@@ -33,6 +33,7 @@ import chat.simplex.common.views.chatlist.openChat
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.ImageResource
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.math.*
|
||||
|
||||
@@ -667,26 +668,30 @@ fun ChatItemView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun E2EEInfoNoPQText() {
|
||||
fun e2eeInfoText(sId: StringResource) {
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(chatEventStyle) { append(annotatedStringResource(MR.strings.e2ee_info_no_pq)) }
|
||||
withStyle(chatEventStyle) { append(annotatedStringResource(sId)) }
|
||||
},
|
||||
Modifier.padding(horizontal = 6.dp, vertical = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun E2EEInfoNoPQText() {
|
||||
e2eeInfoText(MR.strings.e2ee_info_no_pq)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
if (e2EEInfo.pqEnabled) {
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
withStyle(chatEventStyle) { append(annotatedStringResource(MR.strings.e2ee_info_pq)) }
|
||||
},
|
||||
Modifier.padding(horizontal = 6.dp, vertical = 6.dp)
|
||||
)
|
||||
if (e2EEInfo.pqEnabled != null) {
|
||||
if (e2EEInfo.pqEnabled) {
|
||||
e2eeInfoText(MR.strings.e2ee_info_pq)
|
||||
} else {
|
||||
E2EEInfoNoPQText()
|
||||
}
|
||||
} else {
|
||||
E2EEInfoNoPQText()
|
||||
e2eeInfoText(MR.strings.e2ee_info_e2ee)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+56
-37
@@ -132,7 +132,7 @@ fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
|
||||
click = { contactRequestAlertDialog(chat.remoteHostId, chat.chatInfo, chatModel) { onRequestAccepted(it) } },
|
||||
dropdownMenuItems = {
|
||||
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
|
||||
ContactRequestMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu)
|
||||
ContactRequestMenuItems(chat.remoteHostId, contactRequestId = chat.chatInfo.apiId, chatModel, showMenu)
|
||||
}
|
||||
},
|
||||
showMenu,
|
||||
@@ -271,18 +271,22 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo
|
||||
|
||||
@Composable
|
||||
fun ContactMenuItems(chat: Chat, contact: Contact, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
|
||||
if (contact.activeConn != null) {
|
||||
if (showMarkRead) {
|
||||
MarkReadChatAction(chat, showMenu)
|
||||
} else {
|
||||
MarkUnreadChatAction(chat, chatModel, showMenu)
|
||||
if (contact.nextAcceptContactRequest && contact.contactRequestId != null) {
|
||||
ContactRequestMenuItems(chat.remoteHostId, contactRequestId = contact.contactRequestId, chatModel, showMenu)
|
||||
} else {
|
||||
if (contact.activeConn != null) {
|
||||
if (showMarkRead) {
|
||||
MarkReadChatAction(chat, showMenu)
|
||||
} else {
|
||||
MarkUnreadChatAction(chat, chatModel, showMenu)
|
||||
}
|
||||
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
|
||||
ToggleNotificationsChatAction(chat, chatModel, contact.chatSettings.enableNtfs.nextMode(false), showMenu)
|
||||
TagListAction(chat, showMenu)
|
||||
ClearChatAction(chat, showMenu)
|
||||
}
|
||||
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
|
||||
ToggleNotificationsChatAction(chat, chatModel, contact.chatSettings.enableNtfs.nextMode(false), showMenu)
|
||||
TagListAction(chat, showMenu)
|
||||
ClearChatAction(chat, showMenu)
|
||||
DeleteContactAction(chat, chatModel, showMenu)
|
||||
}
|
||||
DeleteContactAction(chat, chatModel, showMenu)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -509,30 +513,32 @@ fun LeaveGroupAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, sh
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContactRequestMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactRequest, chatModel: ChatModel, showMenu: MutableState<Boolean>, onSuccess: ((chat: Chat) -> Unit)? = null) {
|
||||
fun ContactRequestMenuItems(rhId: Long?, contactRequestId: Long, chatModel: ChatModel, showMenu: MutableState<Boolean>, onSuccess: ((chat: Chat) -> Unit)? = null) {
|
||||
ItemAction(
|
||||
stringResource(MR.strings.accept_contact_button),
|
||||
painterResource(MR.images.ic_check),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
onClick = {
|
||||
acceptContactRequest(rhId, incognito = false, chatInfo.apiId, chatInfo, true, chatModel, onSuccess)
|
||||
showMenu.value = false
|
||||
}
|
||||
)
|
||||
ItemAction(
|
||||
stringResource(MR.strings.accept_contact_incognito_button),
|
||||
painterResource(MR.images.ic_theater_comedy),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
onClick = {
|
||||
acceptContactRequest(rhId, incognito = true, chatInfo.apiId, chatInfo, true, chatModel, onSuccess)
|
||||
acceptContactRequest(rhId, incognito = false, contactRequestId, true, chatModel, onSuccess)
|
||||
showMenu.value = false
|
||||
}
|
||||
)
|
||||
if (!chatModel.addressShortLinkDataSet) {
|
||||
ItemAction(
|
||||
stringResource(MR.strings.accept_contact_incognito_button),
|
||||
painterResource(MR.images.ic_theater_comedy),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
onClick = {
|
||||
acceptContactRequest(rhId, incognito = true, contactRequestId, true, chatModel, onSuccess)
|
||||
showMenu.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
ItemAction(
|
||||
stringResource(MR.strings.reject_contact_button),
|
||||
painterResource(MR.images.ic_close),
|
||||
onClick = {
|
||||
rejectContactRequest(rhId, chatInfo, chatModel)
|
||||
rejectContactRequest(rhId, contactRequestId, chatModel)
|
||||
showMenu.value = false
|
||||
},
|
||||
color = Color.Red
|
||||
@@ -665,19 +671,21 @@ fun contactRequestAlertDialog(rhId: Long?, contactRequest: ChatInfo.ContactReque
|
||||
Column {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
acceptContactRequest(rhId, incognito = false, contactRequest.apiId, contactRequest, true, chatModel, onSucess)
|
||||
acceptContactRequest(rhId, incognito = false, contactRequest.apiId, true, chatModel, onSucess)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.accept_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
acceptContactRequest(rhId, incognito = true, contactRequest.apiId, contactRequest, true, chatModel, onSucess)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
if (!chatModel.addressShortLinkDataSet) {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
acceptContactRequest(rhId, incognito = true, contactRequest.apiId, true, chatModel, onSucess)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
rejectContactRequest(rhId, contactRequest, chatModel)
|
||||
rejectContactRequest(rhId, contactRequest.apiId, chatModel)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.reject_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red)
|
||||
}
|
||||
@@ -687,13 +695,17 @@ fun contactRequestAlertDialog(rhId: Long?, contactRequest: ChatInfo.ContactReque
|
||||
)
|
||||
}
|
||||
|
||||
fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel, close: ((chat: Chat) -> Unit)? = null ) {
|
||||
fun acceptContactRequest(rhId: Long?, incognito: Boolean, contactRequestId: Long, isCurrentUser: Boolean, chatModel: ChatModel, close: ((chat: Chat) -> Unit)? = null ) {
|
||||
withBGApi {
|
||||
val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId)
|
||||
if (contact != null && isCurrentUser && contactRequest != null) {
|
||||
val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, contactRequestId)
|
||||
if (contact != null && isCurrentUser) {
|
||||
val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf())
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.replaceChat(rhId, contactRequest.id, chat)
|
||||
if (contact.contactRequestId != null) { // means contact request was initially created with contact, so we don't need to replace it
|
||||
chatModel.chatsContext.updateContact(rhId, contact)
|
||||
} else {
|
||||
chatModel.chatsContext.replaceChat(rhId, contactRequestChatId(contactRequestId), chat)
|
||||
}
|
||||
}
|
||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||
close?.invoke(chat)
|
||||
@@ -701,11 +713,18 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe
|
||||
}
|
||||
}
|
||||
|
||||
fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
|
||||
fun rejectContactRequest(rhId: Long?, contactRequestId: Long, chatModel: ChatModel, dismissToChatList: Boolean = false) {
|
||||
withBGApi {
|
||||
chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId)
|
||||
val contact_ = chatModel.controller.apiRejectContactRequest(rhId, contactRequestId)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatsContext.removeChat(rhId, contactRequest.id)
|
||||
if (contact_ != null) { // means contact request was initially created with contact, so we need to remove contact chat
|
||||
chatModel.chatsContext.removeChat(rhId, contact_.id)
|
||||
} else {
|
||||
chatModel.chatsContext.removeChat(rhId, contactRequestChatId(contactRequestId))
|
||||
}
|
||||
if (dismissToChatList) {
|
||||
chatModel.chatId.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -590,7 +590,7 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: String, chatModel: ChatModel) {
|
||||
chatModel.appOpenUrl.value = rhId to uri
|
||||
} else {
|
||||
withBGApi {
|
||||
planAndConnect(rhId, uri, incognito = null, close = null)
|
||||
planAndConnect(rhId, uri, close = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -677,7 +677,6 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<
|
||||
planAndConnect(
|
||||
chatModel.remoteHostId(),
|
||||
link,
|
||||
incognito = null,
|
||||
filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id },
|
||||
filterKnownGroup = { searchChatFilteredBySimplexLink.value = it.id },
|
||||
close = null,
|
||||
|
||||
+21
-14
@@ -145,8 +145,11 @@ fun ChatPreviewView(
|
||||
chatPreviewTitleText(
|
||||
if (deleting)
|
||||
MaterialTheme.colors.secondary
|
||||
else
|
||||
else if (cInfo.contact.nextAcceptContactRequest) {
|
||||
MaterialTheme.colors.primary
|
||||
} else {
|
||||
Color.Unspecified
|
||||
}
|
||||
)
|
||||
}
|
||||
is ChatInfo.Group ->
|
||||
@@ -229,21 +232,25 @@ fun ChatPreviewView(
|
||||
is ChatInfo.Direct ->
|
||||
if (cInfo.contact.activeConn == null && cInfo.contact.profile.contactLink != null && cInfo.contact.active) {
|
||||
Text(stringResource(MR.strings.contact_tap_to_connect), color = MaterialTheme.colors.primary)
|
||||
} else if (!cInfo.contact.sndReady && cInfo.contact.activeConn != null) {
|
||||
if (cInfo.contact.nextSendGrpInv) {
|
||||
Text(stringResource(MR.strings.member_contact_send_direct_message), color = MaterialTheme.colors.secondary)
|
||||
} else if (cInfo.contact.active) {
|
||||
Text(stringResource(MR.strings.contact_connection_pending), color = MaterialTheme.colors.secondary)
|
||||
}
|
||||
} else if (cInfo.contact.nextAcceptContactRequest) {
|
||||
Text(stringResource(MR.strings.hold_or_open_to_connect))
|
||||
} else if (cInfo.contact.sendMsgToConnect) {
|
||||
Text(stringResource(MR.strings.member_contact_send_direct_message))
|
||||
} else if (!cInfo.contact.sndReady && cInfo.contact.activeConn != null && cInfo.contact.active) {
|
||||
Text(stringResource(MR.strings.contact_connection_pending), color = MaterialTheme.colors.secondary)
|
||||
}
|
||||
is ChatInfo.Group ->
|
||||
when (cInfo.groupInfo.membership.memberStatus) {
|
||||
GroupMemberStatus.MemRejected -> Text(stringResource(MR.strings.group_preview_rejected))
|
||||
GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo))
|
||||
GroupMemberStatus.MemAccepted -> Text(stringResource(MR.strings.group_connection_pending), color = MaterialTheme.colors.secondary)
|
||||
GroupMemberStatus.MemPendingReview, GroupMemberStatus.MemPendingApproval ->
|
||||
Text(stringResource(MR.strings.reviewed_by_admins), color = MaterialTheme.colors.secondary)
|
||||
else -> {}
|
||||
if (cInfo.groupInfo.nextConnectPrepared) {
|
||||
Text(stringResource(MR.strings.group_preview_open_to_join))
|
||||
} else {
|
||||
when (cInfo.groupInfo.membership.memberStatus) {
|
||||
GroupMemberStatus.MemRejected -> Text(stringResource(MR.strings.group_preview_rejected))
|
||||
GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo))
|
||||
GroupMemberStatus.MemAccepted -> Text(stringResource(MR.strings.group_connection_pending), color = MaterialTheme.colors.secondary)
|
||||
GroupMemberStatus.MemPendingReview, GroupMemberStatus.MemPendingApproval ->
|
||||
Text(stringResource(MR.strings.reviewed_by_admins), color = MaterialTheme.colors.secondary)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
+13
-9
@@ -53,13 +53,7 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>, showDel
|
||||
click = {
|
||||
hideKeyboard(view)
|
||||
when (contactType) {
|
||||
ContactType.RECENT -> {
|
||||
withApi {
|
||||
openChat(secondaryChatsCtx = null, rhId, chat.chatInfo)
|
||||
ModalManager.start.closeModals()
|
||||
}
|
||||
}
|
||||
ContactType.CHAT_DELETED -> {
|
||||
ContactType.RECENT, ContactType.CONTACT_WITH_REQUEST, ContactType.CHAT_DELETED -> {
|
||||
withApi {
|
||||
openChat(secondaryChatsCtx = null, rhId, chat.chatInfo)
|
||||
ModalManager.start.closeModals()
|
||||
@@ -79,7 +73,17 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>, showDel
|
||||
},
|
||||
dropdownMenuItems = {
|
||||
tryOrShowError("${chat.id}ContactListNavLinkDropdown", error = {}) {
|
||||
DeleteContactAction(chat, chatModel, showMenu)
|
||||
if (contactType == ContactType.CONTACT_WITH_REQUEST && chat.chatInfo.contact.contactRequestId != null) {
|
||||
ContactRequestMenuItems(
|
||||
rhId = chat.remoteHostId,
|
||||
contactRequestId = chat.chatInfo.contact.contactRequestId,
|
||||
chatModel = chatModel,
|
||||
showMenu = showMenu,
|
||||
onSuccess = { onRequestAccepted(it) }
|
||||
)
|
||||
} else {
|
||||
DeleteContactAction(chat, chatModel, showMenu)
|
||||
}
|
||||
}
|
||||
},
|
||||
showMenu,
|
||||
@@ -108,7 +112,7 @@ fun ContactListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>, showDel
|
||||
tryOrShowError("${chat.id}ContactListNavLinkDropdown", error = {}) {
|
||||
ContactRequestMenuItems(
|
||||
rhId = chat.remoteHostId,
|
||||
chatInfo = chat.chatInfo,
|
||||
contactRequestId = chat.chatInfo.apiId,
|
||||
chatModel = chatModel,
|
||||
showMenu = showMenu,
|
||||
onSuccess = { onRequestAccepted(it) }
|
||||
|
||||
+2
-1
@@ -38,6 +38,7 @@ fun ContactPreviewView(
|
||||
val textColor = when {
|
||||
deleting -> MaterialTheme.colors.secondary
|
||||
contactType == ContactType.CARD -> MaterialTheme.colors.primary
|
||||
contactType == ContactType.CONTACT_WITH_REQUEST -> MaterialTheme.colors.primary
|
||||
contactType == ContactType.REQUEST -> MaterialTheme.colors.primary
|
||||
contactType == ContactType.RECENT && chat.chatInfo.incognito -> Indigo
|
||||
else -> Color.Unspecified
|
||||
@@ -85,7 +86,7 @@ fun ContactPreviewView(
|
||||
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
|
||||
if (chat.chatInfo is ChatInfo.ContactRequest) {
|
||||
if (chat.chatInfo is ChatInfo.ContactRequest || contactType == ContactType.CONTACT_WITH_REQUEST) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_check),
|
||||
contentDescription = null,
|
||||
|
||||
+73
@@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.ChatModel
|
||||
@@ -266,6 +267,78 @@ class AlertManager {
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
) = showAlertMsg(generalGetString(title), if (text != null) generalGetString(text) else null, generalGetString(confirmText), onConfirm, hostDevice)
|
||||
|
||||
fun showOpenChatAlert(
|
||||
profileName: String,
|
||||
profileImage: @Composable () -> Unit,
|
||||
confirmText: String = generalGetString(MR.strings.connect_plan_open_chat),
|
||||
onConfirm: () -> Unit,
|
||||
dismissText: String = generalGetString(MR.strings.cancel_verb),
|
||||
onDismiss: (() -> Unit)?,
|
||||
) {
|
||||
showAlert {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
onDismiss?.invoke()
|
||||
hideAlert()
|
||||
},
|
||||
buttons = {
|
||||
AlertContent(text = null as String?, null) {
|
||||
Column(
|
||||
Modifier
|
||||
.width(360.dp)
|
||||
.padding(top = DEFAULT_PADDING),
|
||||
verticalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
profileImage()
|
||||
|
||||
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
|
||||
|
||||
Text(
|
||||
profileName,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
// Wait before focusing to prevent auto-confirming if a user used Enter key on hardware keyboard
|
||||
delay(200)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
TextButton(onClick = {
|
||||
onDismiss?.invoke()
|
||||
hideAlert()
|
||||
}) {
|
||||
Text(dismissText)
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(0.dp))
|
||||
|
||||
TextButton(onClick = {
|
||||
onConfirm.invoke()
|
||||
hideAlert()
|
||||
}, Modifier.focusRequester(focusRequester)) {
|
||||
Text(confirmText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun showInView() {
|
||||
alertViews.collectAsState().value.lastOrNull()?.invoke()
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c
|
||||
}
|
||||
} else {
|
||||
ModalManager.end.showModalCloseable(true) { close ->
|
||||
GroupLinkView(chatModel, rhId, groupInfo, connLinkContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close)
|
||||
GroupLinkView(chatModel, rhId, groupInfo, groupLink = null, onGroupLinkUpdated = null, creatingGroup = true, close)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+156
-163
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
@@ -20,7 +21,6 @@ enum class ConnectionLinkType {
|
||||
suspend fun planAndConnect(
|
||||
rhId: Long?,
|
||||
shortOrFullLink: String,
|
||||
incognito: Boolean?,
|
||||
close: (() -> Unit)?,
|
||||
cleanup: (() -> Unit)? = null,
|
||||
filterKnownContact: ((Contact) -> Unit)? = null,
|
||||
@@ -46,11 +46,18 @@ suspend fun planAndConnect(
|
||||
""
|
||||
when (connectionPlan) {
|
||||
is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) {
|
||||
InvitationLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup)
|
||||
is InvitationLinkPlan.Ok ->
|
||||
if (connectionPlan.invitationLinkPlan.contactSLinkData_ != null) {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, short link data present")
|
||||
showPrepareContactAlert(
|
||||
rhId,
|
||||
connectionLink,
|
||||
connectionPlan.invitationLinkPlan.contactSLinkData_,
|
||||
close,
|
||||
cleanup
|
||||
)
|
||||
} else {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, no short link data")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_invitation_link),
|
||||
@@ -59,32 +66,18 @@ suspend fun planAndConnect(
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
}
|
||||
InvitationLinkPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.privacySensitive.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
destructive = true,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
is InvitationLinkPlan.Connecting -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting")
|
||||
val contact = connectionPlan.invitationLinkPlan.contact_
|
||||
if (contact != null) {
|
||||
if (filterKnownContact != null) {
|
||||
@@ -108,7 +101,7 @@ suspend fun planAndConnect(
|
||||
}
|
||||
}
|
||||
is InvitationLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .InvitationLink, .Known")
|
||||
val contact = connectionPlan.invitationLinkPlan.contact
|
||||
if (filterKnownContact != null) {
|
||||
filterKnownContact(contact)
|
||||
@@ -124,11 +117,18 @@ suspend fun planAndConnect(
|
||||
}
|
||||
}
|
||||
is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) {
|
||||
ContactAddressPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup)
|
||||
is ContactAddressPlan.Ok ->
|
||||
if (connectionPlan.contactAddressPlan.contactSLinkData_ != null) {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, short link data present")
|
||||
showPrepareContactAlert(
|
||||
rhId,
|
||||
connectionLink,
|
||||
connectionPlan.contactAddressPlan.contactSLinkData_,
|
||||
close,
|
||||
cleanup
|
||||
)
|
||||
} else {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, no short link data")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_contact_link),
|
||||
@@ -137,55 +137,28 @@ suspend fun planAndConnect(
|
||||
cleanup,
|
||||
)
|
||||
}
|
||||
}
|
||||
ContactAddressPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.privacySensitive.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
destructive = true,
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
ContactAddressPlan.ConnectingConfirmReconnect -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.privacySensitive.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
destructive = true,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
is ContactAddressPlan.ConnectingProhibit -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
if (filterKnownContact != null) {
|
||||
filterKnownContact(contact)
|
||||
@@ -200,7 +173,7 @@ suspend fun planAndConnect(
|
||||
}
|
||||
}
|
||||
is ContactAddressPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .Known")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
if (filterKnownContact != null) {
|
||||
filterKnownContact(contact)
|
||||
@@ -215,31 +188,25 @@ suspend fun planAndConnect(
|
||||
}
|
||||
}
|
||||
is ContactAddressPlan.ContactViaAddress -> {
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress")
|
||||
val contact = connectionPlan.contactAddressPlan.contact
|
||||
if (incognito != null) {
|
||||
close()
|
||||
connectContactViaAddress(chatModel, rhId, contact.contactId, incognito)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false)
|
||||
}
|
||||
askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false)
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) {
|
||||
GroupLinkPlan.Ok -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.privacySensitive.showAlertDialog(
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
text = generalGetString(MR.strings.you_will_join_group) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
hostDevice = hostDevice(rhId),
|
||||
is GroupLinkPlan.Ok ->
|
||||
if (connectionPlan.groupLinkPlan.groupSLinkData_ != null) {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, short link data present")
|
||||
showPrepareGroupAlert(
|
||||
rhId,
|
||||
connectionLink,
|
||||
connectionPlan.groupLinkPlan.groupSLinkData_,
|
||||
close,
|
||||
cleanup
|
||||
)
|
||||
} else {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, no short link data")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_via_group_link),
|
||||
@@ -248,41 +215,27 @@ suspend fun planAndConnect(
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
}
|
||||
is GroupLinkPlan.OwnLink -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
if (filterKnownGroup != null) {
|
||||
filterKnownGroup(groupInfo)
|
||||
} else {
|
||||
ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, incognito, connectionPlan, groupInfo, close, cleanup)
|
||||
ownGroupLinkConfirmConnect(chatModel, rhId, connectionLink, linkText, connectionPlan, groupInfo, close, cleanup)
|
||||
}
|
||||
}
|
||||
GroupLinkPlan.ConnectingConfirmReconnect -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito")
|
||||
if (incognito != null) {
|
||||
AlertManager.privacySensitive.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) + linkText,
|
||||
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||
onConfirm = { withBGApi { connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup) } },
|
||||
onDismiss = cleanup,
|
||||
onDismissRequest = cleanup,
|
||||
destructive = true,
|
||||
hostDevice = hostDevice(rhId),
|
||||
)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect")
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan, close,
|
||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
||||
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
|
||||
connectDestructive = true,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
is GroupLinkPlan.ConnectingProhibit -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo_
|
||||
if (groupInfo != null) {
|
||||
if (groupInfo.businessChat == null) {
|
||||
@@ -306,7 +259,7 @@ suspend fun planAndConnect(
|
||||
cleanup()
|
||||
}
|
||||
is GroupLinkPlan.Known -> {
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
|
||||
Log.d(TAG, "planAndConnect, .GroupLink, .Known")
|
||||
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
|
||||
if (filterKnownGroup != null) {
|
||||
filterKnownGroup(groupInfo)
|
||||
@@ -331,16 +284,12 @@ suspend fun planAndConnect(
|
||||
}
|
||||
is ConnectionPlan.Error -> {
|
||||
Log.d(TAG, "planAndConnect, error ${connectionPlan.chatError}")
|
||||
if (incognito != null) {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan = null, close, cleanup)
|
||||
} else {
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan = null, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_via_link),
|
||||
connectDestructive = false,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
askCurrentOrIncognitoProfileAlert(
|
||||
chatModel, rhId, connectionLink, connectionPlan = null, close,
|
||||
title = generalGetString(MR.strings.connect_plan_connect_via_link),
|
||||
connectDestructive = false,
|
||||
cleanup = cleanup,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,7 +397,6 @@ fun ownGroupLinkConfirmConnect(
|
||||
rhId: Long?,
|
||||
connectionLink: CreatedConnLink,
|
||||
linkText: String,
|
||||
incognito: Boolean?,
|
||||
connectionPlan: ConnectionPlan?,
|
||||
groupInfo: GroupInfo,
|
||||
close: (() -> Unit)?,
|
||||
@@ -467,38 +415,23 @@ fun ownGroupLinkConfirmConnect(
|
||||
}) {
|
||||
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.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
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
|
||||
)
|
||||
// Use current profile
|
||||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup)
|
||||
}
|
||||
} else {
|
||||
// Use current profile
|
||||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = false, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
// Use new incognito profile
|
||||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
// Use new incognito profile
|
||||
SectionItemView({
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
connectViaUri(chatModel, rhId, connectionLink, incognito = true, connectionPlan, close, cleanup)
|
||||
}
|
||||
}) {
|
||||
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||
}
|
||||
// Cancel
|
||||
SectionItemView({
|
||||
@@ -523,3 +456,63 @@ fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, grou
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showPrepareContactAlert(
|
||||
rhId: Long?,
|
||||
connectionLink: CreatedConnLink,
|
||||
contactShortLinkData: ContactShortLinkData,
|
||||
close: (() -> Unit)?,
|
||||
cleanup: (() -> Unit)?
|
||||
) {
|
||||
AlertManager.privacySensitive.showOpenChatAlert(
|
||||
profileName = contactShortLinkData.profile.displayName,
|
||||
profileImage = { ProfileImage(size = 72.dp, image = contactShortLinkData.profile.image) },
|
||||
onConfirm = {
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
val contact = chatModel.controller.apiPrepareContact(rhId, connectionLink, contactShortLinkData)
|
||||
if (contact != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val chatInfo = ChatInfo.Direct(contact)
|
||||
ChatController.chatModel.chatsContext.addChat(Chat(rhId, chatInfo, chatItems = listOf()))
|
||||
openKnownContact(chatModel, rhId, close, contact)
|
||||
}
|
||||
}
|
||||
cleanup?.invoke()
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
cleanup?.invoke()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun showPrepareGroupAlert(
|
||||
rhId: Long?,
|
||||
connectionLink: CreatedConnLink,
|
||||
groupShortLinkData: GroupShortLinkData,
|
||||
close: (() -> Unit)?,
|
||||
cleanup: (() -> Unit)?
|
||||
) {
|
||||
AlertManager.privacySensitive.showOpenChatAlert(
|
||||
profileName = groupShortLinkData.groupProfile.displayName,
|
||||
profileImage = { ProfileImage(size = 72.dp, image = groupShortLinkData.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled) },
|
||||
onConfirm = {
|
||||
AlertManager.privacySensitive.hideAlert()
|
||||
withBGApi {
|
||||
val groupInfo = chatModel.controller.apiPrepareGroup(rhId, connectionLink, groupShortLinkData)
|
||||
if (groupInfo != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val chatInfo = ChatInfo.Group(groupInfo, groupChatScope = null)
|
||||
ChatController.chatModel.chatsContext.addChat(Chat(rhId, chatInfo, chatItems = listOf()))
|
||||
openKnownGroup(chatModel, rhId, close, groupInfo)
|
||||
}
|
||||
}
|
||||
cleanup?.invoke()
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
cleanup?.invoke()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+3
-4
@@ -73,7 +73,7 @@ fun ModalData.NewChatSheet(rh: RemoteHostInfo?, close: () -> Unit) {
|
||||
}
|
||||
|
||||
enum class ContactType {
|
||||
CARD, REQUEST, RECENT, CHAT_DELETED, UNLISTED
|
||||
CARD, CONTACT_WITH_REQUEST, REQUEST, RECENT, CHAT_DELETED, UNLISTED
|
||||
}
|
||||
|
||||
fun chatContactType(chat: Chat): ContactType {
|
||||
@@ -81,8 +81,8 @@ fun chatContactType(chat: Chat): ContactType {
|
||||
is ChatInfo.ContactRequest -> ContactType.REQUEST
|
||||
is ChatInfo.Direct -> {
|
||||
val contact = cInfo.contact
|
||||
|
||||
when {
|
||||
contact.nextAcceptContactRequest -> ContactType.CONTACT_WITH_REQUEST
|
||||
contact.activeConn == null && contact.profile.contactLink != null && contact.active -> ContactType.CARD
|
||||
contact.chatDeleted -> ContactType.CHAT_DELETED
|
||||
contact.contactStatus == ContactStatus.Active -> ContactType.RECENT
|
||||
@@ -127,7 +127,7 @@ private fun ModalData.NewChatSheetLayout(
|
||||
val searchShowingSimplexLink = remember { mutableStateOf(false) }
|
||||
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
|
||||
val showUnreadAndFavorites = remember { ChatController.appPrefs.showUnreadAndFavorites.state }.value
|
||||
val baseContactTypes = remember { listOf(ContactType.CARD, ContactType.RECENT, ContactType.REQUEST) }
|
||||
val baseContactTypes = remember { listOf(ContactType.CARD, ContactType.CONTACT_WITH_REQUEST, ContactType.REQUEST, ContactType.RECENT) }
|
||||
val contactTypes by remember(searchText.value.text.isEmpty()) {
|
||||
derivedStateOf { contactTypesSearchTargets(baseContactTypes, searchText.value.text.isEmpty()) }
|
||||
}
|
||||
@@ -557,7 +557,6 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<
|
||||
planAndConnect(
|
||||
chatModel.remoteHostId(),
|
||||
link,
|
||||
incognito = null,
|
||||
filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id },
|
||||
close = close,
|
||||
cleanup = cleanup,
|
||||
|
||||
+5
-3
@@ -409,7 +409,10 @@ fun ActiveProfilePicker(
|
||||
val activeProfile = filteredProfiles.firstOrNull { it.activeUser }
|
||||
|
||||
if (activeProfile != null) {
|
||||
val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId }
|
||||
val otherProfiles =
|
||||
filteredProfiles
|
||||
.filter { it.userId != activeProfile.userId }
|
||||
.sortedByDescending { it.activeOrder }
|
||||
item {
|
||||
when {
|
||||
!showIncognito ->
|
||||
@@ -687,8 +690,7 @@ private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanu
|
||||
rhId,
|
||||
link,
|
||||
close = close,
|
||||
cleanup = cleanup,
|
||||
incognito = null
|
||||
cleanup = cleanup
|
||||
).await()
|
||||
|
||||
private fun createInvitation(
|
||||
|
||||
+23
-10
@@ -65,7 +65,7 @@ fun UserAddressView(
|
||||
progressIndicator = true
|
||||
val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId)
|
||||
if (connReqContact != null) {
|
||||
chatModel.userAddress.value = UserContactLinkRec(connReqContact)
|
||||
chatModel.userAddress.value = UserContactLinkRec(connReqContact, shortLinkDataSet = connReqContact.connShortLink != null)
|
||||
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.share_address_with_contacts_question),
|
||||
@@ -92,6 +92,17 @@ fun UserAddressView(
|
||||
}
|
||||
}
|
||||
|
||||
fun showAddShortLinkAlert() {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.share_profile_via_link),
|
||||
text = generalGetString(MR.strings.share_profile_via_link_alert_text),
|
||||
confirmText = generalGetString(MR.strings.share_profile_via_link_alert_confirm),
|
||||
onConfirm = {
|
||||
addShortLink()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(autoCreateAddress) {
|
||||
if (chatModel.userAddress.value == null && autoCreateAddress) {
|
||||
createAddress()
|
||||
@@ -106,7 +117,7 @@ fun UserAddressView(
|
||||
userAddress = userAddress.value,
|
||||
shareViaProfile,
|
||||
createAddress = { createAddress() },
|
||||
addShortLink = { addShortLink() },
|
||||
showAddShortLinkAlert = { showAddShortLinkAlert() },
|
||||
learnMore = {
|
||||
ModalManager.start.showModal {
|
||||
UserAddressLearnMore()
|
||||
@@ -181,7 +192,7 @@ private fun UserAddressLayout(
|
||||
userAddress: UserContactLinkRec?,
|
||||
shareViaProfile: MutableState<Boolean>,
|
||||
createAddress: () -> Unit,
|
||||
addShortLink: () -> Unit,
|
||||
showAddShortLinkAlert: () -> Unit,
|
||||
learnMore: () -> Unit,
|
||||
share: (String) -> Unit,
|
||||
sendEmail: (UserContactLinkRec) -> Unit,
|
||||
@@ -225,7 +236,9 @@ private fun UserAddressLayout(
|
||||
BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
|
||||
AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas)
|
||||
if (userAddress.connLinkContact.connShortLink == null) {
|
||||
AddShortLinkButton(addShortLink)
|
||||
AddShortLinkButton(text = stringResource(MR.strings.add_short_link), showAddShortLinkAlert)
|
||||
} else if (!userAddress.shortLinkDataSet) {
|
||||
AddShortLinkButton(text = stringResource(MR.strings.share_profile_via_link), showAddShortLinkAlert)
|
||||
}
|
||||
|
||||
if (autoAcceptState.value.business) {
|
||||
@@ -265,10 +278,10 @@ private fun CreateAddressButton(onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddShortLinkButton(onClick: () -> Unit) {
|
||||
private fun AddShortLinkButton(text: String, onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_add),
|
||||
stringResource(MR.strings.add_short_link),
|
||||
text,
|
||||
onClick,
|
||||
iconColor = MaterialTheme.colors.primary,
|
||||
textColor = MaterialTheme.colors.primary,
|
||||
@@ -536,7 +549,7 @@ private fun AutoAcceptSection(
|
||||
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit
|
||||
) {
|
||||
SectionView(stringResource(MR.strings.auto_accept_contact).uppercase()) {
|
||||
if (!autoAcceptState.value.business) {
|
||||
if (!chatModel.addressShortLinkDataSet && !autoAcceptState.value.business) {
|
||||
AcceptIncognitoToggle(autoAcceptState)
|
||||
}
|
||||
WelcomeMessageEditor(autoAcceptState)
|
||||
@@ -586,7 +599,7 @@ fun PreviewUserAddressLayoutNoAddress() {
|
||||
user = User.sampleData,
|
||||
userAddress = null,
|
||||
createAddress = {},
|
||||
addShortLink = {},
|
||||
showAddShortLinkAlert = {},
|
||||
share = { _ -> },
|
||||
deleteAddress = {},
|
||||
saveAas = { _, _ -> },
|
||||
@@ -618,9 +631,9 @@ fun PreviewUserAddressLayoutAddressCreated() {
|
||||
SimpleXTheme {
|
||||
UserAddressLayout(
|
||||
user = User.sampleData,
|
||||
userAddress = UserContactLinkRec(CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null)),
|
||||
userAddress = UserContactLinkRec(CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null), shortLinkDataSet = false),
|
||||
createAddress = {},
|
||||
addShortLink = {},
|
||||
showAddShortLinkAlert = {},
|
||||
share = { _ -> },
|
||||
deleteAddress = {},
|
||||
saveAas = { _, _ -> },
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<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>
|
||||
<string name="connect_plan_open_chat">Open</string>
|
||||
<string name="error_parsing_uri_title">Invalid link</string>
|
||||
<string name="error_parsing_uri_desc">Please check that SimpleX link is correct.</string>
|
||||
|
||||
@@ -63,6 +64,7 @@
|
||||
<string name="decryption_error">Decryption error</string>
|
||||
<string name="encryption_renegotiation_error">Encryption re-negotiation error</string>
|
||||
|
||||
<string name="e2ee_info_e2ee"><![CDATA[Messages are protected by <b>end-to-end encryption</b>.]]></string>
|
||||
<string name="e2ee_info_no_pq"><![CDATA[Messages, files and calls are protected by <b>end-to-end encryption</b> with perfect forward secrecy, repudiation and break-in recovery.]]></string>
|
||||
<string name="e2ee_info_pq"><![CDATA[Messages, files and calls are protected by <b>quantum resistant e2e encryption</b> with perfect forward secrecy, repudiation and break-in recovery.]]></string>
|
||||
<string name="e2ee_info_no_pq_short">This chat is protected by end-to-end encryption.</string>
|
||||
@@ -180,6 +182,7 @@
|
||||
<string name="connection_error_quota">Undelivered messages</string>
|
||||
<string name="connection_error_quota_desc">The connection reached the limit of undelivered messages, your contact may be offline.</string>
|
||||
<string name="error_accepting_contact_request">Error accepting contact request</string>
|
||||
<string name="error_rejecting_contact_request">Error rejecting contact request</string>
|
||||
<string name="sender_may_have_deleted_the_connection_request">Sender may have deleted the connection request.</string>
|
||||
<string name="error_deleting_contact">Error deleting contact</string>
|
||||
<string name="error_deleting_group">Error deleting group</string>
|
||||
@@ -212,6 +215,10 @@
|
||||
<string name="error_updating_chat_tags">Error updating chat list</string>
|
||||
<string name="error_creating_chat_tags">Error creating chat list</string>
|
||||
<string name="error_loading_chat_tags">Error loading chat lists</string>
|
||||
<string name="error_preparing_contact">Error preparing contact</string>
|
||||
<string name="error_preparing_group">Error preparing group</string>
|
||||
<string name="error_changing_contact_user">Error changing contact user</string>
|
||||
<string name="error_changing_group_user">Error changing group user</string>
|
||||
|
||||
<!-- background service notice - SimpleXAPI.kt -->
|
||||
<string name="icon_descr_instant_notifications">Instant notifications</string>
|
||||
@@ -421,6 +428,7 @@
|
||||
<string name="toolbar_settings">Settings</string>
|
||||
<string name="contact_connection_pending">connecting…</string>
|
||||
<string name="member_contact_send_direct_message">send to connect</string>
|
||||
<string name="group_preview_open_to_join">open to join</string>
|
||||
<string name="group_preview_you_are_invited">you are invited to group</string>
|
||||
<string name="group_preview_join_as">join as %s</string>
|
||||
<string name="group_preview_rejected">rejected</string>
|
||||
@@ -435,6 +443,7 @@
|
||||
<string name="no_chats">No chats</string>
|
||||
<string name="no_chats_found">No chats found</string>
|
||||
<string name="contact_tap_to_connect">Tap to Connect</string>
|
||||
<string name="hold_or_open_to_connect">hold or open to connect</string>
|
||||
<string name="connect_with_contact_name_question">Connect with %1$s?</string>
|
||||
<string name="search_or_paste_simplex_link">Search or paste SimpleX link</string>
|
||||
<string name="address_creation_instruction">Tap Create SimpleX address in the menu to create it later.</string>
|
||||
@@ -693,6 +702,9 @@
|
||||
<string name="accept_contact_button">Accept</string>
|
||||
<string name="accept_contact_incognito_button">Accept incognito</string>
|
||||
<string name="reject_contact_button">Reject</string>
|
||||
<string name="accept_contact_request">Accept contact request</string>
|
||||
<string name="reject_contact_request">Reject contact request</string>
|
||||
<string name="the_sender_will_not_be_notified">The sender will NOT be notified.</string>
|
||||
|
||||
<!-- Clear Chat - ChatListNavLinkView.kt -->
|
||||
<string name="clear_chat_question">Clear chat?</string>
|
||||
@@ -1058,6 +1070,10 @@
|
||||
<string name="business_address">Business address</string>
|
||||
<string name="add_your_team_members_to_conversations">Add your team members to the conversations.</string>
|
||||
<string name="add_short_link">Add short link</string>
|
||||
<string name="share_profile_via_link">Share profile via link</string>
|
||||
<string name="share_profile_via_link_alert_text">Profile will be shared via the address short link. This change to the address cannot be reversed, other than fully deleting it. Do you wish to update the address?</string>
|
||||
<string name="share_profile_via_link_alert_confirm">Update (and share profile)</string>
|
||||
<string name="share_group_profile_via_link">Share group profile via link</string>
|
||||
|
||||
<!-- CreateSimpleXAddress.kt -->
|
||||
<string name="continue_to_next_step">Continue</string>
|
||||
|
||||
Reference in New Issue
Block a user