Merge branch 'master' into badge-in-app-checkout

This commit is contained in:
spaced4ndy
2026-10-09 15:00:36 +04:00
94 changed files with 2255 additions and 405 deletions
@@ -1115,7 +1115,7 @@ object ChatModel {
fun updateCurrentUser(rhId: Long?, newProfile: Profile, preferences: FullChatPreferences? = null) {
val current = currentUser.value ?: return
val updated = current.copy(
profile = newProfile.toLocalProfile(current.profile.profileId),
profile = newProfile.toLocalProfile(current.profile.profileId).copy(localBadge = current.profile.localBadge, contactDomainVerified = current.profile.contactDomainVerified),
fullPreferences = preferences ?: current.fullPreferences
)
val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
@@ -1959,8 +1959,10 @@ object ChatController {
val r = sendCmd(rh, CC.ApiUpdateProfile(userId, profile))
if (r is API.Result && r.res is CR.UserProfileNoChange) return profile to emptyList()
if (r is API.Result && r.res is CR.UserProfileUpdated) return r.res.toProfile to r.res.updateSummary.changedContacts
if (r is API.Error && r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.DuplicateName) {
if (r is API.Error && (r.err is ChatError.ChatErrorStore && r.err.storeError is StoreError.DuplicateName || r.err is ChatError.ChatErrorChat && r.err.errorType is ChatErrorType.UserExists)) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_duplicate_title), generalGetString(MR.strings.failed_to_create_user_duplicate_desc))
} else if (!(networkErrorAlert(r))) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_profile), "${r.responseType}: ${r.details}")
}
Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}")
return null
@@ -1972,7 +1974,12 @@ object ChatController {
return when {
r is API.Result && r.res is CR.UserProfileNoChange -> null
r is API.Result && r.res is CR.UserProfileUpdated -> r.res.user.updateRemoteHostId(rh)
else -> throw Exception("failed to set profile address: ${r.responseType} ${r.details}")
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiSetProfileAddress", generalGetString(MR.strings.error_saving_profile), r)
}
throw Exception("failed to set profile address: ${r.responseType} ${r.details}")
}
}
}
@@ -4948,6 +4955,7 @@ sealed class UserServersError {
@Serializable @SerialName("storageMissing") data class StorageMissing(val protocol: ServerProtocol, val user: UserRef?): UserServersError()
@Serializable @SerialName("proxyMissing") data class ProxyMissing(val protocol: ServerProtocol, val user: UserRef?): UserServersError()
@Serializable @SerialName("duplicateServer") data class DuplicateServer(val protocol: ServerProtocol, val duplicateServer: String, val duplicateHost: String): UserServersError()
@Serializable @SerialName("tooManyHosts") data class TooManyHosts(val protocol: ServerProtocol, val tooManyHostsServer: String): UserServersError()
@Serializable @SerialName("duplicateChatRelayAddress") data class DuplicateChatRelayAddress(val duplicateChatRelay: String, val duplicateAddress: String): UserServersError()
val globalError: String?
@@ -4963,6 +4971,7 @@ sealed class UserServersError {
is StorageMissing -> this.protocol
is ProxyMissing -> this.protocol
is DuplicateServer -> this.protocol
is TooManyHosts -> this.protocol
is DuplicateChatRelayAddress -> null
}
@@ -4978,6 +4987,8 @@ sealed class UserServersError {
is ProxyMissing -> this.user?.let { "${userStr(it)} ${generalGetString(MR.strings.no_message_servers_configured_for_private_routing)}" }
?: generalGetString(MR.strings.no_message_servers_configured_for_private_routing)
is TooManyHosts -> String.format(generalGetString(MR.strings.server_has_too_many_hosts), ServerAddress.parseServerAddress(this.tooManyHostsServer)?.hostnames?.firstOrNull() ?: this.tooManyHostsServer)
else -> null
}
} else {
@@ -7651,7 +7662,8 @@ enum class RcvSwitchStatus {
@SerialName("switch_started") SwitchStarted,
@SerialName("sending_qadd") SendingQADD,
@SerialName("sending_quse") SendingQUSE,
@SerialName("received_message") ReceivedMessage
@SerialName("received_message") ReceivedMessage,
@SerialName("received_qend") ReceivedQEND
}
@Serializable
@@ -119,6 +119,20 @@ fun ChatView(
}
chat
} }
if (chatsCtx.secondaryContextFilter == null) {
// cleared when the view is gone rather than on back: Android keeps it on screen while it slides out,
// and by then a channel being created may already be using this state
DisposableEffect(Unit) {
onDispose {
if (chatModel.chatId.value == null && chatModel.creatingChannelId.value == null) {
chatModel.groupMembers.value = emptyList()
chatModel.groupMembersIndexes.value = emptyMap()
chatModel.membersLoaded.value = false
ChannelRelaysModel.reset()
}
}
}
}
val user = chatModel.currentUser.value
val chatInfo = activeChat.value?.chatInfo
if (chat == null || chatInfo == null || user == null) {
@@ -382,10 +396,6 @@ fun ChatView(
hideKeyboard(view)
AudioPlayer.stop()
chatModel.chatId.value = null
chatModel.groupMembers.value = emptyList()
chatModel.groupMembersIndexes.value = emptyMap()
chatModel.membersLoaded.value = false
ChannelRelaysModel.reset()
},
info = {
if (ModalManager.end.hasModalsOpen()) {
@@ -2153,7 +2153,8 @@ private fun ownerRelayState(chat: Chat, chatModel: ChatModel): OwnerRelayState?
if (!gInfo.useRelays || gInfo.membership.memberRole != GroupMemberRole.Owner ||
gInfo.membership.memberStatus in listOf(GroupMemberStatus.MemLeft, GroupMemberStatus.MemRemoved, GroupMemberStatus.MemGroupDeleted)
) return null
val relays = if (ChannelRelaysModel.groupId.value == gInfo.groupId) ChannelRelaysModel.groupRelays.toList() else emptyList()
if (ChannelRelaysModel.groupId.value != gInfo.groupId) return null
val relays = ChannelRelaysModel.groupRelays.toList()
if (relays.isEmpty()) return OwnerRelayState(emptyList(), 0, 0, 0, true)
val relayMembers = relays.map { relay ->
relay to chatModel.groupMembers.value.firstOrNull { it.groupMemberId == relay.groupMemberId }
@@ -2186,6 +2187,7 @@ private fun subscriberRelayState(chat: Chat, chatModel: ChatModel): SubscriberRe
val connectedCount = relayMembers.count { !relayMemberRemoved(it.memberStatus) && it.activeConn?.connStatus == ConnStatus.Ready && it.activeConn?.connFailedErr == null }
val failedCount = relayMembers.count { !relayMemberRemoved(it.memberStatus) && it.activeConn?.connFailedErr != null }
val total = if (relayMembers.isNotEmpty()) relayMembers.size else hostnames.size
if (total == 0 && !chatModel.membersLoaded.value) return null
val noActiveRelays = connectedCount == 0 && (removedCount + failedCount) == total
return SubscriberRelayState(hostnames, relayMembers, connectedCount, removedCount, failedCount, total, noActiveRelays)
}
@@ -265,10 +265,13 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo
newMember
}
}
chatModel.groupMembersIndexes.value = emptyMap()
chatModel.groupMembers.value = newMembers
chatModel.membersLoaded.value = true
chatModel.populateGroupMembersIndexes()
withContext(Dispatchers.Main) {
if (chatModel.chatId.value != groupInfo.id) return@withContext
chatModel.groupMembersIndexes.value = emptyMap()
chatModel.groupMembers.value = newMembers
chatModel.membersLoaded.value = true
chatModel.populateGroupMembersIndexes()
}
}
@Composable
@@ -63,6 +63,7 @@ fun UserAddressView(
chatModel.updateUser(u)
}
} catch (e: Exception) {
shareViaProfile.value = !on
Log.e(TAG, "UserAddressView apiSetProfileAddress: ${e.stackTraceToString()}")
} finally {
progressIndicator.value = false
@@ -141,6 +141,7 @@
<string name="no_message_servers_configured">No message servers.</string>
<string name="no_message_servers_configured_for_receiving">No servers to receive messages.</string>
<string name="no_message_servers_configured_for_private_routing">No servers for private message routing.</string>
<string name="server_has_too_many_hosts">Server %1$s has more than two host names.</string>
<string name="no_media_servers_configured">No media &amp; file servers.</string>
<string name="no_media_servers_configured_for_sending">No servers to send files.</string>
<string name="no_media_servers_configured_for_private_routing">No servers to receive files.</string>
@@ -255,6 +256,7 @@
<string name="error_preparing_contact">Error opening chat</string>
<string name="error_preparing_group">Error opening group</string>
<string name="error_changing_user">Error changing profile</string>
<string name="error_saving_profile">Error saving profile</string>
<!-- background service notice - SimpleXAPI.kt -->
<string name="icon_descr_instant_notifications">Instant notifications</string>