desktop, android: align types, address settings

This commit is contained in:
spaced4ndy 2025-06-27 13:11:24 +04:00
parent 8b770ca03e
commit aa5901804b
10 changed files with 265 additions and 177 deletions

View file

@ -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)

View file

@ -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
} }

View file

@ -438,14 +438,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 +1504,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 +1540,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
@ -1699,6 +1709,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 +1919,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 +1931,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 +1958,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 +1991,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 +2001,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 +2910,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 +2936,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 +3082,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 +3122,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 +3142,8 @@ data class CIMeta (
itemLive = false, itemLive = false,
deletable = false, deletable = false,
editable = false, editable = false,
userMention = false userMention = false,
showGroupAsSender = false
) )
} }
} }
@ -3469,6 +3498,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

View file

@ -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()

View file

@ -527,9 +527,10 @@ fun ComposeView(
} }
} }
suspend fun connectPreparedGroup() { suspend fun sendConnectPreparedGroup() {
val mc = checkLinkPreview()
// TODO [short links] use incognito default (incognito choice will be available via context profile picker) // TODO [short links] use incognito default (incognito choice will be available via context profile picker)
val groupInfo = chatModel.controller.apiConnectPreparedGroup(chat.remoteHostId, chat.chatInfo.apiId, incognito = false) val groupInfo = chatModel.controller.apiConnectPreparedGroup(chat.remoteHostId, chat.chatInfo.apiId, incognito = false, 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 +549,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()
} }
} }
}) { }) {

View file

@ -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) {

View file

@ -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
} }

View file

@ -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)
@ -470,12 +477,11 @@ fun showPrepareContactAlert(
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()
@ -500,12 +506,11 @@ fun showPrepareGroupAlert(
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()

View file

@ -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) },

View file

@ -429,8 +429,8 @@
<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>
@ -523,6 +523,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>
@ -1055,9 +1056,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>