mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-29 04:39:53 +00:00
Merge branch 'short-links' into ep/contact-request-messages
This commit is contained in:
commit
5d374dac76
19 changed files with 735 additions and 270 deletions
|
@ -758,6 +758,8 @@ public enum StoreError: Decodable, Hashable {
|
||||||
case userContactLinkNotFound
|
case userContactLinkNotFound
|
||||||
case contactRequestNotFound(contactRequestId: Int64)
|
case contactRequestNotFound(contactRequestId: Int64)
|
||||||
case contactRequestNotFoundByName(contactName: ContactName)
|
case contactRequestNotFoundByName(contactName: ContactName)
|
||||||
|
case invalidContactRequestEntity(contactRequestId: Int64)
|
||||||
|
case invalidBusinessChatContactRequest
|
||||||
case groupNotFound(groupId: Int64)
|
case groupNotFound(groupId: Int64)
|
||||||
case groupNotFoundByName(groupName: GroupName)
|
case groupNotFoundByName(groupName: GroupName)
|
||||||
case groupMemberNameNotFound(groupId: Int64, groupMemberName: ContactName)
|
case groupMemberNameNotFound(groupId: Int64, groupMemberName: ContactName)
|
||||||
|
|
|
@ -2142,7 +2142,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct PreparedGroup: Decodable, Hashable {
|
public struct PreparedGroup: Decodable, Hashable {
|
||||||
public var connLinkToConnect: CreatedConnLink?
|
public var connLinkToConnect: CreatedConnLink
|
||||||
public var connLinkStartedConnection: Bool
|
public var connLinkStartedConnection: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -398,8 +398,19 @@ object ChatModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateChats(newChats: List<Chat>) {
|
fun updateChats(newChats: List<Chat>, keepingChatId: String? = null) {
|
||||||
chats.replaceAll(newChats)
|
if (keepingChatId != null) {
|
||||||
|
val chatToKeep = getChat(keepingChatId)
|
||||||
|
val indexToRemove = newChats.indexOfFirst { it.id == keepingChatId }
|
||||||
|
if (chatToKeep != null && indexToRemove != -1) {
|
||||||
|
val remainingNewChats = newChats.toMutableList().apply { removeAt(indexToRemove) }
|
||||||
|
chats.replaceAll(listOf(chatToKeep) + remainingNewChats)
|
||||||
|
} else {
|
||||||
|
chats.replaceAll(newChats)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chats.replaceAll(newChats)
|
||||||
|
}
|
||||||
popChatCollector.clear()
|
popChatCollector.clear()
|
||||||
|
|
||||||
val cId = chatId.value
|
val cId = chatId.value
|
||||||
|
@ -438,14 +449,16 @@ object ChatModel {
|
||||||
chatState.itemsRemoved(listOf(removed), chatItems.value)
|
chatState.itemsRemoved(listOf(removed), chatItems.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
|
suspend fun addChatItem(rhId: Long?, chatInfo: ChatInfo, cItem: ChatItem) {
|
||||||
// updates membersRequireAttention
|
// updates membersRequireAttention
|
||||||
updateChatInfo(rhId, cInfo)
|
val cInfo = if (chatInfo is ChatInfo.Direct && chatInfo.chatDeleted) {
|
||||||
// mark chat non deleted
|
// mark chat non deleted
|
||||||
if (cInfo is ChatInfo.Direct && cInfo.chatDeleted) {
|
val updatedContact = chatInfo.contact.copy(chatDeleted = false)
|
||||||
val updatedContact = cInfo.contact.copy(chatDeleted = false)
|
ChatInfo.Direct(updatedContact)
|
||||||
updateContact(rhId, updatedContact)
|
} else {
|
||||||
|
chatInfo
|
||||||
}
|
}
|
||||||
|
updateChatInfo(rhId, cInfo)
|
||||||
// update chat list
|
// update chat list
|
||||||
val i = getChatIndex(rhId, cInfo.id)
|
val i = getChatIndex(rhId, cInfo.id)
|
||||||
val chat: Chat
|
val chat: Chat
|
||||||
|
@ -1502,7 +1515,13 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||||
if (contact.sendMsgToConnect) return null
|
if (contact.sendMsgToConnect) return null
|
||||||
if (contact.nextAcceptContactRequest) { return generalGetString(MR.strings.cant_send_message_generic) to 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.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.sndReady) {
|
||||||
|
return if (contact.preparedContact?.uiConnLinkType == ConnectionMode.Con) {
|
||||||
|
generalGetString(MR.strings.cant_send_message_request_is_sent) to null
|
||||||
|
} else {
|
||||||
|
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
|
if (contact.activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null
|
||||||
if (contact.activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null
|
if (contact.activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null
|
||||||
return null
|
return null
|
||||||
|
@ -1532,6 +1551,8 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (groupInfo.nextConnectPrepared) {
|
||||||
|
return null
|
||||||
} else {
|
} else {
|
||||||
return when (groupInfo.membership.memberStatus) {
|
return when (groupInfo.membership.memberStatus) {
|
||||||
GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null
|
GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null
|
||||||
|
@ -1667,8 +1688,8 @@ data class Contact(
|
||||||
val active get() = contactStatus == ContactStatus.Active
|
val active get() = contactStatus == ContactStatus.Active
|
||||||
override val nextConnect get() = sendMsgToConnect
|
override val nextConnect get() = sendMsgToConnect
|
||||||
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
|
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
|
||||||
val nextConnectPrepared get() = preparedContact != null && activeConn == null
|
val nextConnectPrepared get() = preparedContact != null && (activeConn == null || activeConn.connStatus == ConnStatus.Prepared)
|
||||||
val nextAcceptContactRequest get() = contactRequestId != null && activeConn == null
|
val nextAcceptContactRequest get() = contactRequestId != null && (activeConn == null || activeConn.connStatus == ConnStatus.New)
|
||||||
val sendMsgToConnect get() = nextSendGrpInv || nextConnectPrepared
|
val sendMsgToConnect get() = nextSendGrpInv || nextConnectPrepared
|
||||||
override val incognito get() = contactConnIncognito
|
override val incognito get() = contactConnIncognito
|
||||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||||
|
@ -1699,6 +1720,9 @@ data class Contact(
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isContactCard: Boolean =
|
||||||
|
activeConn == null && profile.contactLink != null && active
|
||||||
|
|
||||||
val contactConnIncognito =
|
val contactConnIncognito =
|
||||||
activeConn?.customUserProfileId != null
|
activeConn?.customUserProfileId != null
|
||||||
|
|
||||||
|
@ -1906,8 +1930,7 @@ data class GroupInfo (
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant,
|
||||||
override val updatedAt: Instant,
|
override val updatedAt: Instant,
|
||||||
val chatTs: Instant?,
|
val chatTs: Instant?,
|
||||||
val connLinkToConnect: CreatedConnLink?,
|
val preparedGroup: PreparedGroup?,
|
||||||
val connLinkStartedConnection: Boolean,
|
|
||||||
val uiThemes: ThemeModeOverrides? = null,
|
val uiThemes: ThemeModeOverrides? = null,
|
||||||
val membersRequireAttention: Int,
|
val membersRequireAttention: Int,
|
||||||
val chatTags: List<Long>,
|
val chatTags: List<Long>,
|
||||||
|
@ -1919,7 +1942,7 @@ data class GroupInfo (
|
||||||
override val apiId get() = groupId
|
override val apiId get() = groupId
|
||||||
override val ready get() = membership.memberActive
|
override val ready get() = membership.memberActive
|
||||||
override val nextConnect get() = nextConnectPrepared
|
override val nextConnect get() = nextConnectPrepared
|
||||||
val nextConnectPrepared = connLinkToConnect != null && !connLinkStartedConnection
|
val nextConnectPrepared = if (preparedGroup != null) !preparedGroup.connLinkStartedConnection else false
|
||||||
override val chatDeleted get() = false
|
override val chatDeleted get() = false
|
||||||
override val incognito get() = membership.memberIncognito
|
override val incognito get() = membership.memberIncognito
|
||||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||||
|
@ -1946,6 +1969,13 @@ data class GroupInfo (
|
||||||
val canModerate: Boolean
|
val canModerate: Boolean
|
||||||
get() = membership.memberRole >= GroupMemberRole.Moderator && membership.memberActive
|
get() = membership.memberRole >= GroupMemberRole.Moderator && membership.memberActive
|
||||||
|
|
||||||
|
val chatIconName: ImageResource
|
||||||
|
get() = when (businessChat?.chatType) {
|
||||||
|
null -> MR.images.ic_supervised_user_circle_filled
|
||||||
|
BusinessChatType.Business -> MR.images.ic_work_filled_padded
|
||||||
|
BusinessChatType.Customer -> MR.images.ic_account_circle_filled
|
||||||
|
}
|
||||||
|
|
||||||
fun groupFeatureEnabled(feature: GroupFeature): Boolean {
|
fun groupFeatureEnabled(feature: GroupFeature): Boolean {
|
||||||
val p = fullGroupPreferences
|
val p = fullGroupPreferences
|
||||||
return when (feature) {
|
return when (feature) {
|
||||||
|
@ -1972,8 +2002,7 @@ data class GroupInfo (
|
||||||
createdAt = Clock.System.now(),
|
createdAt = Clock.System.now(),
|
||||||
updatedAt = Clock.System.now(),
|
updatedAt = Clock.System.now(),
|
||||||
chatTs = Clock.System.now(),
|
chatTs = Clock.System.now(),
|
||||||
connLinkToConnect = null,
|
preparedGroup = null,
|
||||||
connLinkStartedConnection = false,
|
|
||||||
uiThemes = null,
|
uiThemes = null,
|
||||||
membersRequireAttention = 0,
|
membersRequireAttention = 0,
|
||||||
chatTags = emptyList(),
|
chatTags = emptyList(),
|
||||||
|
@ -1983,6 +2012,12 @@ data class GroupInfo (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PreparedGroup (
|
||||||
|
val connLinkToConnect: CreatedConnLink,
|
||||||
|
val connLinkStartedConnection: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GroupRef(val groupId: Long, val localDisplayName: String)
|
data class GroupRef(val groupId: Long, val localDisplayName: String)
|
||||||
|
|
||||||
|
@ -2886,6 +2921,7 @@ data class ChatItem (
|
||||||
deletable = false,
|
deletable = false,
|
||||||
editable = false,
|
editable = false,
|
||||||
userMention = false,
|
userMention = false,
|
||||||
|
showGroupAsSender = false,
|
||||||
),
|
),
|
||||||
content = CIContent.RcvDeleted(deleteMode = CIDeleteMode.cidmBroadcast),
|
content = CIContent.RcvDeleted(deleteMode = CIDeleteMode.cidmBroadcast),
|
||||||
quotedItem = null,
|
quotedItem = null,
|
||||||
|
@ -2911,6 +2947,7 @@ data class ChatItem (
|
||||||
deletable = false,
|
deletable = false,
|
||||||
editable = false,
|
editable = false,
|
||||||
userMention = false,
|
userMention = false,
|
||||||
|
showGroupAsSender = false
|
||||||
),
|
),
|
||||||
content = CIContent.SndMsgContent(MsgContent.MCText("")),
|
content = CIContent.SndMsgContent(MsgContent.MCText("")),
|
||||||
quotedItem = null,
|
quotedItem = null,
|
||||||
|
@ -3056,7 +3093,8 @@ data class CIMeta (
|
||||||
val itemLive: Boolean?,
|
val itemLive: Boolean?,
|
||||||
val userMention: Boolean,
|
val userMention: Boolean,
|
||||||
val deletable: Boolean,
|
val deletable: Boolean,
|
||||||
val editable: Boolean
|
val editable: Boolean,
|
||||||
|
val showGroupAsSender: Boolean
|
||||||
) {
|
) {
|
||||||
val timestampText: String get() = getTimestampText(itemTs, true)
|
val timestampText: String get() = getTimestampText(itemTs, true)
|
||||||
|
|
||||||
|
@ -3095,6 +3133,7 @@ data class CIMeta (
|
||||||
deletable = deletable,
|
deletable = deletable,
|
||||||
editable = editable,
|
editable = editable,
|
||||||
userMention = false,
|
userMention = false,
|
||||||
|
showGroupAsSender = false
|
||||||
)
|
)
|
||||||
|
|
||||||
fun invalidJSON(): CIMeta =
|
fun invalidJSON(): CIMeta =
|
||||||
|
@ -3114,7 +3153,8 @@ data class CIMeta (
|
||||||
itemLive = false,
|
itemLive = false,
|
||||||
deletable = false,
|
deletable = false,
|
||||||
editable = false,
|
editable = false,
|
||||||
userMention = false
|
userMention = false,
|
||||||
|
showGroupAsSender = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3469,6 +3509,13 @@ sealed class CIContent: ItemContent {
|
||||||
is InvalidJSON -> "invalid data"
|
is InvalidJSON -> "invalid data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasMsgContent: Boolean get() =
|
||||||
|
if (msgContent != null) {
|
||||||
|
(msgContent as MsgContent).text.trim().isNotEmpty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
val showMemberName: Boolean get() =
|
val showMemberName: Boolean get() =
|
||||||
when (this) {
|
when (this) {
|
||||||
is RcvMsgContent -> true
|
is RcvMsgContent -> true
|
||||||
|
|
|
@ -591,7 +591,7 @@ object ChatController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun changeActiveUser_(rhId: Long?, toUserId: Long?, viewPwd: String?) {
|
suspend fun changeActiveUser_(rhId: Long?, toUserId: Long?, viewPwd: String?, keepingChatId: String? = null) {
|
||||||
val prevActiveUser = chatModel.currentUser.value
|
val prevActiveUser = chatModel.currentUser.value
|
||||||
val currentUser = changingActiveUserMutex.withLock {
|
val currentUser = changingActiveUserMutex.withLock {
|
||||||
(if (toUserId != null) apiSetActiveUser(rhId, toUserId, viewPwd) else apiGetActiveUser(rhId)).also {
|
(if (toUserId != null) apiSetActiveUser(rhId, toUserId, viewPwd) else apiGetActiveUser(rhId)).also {
|
||||||
|
@ -604,20 +604,20 @@ object ChatController {
|
||||||
val users = listUsers(rhId)
|
val users = listUsers(rhId)
|
||||||
chatModel.users.clear()
|
chatModel.users.clear()
|
||||||
chatModel.users.addAll(users)
|
chatModel.users.addAll(users)
|
||||||
getUserChatData(rhId)
|
getUserChatData(rhId, keepingChatId = keepingChatId)
|
||||||
val invitation = chatModel.callInvitations.values.firstOrNull { inv -> inv.user.userId == toUserId }
|
val invitation = chatModel.callInvitations.values.firstOrNull { inv -> inv.user.userId == toUserId }
|
||||||
if (invitation != null && currentUser != null) {
|
if (invitation != null && currentUser != null) {
|
||||||
chatModel.callManager.reportNewIncomingCall(invitation.copy(user = currentUser))
|
chatModel.callManager.reportNewIncomingCall(invitation.copy(user = currentUser))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserChatData(rhId: Long?) {
|
suspend fun getUserChatData(rhId: Long?, keepingChatId: String? = null) {
|
||||||
val hasUser = chatModel.currentUser.value != null
|
val hasUser = chatModel.currentUser.value != null
|
||||||
chatModel.userAddress.value = if (hasUser) apiGetUserAddress(rhId) else null
|
chatModel.userAddress.value = if (hasUser) apiGetUserAddress(rhId) else null
|
||||||
chatModel.chatItemTTL.value = if (hasUser) getChatItemTTL(rhId) else ChatItemTTL.None
|
chatModel.chatItemTTL.value = if (hasUser) getChatItemTTL(rhId) else ChatItemTTL.None
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val chats = apiGetChats(rhId)
|
val chats = apiGetChats(rhId)
|
||||||
chatModel.chatsContext.updateChats(chats)
|
chatModel.chatsContext.updateChats(chats, keepingChatId = keepingChatId)
|
||||||
}
|
}
|
||||||
chatModel.userTags.value = apiGetChatTags(rhId).takeIf { hasUser } ?: emptyList()
|
chatModel.userTags.value = apiGetChatTags(rhId).takeIf { hasUser } ?: emptyList()
|
||||||
chatModel.activeChatTagFilter.value = null
|
chatModel.activeChatTagFilter.value = null
|
||||||
|
@ -793,7 +793,7 @@ object ChatController {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiStartChat(ctrl: ChatCtrl? = null): Boolean {
|
suspend fun apiStartChat(ctrl: ChatCtrl? = null): Boolean {
|
||||||
val r = sendCmd(null, CC.StartChat(mainApp = true, largeLinkData = false), ctrl)
|
val r = sendCmd(null, CC.StartChat(mainApp = true, largeLinkData = true), ctrl)
|
||||||
when (r.result) {
|
when (r.result) {
|
||||||
is CR.ChatStarted -> return true
|
is CR.ChatStarted -> return true
|
||||||
is CR.ChatRunning -> return false
|
is CR.ChatRunning -> return false
|
||||||
|
@ -1391,19 +1391,19 @@ object ChatController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiPrepareContact(rh: Long?, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData): Contact? {
|
suspend fun apiPrepareContact(rh: Long?, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData): Chat? {
|
||||||
val userId = try { currentUserId("apiPrepareContact") } catch (e: Exception) { return null }
|
val userId = try { currentUserId("apiPrepareContact") } catch (e: Exception) { return null }
|
||||||
val r = sendCmd(rh, CC.APIPrepareContact(userId, connLink, contactShortLinkData))
|
val r = sendCmd(rh, CC.APIPrepareContact(userId, connLink, contactShortLinkData))
|
||||||
if (r is API.Result && r.res is CR.NewPreparedContact) return r.res.contact
|
if (r is API.Result && r.res is CR.NewPreparedChat) return r.res.chat
|
||||||
Log.e(TAG, "apiPrepareContact bad response: ${r.responseType} ${r.details}")
|
Log.e(TAG, "apiPrepareContact bad response: ${r.responseType} ${r.details}")
|
||||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_contact), "${r.responseType}: ${r.details}")
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_contact), "${r.responseType}: ${r.details}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiPrepareGroup(rh: Long?, connLink: CreatedConnLink, groupShortLinkData: GroupShortLinkData): GroupInfo? {
|
suspend fun apiPrepareGroup(rh: Long?, connLink: CreatedConnLink, groupShortLinkData: GroupShortLinkData): Chat? {
|
||||||
val userId = try { currentUserId("apiPrepareGroup") } catch (e: Exception) { return null }
|
val userId = try { currentUserId("apiPrepareGroup") } catch (e: Exception) { return null }
|
||||||
val r = sendCmd(rh, CC.APIPrepareGroup(userId, connLink, groupShortLinkData))
|
val r = sendCmd(rh, CC.APIPrepareGroup(userId, connLink, groupShortLinkData))
|
||||||
if (r is API.Result && r.res is CR.NewPreparedGroup) return r.res.groupInfo
|
if (r is API.Result && r.res is CR.NewPreparedChat) return r.res.chat
|
||||||
Log.e(TAG, "apiPrepareGroup bad response: ${r.responseType} ${r.details}")
|
Log.e(TAG, "apiPrepareGroup bad response: ${r.responseType} ${r.details}")
|
||||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_group), "${r.responseType}: ${r.details}")
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_preparing_group), "${r.responseType}: ${r.details}")
|
||||||
return null
|
return null
|
||||||
|
@ -1425,7 +1425,7 @@ object ChatController {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiConnectPreparedContact(rh: Long?, contactId: Long, incognito: Boolean, msg: MsgContent): Contact? {
|
suspend fun apiConnectPreparedContact(rh: Long?, contactId: Long, incognito: Boolean, msg: MsgContent?): Contact? {
|
||||||
val r = sendCmd(rh, CC.APIConnectPreparedContact(contactId, incognito, msg))
|
val r = sendCmd(rh, CC.APIConnectPreparedContact(contactId, incognito, msg))
|
||||||
if (r is API.Result && r.res is CR.StartedConnectionToContact) return r.res.contact
|
if (r is API.Result && r.res is CR.StartedConnectionToContact) return r.res.contact
|
||||||
Log.e(TAG, "apiConnectPreparedContact bad response: ${r.responseType} ${r.details}")
|
Log.e(TAG, "apiConnectPreparedContact bad response: ${r.responseType} ${r.details}")
|
||||||
|
@ -1435,8 +1435,8 @@ object ChatController {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean): GroupInfo? {
|
suspend fun apiConnectPreparedGroup(rh: Long?, groupId: Long, incognito: Boolean, msg: MsgContent?): GroupInfo? {
|
||||||
val r = sendCmd(rh, CC.APIConnectPreparedGroup(groupId, incognito))
|
val r = sendCmd(rh, CC.APIConnectPreparedGroup(groupId, incognito, msg))
|
||||||
if (r is API.Result && r.res is CR.StartedConnectionToGroup) return r.res.groupInfo
|
if (r is API.Result && r.res is CR.StartedConnectionToGroup) return r.res.groupInfo
|
||||||
Log.e(TAG, "apiConnectPreparedGroup bad response: ${r.responseType} ${r.details}")
|
Log.e(TAG, "apiConnectPreparedGroup bad response: ${r.responseType} ${r.details}")
|
||||||
if (!(networkErrorAlert(r))) {
|
if (!(networkErrorAlert(r))) {
|
||||||
|
@ -1631,9 +1631,9 @@ object ChatController {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun userAddressAutoAccept(rh: Long?, autoAccept: AutoAccept?): UserContactLinkRec? {
|
suspend fun apiSetUserAddressSettings(rh: Long?, settings: AddressSettings): UserContactLinkRec? {
|
||||||
val userId = kotlin.runCatching { currentUserId("userAddressAutoAccept") }.getOrElse { return null }
|
val userId = kotlin.runCatching { currentUserId("apiSetUserAddressSettings") }.getOrElse { return null }
|
||||||
val r = sendCmd(rh, CC.ApiAddressAutoAccept(userId, autoAccept))
|
val r = sendCmd(rh, CC.ApiSetAddressSettings(userId, settings))
|
||||||
if (r is API.Result && r.res is CR.UserContactLinkUpdated) return r.res.contactLink
|
if (r is API.Result && r.res is CR.UserContactLinkUpdated) return r.res.contactLink
|
||||||
if (r is API.Error && r.err is ChatError.ChatErrorStore
|
if (r is API.Error && r.err is ChatError.ChatErrorStore
|
||||||
&& r.err.storeError is StoreError.UserContactLinkNotFound
|
&& r.err.storeError is StoreError.UserContactLinkNotFound
|
||||||
|
@ -3493,8 +3493,8 @@ sealed class CC {
|
||||||
class APIPrepareGroup(val userId: Long, val connLink: CreatedConnLink, val groupShortLinkData: GroupShortLinkData): CC()
|
class APIPrepareGroup(val userId: Long, val connLink: CreatedConnLink, val groupShortLinkData: GroupShortLinkData): CC()
|
||||||
class APIChangePreparedContactUser(val contactId: Long, val newUserId: Long): CC()
|
class APIChangePreparedContactUser(val contactId: Long, val newUserId: Long): CC()
|
||||||
class APIChangePreparedGroupUser(val groupId: 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 APIConnectPreparedContact(val contactId: Long, val incognito: Boolean, val msg: MsgContent?): CC()
|
||||||
class APIConnectPreparedGroup(val groupId: Long, val incognito: Boolean): CC()
|
class APIConnectPreparedGroup(val groupId: Long, val incognito: Boolean, val msg: MsgContent?): CC()
|
||||||
class APIConnect(val userId: Long, val incognito: Boolean, val connLink: CreatedConnLink): 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 ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC()
|
||||||
class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC()
|
class ApiDeleteChat(val type: ChatType, val id: Long, val chatDeleteMode: ChatDeleteMode): CC()
|
||||||
|
@ -3512,7 +3512,7 @@ sealed class CC {
|
||||||
class ApiShowMyAddress(val userId: Long): CC()
|
class ApiShowMyAddress(val userId: Long): CC()
|
||||||
class ApiAddMyAddressShortLink(val userId: Long): CC()
|
class ApiAddMyAddressShortLink(val userId: Long): CC()
|
||||||
class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC()
|
class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC()
|
||||||
class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC()
|
class ApiSetAddressSettings(val userId: Long, val addressSettings: AddressSettings): CC()
|
||||||
class ApiGetCallInvitations: CC()
|
class ApiGetCallInvitations: CC()
|
||||||
class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC()
|
class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC()
|
||||||
class ApiRejectCall(val contact: Contact): CC()
|
class ApiRejectCall(val contact: Contact): CC()
|
||||||
|
@ -3688,8 +3688,8 @@ sealed class CC {
|
||||||
is APIPrepareGroup -> "/_prepare group $userId ${connLink.connFullLink} ${connLink.connShortLink ?: ""} ${json.encodeToString(groupShortLinkData)}"
|
is APIPrepareGroup -> "/_prepare group $userId ${connLink.connFullLink} ${connLink.connShortLink ?: ""} ${json.encodeToString(groupShortLinkData)}"
|
||||||
is APIChangePreparedContactUser -> "/_set contact user @$contactId $newUserId"
|
is APIChangePreparedContactUser -> "/_set contact user @$contactId $newUserId"
|
||||||
is APIChangePreparedGroupUser -> "/_set group user #$groupId $newUserId"
|
is APIChangePreparedGroupUser -> "/_set group user #$groupId $newUserId"
|
||||||
is APIConnectPreparedContact -> "/_connect contact @$contactId incognito=${onOff(incognito)} ${msg.cmdString}"
|
is APIConnectPreparedContact -> "/_connect contact @$contactId incognito=${onOff(incognito)} ${maybeContent(msg)}"
|
||||||
is APIConnectPreparedGroup -> "/_connect group #$groupId incognito=${onOff(incognito)}"
|
is APIConnectPreparedGroup -> "/_connect group #$groupId incognito=${onOff(incognito)} ${maybeContent(msg)}"
|
||||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}"
|
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}"
|
||||||
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
|
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
|
||||||
is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}"
|
is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}"
|
||||||
|
@ -3707,7 +3707,7 @@ sealed class CC {
|
||||||
is ApiShowMyAddress -> "/_show_address $userId"
|
is ApiShowMyAddress -> "/_show_address $userId"
|
||||||
is ApiAddMyAddressShortLink -> "/_short_link_address $userId"
|
is ApiAddMyAddressShortLink -> "/_short_link_address $userId"
|
||||||
is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}"
|
is ApiSetProfileAddress -> "/_profile_address $userId ${onOff(on)}"
|
||||||
is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}"
|
is ApiSetAddressSettings -> "/_address_settings $userId ${json.encodeToString(addressSettings)}"
|
||||||
is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId"
|
is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId"
|
||||||
is ApiRejectContact -> "/_reject $contactReqId"
|
is ApiRejectContact -> "/_reject $contactReqId"
|
||||||
is ApiGetCallInvitations -> "/_call get"
|
is ApiGetCallInvitations -> "/_call get"
|
||||||
|
@ -3880,7 +3880,7 @@ sealed class CC {
|
||||||
is ApiShowMyAddress -> "apiShowMyAddress"
|
is ApiShowMyAddress -> "apiShowMyAddress"
|
||||||
is ApiAddMyAddressShortLink -> "apiAddMyAddressShortLink"
|
is ApiAddMyAddressShortLink -> "apiAddMyAddressShortLink"
|
||||||
is ApiSetProfileAddress -> "apiSetProfileAddress"
|
is ApiSetProfileAddress -> "apiSetProfileAddress"
|
||||||
is ApiAddressAutoAccept -> "apiAddressAutoAccept"
|
is ApiSetAddressSettings -> "apiSetAddressSettings"
|
||||||
is ApiAcceptContact -> "apiAcceptContact"
|
is ApiAcceptContact -> "apiAcceptContact"
|
||||||
is ApiRejectContact -> "apiRejectContact"
|
is ApiRejectContact -> "apiRejectContact"
|
||||||
is ApiGetCallInvitations -> "apiGetCallInvitations"
|
is ApiGetCallInvitations -> "apiGetCallInvitations"
|
||||||
|
@ -3950,6 +3950,14 @@ sealed class CC {
|
||||||
|
|
||||||
private fun maybePwd(pwd: String?): String = if (pwd == "" || pwd == null) "" else " " + json.encodeToString(pwd)
|
private fun maybePwd(pwd: String?): String = if (pwd == "" || pwd == null) "" else " " + json.encodeToString(pwd)
|
||||||
|
|
||||||
|
private fun maybeContent(mc: MsgContent?): String {
|
||||||
|
return when {
|
||||||
|
mc is MsgContent.MCText && mc.text.isEmpty() -> ""
|
||||||
|
mc != null -> " " + mc.cmdString
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun chatRef(chatType: ChatType, id: Long, scope: GroupChatScope?) = when (scope) {
|
fun chatRef(chatType: ChatType, id: Long, scope: GroupChatScope?) = when (scope) {
|
||||||
null -> "${chatType.type}${id}"
|
null -> "${chatType.type}${id}"
|
||||||
|
@ -5913,8 +5921,7 @@ sealed class CR {
|
||||||
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
|
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
|
||||||
@Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR()
|
@Serializable @SerialName("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("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("newPreparedChat") class NewPreparedChat(val user: UserRef, val chat: Chat): 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("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("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("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||||
|
@ -6101,8 +6108,7 @@ sealed class CR {
|
||||||
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
|
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
|
||||||
is ConnectionUserChanged -> "ConnectionUserChanged"
|
is ConnectionUserChanged -> "ConnectionUserChanged"
|
||||||
is CRConnectionPlan -> "connectionPlan"
|
is CRConnectionPlan -> "connectionPlan"
|
||||||
is NewPreparedContact -> "newPreparedContact"
|
is NewPreparedChat -> "newPreparedChat"
|
||||||
is NewPreparedGroup -> "newPreparedGroup"
|
|
||||||
is ContactUserChanged -> "contactUserChanged"
|
is ContactUserChanged -> "contactUserChanged"
|
||||||
is GroupUserChanged -> "groupUserChanged"
|
is GroupUserChanged -> "groupUserChanged"
|
||||||
is SentConfirmation -> "sentConfirmation"
|
is SentConfirmation -> "sentConfirmation"
|
||||||
|
@ -6279,8 +6285,7 @@ sealed class CR {
|
||||||
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||||
is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" )
|
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 CRConnectionPlan -> withUser(user, "connLink: ${json.encodeToString(connLink)}\nconnectionPlan: ${json.encodeToString(connectionPlan)}")
|
||||||
is NewPreparedContact -> withUser(user, json.encodeToString(contact))
|
is NewPreparedChat -> withUser(user, json.encodeToString(chat))
|
||||||
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 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 GroupUserChanged -> withUser(user, "fromGroup: ${json.encodeToString(fromGroup)}\nnewUserId: ${json.encodeToString(newUser.userId)}\ntoGroup: ${json.encodeToString(toGroup)}")
|
||||||
is SentConfirmation -> withUser(user, json.encodeToString(connection))
|
is SentConfirmation -> withUser(user, json.encodeToString(connection))
|
||||||
|
@ -6621,25 +6626,18 @@ enum class RatchetSyncState {
|
||||||
data class UserContactLinkRec(
|
data class UserContactLinkRec(
|
||||||
val connLinkContact: CreatedConnLink,
|
val connLinkContact: CreatedConnLink,
|
||||||
val shortLinkDataSet: Boolean,
|
val shortLinkDataSet: Boolean,
|
||||||
val autoAccept: AutoAccept? = null
|
val addressSettings: AddressSettings
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class AutoAccept(val businessAddress: Boolean, val acceptIncognito: Boolean, val autoReply: MsgContent?) {
|
data class AddressSettings(
|
||||||
companion object {
|
val businessAddress: Boolean,
|
||||||
fun cmdString(autoAccept: AutoAccept?): String {
|
val autoAccept: AutoAccept?,
|
||||||
if (autoAccept == null) return "off"
|
val autoReply: MsgContent?
|
||||||
var s = "on"
|
)
|
||||||
if (autoAccept.acceptIncognito) {
|
|
||||||
s += " incognito=on"
|
@Serializable
|
||||||
} else if (autoAccept.businessAddress) {
|
data class AutoAccept(val acceptIncognito: Boolean)
|
||||||
s += " business"
|
|
||||||
}
|
|
||||||
val msg = autoAccept.autoReply ?: return s
|
|
||||||
return s + " " + msg.cmdString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class GroupLink(
|
data class GroupLink(
|
||||||
|
@ -6892,6 +6890,8 @@ sealed class StoreError {
|
||||||
is UserContactLinkNotFound -> "userContactLinkNotFound"
|
is UserContactLinkNotFound -> "userContactLinkNotFound"
|
||||||
is ContactRequestNotFound -> "contactRequestNotFound $contactRequestId"
|
is ContactRequestNotFound -> "contactRequestNotFound $contactRequestId"
|
||||||
is ContactRequestNotFoundByName -> "contactRequestNotFoundByName $contactName"
|
is ContactRequestNotFoundByName -> "contactRequestNotFoundByName $contactName"
|
||||||
|
is InvalidContactRequestEntity -> "invalidContactRequestEntity $contactRequestId"
|
||||||
|
is InvalidBusinessChatContactRequest -> "invalidBusinessChatContactRequest"
|
||||||
is GroupNotFound -> "groupNotFound $groupId"
|
is GroupNotFound -> "groupNotFound $groupId"
|
||||||
is GroupNotFoundByName -> "groupNotFoundByName $groupName"
|
is GroupNotFoundByName -> "groupNotFoundByName $groupName"
|
||||||
is GroupMemberNameNotFound -> "groupMemberNameNotFound $groupId $groupMemberName"
|
is GroupMemberNameNotFound -> "groupMemberNameNotFound $groupId $groupMemberName"
|
||||||
|
@ -6968,6 +6968,8 @@ sealed class StoreError {
|
||||||
@Serializable @SerialName("userContactLinkNotFound") object UserContactLinkNotFound: StoreError()
|
@Serializable @SerialName("userContactLinkNotFound") object UserContactLinkNotFound: StoreError()
|
||||||
@Serializable @SerialName("contactRequestNotFound") class ContactRequestNotFound(val contactRequestId: Long): StoreError()
|
@Serializable @SerialName("contactRequestNotFound") class ContactRequestNotFound(val contactRequestId: Long): StoreError()
|
||||||
@Serializable @SerialName("contactRequestNotFoundByName") class ContactRequestNotFoundByName(val contactName: String): StoreError()
|
@Serializable @SerialName("contactRequestNotFoundByName") class ContactRequestNotFoundByName(val contactName: String): StoreError()
|
||||||
|
@Serializable @SerialName("invalidContactRequestEntity") class InvalidContactRequestEntity(val contactRequestId: Long): StoreError()
|
||||||
|
@Serializable @SerialName("invalidBusinessChatContactRequest") object InvalidBusinessChatContactRequest: StoreError()
|
||||||
@Serializable @SerialName("groupNotFound") class GroupNotFound(val groupId: Long): StoreError()
|
@Serializable @SerialName("groupNotFound") class GroupNotFound(val groupId: Long): StoreError()
|
||||||
@Serializable @SerialName("groupNotFoundByName") class GroupNotFoundByName(val groupName: String): StoreError()
|
@Serializable @SerialName("groupNotFoundByName") class GroupNotFoundByName(val groupName: String): StoreError()
|
||||||
@Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError()
|
@Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError()
|
||||||
|
|
|
@ -177,15 +177,10 @@ fun ChatView(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if (
|
val connectingText = connectingText(chatInfo)
|
||||||
chatInfo is ChatInfo.Direct
|
if (connectingText != null) {
|
||||||
&& !chatInfo.contact.sndReady
|
|
||||||
&& chatInfo.contact.active
|
|
||||||
&& !chatInfo.contact.sendMsgToConnect
|
|
||||||
&& !chatInfo.contact.nextAcceptContactRequest
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
generalGetString(MR.strings.contact_connection_pending),
|
connectingText,
|
||||||
Modifier.padding(top = 4.dp),
|
Modifier.padding(top = 4.dp),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colors.secondary
|
color = MaterialTheme.colors.secondary
|
||||||
|
@ -714,6 +709,34 @@ fun ChatView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun connectingText(chatInfo: ChatInfo): String? {
|
||||||
|
return when (chatInfo) {
|
||||||
|
is ChatInfo.Direct ->
|
||||||
|
if (
|
||||||
|
!chatInfo.contact.sndReady
|
||||||
|
&& chatInfo.contact.active
|
||||||
|
&& !chatInfo.contact.sendMsgToConnect
|
||||||
|
&& !chatInfo.contact.nextAcceptContactRequest
|
||||||
|
) {
|
||||||
|
if (chatInfo.contact.preparedContact?.uiConnLinkType == ConnectionMode.Con) {
|
||||||
|
generalGetString(MR.strings.contact_should_accept)
|
||||||
|
} else {
|
||||||
|
generalGetString(MR.strings.contact_connection_pending)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
is ChatInfo.Group ->
|
||||||
|
when (chatInfo.groupInfo.membership.memberStatus) {
|
||||||
|
GroupMemberStatus.MemAccepted -> generalGetString(MR.strings.group_connection_pending) // TODO [short links] add member status to show transition from prepared group to started connection earlier?
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) {
|
fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
if (chatInfo is ChatInfo.Direct) {
|
if (chatInfo is ChatInfo.Direct) {
|
||||||
|
@ -1705,7 +1728,7 @@ fun BoxScope.ChatItemsList(
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val showAvatar = shouldShowAvatar(item, listItem.nextItem)
|
val showAvatar = shouldShowAvatar(item, merged.oldest().nextItem)
|
||||||
val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } }
|
val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } }
|
||||||
val itemSeparation: ItemSeparation
|
val itemSeparation: ItemSeparation
|
||||||
val prevItemSeparationLargeGap: Boolean
|
val prevItemSeparationLargeGap: Boolean
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
package chat.simplex.common.views.chat
|
||||||
|
|
||||||
|
import TextIconSpaced
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.*
|
||||||
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.helpers.*
|
||||||
|
import chat.simplex.common.views.newchat.IncognitoOptionImage
|
||||||
|
import chat.simplex.common.views.usersettings.IncognitoView
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
|
private val USER_ROW_AVATAR_SIZE = 42.dp
|
||||||
|
private val USER_ROW_VERTICAL_PADDING = 8.dp
|
||||||
|
private val USER_PICKER_ROW_SIZE = USER_ROW_AVATAR_SIZE + (USER_ROW_VERTICAL_PADDING * 2f)
|
||||||
|
private val MAX_USER_PICKER_HEIGHT = (USER_PICKER_ROW_SIZE * 4) + (USER_ROW_AVATAR_SIZE + USER_ROW_VERTICAL_PADDING - 4.dp)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ComposeContextProfilePickerView(
|
||||||
|
rhId: Long?,
|
||||||
|
chat: Chat,
|
||||||
|
currentUser: User
|
||||||
|
) {
|
||||||
|
val selectedUser = remember { mutableStateOf(currentUser) }
|
||||||
|
val incognitoDefault = chatModel.controller.appPrefs.incognito.get()
|
||||||
|
val users = chatModel.users.map { it.user }.filter { u -> u.activeUser || !u.hidden }
|
||||||
|
val listExpanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val maxHeightInPx = with(LocalDensity.current) { windowHeight().toPx() }
|
||||||
|
val isVisible = remember { mutableStateOf(false) }
|
||||||
|
val offsetY = remember { Animatable(maxHeightInPx) }
|
||||||
|
|
||||||
|
LaunchedEffect(isVisible.value) {
|
||||||
|
if (isVisible.value) {
|
||||||
|
offsetY.animateTo(
|
||||||
|
targetValue = 0f,
|
||||||
|
animationSpec = contextUserPickerAnimSpec()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExpandCollapseChevron() {
|
||||||
|
if (listExpanded.value) {
|
||||||
|
Icon(
|
||||||
|
painterResource(
|
||||||
|
MR.images.ic_chevron_down
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
Modifier.size(20.dp),
|
||||||
|
tint = MaterialTheme.colors.secondary,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
painterResource(
|
||||||
|
MR.images.ic_chevron_up
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
Modifier.size(20.dp),
|
||||||
|
tint = MaterialTheme.colors.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeProfile(newUser: User) {
|
||||||
|
withApi {
|
||||||
|
if (chat.chatInfo is ChatInfo.Direct) {
|
||||||
|
val updatedContact = chatModel.controller.apiChangePreparedContactUser(rhId, chat.chatInfo.contact.contactId, newUser.userId)
|
||||||
|
if (updatedContact != null) {
|
||||||
|
selectedUser.value = newUser
|
||||||
|
chatModel.controller.appPrefs.incognito.set(false)
|
||||||
|
listExpanded.value = false
|
||||||
|
chatModel.chatsContext.updateContact(rhId, updatedContact)
|
||||||
|
}
|
||||||
|
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||||
|
val updatedGroup = chatModel.controller.apiChangePreparedGroupUser(rhId, chat.chatInfo.groupInfo.groupId, newUser.userId)
|
||||||
|
if (updatedGroup != null) {
|
||||||
|
selectedUser.value = newUser
|
||||||
|
chatModel.controller.appPrefs.incognito.set(false)
|
||||||
|
listExpanded.value = false
|
||||||
|
chatModel.chatsContext.updateGroup(rhId, updatedGroup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chatModel.controller.changeActiveUser_(
|
||||||
|
rhId = newUser.remoteHostId,
|
||||||
|
toUserId = newUser.userId,
|
||||||
|
viewPwd = null,
|
||||||
|
keepingChatId = chat.id
|
||||||
|
)
|
||||||
|
if (chatModel.currentUser.value?.userId != newUser.userId) {
|
||||||
|
AlertManager.shared.showAlertMsg(generalGetString(
|
||||||
|
MR.strings.switching_profile_error_title),
|
||||||
|
String.format(generalGetString(MR.strings.switching_profile_error_message), newUser.chatViewName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfilePickerUserOption(user: User) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
|
||||||
|
.clickable(onClick = {
|
||||||
|
if (selectedUser.value.userId == user.userId) {
|
||||||
|
if (!incognitoDefault) {
|
||||||
|
listExpanded.value = !listExpanded.value
|
||||||
|
} else {
|
||||||
|
chatModel.controller.appPrefs.incognito.set(false)
|
||||||
|
listExpanded.value = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
changeProfile(user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
ProfileImage(size = USER_ROW_AVATAR_SIZE, image = user.image)
|
||||||
|
TextIconSpaced(false)
|
||||||
|
Text(
|
||||||
|
user.chatViewName,
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
|
fontWeight = if (selectedUser.value.userId == user.userId && !incognitoDefault) FontWeight.Medium else FontWeight.Normal
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
if (selectedUser.value.userId == user.userId && !incognitoDefault) {
|
||||||
|
ExpandCollapseChevron()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IncognitoOption() {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
|
||||||
|
.clickable(onClick = {
|
||||||
|
if (incognitoDefault) {
|
||||||
|
listExpanded.value = !listExpanded.value
|
||||||
|
} else {
|
||||||
|
chatModel.controller.appPrefs.incognito.set(true)
|
||||||
|
listExpanded.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
IncognitoOptionImage()
|
||||||
|
TextIconSpaced(false)
|
||||||
|
Text(
|
||||||
|
stringResource(MR.strings.incognito),
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
|
fontWeight = if (incognitoDefault) FontWeight.Medium else FontWeight.Normal
|
||||||
|
)
|
||||||
|
Spacer(Modifier.padding(6.dp))
|
||||||
|
Column(Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
if (ModalManager.end.isLastModalOpen(ModalViewId.CONTEXT_USER_PICKER_INCOGNITO)) {
|
||||||
|
ModalManager.end.closeModal()
|
||||||
|
} else {
|
||||||
|
ModalManager.end.showModal(id = ModalViewId.CONTEXT_USER_PICKER_INCOGNITO) { IncognitoView() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(MR.images.ic_info),
|
||||||
|
stringResource(MR.strings.incognito),
|
||||||
|
tint = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
|
if (incognitoDefault) {
|
||||||
|
ExpandCollapseChevron()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfilePicker() {
|
||||||
|
LazyColumnWithScrollBarNoAppBar(
|
||||||
|
Modifier
|
||||||
|
.heightIn(max = MAX_USER_PICKER_HEIGHT)
|
||||||
|
.background(MaterialTheme.colors.surface),
|
||||||
|
reverseLayout = true,
|
||||||
|
maxHeight = remember { mutableStateOf(MAX_USER_PICKER_HEIGHT) },
|
||||||
|
containerAlignment = Alignment.BottomEnd
|
||||||
|
) {
|
||||||
|
val otherUsers = users.filter { u -> u.userId != selectedUser.value.userId }.sortedByDescending { it.activeOrder }
|
||||||
|
|
||||||
|
if (incognitoDefault) {
|
||||||
|
item {
|
||||||
|
IncognitoOption()
|
||||||
|
Divider(
|
||||||
|
Modifier.padding(
|
||||||
|
start = DEFAULT_PADDING_HALF,
|
||||||
|
end = DEFAULT_PADDING_HALF,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ProfilePickerUserOption(selectedUser.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item {
|
||||||
|
ProfilePickerUserOption(selectedUser.value)
|
||||||
|
Divider(
|
||||||
|
Modifier.padding(
|
||||||
|
start = DEFAULT_PADDING_HALF,
|
||||||
|
end = DEFAULT_PADDING_HALF,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
IncognitoOption()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(otherUsers, key = { it.userId }) { user ->
|
||||||
|
Divider(
|
||||||
|
Modifier.padding(
|
||||||
|
start = DEFAULT_PADDING_HALF,
|
||||||
|
end = DEFAULT_PADDING_HALF,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ProfilePickerUserOption(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CurrentSelection() {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.background(MaterialTheme.colors.surface),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
generalGetString(MR.strings.context_user_picker_your_profile),
|
||||||
|
Modifier.padding(horizontal = 14.dp).padding(top = 8.dp),
|
||||||
|
color = MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
|
||||||
|
if (incognitoDefault) {
|
||||||
|
IncognitoOption()
|
||||||
|
} else {
|
||||||
|
ProfilePickerUserOption(selectedUser.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listExpanded.value) {
|
||||||
|
ProfilePicker()
|
||||||
|
} else {
|
||||||
|
CurrentSelection()
|
||||||
|
}
|
||||||
|
}
|
|
@ -511,25 +511,36 @@ fun ComposeView(
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||||
clearState()
|
clearState()
|
||||||
|
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendConnectPreparedContact() {
|
suspend fun sendConnectPreparedContact() {
|
||||||
val mc = checkLinkPreview()
|
val mc = checkLinkPreview()
|
||||||
// TODO [short links] use incognito default (incognito choice will be available via context profile picker)
|
val contact = chatModel.controller.apiConnectPreparedContact(
|
||||||
val contact = chatModel.controller.apiConnectPreparedContact(chat.remoteHostId, chat.chatInfo.apiId, incognito = false, msg = mc)
|
rh = chat.remoteHostId,
|
||||||
|
contactId = chat.chatInfo.apiId,
|
||||||
|
incognito = chatModel.controller.appPrefs.incognito.get(),
|
||||||
|
msg = mc
|
||||||
|
)
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
chatsCtx.updateContact(chat.remoteHostId, contact)
|
chatsCtx.updateContact(chat.remoteHostId, contact)
|
||||||
clearState()
|
clearState()
|
||||||
|
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun connectPreparedGroup() {
|
suspend fun sendConnectPreparedGroup() {
|
||||||
// TODO [short links] use incognito default (incognito choice will be available via context profile picker)
|
val mc = checkLinkPreview()
|
||||||
val groupInfo = chatModel.controller.apiConnectPreparedGroup(chat.remoteHostId, chat.chatInfo.apiId, incognito = false)
|
val groupInfo = chatModel.controller.apiConnectPreparedGroup(
|
||||||
|
rh = chat.remoteHostId,
|
||||||
|
groupId = chat.chatInfo.apiId,
|
||||||
|
incognito = chatModel.controller.appPrefs.incognito.get(),
|
||||||
|
msg = mc
|
||||||
|
)
|
||||||
if (groupInfo != null) {
|
if (groupInfo != null) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
chatsCtx.updateGroup(chat.remoteHostId, groupInfo)
|
chatsCtx.updateGroup(chat.remoteHostId, groupInfo)
|
||||||
|
@ -548,7 +559,7 @@ fun ComposeView(
|
||||||
} else if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextConnectPrepared) {
|
} else if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextConnectPrepared) {
|
||||||
sendConnectPreparedContact()
|
sendConnectPreparedContact()
|
||||||
} else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared) {
|
} else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared) {
|
||||||
connectPreparedGroup()
|
sendConnectPreparedGroup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
@ -1071,6 +1082,20 @@ fun ComposeView(
|
||||||
}
|
}
|
||||||
// TODO ^^^ (this shouldn't be here)
|
// TODO ^^^ (this shouldn't be here)
|
||||||
|
|
||||||
|
val currentUser = chatModel.currentUser.value
|
||||||
|
if ((
|
||||||
|
(chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextConnectPrepared)
|
||||||
|
|| (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared)
|
||||||
|
)
|
||||||
|
&& currentUser != null
|
||||||
|
) {
|
||||||
|
ComposeContextProfilePickerView(
|
||||||
|
rhId = rhId,
|
||||||
|
chat = chat,
|
||||||
|
currentUser = currentUser
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
chat.chatInfo is ChatInfo.Direct
|
chat.chatInfo is ChatInfo.Direct
|
||||||
&& chat.chatInfo.contact.nextAcceptContactRequest
|
&& chat.chatInfo.contact.nextAcceptContactRequest
|
||||||
|
|
|
@ -592,7 +592,9 @@ fun ModalData.GroupChatInfoLayout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
if (!groupInfo.nextConnectPrepared) {
|
||||||
|
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
||||||
|
}
|
||||||
SectionView {
|
SectionView {
|
||||||
ClearChatButton(clearChat)
|
ClearChatButton(clearChat)
|
||||||
if (groupInfo.canDelete) {
|
if (groupInfo.canDelete) {
|
||||||
|
|
|
@ -137,45 +137,82 @@ fun ChatPreviewView(
|
||||||
fun chatPreviewTitle() {
|
fun chatPreviewTitle() {
|
||||||
val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) }
|
val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) }
|
||||||
when (cInfo) {
|
when (cInfo) {
|
||||||
is ChatInfo.Direct ->
|
is ChatInfo.Direct -> {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
if (cInfo.contact.verified) {
|
||||||
if (cInfo.contact.verified) {
|
VerifiedIcon()
|
||||||
VerifiedIcon()
|
}
|
||||||
|
val color = if (deleting)
|
||||||
|
MaterialTheme.colors.secondary
|
||||||
|
else if (cInfo.contact.nextAcceptContactRequest || cInfo.contact.sendMsgToConnect) {
|
||||||
|
MaterialTheme.colors.primary
|
||||||
|
} else if (!cInfo.contact.sndReady) {
|
||||||
|
MaterialTheme.colors.secondary
|
||||||
|
} else {
|
||||||
|
Color.Unspecified
|
||||||
|
}
|
||||||
|
chatPreviewTitleText(color = color)
|
||||||
|
}
|
||||||
|
is ChatInfo.Group -> {
|
||||||
|
val color = if (deleting) {
|
||||||
|
MaterialTheme.colors.secondary
|
||||||
|
} else {
|
||||||
|
when (cInfo.groupInfo.membership.memberStatus) {
|
||||||
|
GroupMemberStatus.MemInvited -> if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary
|
||||||
|
GroupMemberStatus.MemAccepted, GroupMemberStatus.MemRejected -> MaterialTheme.colors.secondary
|
||||||
|
else -> if (cInfo.groupInfo.nextConnectPrepared) MaterialTheme.colors.primary else Color.Unspecified
|
||||||
}
|
}
|
||||||
chatPreviewTitleText(
|
|
||||||
if (deleting)
|
|
||||||
MaterialTheme.colors.secondary
|
|
||||||
else if (cInfo.contact.nextAcceptContactRequest) {
|
|
||||||
MaterialTheme.colors.primary
|
|
||||||
} else {
|
|
||||||
Color.Unspecified
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is ChatInfo.Group ->
|
|
||||||
when (cInfo.groupInfo.membership.memberStatus) {
|
|
||||||
GroupMemberStatus.MemInvited -> chatPreviewTitleText(
|
|
||||||
if (inProgress || deleting)
|
|
||||||
MaterialTheme.colors.secondary
|
|
||||||
else
|
|
||||||
if (chat.chatInfo.incognito) Indigo else MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
GroupMemberStatus.MemAccepted, GroupMemberStatus.MemRejected -> chatPreviewTitleText(MaterialTheme.colors.secondary)
|
|
||||||
else -> chatPreviewTitleText(
|
|
||||||
if (deleting)
|
|
||||||
MaterialTheme.colors.secondary
|
|
||||||
else
|
|
||||||
Color.Unspecified
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
chatPreviewTitleText(color = color)
|
||||||
|
}
|
||||||
else -> chatPreviewTitleText()
|
else -> chatPreviewTitleText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun chatPreviewInfoText(): Pair<String, Color>? {
|
||||||
|
return when (cInfo) {
|
||||||
|
is ChatInfo.Direct ->
|
||||||
|
if (cInfo.contact.activeConn == null && cInfo.contact.profile.contactLink != null && cInfo.contact.active) {
|
||||||
|
stringResource(MR.strings.contact_tap_to_connect) to MaterialTheme.colors.primary
|
||||||
|
} else if (cInfo.contact.sendMsgToConnect) {
|
||||||
|
stringResource(MR.strings.open_to_connect) to Color.Unspecified
|
||||||
|
} else if (cInfo.contact.nextAcceptContactRequest) {
|
||||||
|
stringResource(MR.strings.open_to_accept) to Color.Unspecified
|
||||||
|
} else if (!cInfo.contact.sndReady && cInfo.contact.activeConn != null && cInfo.contact.active) {
|
||||||
|
if (cInfo.contact.preparedContact?.uiConnLinkType == ConnectionMode.Con) {
|
||||||
|
stringResource(MR.strings.contact_should_accept) to Color.Unspecified
|
||||||
|
} else {
|
||||||
|
stringResource(MR.strings.contact_connection_pending) to Color.Unspecified
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
is ChatInfo.Group ->
|
||||||
|
if (cInfo.groupInfo.nextConnectPrepared) {
|
||||||
|
stringResource(MR.strings.group_preview_open_to_join) to Color.Unspecified
|
||||||
|
} else {
|
||||||
|
when (cInfo.groupInfo.membership.memberStatus) {
|
||||||
|
GroupMemberStatus.MemRejected -> stringResource(MR.strings.group_preview_rejected) to Color.Unspecified
|
||||||
|
GroupMemberStatus.MemInvited -> groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo) to Color.Unspecified
|
||||||
|
GroupMemberStatus.MemAccepted -> stringResource(MR.strings.group_connection_pending) to Color.Unspecified
|
||||||
|
GroupMemberStatus.MemPendingReview, GroupMemberStatus.MemPendingApproval ->
|
||||||
|
stringResource(MR.strings.reviewed_by_admins) to MaterialTheme.colors.secondary
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun chatPreviewText() {
|
fun chatPreviewText() {
|
||||||
|
val previewText = chatPreviewInfoText()
|
||||||
val ci = chat.chatItems.lastOrNull()
|
val ci = chat.chatItems.lastOrNull()
|
||||||
if (ci != null) {
|
if (ci?.content?.hasMsgContent != true && previewText != null) {
|
||||||
|
Text(previewText.first, color = previewText.second)
|
||||||
|
} else if (ci != null) {
|
||||||
if (showChatPreviews || (chatModelDraftChatId == chat.id && chatModelDraft != null)) {
|
if (showChatPreviews || (chatModelDraftChatId == chat.id && chatModelDraft != null)) {
|
||||||
val sp20 = with(LocalDensity.current) { 20.sp.toDp() }
|
val sp20 = with(LocalDensity.current) { 20.sp.toDp() }
|
||||||
val (text: CharSequence, inlineTextContent) = when {
|
val (text: CharSequence, inlineTextContent) = when {
|
||||||
|
@ -195,6 +232,7 @@ fun ChatPreviewView(
|
||||||
append(if (text.isEmpty()) mc.reason.text else "${mc.reason.text}: ")
|
append(if (text.isEmpty()) mc.reason.text else "${mc.reason.text}: ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +241,7 @@ fun ChatPreviewView(
|
||||||
formattedText,
|
formattedText,
|
||||||
sender = when {
|
sender = when {
|
||||||
chatModelDraftChatId == chat.id && chatModelDraft != null -> null
|
chatModelDraftChatId == chat.id && chatModelDraft != null -> null
|
||||||
cInfo is ChatInfo.Group && !ci.chatDir.sent -> ci.memberDisplayName
|
cInfo is ChatInfo.Group && !ci.chatDir.sent && !ci.meta.showGroupAsSender -> ci.memberDisplayName
|
||||||
else -> null
|
else -> null
|
||||||
},
|
},
|
||||||
mentions = ci.mentions,
|
mentions = ci.mentions,
|
||||||
|
@ -227,33 +265,6 @@ fun ChatPreviewView(
|
||||||
prefix = prefix
|
prefix = prefix
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
when (cInfo) {
|
|
||||||
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.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 ->
|
|
||||||
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 -> {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +357,10 @@ fun ChatPreviewView(
|
||||||
@Composable
|
@Composable
|
||||||
fun chatStatusImage() {
|
fun chatStatusImage() {
|
||||||
if (cInfo is ChatInfo.Direct) {
|
if (cInfo is ChatInfo.Direct) {
|
||||||
if (cInfo.contact.active && cInfo.contact.activeConn != null) {
|
if (
|
||||||
|
cInfo.contact.active &&
|
||||||
|
(cInfo.contact.activeConn?.connStatus == ConnStatus.Ready || cInfo.contact.activeConn?.connStatus == ConnStatus.SndReady)
|
||||||
|
) {
|
||||||
val descr = contactNetworkStatus?.statusString
|
val descr = contactNetworkStatus?.statusString
|
||||||
when (contactNetworkStatus) {
|
when (contactNetworkStatus) {
|
||||||
is NetworkStatus.Connected ->
|
is NetworkStatus.Connected ->
|
||||||
|
|
|
@ -40,7 +40,7 @@ fun ContactPreviewView(
|
||||||
contactType == ContactType.CARD -> MaterialTheme.colors.primary
|
contactType == ContactType.CARD -> MaterialTheme.colors.primary
|
||||||
contactType == ContactType.CONTACT_WITH_REQUEST -> MaterialTheme.colors.primary
|
contactType == ContactType.CONTACT_WITH_REQUEST -> MaterialTheme.colors.primary
|
||||||
contactType == ContactType.REQUEST -> MaterialTheme.colors.primary
|
contactType == ContactType.REQUEST -> MaterialTheme.colors.primary
|
||||||
contactType == ContactType.RECENT && chat.chatInfo.incognito -> Indigo
|
contactType == ContactType.RECENT -> if (chat.chatInfo.nextConnect) MaterialTheme.colors.primary else Color.Unspecified
|
||||||
else -> Color.Unspecified
|
else -> Color.Unspecified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,3 +11,5 @@ fun <T> audioProgressBarAnimationSpec() = tween<T>(durationMillis = 30, easing =
|
||||||
fun <T> userPickerAnimSpec() = tween<T>(256, 0, FastOutSlowInEasing)
|
fun <T> userPickerAnimSpec() = tween<T>(256, 0, FastOutSlowInEasing)
|
||||||
|
|
||||||
fun <T> mentionPickerAnimSpec() = tween<T>(256, 0, FastOutSlowInEasing)
|
fun <T> mentionPickerAnimSpec() = tween<T>(256, 0, FastOutSlowInEasing)
|
||||||
|
|
||||||
|
fun <T> contextUserPickerAnimSpec() = tween<T>(256, 0, FastOutSlowInEasing)
|
||||||
|
|
|
@ -31,12 +31,7 @@ import kotlin.math.max
|
||||||
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, shadow: Boolean = false) {
|
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, shadow: Boolean = false) {
|
||||||
val icon =
|
val icon =
|
||||||
when (chatInfo) {
|
when (chatInfo) {
|
||||||
is ChatInfo.Group ->
|
is ChatInfo.Group -> chatInfo.groupInfo.chatIconName
|
||||||
when (chatInfo.groupInfo.businessChat?.chatType) {
|
|
||||||
BusinessChatType.Business -> MR.images.ic_work_filled_padded
|
|
||||||
BusinessChatType.Customer -> MR.images.ic_account_circle_filled
|
|
||||||
null -> MR.images.ic_supervised_user_circle_filled
|
|
||||||
}
|
|
||||||
is ChatInfo.Local -> MR.images.ic_folder_filled
|
is ChatInfo.Local -> MR.images.ic_folder_filled
|
||||||
else -> MR.images.ic_account_circle_filled
|
else -> MR.images.ic_account_circle_filled
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,8 @@ class ModalData(val keyboardCoversBar: Boolean = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ModalViewId {
|
enum class ModalViewId {
|
||||||
SECONDARY_CHAT
|
SECONDARY_CHAT,
|
||||||
|
CONTEXT_USER_PICKER_INCOGNITO
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModalManager(private val placement: ModalPlacement? = null) {
|
class ModalManager(private val placement: ModalPlacement? = null) {
|
||||||
|
|
|
@ -382,6 +382,13 @@ fun askCurrentOrIncognitoProfileAlert(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openChat_(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, chat: Chat) {
|
||||||
|
withBGApi {
|
||||||
|
close?.invoke()
|
||||||
|
openChat(secondaryChatsCtx = null, rhId, chat.chatInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) {
|
fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val c = chatModel.getContactChat(contact.contactId)
|
val c = chatModel.getContactChat(contact.contactId)
|
||||||
|
@ -466,16 +473,22 @@ fun showPrepareContactAlert(
|
||||||
) {
|
) {
|
||||||
AlertManager.privacySensitive.showOpenChatAlert(
|
AlertManager.privacySensitive.showOpenChatAlert(
|
||||||
profileName = contactShortLinkData.profile.displayName,
|
profileName = contactShortLinkData.profile.displayName,
|
||||||
profileImage = { ProfileImage(size = 72.dp, image = contactShortLinkData.profile.image) },
|
profileImage = {
|
||||||
|
ProfileImage(
|
||||||
|
size = 72.dp,
|
||||||
|
image = contactShortLinkData.profile.image,
|
||||||
|
icon = if (contactShortLinkData.business) MR.images.ic_work_filled_padded else MR.images.ic_account_circle_filled
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmText = generalGetString(MR.strings.connect_plan_open_chat),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val contact = chatModel.controller.apiPrepareContact(rhId, connectionLink, contactShortLinkData)
|
val chat = chatModel.controller.apiPrepareContact(rhId, connectionLink, contactShortLinkData)
|
||||||
if (contact != null) {
|
if (chat != null) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val chatInfo = ChatInfo.Direct(contact)
|
ChatController.chatModel.chatsContext.addChat(chat)
|
||||||
ChatController.chatModel.chatsContext.addChat(Chat(rhId, chatInfo, chatItems = listOf()))
|
openChat_(chatModel, rhId, close, chat)
|
||||||
openKnownContact(chatModel, rhId, close, contact)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cleanup?.invoke()
|
cleanup?.invoke()
|
||||||
|
@ -497,15 +510,15 @@ fun showPrepareGroupAlert(
|
||||||
AlertManager.privacySensitive.showOpenChatAlert(
|
AlertManager.privacySensitive.showOpenChatAlert(
|
||||||
profileName = groupShortLinkData.groupProfile.displayName,
|
profileName = groupShortLinkData.groupProfile.displayName,
|
||||||
profileImage = { ProfileImage(size = 72.dp, image = groupShortLinkData.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled) },
|
profileImage = { ProfileImage(size = 72.dp, image = groupShortLinkData.groupProfile.image, icon = MR.images.ic_supervised_user_circle_filled) },
|
||||||
|
confirmText = generalGetString(MR.strings.connect_plan_open_group),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val groupInfo = chatModel.controller.apiPrepareGroup(rhId, connectionLink, groupShortLinkData)
|
val chat = chatModel.controller.apiPrepareGroup(rhId, connectionLink, groupShortLinkData)
|
||||||
if (groupInfo != null) {
|
if (chat != null) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val chatInfo = ChatInfo.Group(groupInfo, groupChatScope = null)
|
ChatController.chatModel.chatsContext.addChat(chat)
|
||||||
ChatController.chatModel.chatsContext.addChat(Chat(rhId, chatInfo, chatItems = listOf()))
|
openChat_(chatModel, rhId, close, chat)
|
||||||
openKnownGroup(chatModel, rhId, close, groupInfo)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cleanup?.invoke()
|
cleanup?.invoke()
|
||||||
|
|
|
@ -379,14 +379,7 @@ fun ActiveProfilePicker(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
image = {
|
image = {
|
||||||
Spacer(Modifier.width(8.dp))
|
IncognitoOptionImage()
|
||||||
Icon(
|
|
||||||
painterResource(MR.images.ic_theater_comedy_filled),
|
|
||||||
contentDescription = stringResource(MR.strings.incognito),
|
|
||||||
Modifier.size(32.dp),
|
|
||||||
tint = Indigo,
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(2.dp))
|
|
||||||
},
|
},
|
||||||
onInfo = { ModalManager.start.showModal { IncognitoView() } },
|
onInfo = { ModalManager.start.showModal { IncognitoView() } },
|
||||||
)
|
)
|
||||||
|
@ -550,6 +543,18 @@ fun ToggleShortLinkButton(short: MutableState<Boolean>) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IncognitoOptionImage() {
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Icon(
|
||||||
|
painterResource(MR.images.ic_theater_comedy_filled),
|
||||||
|
contentDescription = stringResource(MR.strings.incognito),
|
||||||
|
Modifier.size(32.dp),
|
||||||
|
tint = Indigo,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(2.dp))
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddContactLearnMoreButton() {
|
fun AddContactLearnMoreButton() {
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
|
@ -48,7 +48,7 @@ fun UserAddressView(
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
withBGApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
val u = chatModel.controller.apiSetProfileAddress(user?.value?.remoteHostId, on)
|
val u = chatModel.controller.apiSetProfileAddress(user.value?.remoteHostId, on)
|
||||||
if (u != null) {
|
if (u != null) {
|
||||||
chatModel.updateUser(u)
|
chatModel.updateUser(u)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,11 @@ fun UserAddressView(
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId)
|
val connReqContact = chatModel.controller.apiCreateUserAddress(user.value?.remoteHostId)
|
||||||
if (connReqContact != null) {
|
if (connReqContact != null) {
|
||||||
chatModel.userAddress.value = UserContactLinkRec(connReqContact, shortLinkDataSet = connReqContact.connShortLink != null)
|
chatModel.userAddress.value = UserContactLinkRec(
|
||||||
|
connReqContact,
|
||||||
|
shortLinkDataSet = connReqContact.connShortLink != null,
|
||||||
|
addressSettings = AddressSettings(businessAddress = false, autoAccept = null, autoReply = null)
|
||||||
|
)
|
||||||
|
|
||||||
AlertManager.shared.showAlertDialog(
|
AlertManager.shared.showAlertDialog(
|
||||||
title = generalGetString(MR.strings.share_address_with_contacts_question),
|
title = generalGetString(MR.strings.share_address_with_contacts_question),
|
||||||
|
@ -139,7 +143,7 @@ fun UserAddressView(
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val u = chatModel.controller.apiDeleteUserAddress(user?.value?.remoteHostId)
|
val u = chatModel.controller.apiDeleteUserAddress(user.value?.remoteHostId)
|
||||||
if (u != null) {
|
if (u != null) {
|
||||||
chatModel.userAddress.value = null
|
chatModel.userAddress.value = null
|
||||||
chatModel.updateUser(u)
|
chatModel.updateUser(u)
|
||||||
|
@ -151,12 +155,12 @@ fun UserAddressView(
|
||||||
destructive = true,
|
destructive = true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
saveAas = { aas: AutoAcceptState, savedAAS: MutableState<AutoAcceptState> ->
|
saveAddressSettings = { settings: AddressSettingsState, savedSettings: MutableState<AddressSettingsState> ->
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val address = chatModel.controller.userAddressAutoAccept(user?.value?.remoteHostId, aas.autoAccept)
|
val address = chatModel.controller.apiSetUserAddressSettings(user.value?.remoteHostId, settings.addressSettings)
|
||||||
if (address != null) {
|
if (address != null) {
|
||||||
chatModel.userAddress.value = address
|
chatModel.userAddress.value = address
|
||||||
savedAAS.value = aas
|
savedSettings.value = settings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -198,7 +202,7 @@ private fun UserAddressLayout(
|
||||||
sendEmail: (UserContactLinkRec) -> Unit,
|
sendEmail: (UserContactLinkRec) -> Unit,
|
||||||
setProfileAddress: (Boolean) -> Unit,
|
setProfileAddress: (Boolean) -> Unit,
|
||||||
deleteAddress: () -> Unit,
|
deleteAddress: () -> Unit,
|
||||||
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit,
|
saveAddressSettings: (AddressSettingsState, MutableState<AddressSettingsState>) -> Unit,
|
||||||
) {
|
) {
|
||||||
ColumnWithScrollBar {
|
ColumnWithScrollBar {
|
||||||
AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId))
|
AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId))
|
||||||
|
@ -222,8 +226,8 @@ private fun UserAddressLayout(
|
||||||
LearnMoreButton(learnMore)
|
LearnMoreButton(learnMore)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
|
val addressSettingsState = remember { mutableStateOf(AddressSettingsState(settings = userAddress.addressSettings)) }
|
||||||
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
|
val savedAddressSettingsState = remember { mutableStateOf(addressSettingsState.value) }
|
||||||
val showShortLink = remember { mutableStateOf(true) }
|
val showShortLink = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
SectionViewWithButton(
|
SectionViewWithButton(
|
||||||
|
@ -233,20 +237,20 @@ private fun UserAddressLayout(
|
||||||
SimpleXCreatedLinkQRCode(userAddress.connLinkContact, short = showShortLink.value)
|
SimpleXCreatedLinkQRCode(userAddress.connLinkContact, short = showShortLink.value)
|
||||||
ShareAddressButton { share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) }
|
ShareAddressButton { share(userAddress.connLinkContact.simplexChatUri(short = showShortLink.value)) }
|
||||||
// ShareViaEmailButton { sendEmail(userAddress) }
|
// ShareViaEmailButton { sendEmail(userAddress) }
|
||||||
BusinessAddressToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
|
BusinessAddressToggle(addressSettingsState) { saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) }
|
||||||
AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAas)
|
AddressSettingsButton(user, userAddress, shareViaProfile, setProfileAddress, saveAddressSettings)
|
||||||
if (userAddress.connLinkContact.connShortLink == null) {
|
if (userAddress.connLinkContact.connShortLink == null) {
|
||||||
AddShortLinkButton(text = stringResource(MR.strings.add_short_link), showAddShortLinkAlert)
|
AddShortLinkButton(text = stringResource(MR.strings.add_short_link), showAddShortLinkAlert)
|
||||||
} else if (!userAddress.shortLinkDataSet) {
|
} else if (!userAddress.shortLinkDataSet) {
|
||||||
AddShortLinkButton(text = stringResource(MR.strings.share_profile_via_link), showAddShortLinkAlert)
|
AddShortLinkButton(text = stringResource(MR.strings.share_profile_via_link), showAddShortLinkAlert)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoAcceptState.value.business) {
|
if (addressSettingsState.value.businessAddress) {
|
||||||
SectionTextFooter(stringResource(MR.strings.add_your_team_members_to_conversations))
|
SectionTextFooter(stringResource(MR.strings.add_your_team_members_to_conversations))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionDividerSpaced(maxTopPadding = autoAcceptState.value.business)
|
SectionDividerSpaced(maxTopPadding = addressSettingsState.value.businessAddress)
|
||||||
SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) {
|
SectionView(generalGetString(MR.strings.or_to_share_privately).uppercase()) {
|
||||||
CreateOneTimeLinkButton()
|
CreateOneTimeLinkButton()
|
||||||
}
|
}
|
||||||
|
@ -330,14 +334,14 @@ private fun AddressSettingsButton(
|
||||||
userAddress: UserContactLinkRec,
|
userAddress: UserContactLinkRec,
|
||||||
shareViaProfile: MutableState<Boolean>,
|
shareViaProfile: MutableState<Boolean>,
|
||||||
setProfileAddress: (Boolean) -> Unit,
|
setProfileAddress: (Boolean) -> Unit,
|
||||||
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit,
|
saveAddressSettings: (AddressSettingsState, MutableState<AddressSettingsState>) -> Unit,
|
||||||
) {
|
) {
|
||||||
SettingsActionItem(
|
SettingsActionItem(
|
||||||
painterResource(MR.images.ic_settings),
|
painterResource(MR.images.ic_settings),
|
||||||
stringResource(MR.strings.address_settings),
|
stringResource(MR.strings.address_settings),
|
||||||
click = {
|
click = {
|
||||||
ModalManager.start.showCustomModal { close ->
|
ModalManager.start.showCustomModal { close ->
|
||||||
UserAddressSettings(user, userAddress, shareViaProfile, setProfileAddress, saveAas, close = close)
|
UserAddressSettings(user, userAddress, shareViaProfile, setProfileAddress, saveAddressSettings, close = close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -349,20 +353,20 @@ private fun ModalData.UserAddressSettings(
|
||||||
userAddress: UserContactLinkRec,
|
userAddress: UserContactLinkRec,
|
||||||
shareViaProfile: MutableState<Boolean>,
|
shareViaProfile: MutableState<Boolean>,
|
||||||
setProfileAddress: (Boolean) -> Unit,
|
setProfileAddress: (Boolean) -> Unit,
|
||||||
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit,
|
saveAddressSettings: (AddressSettingsState, MutableState<AddressSettingsState>) -> Unit,
|
||||||
close: () -> Unit
|
close: () -> Unit
|
||||||
) {
|
) {
|
||||||
val autoAcceptState = remember { stateGetOrPut("autoAcceptState") { (AutoAcceptState(userAddress)) } }
|
val addressSettingsState = remember { stateGetOrPut("autoAcceptState") { (AddressSettingsState(userAddress.addressSettings)) } }
|
||||||
val autoAcceptStateSaved = remember { stateGetOrPut("autoAcceptStateSaved") { (autoAcceptState.value) } }
|
val savedAddressSettingsState = remember { stateGetOrPut("autoAcceptStateSaved") { (addressSettingsState.value) } }
|
||||||
|
|
||||||
fun onClose(close: () -> Unit): Boolean = if (autoAcceptState.value == autoAcceptStateSaved.value) {
|
fun onClose(close: () -> Unit): Boolean = if (addressSettingsState.value == savedAddressSettingsState.value) {
|
||||||
chatModel.centerPanelBackgroundClickHandler = null
|
chatModel.centerPanelBackgroundClickHandler = null
|
||||||
close()
|
close()
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
showUnsavedChangesAlert(
|
showUnsavedChangesAlert(
|
||||||
save = {
|
save = {
|
||||||
saveAas(autoAcceptState.value, autoAcceptStateSaved)
|
saveAddressSettings(addressSettingsState.value, savedAddressSettingsState)
|
||||||
chatModel.centerPanelBackgroundClickHandler = null
|
chatModel.centerPanelBackgroundClickHandler = null
|
||||||
close()
|
close()
|
||||||
},
|
},
|
||||||
|
@ -391,12 +395,20 @@ private fun ModalData.UserAddressSettings(
|
||||||
) {
|
) {
|
||||||
SectionView {
|
SectionView {
|
||||||
ShareWithContactsButton(shareViaProfile, setProfileAddress)
|
ShareWithContactsButton(shareViaProfile, setProfileAddress)
|
||||||
AutoAcceptToggle(autoAcceptState) { saveAas(autoAcceptState.value, autoAcceptStateSaved) }
|
AutoAcceptToggle(addressSettingsState) { saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) }
|
||||||
|
if (!chatModel.addressShortLinkDataSet && !addressSettingsState.value.businessAddress) {
|
||||||
|
AcceptIncognitoToggle(addressSettingsState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
SectionDividerSpaced()
|
||||||
|
|
||||||
if (autoAcceptState.value.enable) {
|
SectionView(stringResource(MR.strings.address_welcome_message).uppercase()) {
|
||||||
SectionDividerSpaced()
|
AutoReplyEditor(addressSettingsState)
|
||||||
AutoAcceptSection(autoAcceptState, autoAcceptStateSaved, saveAas)
|
}
|
||||||
|
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
||||||
|
|
||||||
|
saveAddressSettingsButton(addressSettingsState.value == savedAddressSettingsState.value) {
|
||||||
|
saveAddressSettings(addressSettingsState.value, savedAddressSettingsState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,33 +456,53 @@ fun ShareWithContactsButton(shareViaProfile: MutableState<Boolean>, setProfileAd
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BusinessAddressToggle(autoAcceptState: MutableState<AutoAcceptState>, saveAas: (AutoAcceptState) -> Unit) {
|
private fun BusinessAddressToggle(addressSettingsState: MutableState<AddressSettingsState>, saveAddressSettings: (AddressSettingsState) -> Unit) {
|
||||||
PreferenceToggleWithIcon(
|
PreferenceToggleWithIcon(
|
||||||
stringResource(MR.strings.business_address),
|
stringResource(MR.strings.business_address),
|
||||||
painterResource(MR.images.ic_work),
|
painterResource(MR.images.ic_work),
|
||||||
checked = autoAcceptState.value.business,
|
checked = addressSettingsState.value.businessAddress,
|
||||||
) { ba ->
|
) { businessToggle ->
|
||||||
autoAcceptState.value = if (ba)
|
addressSettingsState.value = if (businessToggle)
|
||||||
AutoAcceptState(enable = true, incognito = false, business = true, autoAcceptState.value.welcomeText)
|
AddressSettingsState(
|
||||||
|
businessAddress = true,
|
||||||
|
autoAccept = true,
|
||||||
|
autoAcceptIncognito = false,
|
||||||
|
autoReply = addressSettingsState.value.autoReply
|
||||||
|
)
|
||||||
else
|
else
|
||||||
AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, business = false, autoAcceptState.value.welcomeText)
|
AddressSettingsState(
|
||||||
saveAas(autoAcceptState.value)
|
businessAddress = false,
|
||||||
|
autoAccept = addressSettingsState.value.autoAccept,
|
||||||
|
autoAcceptIncognito = addressSettingsState.value.autoAcceptIncognito,
|
||||||
|
autoReply = addressSettingsState.value.autoReply
|
||||||
|
)
|
||||||
|
saveAddressSettings(addressSettingsState.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AutoAcceptToggle(autoAcceptState: MutableState<AutoAcceptState>, saveAas: (AutoAcceptState) -> Unit) {
|
private fun AutoAcceptToggle(addressSettingsState: MutableState<AddressSettingsState>, saveAddressSettings: (AddressSettingsState) -> Unit) {
|
||||||
PreferenceToggleWithIcon(
|
PreferenceToggleWithIcon(
|
||||||
stringResource(MR.strings.auto_accept_contact),
|
stringResource(MR.strings.auto_accept_contact),
|
||||||
painterResource(MR.images.ic_check),
|
painterResource(MR.images.ic_check),
|
||||||
disabled = autoAcceptState.value.business,
|
disabled = addressSettingsState.value.businessAddress,
|
||||||
checked = autoAcceptState.value.enable
|
checked = addressSettingsState.value.autoAccept
|
||||||
) {
|
) { autoAcceptToggle ->
|
||||||
autoAcceptState.value = if (!it)
|
addressSettingsState.value = if (autoAcceptToggle)
|
||||||
AutoAcceptState()
|
AddressSettingsState(
|
||||||
|
businessAddress = addressSettingsState.value.businessAddress,
|
||||||
|
autoAccept = true,
|
||||||
|
autoAcceptIncognito = addressSettingsState.value.autoAcceptIncognito,
|
||||||
|
autoReply = addressSettingsState.value.autoReply
|
||||||
|
)
|
||||||
else
|
else
|
||||||
AutoAcceptState(it, autoAcceptState.value.incognito, autoAcceptState.value.business, autoAcceptState.value.welcomeText)
|
AddressSettingsState(
|
||||||
saveAas(autoAcceptState.value)
|
businessAddress = false,
|
||||||
|
autoAccept = false,
|
||||||
|
autoAcceptIncognito = addressSettingsState.value.autoAcceptIncognito,
|
||||||
|
autoReply = addressSettingsState.value.autoReply
|
||||||
|
)
|
||||||
|
saveAddressSettings(addressSettingsState.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,103 +517,110 @@ private fun DeleteAddressButton(onClick: () -> Unit) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AutoAcceptState {
|
private class AddressSettingsState {
|
||||||
var enable: Boolean = false
|
var businessAddress: Boolean = false
|
||||||
private set
|
private set
|
||||||
var incognito: Boolean = false
|
var autoAccept: Boolean = false
|
||||||
private set
|
private set
|
||||||
var business: Boolean = false
|
var autoAcceptIncognito: Boolean = false
|
||||||
private set
|
private set
|
||||||
var welcomeText: String = ""
|
var autoReply: String = ""
|
||||||
private set
|
private set
|
||||||
|
|
||||||
constructor(enable: Boolean = false, incognito: Boolean = false, business: Boolean = false, welcomeText: String = "") {
|
constructor(businessAddress: Boolean = false, autoAccept: Boolean = false, autoAcceptIncognito: Boolean = false, autoReply: String = "") {
|
||||||
this.enable = enable
|
this.businessAddress = businessAddress
|
||||||
this.incognito = incognito
|
this.autoAccept = autoAccept
|
||||||
this.business = business
|
this.autoAcceptIncognito = autoAcceptIncognito
|
||||||
this.welcomeText = welcomeText
|
this.autoReply = autoReply
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(contactLink: UserContactLinkRec) {
|
constructor(settings: AddressSettings) {
|
||||||
contactLink.autoAccept?.let { aa ->
|
this.businessAddress = settings.businessAddress
|
||||||
enable = true
|
this.autoAccept = settings.autoAccept != null
|
||||||
incognito = aa.acceptIncognito
|
this.autoAcceptIncognito = settings.autoAccept?.acceptIncognito == true
|
||||||
business = aa.businessAddress
|
this.autoReply = settings.autoReply?.text ?: ""
|
||||||
aa.autoReply?.let { msg ->
|
|
||||||
welcomeText = msg.text
|
|
||||||
} ?: run {
|
|
||||||
welcomeText = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val autoAccept: AutoAccept?
|
val addressSettings: AddressSettings
|
||||||
get() {
|
get() {
|
||||||
if (enable) {
|
return AddressSettings(
|
||||||
var autoReply: MsgContent? = null
|
businessAddress = this.businessAddress,
|
||||||
val s = welcomeText.trim()
|
autoAccept = if (this.autoAccept) AutoAccept(acceptIncognito = this.autoAcceptIncognito) else null,
|
||||||
if (s != "") {
|
autoReply = if (this.autoReply.isEmpty()) null else MsgContent.MCText(this.autoReply)
|
||||||
autoReply = MsgContent.MCText(s)
|
)
|
||||||
}
|
|
||||||
return AutoAccept(business, incognito, autoReply)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other !is AutoAcceptState) return false
|
if (other !is AddressSettingsState) return false
|
||||||
return this.enable == other.enable && this.incognito == other.incognito && this.business == other.business && this.welcomeText == other.welcomeText
|
return (
|
||||||
|
this.businessAddress == other.businessAddress
|
||||||
|
&& this.autoAccept == other.autoAccept
|
||||||
|
&& this.autoAcceptIncognito == other.autoAcceptIncognito
|
||||||
|
&& this.autoReply == other.autoReply
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = enable.hashCode()
|
var result = businessAddress.hashCode()
|
||||||
result = 31 * result + incognito.hashCode()
|
result = 31 * result + autoAccept.hashCode()
|
||||||
result = 31 * result + business.hashCode()
|
result = 31 * result + autoAcceptIncognito.hashCode()
|
||||||
result = 31 * result + welcomeText.hashCode()
|
result = 31 * result + autoReply.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AutoAcceptSection(
|
private fun AutoAcceptSection(
|
||||||
autoAcceptState: MutableState<AutoAcceptState>,
|
addressSettingsState: MutableState<AddressSettingsState>,
|
||||||
savedAutoAcceptState: MutableState<AutoAcceptState>,
|
savedAddressSettingsStatee: MutableState<AddressSettingsState>,
|
||||||
saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit
|
saveAddressSettings: (AddressSettingsState, MutableState<AddressSettingsState>) -> Unit
|
||||||
) {
|
) {
|
||||||
SectionView(stringResource(MR.strings.auto_accept_contact).uppercase()) {
|
SectionView(stringResource(MR.strings.auto_accept_contact).uppercase()) {
|
||||||
if (!chatModel.addressShortLinkDataSet && !autoAcceptState.value.business) {
|
if (!chatModel.addressShortLinkDataSet && !addressSettingsState.value.businessAddress) {
|
||||||
AcceptIncognitoToggle(autoAcceptState)
|
AcceptIncognitoToggle(addressSettingsState)
|
||||||
|
}
|
||||||
|
AutoReplyEditor(addressSettingsState)
|
||||||
|
saveAddressSettingsButton(addressSettingsState.value == savedAddressSettingsStatee.value) {
|
||||||
|
saveAddressSettings(addressSettingsState.value, savedAddressSettingsStatee)
|
||||||
}
|
}
|
||||||
WelcomeMessageEditor(autoAcceptState)
|
|
||||||
SaveAASButton(autoAcceptState.value == savedAutoAcceptState.value) { saveAas(autoAcceptState.value, savedAutoAcceptState) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AcceptIncognitoToggle(autoAcceptState: MutableState<AutoAcceptState>) {
|
private fun AcceptIncognitoToggle(addressSettingsState: MutableState<AddressSettingsState>) {
|
||||||
PreferenceToggleWithIcon(
|
PreferenceToggleWithIcon(
|
||||||
stringResource(MR.strings.accept_contact_incognito_button),
|
stringResource(MR.strings.accept_contact_incognito_button),
|
||||||
if (autoAcceptState.value.incognito) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy),
|
if (addressSettingsState.value.autoAcceptIncognito) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy),
|
||||||
if (autoAcceptState.value.incognito) Indigo else MaterialTheme.colors.secondary,
|
if (addressSettingsState.value.autoAcceptIncognito) Indigo else MaterialTheme.colors.secondary,
|
||||||
checked = autoAcceptState.value.incognito,
|
checked = addressSettingsState.value.autoAcceptIncognito,
|
||||||
) {
|
) { incognitoToggle ->
|
||||||
autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, it, autoAcceptState.value.business, autoAcceptState.value.welcomeText)
|
addressSettingsState.value = AddressSettingsState(
|
||||||
|
businessAddress = addressSettingsState.value.businessAddress,
|
||||||
|
autoAccept = addressSettingsState.value.autoAccept,
|
||||||
|
autoAcceptIncognito = incognitoToggle,
|
||||||
|
autoReply = addressSettingsState.value.autoReply
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WelcomeMessageEditor(autoAcceptState: MutableState<AutoAcceptState>) {
|
private fun AutoReplyEditor(addressSettingsState: MutableState<AddressSettingsState>) {
|
||||||
val welcomeText = rememberSaveable { mutableStateOf(autoAcceptState.value.welcomeText) }
|
val autoReply = rememberSaveable { mutableStateOf(addressSettingsState.value.autoReply) }
|
||||||
TextEditor(welcomeText, Modifier.height(100.dp), placeholder = stringResource(MR.strings.enter_welcome_message_optional))
|
TextEditor(autoReply, Modifier.height(100.dp), placeholder = stringResource(MR.strings.enter_welcome_message_optional))
|
||||||
LaunchedEffect(welcomeText.value) {
|
LaunchedEffect(autoReply.value) {
|
||||||
if (welcomeText.value != autoAcceptState.value.welcomeText) {
|
if (autoReply.value != addressSettingsState.value.autoReply) {
|
||||||
autoAcceptState.value = AutoAcceptState(autoAcceptState.value.enable, autoAcceptState.value.incognito, autoAcceptState.value.business, welcomeText.value)
|
addressSettingsState.value = AddressSettingsState(
|
||||||
|
businessAddress = addressSettingsState.value.businessAddress,
|
||||||
|
autoAccept = addressSettingsState.value.autoAccept,
|
||||||
|
autoAcceptIncognito = addressSettingsState.value.autoAcceptIncognito,
|
||||||
|
autoReply = autoReply.value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SaveAASButton(disabled: Boolean, onClick: () -> Unit) {
|
private fun saveAddressSettingsButton(disabled: Boolean, onClick: () -> Unit) {
|
||||||
SectionItemView(onClick, disabled = disabled) {
|
SectionItemView(onClick, disabled = disabled) {
|
||||||
Text(stringResource(MR.strings.save_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
|
Text(stringResource(MR.strings.save_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
|
||||||
}
|
}
|
||||||
|
@ -602,7 +641,7 @@ fun PreviewUserAddressLayoutNoAddress() {
|
||||||
showAddShortLinkAlert = {},
|
showAddShortLinkAlert = {},
|
||||||
share = { _ -> },
|
share = { _ -> },
|
||||||
deleteAddress = {},
|
deleteAddress = {},
|
||||||
saveAas = { _, _ -> },
|
saveAddressSettings = { _, _ -> },
|
||||||
setProfileAddress = { _ -> },
|
setProfileAddress = { _ -> },
|
||||||
learnMore = {},
|
learnMore = {},
|
||||||
shareViaProfile = remember { mutableStateOf(false) },
|
shareViaProfile = remember { mutableStateOf(false) },
|
||||||
|
@ -631,12 +670,16 @@ fun PreviewUserAddressLayoutAddressCreated() {
|
||||||
SimpleXTheme {
|
SimpleXTheme {
|
||||||
UserAddressLayout(
|
UserAddressLayout(
|
||||||
user = User.sampleData,
|
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), shortLinkDataSet = false),
|
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,
|
||||||
|
addressSettings = AddressSettings(businessAddress = false, autoAccept = null, autoReply = null)
|
||||||
|
),
|
||||||
createAddress = {},
|
createAddress = {},
|
||||||
showAddShortLinkAlert = {},
|
showAddShortLinkAlert = {},
|
||||||
share = { _ -> },
|
share = { _ -> },
|
||||||
deleteAddress = {},
|
deleteAddress = {},
|
||||||
saveAas = { _, _ -> },
|
saveAddressSettings = { _, _ -> },
|
||||||
setProfileAddress = { _ -> },
|
setProfileAddress = { _ -> },
|
||||||
learnMore = {},
|
learnMore = {},
|
||||||
shareViaProfile = remember { mutableStateOf(false) },
|
shareViaProfile = remember { mutableStateOf(false) },
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
<string name="you_will_join_group">You will connect to all group members.</string>
|
<string name="you_will_join_group">You will connect to all group members.</string>
|
||||||
<string name="connect_via_link_verb">Connect</string>
|
<string name="connect_via_link_verb">Connect</string>
|
||||||
<string name="connect_via_link_incognito">Connect incognito</string>
|
<string name="connect_via_link_incognito">Connect incognito</string>
|
||||||
<string name="connect_plan_open_chat">Open</string>
|
<string name="connect_plan_open_chat">Open chat</string>
|
||||||
|
<string name="connect_plan_open_group">Open group</string>
|
||||||
<string name="error_parsing_uri_title">Invalid link</string>
|
<string name="error_parsing_uri_title">Invalid link</string>
|
||||||
<string name="error_parsing_uri_desc">Please check that SimpleX link is correct.</string>
|
<string name="error_parsing_uri_desc">Please check that SimpleX link is correct.</string>
|
||||||
|
|
||||||
|
@ -428,9 +429,9 @@
|
||||||
<string name="toolbar_settings">Settings</string>
|
<string name="toolbar_settings">Settings</string>
|
||||||
<string name="contact_connection_pending">connecting…</string>
|
<string name="contact_connection_pending">connecting…</string>
|
||||||
<string name="member_contact_send_direct_message">send to connect</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_open_to_join">Open to join</string>
|
||||||
<string name="group_preview_you_are_invited">you are invited to group</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_join_as">Join as %s</string>
|
||||||
<string name="group_preview_rejected">rejected</string>
|
<string name="group_preview_rejected">rejected</string>
|
||||||
<string name="group_connection_pending">connecting…</string>
|
<string name="group_connection_pending">connecting…</string>
|
||||||
<string name="tap_to_start_new_chat">Tap to start a new chat</string>
|
<string name="tap_to_start_new_chat">Tap to start a new chat</string>
|
||||||
|
@ -443,7 +444,9 @@
|
||||||
<string name="no_chats">No chats</string>
|
<string name="no_chats">No chats</string>
|
||||||
<string name="no_chats_found">No chats found</string>
|
<string name="no_chats_found">No chats found</string>
|
||||||
<string name="contact_tap_to_connect">Tap to Connect</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="open_to_connect">Open to connect</string>
|
||||||
|
<string name="open_to_accept">Open to accept</string>
|
||||||
|
<string name="contact_should_accept">contact should accept…</string>
|
||||||
<string name="connect_with_contact_name_question">Connect with %1$s?</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="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>
|
<string name="address_creation_instruction">Tap Create SimpleX address in the menu to create it later.</string>
|
||||||
|
@ -523,6 +526,7 @@
|
||||||
|
|
||||||
<string name="cant_send_message_alert_title">You can\'t send messages!</string>
|
<string name="cant_send_message_alert_title">You can\'t send messages!</string>
|
||||||
<string name="cant_send_message_contact_not_ready">contact not ready</string>
|
<string name="cant_send_message_contact_not_ready">contact not ready</string>
|
||||||
|
<string name="cant_send_message_request_is_sent">request is sent</string>
|
||||||
<string name="cant_send_message_contact_deleted">contact deleted</string>
|
<string name="cant_send_message_contact_deleted">contact deleted</string>
|
||||||
<string name="cant_send_message_contact_not_synchronized">not synchronized</string>
|
<string name="cant_send_message_contact_not_synchronized">not synchronized</string>
|
||||||
<string name="cant_send_message_contact_disabled">contact disabled</string>
|
<string name="cant_send_message_contact_disabled">contact disabled</string>
|
||||||
|
@ -855,6 +859,9 @@
|
||||||
<string name="no_filtered_contacts">No filtered contacts</string>
|
<string name="no_filtered_contacts">No filtered contacts</string>
|
||||||
<string name="contact_list_header_title">Your contacts</string>
|
<string name="contact_list_header_title">Your contacts</string>
|
||||||
|
|
||||||
|
<!-- ComposeContextProfilePickerView.kt -->
|
||||||
|
<string name="context_user_picker_your_profile">Your profile</string>
|
||||||
|
|
||||||
<!-- ScanCodeView.kt -->
|
<!-- ScanCodeView.kt -->
|
||||||
<string name="scan_code">Scan code</string>
|
<string name="scan_code">Scan code</string>
|
||||||
<string name="incorrect_code">Incorrect security code!</string>
|
<string name="incorrect_code">Incorrect security code!</string>
|
||||||
|
@ -1055,9 +1062,10 @@
|
||||||
<string name="stop_sharing_address">Stop sharing address?</string>
|
<string name="stop_sharing_address">Stop sharing address?</string>
|
||||||
<string name="stop_sharing">Stop sharing</string>
|
<string name="stop_sharing">Stop sharing</string>
|
||||||
<string name="auto_accept_contact">Auto-accept</string>
|
<string name="auto_accept_contact">Auto-accept</string>
|
||||||
|
<string name="address_welcome_message">Welcome message</string>
|
||||||
<string name="enter_welcome_message_optional">Enter welcome message… (optional)</string>
|
<string name="enter_welcome_message_optional">Enter welcome message… (optional)</string>
|
||||||
<string name="save_settings_question">Save settings?</string>
|
<string name="save_settings_question">Save settings?</string>
|
||||||
<string name="save_auto_accept_settings">Save auto-accept settings</string>
|
<string name="save_auto_accept_settings">Save SimpleX address settings</string>
|
||||||
<string name="delete_address">Delete address</string>
|
<string name="delete_address">Delete address</string>
|
||||||
<string name="invite_friends">Invite friends</string>
|
<string name="invite_friends">Invite friends</string>
|
||||||
<string name="email_invite_subject">Let\'s talk in SimpleX Chat</string>
|
<string name="email_invite_subject">Let\'s talk in SimpleX Chat</string>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22" fill="#1f1f1f"><path d="M480-358q-6 0-10.75-2t-9.25-7L263.5-563.5q-9-8.5-8.5-20.5t9.5-20.5q9-8.5 20.75-8.5t20.25 8.5L480-429l175.5-175.5q8.5-8.5 19.75-8t20.25 9q9 8.5 9 20.5t-8.5 20.5L500.5-367q-5 5-9.75 7T480-358Z"/></svg>
|
After Width: | Height: | Size: 311 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="22" viewBox="0 -960 960 960" width="22" fill="#1f1f1f"><path d="M480-555 304.53-379.53Q296-371 284.75-371.5q-11.25-.5-20.25-9t-9-20.5q0-12 9.08-21.08l195.33-195.33q8.59-8.59 20.14-8.59 11.54 0 20.45 8.5L697-421q8.5 9 8.5 20.5t-9 20q-9 8.5-20.75 8.5t-20.28-8.53L480-555Z"/></svg>
|
After Width: | Height: | Size: 326 B |
Loading…
Add table
Add a link
Reference in a new issue