mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-23 02:12:25 +00:00
Merge branch 'master' into badge-in-app-checkout
This commit is contained in:
commit
57ab333a2b
94 files changed
+2255
-405
No files matched your search
@@ -1972,10 +1972,13 @@ enum UserServersError: Decodable {
|
||||
case storageMissing(protocol: ServerProtocol, user: UserRef?)
|
||||
case proxyMissing(protocol: ServerProtocol, user: UserRef?)
|
||||
case duplicateServer(protocol: ServerProtocol, duplicateServer: String, duplicateHost: String)
|
||||
case tooManyHosts(protocol: ServerProtocol, tooManyHostsServer: String)
|
||||
case duplicateChatRelayAddress(duplicateChatRelay: String, duplicateAddress: String)
|
||||
|
||||
var globalError: String? {
|
||||
switch self {
|
||||
case .tooManyHosts:
|
||||
return globalSMPError
|
||||
case let .noServers(`protocol`, _):
|
||||
switch `protocol` {
|
||||
case .smp: return globalSMPError
|
||||
@@ -2018,6 +2021,8 @@ enum UserServersError: Decodable {
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
case let .tooManyHosts(.smp, server):
|
||||
return String.localizedStringWithFormat(NSLocalizedString("Server %@ has more than two host names.", comment: "servers error"), serverHostname(server))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -935,6 +935,8 @@ final class ChatModel: ObservableObject {
|
||||
func updateCurrentUser(_ newProfile: Profile, _ preferences: FullPreferences? = nil) {
|
||||
if let current = currentUser {
|
||||
currentUser?.profile = toLocalProfile(current.profile.profileId, newProfile, "")
|
||||
currentUser?.profile.localBadge = current.profile.localBadge
|
||||
currentUser?.profile.contactDomainVerified = current.profile.contactDomainVerified
|
||||
if let preferences = preferences {
|
||||
currentUser?.fullPreferences = preferences
|
||||
}
|
||||
|
||||
@@ -1359,8 +1359,12 @@ func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? {
|
||||
switch r {
|
||||
case .result(.userProfileNoChange): return (profile, [])
|
||||
case let .result(.userProfileUpdated(_, _, toProfile, updateSummary)): return (toProfile, updateSummary.changedContacts)
|
||||
case .error(.errorStore(.duplicateName)): return nil;
|
||||
default: throw r.unexpected
|
||||
case .error(.errorStore(.duplicateName)), .error(.error(.userExists)): return nil;
|
||||
default:
|
||||
await MainActor.run {
|
||||
showErrorAlert(r.unexpected, NSLocalizedString("Error saving profile", comment: "alert title"))
|
||||
}
|
||||
throw r.unexpected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -767,6 +767,7 @@ struct ComposeView: View {
|
||||
let connectedCount = relayMembers.filter { !relayMemberRemoved($0.wrapped.memberStatus) && $0.wrapped.activeConn?.connStatus == .ready && $0.wrapped.activeConn?.connFailedErr == nil }.count
|
||||
let failedCount = relayMembers.filter { !relayMemberRemoved($0.wrapped.memberStatus) && $0.wrapped.activeConn?.connFailedErr != nil }.count
|
||||
let total = relayMembers.count > 0 ? relayMembers.count : hostnames.count
|
||||
guard total > 0 || chatModel.membersLoaded else { return nil }
|
||||
let noActiveRelays = connectedCount == 0 && (removedCount + failedCount) == total
|
||||
return (hostnames, relayMembers, connectedCount, removedCount, failedCount, total, noActiveRelays)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ struct UserAddressView: View {
|
||||
title: Text("Share address with SimpleX contacts?"),
|
||||
message: Text("Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts."),
|
||||
primaryButton: .default(Text("Share")) {
|
||||
setProfileAddress($progressIndicator, true)
|
||||
setProfileAddress($progressIndicator, true) { shareViaProfile = false }
|
||||
shareViaProfile = true
|
||||
}, secondaryButton: .cancel()
|
||||
)
|
||||
@@ -318,7 +318,7 @@ struct UserAddressView: View {
|
||||
alert = .shareOnCreate
|
||||
progressIndicator = false
|
||||
} else {
|
||||
setProfileAddress($progressIndicator, true)
|
||||
setProfileAddress($progressIndicator, true) { shareViaProfile = false }
|
||||
shareViaProfile = true
|
||||
}
|
||||
} else {
|
||||
@@ -601,7 +601,7 @@ struct AddressSettingsState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func setProfileAddress(_ progressIndicator: Binding<Bool>, _ on: Bool) {
|
||||
private func setProfileAddress(_ progressIndicator: Binding<Bool>, _ on: Bool, revert: @escaping () -> Void) {
|
||||
progressIndicator.wrappedValue = true
|
||||
Task {
|
||||
do {
|
||||
@@ -613,7 +613,11 @@ private func setProfileAddress(_ progressIndicator: Binding<Bool>, _ on: Bool) {
|
||||
await MainActor.run { progressIndicator.wrappedValue = false }
|
||||
} catch let error {
|
||||
logger.error("apiSetProfileAddress: \(responseError(error))")
|
||||
await MainActor.run { progressIndicator.wrappedValue = false }
|
||||
await MainActor.run {
|
||||
progressIndicator.wrappedValue = false
|
||||
revert()
|
||||
showErrorAlert(error, NSLocalizedString("Error saving profile", comment: "alert title"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -709,7 +713,10 @@ struct UserAddressSettingsView: View {
|
||||
title: NSLocalizedString("Share", comment: "alert action"),
|
||||
style: .default,
|
||||
handler: { _ in
|
||||
setProfileAddress($progressIndicator, on)
|
||||
setProfileAddress($progressIndicator, on) {
|
||||
ignoreShareViaProfileChange = true
|
||||
shareViaProfile = !on
|
||||
}
|
||||
}
|
||||
)
|
||||
]}
|
||||
@@ -731,7 +738,10 @@ struct UserAddressSettingsView: View {
|
||||
title: NSLocalizedString("Stop sharing", comment: "alert action"),
|
||||
style: .default,
|
||||
handler: { _ in
|
||||
setProfileAddress($progressIndicator, on)
|
||||
setProfileAddress($progressIndicator, on) {
|
||||
ignoreShareViaProfileChange = true
|
||||
shareViaProfile = !on
|
||||
}
|
||||
}
|
||||
)
|
||||
]}
|
||||
|
||||
@@ -36,7 +36,7 @@ struct UserProfile: View {
|
||||
HStack {
|
||||
TextField("Enter your name…", text: $profile.displayName)
|
||||
.focused($focusDisplayName)
|
||||
if !validDisplayName(profile.displayName) {
|
||||
if !validNewProfileName {
|
||||
Button {
|
||||
alert = .invalidNameError(validName: mkValidName(profile.displayName))
|
||||
} label: {
|
||||
@@ -147,10 +147,15 @@ struct UserProfile: View {
|
||||
(chatModel.currentUser?.profile.description ?? "") != description.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
) &&
|
||||
profile.displayName.trimmingCharacters(in: .whitespaces) != "" &&
|
||||
validDisplayName(profile.displayName) &&
|
||||
validNewProfileName &&
|
||||
bioFitsLimit()
|
||||
}
|
||||
|
||||
private var validNewProfileName: Bool {
|
||||
profile.displayName == chatModel.currentUser?.profile.displayName
|
||||
|| validDisplayName(profile.displayName)
|
||||
}
|
||||
|
||||
private func saveProfile() {
|
||||
focusDisplayName = false
|
||||
Task {
|
||||
|
||||
@@ -598,6 +598,7 @@ public enum RcvSwitchStatus: String, Decodable, Hashable {
|
||||
case sendingQADD = "sending_qadd"
|
||||
case sendingQUSE = "sending_quse"
|
||||
case receivedMessage = "received_message"
|
||||
case receivedQEND = "received_qend"
|
||||
}
|
||||
|
||||
public struct SndQueueInfo: Decodable, Hashable {
|
||||
|
||||
@@ -133,6 +133,7 @@ public struct Profile: Codable, NamedChat, Hashable {
|
||||
self.image = image
|
||||
self.contactLink = contactLink
|
||||
self.preferences = preferences
|
||||
self.peerType = peerType
|
||||
self.contactDomain = contactDomain
|
||||
}
|
||||
|
||||
@@ -593,7 +594,8 @@ public func toLocalProfile (_ profileId: Int64, _ profile: Profile, _ localAlias
|
||||
contactLink: profile.contactLink,
|
||||
preferences: profile.preferences,
|
||||
peerType: profile.peerType,
|
||||
localAlias: localAlias
|
||||
localAlias: localAlias,
|
||||
contactDomain: profile.contactDomain
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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 }
|
||||
|
||||
+15
-3
@@ -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
|
||||
|
||||
+14
-4
@@ -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()) {
|
||||
|
||||
+3
-1
@@ -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)
|
||||
}
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
+1
@@ -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 & 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>
|
||||
|
||||
Reference in new issue
Block a user