From a36a6d44db8d8b91ba9ff5a43e00a13578056147 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 14 May 2025 09:55:03 +0000 Subject: [PATCH 01/14] flatpak: update metainfo (#5899) * flatpak: update metainfo * flatpak: rewrite metainfo --- .../flatpak/chat.simplex.simplex.metainfo.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripts/flatpak/chat.simplex.simplex.metainfo.xml b/scripts/flatpak/chat.simplex.simplex.metainfo.xml index 82987e211a..b15e382207 100644 --- a/scripts/flatpak/chat.simplex.simplex.metainfo.xml +++ b/scripts/flatpak/chat.simplex.simplex.metainfo.xml @@ -38,6 +38,32 @@ + + https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html + +

New in v6.3.1-4:

+
    +
  • fixes mentions with trailing punctuation (e.g., hello @name!).
  • +
  • recognizes domain names as links (e.g., simplex.chat).
  • +
  • forward compatibility with "knocking" (a feature for group admins to review and to chat with the new members prior to admitting them to groups, it will be released in 6.4)
  • +
  • support for connecting via short connection links.
  • +
  • fix related to backward/forward compatibility of the app in some rare cases.
  • +
  • scrolling/navigation improvements.
  • +
  • faster onboarding (conditions and operators are combined to one screen).
  • +
+

New in v6.3.0:

+
    +
  • Mention members and get notified when mentioned.
  • +
  • Send private reports to moderators.
  • +
  • Delete, block and change role for multiple members at once
  • +
  • Faster sending messages and faster deletion.
  • +
  • Organize chats into lists to keep track of what's important.
  • +
  • Jump to found and forwarded messages.
  • +
  • Private media file names.
  • +
  • Message expiration in chats.
  • +
+
+
https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html From 5dd89fe12760f531bd8e51b06cb4cbaeb76ae960 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 15 May 2025 14:25:46 +0100 Subject: [PATCH 02/14] ios: fix swipe on iOS 15, fix onboarding layout on iOS 15 and small screens (#5913) * ios: fix onboarding layout issues on iOS 15 and small screens * fix swipe on iOS 15 --- .../Views/ChatList/ChatListNavLink.swift | 36 ++++++++++++++----- .../Shared/Views/ChatList/ChatListView.swift | 8 +---- .../Onboarding/ChooseServerOperators.swift | 10 ++++-- .../Views/Onboarding/CreateProfile.swift | 35 ++++++++++-------- .../Onboarding/SetNotificationsMode.swift | 8 ++++- .../Shared/Views/Onboarding/SimpleXInfo.swift | 16 ++++++--- .../ar.xcloc/Localized Contents/ar.xliff | 4 +-- .../bg.xcloc/Localized Contents/bg.xliff | 15 +++----- .../bn.xcloc/Localized Contents/bn.xliff | 4 +-- .../cs.xcloc/Localized Contents/cs.xliff | 16 ++++----- .../de.xcloc/Localized Contents/de.xliff | 15 +++----- .../el.xcloc/Localized Contents/el.xliff | 4 +-- .../en.xcloc/Localized Contents/en.xliff | 15 +++----- .../es.xcloc/Localized Contents/es.xliff | 15 +++----- .../fi.xcloc/Localized Contents/fi.xliff | 16 ++++----- .../fr.xcloc/Localized Contents/fr.xliff | 15 +++----- .../he.xcloc/Localized Contents/he.xliff | 4 +-- .../hr.xcloc/Localized Contents/hr.xliff | 4 +-- .../hu.xcloc/Localized Contents/hu.xliff | 15 +++----- .../it.xcloc/Localized Contents/it.xliff | 15 +++----- .../ja.xcloc/Localized Contents/ja.xliff | 16 ++++----- .../ko.xcloc/Localized Contents/ko.xliff | 4 +-- .../lt.xcloc/Localized Contents/lt.xliff | 4 +-- .../nl.xcloc/Localized Contents/nl.xliff | 15 +++----- .../pl.xcloc/Localized Contents/pl.xliff | 15 +++----- .../Localized Contents/pt-BR.xliff | 4 +-- .../pt.xcloc/Localized Contents/pt.xliff | 4 +-- .../ru.xcloc/Localized Contents/ru.xliff | 15 +++----- .../th.xcloc/Localized Contents/th.xliff | 15 +++----- .../tr.xcloc/Localized Contents/tr.xliff | 15 +++----- .../uk.xcloc/Localized Contents/uk.xliff | 15 +++----- .../Localized Contents/zh-Hans.xliff | 15 +++----- .../Localized Contents/zh-Hant.xliff | 4 +-- apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 5 ++- apps/ios/de.lproj/Localizable.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/fi.lproj/Localizable.strings | 5 ++- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 2 +- apps/ios/it.lproj/Localizable.strings | 2 +- apps/ios/ja.lproj/Localizable.strings | 5 ++- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 2 +- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- 49 files changed, 208 insertions(+), 244 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index f9cf5e98e4..81d78fbadd 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -94,7 +94,7 @@ struct ChatListNavLink: View { Group { if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { deleteContactDialog( @@ -121,6 +121,7 @@ struct ChatListNavLink: View { selection: $chatModel.chatId, label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) } ) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() @@ -145,7 +146,6 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: dynamicRowHeight) } } .alert(item: $alert) { $0.alert } @@ -163,7 +163,7 @@ struct ChatListNavLink: View { switch (groupInfo.membership.memberStatus) { case .memInvited: ChatPreviewView(chat: chat, progressByTimeout: $progressByTimeout) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { joinGroupButton() if groupInfo.canDelete { @@ -183,7 +183,7 @@ struct ChatListNavLink: View { .disabled(inProgress) case .memAccepted: ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .onTapGesture { AlertManager.shared.showAlert(groupInvitationAcceptedAlert()) } @@ -203,7 +203,7 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !groupInfo.ready ) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() @@ -250,7 +250,7 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !noteFolder.ready ) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() } @@ -433,6 +433,7 @@ struct ChatListNavLink: View { private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View { ContactRequestView(contactRequest: contactRequest, chat: chat) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } @@ -451,7 +452,6 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: dynamicRowHeight) .contentShape(Rectangle()) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { @@ -463,6 +463,7 @@ struct ChatListNavLink: View { private func contactConnectionNavLink(_ contactConnection: PendingContactConnection) -> some View { ContactConnectionView(chat: chat) + .frameCompat(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { AlertManager.shared.showAlert(deleteContactConnectionAlert(contactConnection) { a in @@ -480,7 +481,6 @@ struct ChatListNavLink: View { } .tint(theme.colors.primary) } - .frame(height: dynamicRowHeight) .appSheet(isPresented: $showContactConnectionInfo) { Group { if case let .contactConnection(contactConnection) = chat.chatInfo { @@ -583,7 +583,7 @@ struct ChatListNavLink: View { Text("invalid chat data") .foregroundColor(.red) .padding(4) - .frame(height: dynamicRowHeight) + .frameCompat(height: dynamicRowHeight) .onTapGesture { showInvalidJSON = true } .appSheet(isPresented: $showInvalidJSON) { invalidJSONView(dataToString(json)) @@ -603,6 +603,24 @@ struct ChatListNavLink: View { } } +extension View { + @inline(__always) + @ViewBuilder fileprivate func frameCompat(height: CGFloat) -> some View { + if #available(iOS 16, *) { + self.frame(height: height) + } else { + VStack(spacing: 0) { + Divider() + .padding(.leading, 16) + self + .frame(height: height) + .padding(.horizontal, 8) + .padding(.vertical, 8) + } + } + } +} + func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert { Alert( title: Text("Reject contact request"), diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 5c491b6303..f34f930c6f 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -367,13 +367,7 @@ struct ChatListView: View { .offset(x: -8) } else { ForEach(cs, id: \.viewId) { chat in - VStack(spacing: .zero) { - Divider() - .padding(.leading, 16) - ChatListNavLink(chat: chat, parentSheet: $sheet) - .padding(.horizontal, 8) - .padding(.vertical, 6) - } + ChatListNavLink(chat: chat, parentSheet: $sheet) .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets()) diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index 17e1735472..656cef4a04 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -67,7 +67,7 @@ struct OnboardingConditionsView: View { var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .leading, spacing: 20) { Text("Conditions of use") .font(.largeTitle) @@ -107,6 +107,7 @@ struct OnboardingConditionsView: View { .frame(minHeight: 40) } } + .padding(25) .frame(minHeight: g.size.height) } .onAppear { @@ -127,9 +128,14 @@ struct OnboardingConditionsView: View { } } .frame(maxHeight: .infinity, alignment: .top) + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } } .frame(maxHeight: .infinity, alignment: .top) - .padding(25) + .navigationBarHidden(true) // necessary on iOS 15 } private func continueToNextStep() { diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index c022a2a012..ae72cb1be5 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -62,8 +62,7 @@ struct CreateProfile: View { .frame(height: 20) } footer: { VStack(alignment: .leading, spacing: 8) { - Text("Your profile, contacts and delivered messages are stored on your device.") - Text("The profile is only shared with your contacts.") + Text("Your profile is stored on your device and only shared with your contacts.") } .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity, alignment: .leading) @@ -118,25 +117,22 @@ struct CreateFirstProfile: View { @State private var nextStepNavLinkActive = false var body: some View { - VStack(alignment: .leading, spacing: 20) { - VStack(alignment: .center, spacing: 20) { - Text("Create your profile") + let v = VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .center, spacing: 16) { + Text("Create profile") .font(.largeTitle) .bold() .multilineTextAlignment(.center) - - Text("Your profile, contacts and delivered messages are stored on your device.") - .font(.callout) - .foregroundColor(theme.colors.secondary) - .multilineTextAlignment(.center) - - Text("The profile is only shared with your contacts.") + + Text("Your profile is stored on your device and only shared with your contacts.") .font(.callout) .foregroundColor(theme.colors.secondary) .multilineTextAlignment(.center) } + .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity) // Ensures it takes up the full width .padding(.horizontal, 10) + .onTapGesture { focusDisplayName = false } HStack { let name = displayName.trimmingCharacters(in: .whitespaces) @@ -174,12 +170,23 @@ struct CreateFirstProfile: View { } } .onAppear() { - focusDisplayName = true + if #available(iOS 16, *) { + focusDisplayName = true + } else { + // it does not work before animation completes on iOS 15 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + focusDisplayName = true + } + } } .padding(.horizontal, 25) - .padding(.top, 10) .padding(.bottom, 25) .frame(maxWidth: .infinity, alignment: .leading) + if #available(iOS 16, *) { + return v.padding(.top, 10) + } else { + return v.padding(.top, 75).ignoresSafeArea(.all, edges: .top) + } } func createProfileButton() -> some View { diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 97e1f49382..31865e7af9 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -17,7 +17,7 @@ struct SetNotificationsMode: View { var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .center, spacing: 20) { Text("Push notifications") .font(.largeTitle) @@ -57,11 +57,17 @@ struct SetNotificationsMode: View { .padding(25) .frame(minHeight: g.size.height) } + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } } .frame(maxHeight: .infinity) .sheet(isPresented: $showInfo) { NotificationsInfoView() } + .navigationBarHidden(true) // necessary on iOS 15 } private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index e55cc4037a..9f41a37b1d 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -18,7 +18,7 @@ struct SimpleXInfo: View { var body: some View { GeometryReader { g in - ScrollView { + let v = ScrollView { VStack(alignment: .leading) { VStack(alignment: .center, spacing: 10) { Image(colorScheme == .light ? "logo" : "logo-light") @@ -36,7 +36,7 @@ struct SimpleXInfo: View { .font(.headline) } } - + Spacer() VStack(alignment: .leading) { @@ -66,6 +66,9 @@ struct SimpleXInfo: View { } } } + .padding(.horizontal, 25) + .padding(.top, 75) + .padding(.bottom, 25) .frame(minHeight: g.size.height) } .sheet(isPresented: Binding( @@ -88,14 +91,17 @@ struct SimpleXInfo: View { createProfileNavLinkActive: $createProfileNavLinkActive ) } + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } } .onAppear() { setLastVersionDefault() } .frame(maxHeight: .infinity) - .padding(.horizontal, 25) - .padding(.top, 75) - .padding(.bottom, 25) + .navigationBarHidden(true) // necessary on iOS 15 } private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff index 278b9ec9b2..e965e5a1a5 100644 --- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff +++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff @@ -2826,8 +2826,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 56553b3283..776199ac1f 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -7329,11 +7329,6 @@ It can happen because of some bug or when the connection is compromised.Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита. No comment provided by engineer. - - The profile is only shared with your contacts. - Профилът се споделя само с вашите контакти. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8577,6 +8572,11 @@ Repeat connection request? Вашият профил **%@** ще бъде споделен. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Профилът се споделя само с вашите контакти. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил. @@ -8586,11 +8586,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство. - No comment provided by engineer. - Your random profile Вашият автоматично генериран профил diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index 7002f790df..bf7753675e 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -3422,8 +3422,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 21bf0aef60..0400839cb0 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1942,6 +1942,7 @@ This is your own one-time link! Create profile + Vytvořte si profil No comment provided by engineer. @@ -7080,11 +7081,6 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Stará databáze nebyla během přenášení odstraněna, lze ji smazat. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil je sdílen pouze s vašimi kontakty. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8271,6 +8267,11 @@ Repeat connection request? Váš profil **%@** bude sdílen. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profil je sdílen pouze s vašimi kontakty. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil. @@ -8280,11 +8281,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení. - No comment provided by engineer. - Your random profile Váš náhodný profil diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index a113d35bbd..06fd7c5a1d 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -7763,11 +7763,6 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden. No comment provided by engineer. - - The profile is only shared with your contacts. - Das Profil wird nur mit Ihren Kontakten geteilt. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. @@ -9081,6 +9076,11 @@ Verbindungsanfrage wiederholen? Ihr Profil **%@** wird geteilt. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Das Profil wird nur mit Ihren Kontakten geteilt. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen. @@ -9091,11 +9091,6 @@ Verbindungsanfrage wiederholen? Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet. alert message - - Your profile, contacts and delivered messages are stored on your device. - Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert. - No comment provided by engineer. - Your random profile Ihr Zufallsprofil diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index b601d1fa74..fc1846942c 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -3043,8 +3043,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 641af86c2a..fd71e0dee6 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -7764,11 +7764,6 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. - The profile is only shared with your contacts. - No comment provided by engineer. - The same conditions will apply to operator **%@**. The same conditions will apply to operator **%@**. @@ -9082,6 +9077,11 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Your profile is stored on your device and only shared with your contacts. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. @@ -9092,11 +9092,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Your profile, contacts and delivered messages are stored on your device. - No comment provided by engineer. - Your random profile Your random profile diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 73f88e1cab..d39fb61249 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -7763,11 +7763,6 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La base de datos antigua no se eliminó durante la migración, puede eliminarse. No comment provided by engineer. - - The profile is only shared with your contacts. - El perfil sólo se comparte con tus contactos. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Las mismas condiciones se aplicarán al operador **%@**. @@ -9081,6 +9076,11 @@ Repeat connection request? El perfil **%@** será compartido. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + El perfil sólo se comparte con tus contactos. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. @@ -9091,11 +9091,6 @@ Repeat connection request? Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos. alert message - - Your profile, contacts and delivered messages are stored on your device. - Tu perfil, contactos y mensajes se almacenan en tu dispositivo. - No comment provided by engineer. - Your random profile Tu perfil aleatorio diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 5281fbc701..a54666bb10 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1923,6 +1923,7 @@ This is your own one-time link! Create profile + Luo profiilisi No comment provided by engineer. @@ -7054,11 +7055,6 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. No comment provided by engineer. - - The profile is only shared with your contacts. - Profiili jaetaan vain kontaktiesi kanssa. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8244,6 +8240,11 @@ Repeat connection request? Profiilisi **%@** jaetaan. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profiili jaetaan vain kontaktiesi kanssa. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi. @@ -8253,11 +8254,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi. - No comment provided by engineer. - Your random profile Satunnainen profiilisi diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 1e5da0b0ed..59bde0650e 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -7696,11 +7696,6 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée. No comment provided by engineer. - - The profile is only shared with your contacts. - Le profil n'est partagé qu'avec vos contacts. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Les mêmes conditions s'appliquent à l'opérateur **%@**. @@ -9003,6 +8998,11 @@ Répéter la demande de connexion ? Votre profil **%@** sera partagé. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Le profil n'est partagé qu'avec vos contacts. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil. @@ -9013,11 +9013,6 @@ Répéter la demande de connexion ? Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil. - No comment provided by engineer. - Your random profile Votre profil aléatoire diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 08f46bb056..f76d7eba1e 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -3569,8 +3569,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index bdb3083f5a..6ad4d159c7 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -2619,8 +2619,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 9be5879eb6..78bee138e4 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -7763,11 +7763,6 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető. No comment provided by engineer. - - The profile is only shared with your contacts. - A profilja csak a partnereivel van megosztva. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**. @@ -9081,6 +9076,11 @@ Megismétli a meghívási kérést? A(z) **%@** nevű profilja meg lesz osztva. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + A profilja csak a partnereivel van megosztva. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját. @@ -9091,11 +9091,6 @@ Megismétli a meghívási kérést? A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára. alert message - - Your profile, contacts and delivered messages are stored on your device. - A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva. - No comment provided by engineer. - Your random profile Véletlenszerű profil diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 0c0c12004e..cf5f61918f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -7763,11 +7763,6 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato. No comment provided by engineer. - - The profile is only shared with your contacts. - Il profilo è condiviso solo con i tuoi contatti. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Le stesse condizioni si applicheranno all'operatore **%@**. @@ -9081,6 +9076,11 @@ Ripetere la richiesta di connessione? Verrà condiviso il tuo profilo **%@**. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Il profilo è condiviso solo con i tuoi contatti. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo. @@ -9091,11 +9091,6 @@ Ripetere la richiesta di connessione? Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti. alert message - - Your profile, contacts and delivered messages are stored on your device. - Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo. - No comment provided by engineer. - Your random profile Il tuo profilo casuale diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 26f415dd13..27134216a7 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1990,6 +1990,7 @@ This is your own one-time link! Create profile + プロフィールを作成する No comment provided by engineer. @@ -7125,11 +7126,6 @@ It can happen because of some bug or when the connection is compromised.古いデータベースは移行時に削除されなかったので、削除することができます。 No comment provided by engineer. - - The profile is only shared with your contacts. - プロフィールは連絡先にしか共有されません。 - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8315,6 +8311,11 @@ Repeat connection request? あなたのプロファイル **%@** が共有されます。 No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + プロフィールは連絡先にしか共有されません。 + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。 @@ -8324,11 +8325,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。 - No comment provided by engineer. - Your random profile あなたのランダム・プロフィール diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index e35732f046..019f63cbc0 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -2867,8 +2867,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index 54a713478f..0f795170c6 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -2631,8 +2631,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 681502e255..4008c57ac0 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -7760,11 +7760,6 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd. No comment provided by engineer. - - The profile is only shared with your contacts. - Het profiel wordt alleen gedeeld met uw contacten. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Dezelfde voorwaarden gelden voor operator **%@**. @@ -9074,6 +9069,11 @@ Verbindingsverzoek herhalen? Uw profiel **%@** wordt gedeeld. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Het profiel wordt alleen gedeeld met uw contacten. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien. @@ -9084,11 +9084,6 @@ Verbindingsverzoek herhalen? Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. alert message - - Your profile, contacts and delivered messages are stored on your device. - Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. - No comment provided by engineer. - Your random profile Je willekeurige profiel diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 01bc0b8508..175c8b4112 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -7583,11 +7583,6 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Stara baza danych nie została usunięta podczas migracji, można ją usunąć. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil jest udostępniany tylko Twoim kontaktom. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8870,6 +8865,11 @@ Powtórzyć prośbę połączenia? Twój profil **%@** zostanie udostępniony. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profil jest udostępniany tylko Twoim kontaktom. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu. @@ -8880,11 +8880,6 @@ Powtórzyć prośbę połączenia? Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów. alert message - - Your profile, contacts and delivered messages are stored on your device. - Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu. - No comment provided by engineer. - Your random profile Twój losowy profil diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 93ba6f357b..bbb6c7d22a 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -3002,8 +3002,8 @@ We will be adding server redundancy to prevent lost messages. The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. O perfil é compartilhado apenas com seus contatos. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index de1787bdad..bc8bf79da1 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -3146,8 +3146,8 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index a7b63e38ba..419fa75375 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -7715,11 +7715,6 @@ It can happen because of some bug or when the connection is compromised.Предыдущая версия данных чата не удалена при перемещении, её можно удалить. No comment provided by engineer. - - The profile is only shared with your contacts. - Профиль отправляется только Вашим контактам. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Те же самые условия будут приняты для оператора **%@**. @@ -9021,6 +9016,11 @@ Repeat connection request? Будет отправлен Ваш профиль **%@**. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Ваш профиль храниться на Вашем устройстве и отправляется только контактам. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю. @@ -9031,11 +9031,6 @@ Repeat connection request? Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам. alert message - - Your profile, contacts and delivered messages are stored on your device. - Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве. - No comment provided by engineer. - Your random profile Случайный профиль diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index be68dc9780..671dd87d7d 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -7028,11 +7028,6 @@ It can happen because of some bug or when the connection is compromised.ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้ No comment provided by engineer. - - The profile is only shared with your contacts. - โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8212,6 +8207,11 @@ Repeat connection request? Your profile **%@** will be shared. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้ @@ -8221,11 +8221,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ - No comment provided by engineer. - Your random profile โปรไฟล์แบบสุ่มของคุณ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 6eb1daf84b..bbee40c2b9 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -7599,11 +7599,6 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir. No comment provided by engineer. - - The profile is only shared with your contacts. - Profil sadece kişilerinle paylaşılacak. - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8886,6 +8881,11 @@ Bağlantı isteği tekrarlansın mı? Profiliniz **%@** paylaşılacaktır. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Profil sadece kişilerinle paylaşılacak. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez. @@ -8896,11 +8896,6 @@ Bağlantı isteği tekrarlansın mı? Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir. alert message - - Your profile, contacts and delivered messages are stored on your device. - Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır. - No comment provided by engineer. - Your random profile Rasgele profiliniz diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 7c8c6f4254..c0375e3b02 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -7638,11 +7638,6 @@ It can happen because of some bug or when the connection is compromised.Стара база даних не була видалена під час міграції, її можна видалити. No comment provided by engineer. - - The profile is only shared with your contacts. - Профіль доступний лише вашим контактам. - No comment provided by engineer. - The same conditions will apply to operator **%@**. Такі ж умови діятимуть і для оператора **%@**. @@ -8945,6 +8940,11 @@ Repeat connection request? Ваш профіль **%@** буде опублікований. No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + Профіль доступний лише вашим контактам. + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль. @@ -8955,11 +8955,6 @@ Repeat connection request? Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам. alert message - - Your profile, contacts and delivered messages are stored on your device. - Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої. - No comment provided by engineer. - Your random profile Ваш випадковий профіль diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 03e053326a..d5411f86e3 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -7708,11 +7708,6 @@ It can happen because of some bug or when the connection is compromised.旧数据库在迁移过程中没有被移除,可以删除。 No comment provided by engineer. - - The profile is only shared with your contacts. - 该资料仅与您的联系人共享。 - No comment provided by engineer. - The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8987,6 +8982,11 @@ Repeat connection request? 您的个人资料 **%@** 将被共享。 No comment provided by engineer. + + Your profile is stored on your device and only shared with your contacts. + 该资料仅与您的联系人共享。 + No comment provided by engineer. + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. 您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。 @@ -8996,11 +8996,6 @@ Repeat connection request? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message - - Your profile, contacts and delivered messages are stored on your device. - 您的资料、联系人和发送的消息存储在您的设备上。 - No comment provided by engineer. - Your random profile 您的随机资料 diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 8a771369e6..3ea46ee364 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -3054,8 +3054,8 @@ We will be adding server redundancy to prevent lost messages. 舊的數據庫在遷移過程中沒有被移除,可以刪除。 No comment provided by engineer. - - The profile is only shared with your contacts. + + Your profile is stored on your device and only shared with your contacts. 你的個人檔案只會和你的聯絡人分享。 No comment provided by engineer. diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index f241158185..e4bc8f2150 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -3777,7 +3777,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профилът се споделя само с вашите контакти."; +"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Втората отметка, която пропуснахме! ✅"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 003ac23325..08a94615a3 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -822,6 +822,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Vytvořit nový profil v [desktop app](https://simplex.chat/downloads/). 💻"; +/* No comment provided by engineer. */ +"Create profile" = "Vytvořte si profil"; + /* server test step */ "Create queue" = "Vytvořit frontu"; @@ -2986,7 +2989,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; +"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 0eab764216..8da7835c43 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; +"Your profile is stored on your device and only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index e797b73b98..28ba0f0642 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; +"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index c4031adf9a..4891c7fb26 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -768,6 +768,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Luo uusi profiili [työpöytäsovelluksessa](https://simplex.chat/downloads/). 💻"; +/* No comment provided by engineer. */ +"Create profile" = "Luo profiilisi"; + /* server test step */ "Create queue" = "Luo jono"; @@ -2908,7 +2911,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; +"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Toinen kuittaus, joka uupui! ✅"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 1c16f8847d..4dd75039dc 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -4884,7 +4884,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; +"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index c190cccaba..5a9b6b4e38 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "A profilja csak a partnereivel van megosztva."; +"Your profile is stored on your device and only shared with your contacts." = "A profilja csak a partnereivel van megosztva."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index f67a492cc4..b914a06079 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -5101,7 +5101,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; +"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 9d0cccf591..d214f88e1c 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -957,6 +957,9 @@ set passcode view */ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "[デスクトップアプリ](https://simplex.chat/downloads/)で新しいプロファイルを作成します。 💻"; +/* No comment provided by engineer. */ +"Create profile" = "プロフィールを作成する"; + /* server test step */ "Create queue" = "キューの作成"; @@ -3109,7 +3112,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。"; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; +"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "長らくお待たせしました! ✅"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index d2cfcba0de..232de56641 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -5092,7 +5092,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; +"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 867f3beff4..31a9b87662 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -4557,7 +4557,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom."; +"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Drugi tik, który przegapiliśmy! ✅"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index b819d013b9..cb837836ff 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -4957,7 +4957,7 @@ report reason */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профиль отправляется только Вашим контактам."; +"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 6b3381922a..57c0466eb9 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -2830,7 +2830,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้"; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น"; +"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "ขีดที่สองที่เราพลาด! ✅"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index ab0703333e..e3bb11d1cc 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -4602,7 +4602,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak."; +"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak."; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Özlediğimiz ikinci tik! ✅"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 8e2b514ed4..734b8dda82 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -4722,7 +4722,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити."; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "Профіль доступний лише вашим контактам."; +"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам."; /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 6ceeeb22d0..e3f9669d9f 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -4923,7 +4923,7 @@ chat item action */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; /* No comment provided by engineer. */ -"The profile is only shared with your contacts." = "该资料仅与您的联系人共享。"; +"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。"; /* No comment provided by engineer. */ "The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅"; From 26e5742354f2d9dd87b5c0cf3a4cf8227eec4813 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 15 May 2025 14:58:40 +0100 Subject: [PATCH 03/14] ios: fix swipe in members list for iOS 15 (#5914) * ios: fix swipe in members list for iOS 15 * refactor --- .../Views/Chat/Group/GroupChatInfoView.swift | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 96a4981be0..15749b0761 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -144,17 +144,9 @@ struct GroupChatInfoView: View { let filteredMembers = s == "" ? members : members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } - MemberRowView(groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert) ForEach(filteredMembers) { member in - ZStack { - NavigationLink { - memberInfoView(member) - } label: { - EmptyView() - } - .opacity(0) - MemberRowView(groupInfo: groupInfo, groupMember: member, alert: $alert) - } + MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, alert: $alert) } } @@ -358,6 +350,7 @@ struct GroupChatInfoView: View { } private struct MemberRowView: View { + var chat: Chat var groupInfo: GroupInfo @ObservedObject var groupMember: GMember @EnvironmentObject var theme: AppTheme @@ -366,7 +359,7 @@ struct GroupChatInfoView: View { var body: some View { let member = groupMember.wrapped - let v = HStack{ + let v1 = HStack{ MemberProfileImage(member, size: 38) .padding(.trailing, 2) // TODO server connection status @@ -383,6 +376,20 @@ struct GroupChatInfoView: View { memberInfo(member) } + let v = ZStack { + if user { + v1 + } else { + NavigationLink { + memberInfoView() + } label: { + EmptyView() + } + .opacity(0) + v1 + } + } + if user { v } else if groupInfo.membership.memberRole >= .admin { @@ -407,6 +414,11 @@ struct GroupChatInfoView: View { } } + private func memberInfoView() -> some View { + GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) + .navigationBarHidden(false) + } + private func memberConnStatus(_ member: GroupMember) -> LocalizedStringKey { if member.activeConn?.connDisabled ?? false { return "disabled" @@ -485,11 +497,6 @@ struct GroupChatInfoView: View { .foregroundColor(theme.colors.secondary) } } - - private func memberInfoView(_ groupMember: GMember) -> some View { - GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember) - .navigationBarHidden(false) - } private func groupLinkButton() -> some View { NavigationLink { From 7b362ff655959a790815931e4db44ced52a9212c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 19 May 2025 15:50:33 +0100 Subject: [PATCH 04/14] ui: label in compose when user cannot send messages (#5922) * ui: label in compose when user cannot send messages * gray buttons when user cannot send messages * improve * kotlin * fix order * fix alert --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Model/ChatModel.swift | 21 ------- apps/ios/Shared/Views/Chat/ChatView.swift | 12 +++- .../Chat/ComposeMessage/ComposeView.swift | 19 +++--- .../Chat/ComposeMessage/SendMessageView.swift | 8 ++- apps/ios/SimpleXChat/ChatTypes.swift | 50 ++++++++++++--- .../platform/PlatformTextField.android.kt | 13 ++-- .../chat/simplex/common/model/ChatModel.kt | 62 ++++++++++++------- .../common/platform/PlatformTextField.kt | 2 +- .../chat/simplex/common/views/TerminalView.kt | 3 +- .../simplex/common/views/chat/ChatView.kt | 2 +- .../simplex/common/views/chat/ComposeView.kt | 11 ++-- .../simplex/common/views/chat/SendMsgView.kt | 62 +++++++++---------- .../commonMain/resources/MR/base/strings.xml | 15 ++++- .../platform/PlatformTextField.desktop.kt | 12 ++-- 14 files changed, 164 insertions(+), 128 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 63d8b38e3c..9b9fda0397 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -1152,27 +1152,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike { ) } - var userCanSend: Bool { - switch chatInfo { - case .direct: return true - case let .group(groupInfo): - let m = groupInfo.membership - return m.memberActive && m.memberRole >= .member - case .local: - return true - default: return false - } - } - - var userIsObserver: Bool { - switch chatInfo { - case let .group(groupInfo): - let m = groupInfo.membership - return m.memberActive && m.memberRole == .observer - default: return false - } - } - var unreadTag: Bool { switch chatInfo.chatSettings?.enableNtfs { case .all: chatStats.unreadChat || chatStats.unreadCount > 0 diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 9e648ef98c..c136ebc01b 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -98,14 +98,24 @@ struct ChatView: View { } connectingText() if selectedChatItems == nil { + let reason = chat.chatInfo.userCantSendReason ComposeView( chat: chat, composeState: $composeState, keyboardVisible: $keyboardVisible, keyboardHiddenDate: $keyboardHiddenDate, - selectedRange: $selectedRange + selectedRange: $selectedRange, + disabledText: reason?.composeLabel ) .disabled(!cInfo.sendMsgEnabled) + .if(!cInfo.sendMsgEnabled) { v in + v.disabled(true).onTapGesture { + AlertManager.shared.showAlertMsg( + title: "You can't send messages!", + message: reason?.alertMessage + ) + } + } } else { SelectedItemsBottomToolbar( chatItems: ItemsModel.shared.reversedChatItems, diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 3e9c340266..8993de886f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -327,6 +327,7 @@ struct ComposeView: View { @Binding var keyboardVisible: Bool @Binding var keyboardHiddenDate: Date @Binding var selectedRange: NSRange + var disabledText: LocalizedStringKey? = nil @State var linkUrl: URL? = nil @State var hasSimplexLink: Bool = false @@ -391,7 +392,7 @@ struct ComposeView: View { Image(systemName: "paperclip") .resizable() } - .disabled(composeState.attachmentDisabled || !chat.userCanSend || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) + .disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) .frame(width: 25, height: 25) .padding(.bottom, 16) .padding(.leading, 12) @@ -441,19 +442,13 @@ struct ComposeView: View { : theme.colors.primary ) .padding(.trailing, 12) - .disabled(!chat.userCanSend) + .disabled(!chat.chatInfo.sendMsgEnabled) - if chat.userIsObserver { - Text("you are observer") + if let disabledText { + Text(disabledText) .italic() .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) - .onTapGesture { - AlertManager.shared.showAlertMsg( - title: "You can't send messages!", - message: "Please contact group admin." - ) - } } } } @@ -479,8 +474,8 @@ struct ComposeView: View { hasSimplexLink = false } } - .onChange(of: chat.userCanSend) { canSend in - if !canSend { + .onChange(of: chat.chatInfo.sendMsgEnabled) { sendEnabled in + if !sendEnabled { cancelCurrentVoiceRecording() clearCurrentDraft() clearState() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index d7b29a0ecb..e7b02c9aea 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -15,6 +15,7 @@ struct SendMessageView: View { @Binding var composeState: ComposeState @Binding var selectedRange: NSRange @EnvironmentObject var theme: AppTheme + @Environment(\.isEnabled) var isEnabled var sendMessage: (Int?) -> Void var sendLiveMessage: (() async -> Void)? = nil var updateLiveMessage: (() async -> Void)? = nil @@ -255,6 +256,7 @@ struct SendMessageView: View { } private struct RecordVoiceMessageButton: View { + @Environment(\.isEnabled) var isEnabled @EnvironmentObject var theme: AppTheme var startVoiceMessageRecording: (() -> Void)? var finishVoiceMessageRecording: (() -> Void)? @@ -263,11 +265,11 @@ struct SendMessageView: View { @State private var pressed: TimeInterval? = nil var body: some View { - Image(systemName: "mic.fill") + Image(systemName: isEnabled ? "mic.fill" : "mic") .resizable() .scaledToFit() .frame(width: 20, height: 20) - .foregroundColor(theme.colors.primary) + .foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary) .opacity(holdingVMR ? 0.7 : 1) .disabled(disabled) .frame(width: 31, height: 31) @@ -352,7 +354,7 @@ struct SendMessageView: View { Image(systemName: "bolt.fill") .resizable() .scaledToFit() - .foregroundColor(theme.colors.primary) + .foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary) .frame(width: 20, height: 20) } .frame(width: 29, height: 29) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 960fdd466d..88246465e1 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1333,6 +1333,19 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + get { + switch self { + case let .direct(contact): return contact.userCantSendReason + case let .group(groupInfo): return groupInfo.userCantSendReason + case let .local(noteFolder): return noteFolder.userCantSendReason + case let .contactRequest(contactRequest): return contactRequest.userCantSendReason + case let .contactConnection(contactConnection): return contactConnection.userCantSendReason + case .invalidJSON: return ("can't send messages", nil) + } + } + } + public var sendMsgEnabled: Bool { get { switch self { @@ -1642,15 +1655,16 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var ready: Bool { get { activeConn?.connStatus == .ready } } public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } public var active: Bool { get { contactStatus == .active } } - public var sendMsgEnabled: Bool { get { - ( - sndReady - && active - && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false) - && !(activeConn?.connDisabled ?? true) - ) - || nextSendGrpInv - } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + // TODO [short links] this will have additional statuses for pending contact requests before they are accepted + if nextSendGrpInv { return nil } + if !active { return ("contact deleted", nil) } + if !sndReady { return ("contact not ready", nil) } + if activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) } + if activeConn?.connDisabled ?? true { return ("contact disabled", nil) } + return nil + } + public var sendMsgEnabled: Bool { userCantSendReason == nil } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } @@ -1829,6 +1843,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable { public var id: ChatId { get { "<@\(contactRequestId)" } } public var apiId: Int64 { get { contactRequestId } } var ready: Bool { get { true } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } public var sendMsgEnabled: Bool { get { false } } public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } @@ -1861,6 +1876,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { public var id: ChatId { get { ":\(pccConnId)" } } public var apiId: Int64 { get { pccConnId } } var ready: Bool { get { false } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) } public var sendMsgEnabled: Bool { get { false } } var localDisplayName: String { get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) } @@ -1990,7 +2006,20 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "#\(groupId)" } } public var apiId: Int64 { get { groupId } } public var ready: Bool { get { true } } - public var sendMsgEnabled: Bool { get { membership.memberActive } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { + return if membership.memberActive { + membership.memberRole == .observer ? ("you are observer", "Please contact group admin.") : nil + } else { + switch membership.memberStatus { + case .memRejected: ("request to join rejected", nil) + case .memGroupDeleted: ("group is deleted", nil) + case .memRemoved: ("removed from group", nil) + case .memLeft: ("you left", nil) + default: ("can't send messages", nil) + } + } + } + public var sendMsgEnabled: Bool { userCantSendReason == nil } public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } public var image: String? { get { groupProfile.image } } @@ -2357,6 +2386,7 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "*\(noteFolderId)" } } public var apiId: Int64 { get { noteFolderId } } public var ready: Bool { get { true } } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { nil } public var sendMsgEnabled: Bool { get { true } } public var displayName: String { get { ChatInfo.privateNotesChatName } } public var fullName: String { get { "" } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index 54e437afb1..4f48ccca52 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -42,7 +42,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import java.lang.reflect.Field import java.net.URI @@ -51,10 +50,10 @@ import java.net.URI actual fun PlatformTextField( composeState: MutableState, sendMsgEnabled: Boolean, + disabledText: String?, sendMsgButtonDisabled: Boolean, textStyle: MutableState, showDeleteTextButton: MutableState, - userIsObserver: Boolean, placeholder: String, showVoiceButton: Boolean, onMessageChange: (ComposeMessage) -> Unit, @@ -197,16 +196,16 @@ actual fun PlatformTextField( showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress } if (composeState.value.preview is ComposePreview.VoicePreview) { - ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) - } else if (userIsObserver) { - ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) + ComposeOverlay(generalGetString(MR.strings.voice_message_send_text), textStyle, padding) + } else if (disabledText != null) { + ComposeOverlay(disabledText, textStyle, padding) } } @Composable -private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { +private fun ComposeOverlay(text: String, textStyle: MutableState, padding: PaddingValues) { Text( - generalGetString(textId), + text, Modifier.padding(padding), color = MaterialTheme.colors.secondary, style = textStyle.value.copy(fontStyle = FontStyle.Italic) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 6ee609020a..61c20587bf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1204,6 +1204,7 @@ interface SomeChat { val apiId: Long val ready: Boolean val chatDeleted: Boolean + val userCantSendReason: Pair? val sendMsgEnabled: Boolean val incognito: Boolean fun featureEnabled(feature: ChatFeature): Boolean @@ -1228,14 +1229,6 @@ data class Chat( else -> false } - val userIsObserver: Boolean get() = when(chatInfo) { - is ChatInfo.Group -> { - val m = chatInfo.groupInfo.membership - m.memberActive && m.memberRole == GroupMemberRole.Observer - } - else -> false - } - val unreadTag: Boolean get() = when (chatInfo.chatSettings?.enableNtfs) { All -> chatStats.unreadChat || chatStats.unreadCount > 0 Mentions -> chatStats.unreadChat || chatStats.unreadMentions > 0 @@ -1282,6 +1275,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contact.apiId override val ready get() = contact.ready override val chatDeleted get() = contact.chatDeleted + override val userCantSendReason get() = contact.userCantSendReason override val sendMsgEnabled get() = contact.sendMsgEnabled override val incognito get() = contact.incognito override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature) @@ -1307,6 +1301,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = groupInfo.apiId override val ready get() = groupInfo.ready override val chatDeleted get() = groupInfo.chatDeleted + override val userCantSendReason get() = groupInfo.userCantSendReason override val sendMsgEnabled get() = groupInfo.sendMsgEnabled override val incognito get() = groupInfo.incognito override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature) @@ -1331,6 +1326,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = noteFolder.apiId override val ready get() = noteFolder.ready override val chatDeleted get() = noteFolder.chatDeleted + override val userCantSendReason get() = noteFolder.userCantSendReason override val sendMsgEnabled get() = noteFolder.sendMsgEnabled override val incognito get() = noteFolder.incognito override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature) @@ -1355,6 +1351,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contactRequest.apiId override val ready get() = contactRequest.ready override val chatDeleted get() = contactRequest.chatDeleted + override val userCantSendReason get() = contactRequest.userCantSendReason override val sendMsgEnabled get() = contactRequest.sendMsgEnabled override val incognito get() = contactRequest.incognito override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature) @@ -1379,6 +1376,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val apiId get() = contactConnection.apiId override val ready get() = contactConnection.ready override val chatDeleted get() = contactConnection.chatDeleted + override val userCantSendReason get() = contactConnection.userCantSendReason override val sendMsgEnabled get() = contactConnection.sendMsgEnabled override val incognito get() = contactConnection.incognito override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature) @@ -1408,6 +1406,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val id get() = "?$apiId" override val ready get() = false override val chatDeleted get() = false + override val userCantSendReason get() = generalGetString(MR.strings.cant_send_message_generic) to null override val sendMsgEnabled get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false @@ -1450,14 +1449,6 @@ sealed class ChatInfo: SomeChat, NamedChat { is InvalidJSON -> updatedAt } - val userCanSend: Boolean - get() = when (this) { - is ChatInfo.Direct -> true - is ChatInfo.Group -> groupInfo.membership.memberRole >= GroupMemberRole.Member - is ChatInfo.Local -> true - else -> false - } - val chatTags: List? get() = when (this) { is Direct -> contact.chatTags @@ -1528,13 +1519,17 @@ data class Contact( override val ready get() = activeConn?.connStatus == ConnStatus.Ready val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady val active get() = contactStatus == ContactStatus.Active - override val sendMsgEnabled get() = ( - sndReady - && active - && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?: false) - && !(activeConn?.connDisabled ?: true) - ) - || nextSendGrpInv + override val userCantSendReason: Pair? + get() { + // TODO [short links] this will have additional statuses for pending contact requests before they are accepted + if (nextSendGrpInv) return null + if (!active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null + if (!sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null + if (activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null + if (activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null + return null + } + override val sendMsgEnabled get() = userCantSendReason == null val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent override val incognito get() = contactConnIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { @@ -1768,7 +1763,23 @@ data class GroupInfo ( override val apiId get() = groupId override val ready get() = membership.memberActive override val chatDeleted get() = false - override val sendMsgEnabled get() = membership.memberActive + override val userCantSendReason: Pair? get() = + if (membership.memberActive) { + if (membership.memberRole == GroupMemberRole.Observer) { + generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc) + } else { + null + } + } else { + when (membership.memberStatus) { + GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null + GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null + GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null + GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null + else -> generalGetString(MR.strings.cant_send_message_generic) to null + } + } + override val sendMsgEnabled get() = userCantSendReason == null override val incognito get() = membership.memberIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on @@ -2144,6 +2155,7 @@ class NoteFolder( override val apiId get() = noteFolderId override val chatDeleted get() = false override val ready get() = true + override val userCantSendReason: Pair? = null override val sendMsgEnabled get() = true override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice @@ -2180,6 +2192,7 @@ class UserContactRequest ( override val apiId get() = contactRequestId override val chatDeleted get() = false override val ready get() = true + override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null override val sendMsgEnabled get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false @@ -2219,6 +2232,7 @@ class PendingContactConnection( override val apiId get() = pccConnId override val chatDeleted get() = false override val ready get() = false + override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null override val sendMsgEnabled get() = false override val incognito get() = customUserProfileId != null override fun featureEnabled(feature: ChatFeature) = false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt index 1daf5a7ba7..6b301b9df4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt @@ -12,10 +12,10 @@ import java.net.URI expect fun PlatformTextField( composeState: MutableState, sendMsgEnabled: Boolean, + disabledText: String?, sendMsgButtonDisabled: Boolean, textStyle: MutableState, showDeleteTextButton: MutableState, - userIsObserver: Boolean, placeholder: String, showVoiceButton: Boolean, onMessageChange: (ComposeMessage) -> Unit, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index ca4d4fc0da..37aa7fc1d1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -99,12 +99,11 @@ fun TerminalLayout( isDirectChat = false, liveMessageAlertShown = SharedPreference(get = { false }, set = {}), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = false, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, placeholder = "", sendMessage = { sendCommand() }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index b3fdcf79c0..6d7cdcdebe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -723,7 +723,7 @@ fun ChatLayout( Modifier .fillMaxWidth() .desktopOnExternalDrag( - enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value, + enabled = remember(attachmentDisabled.value, chatInfo.value?.sendMsgEnabled) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.sendMsgEnabled == true) }.value, onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) }, onImage = { file -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(file.toURI()), null) } }, onText = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index de9fc26905..894bcf3b37 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -999,9 +999,8 @@ fun ComposeView( chatModel.sharedContent.value = null } - val userCanSend = rememberUpdatedState(chat.chatInfo.userCanSend) val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled) - val userIsObserver = rememberUpdatedState(chat.userIsObserver) + val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason) val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv) Column { @@ -1056,7 +1055,6 @@ fun ComposeView( val attachmentEnabled = !composeState.value.attachmentDisabled && sendMsgEnabled.value - && userCanSend.value && !isGroupAndProhibitedFiles && !nextSendGrpInv.value IconButton( @@ -1102,8 +1100,8 @@ fun ComposeView( } } - LaunchedEffect(rememberUpdatedState(chat.chatInfo.userCanSend).value) { - if (!chat.chatInfo.userCanSend) { + LaunchedEffect(rememberUpdatedState(chat.chatInfo.sendMsgEnabled).value) { + if (!chat.chatInfo.sendMsgEnabled) { clearCurrentDraft() clearState() } @@ -1159,13 +1157,12 @@ fun ComposeView( chat.chatInfo is ChatInfo.Direct, liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown, sendMsgEnabled = sendMsgEnabled.value, + userCantSendReason = userCantSendReason.value, sendButtonEnabled = sendMsgEnabled.value && !(simplexLinkProhibited || fileProhibited || voiceProhibited), nextSendGrpInv = nextSendGrpInv.value, needToAllowVoiceToContact, allowedVoiceByPrefs, allowVoiceToContact = ::allowVoiceToContact, - userIsObserver = userIsObserver.value, - userCanSend = userCanSend.value, sendButtonColor = sendButtonColor, timedMessageAllowed = timedMessageAllowed, customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index 5524eff655..5710f09ed5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -15,9 +15,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.semantics.Role -import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.* import chat.simplex.common.model.* @@ -41,12 +39,11 @@ fun SendMsgView( isDirectChat: Boolean, liveMessageAlertShown: SharedPreference, sendMsgEnabled: Boolean, + userCantSendReason: Pair?, sendButtonEnabled: Boolean, nextSendGrpInv: Boolean, needToAllowVoiceToContact: Boolean, allowedVoiceByPrefs: Boolean, - userIsObserver: Boolean, - userCanSend: Boolean, sendButtonColor: Color = MaterialTheme.colors.primary, allowVoiceToContact: () -> Unit, timedMessageAllowed: Boolean = false, @@ -82,14 +79,14 @@ fun SendMsgView( (!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) || cs.endLiveDisabled || !sendButtonEnabled - val clicksOnTextFieldDisabled = !sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress + val clicksOnTextFieldDisabled = !sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || cs.inProgress PlatformTextField( composeState, sendMsgEnabled, + disabledText = userCantSendReason?.first, sendMsgButtonDisabled, textStyle, showDeleteTextButton, - userIsObserver, if (clicksOnTextFieldDisabled) "" else placeholder, showVoiceButton, onMessageChange, @@ -102,16 +99,23 @@ fun SendMsgView( } } if (clicksOnTextFieldDisabled) { - Box( - Modifier - .matchParentSize() - .clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.observer_cant_send_message_title), - text = generalGetString(MR.strings.observer_cant_send_message_desc) - ) - }) - ) + if (userCantSendReason != null) { + Box( + Modifier + .matchParentSize() + .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.cant_send_message_alert_title), + text = userCantSendReason.second + ) + }) + ) + } else { + Box( + Modifier + .matchParentSize() + ) + } } if (showDeleteTextButton.value) { DeleteTextButton(composeState) @@ -135,11 +139,11 @@ fun SendMsgView( Row(verticalAlignment = Alignment.CenterVertically) { val stopRecOnNextClick = remember { mutableStateOf(false) } when { - needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> { - DisallowedVoiceButton(userCanSend) { + needToAllowVoiceToContact || !allowedVoiceByPrefs -> { + DisallowedVoiceButton { if (needToAllowVoiceToContact) { showNeedToAllowVoiceAlert(allowVoiceToContact) - } else if (!allowedVoiceByPrefs) { + } else { showDisabledVoiceAlert(isDirectChat) } } @@ -155,7 +159,7 @@ fun SendMsgView( && cs.contextItem is ComposeContextItem.NoContextItem ) { Spacer(Modifier.width(12.dp)) - StartLiveMessageButton(userCanSend) { + StartLiveMessageButton { if (composeState.value.preview is ComposePreview.NoPreview) { startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown) } @@ -343,8 +347,8 @@ private fun RecordVoiceView(recState: MutableState, stopRecOnNex } @Composable -private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) { - IconButton(onClick, Modifier.size(36.dp), enabled = enabled) { +private fun DisallowedVoiceButton(onClick: () -> Unit) { + IconButton(onClick, Modifier.size(36.dp)) { Icon( painterResource(MR.images.ic_keyboard_voice), stringResource(MR.strings.icon_descr_record_voice_message), @@ -460,14 +464,13 @@ private fun SendMsgButton( } @Composable -private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { +private fun StartLiveMessageButton(onClick: () -> Unit) { val interactionSource = remember { MutableInteractionSource() } val ripple = remember { ripple(bounded = false, radius = 24.dp) } Box( modifier = Modifier.requiredSize(36.dp) .clickable( onClick = onClick, - enabled = enabled, role = Role.Button, interactionSource = interactionSource, indication = ripple @@ -477,7 +480,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) { Icon( BoltFilled, stringResource(MR.strings.icon_descr_send_message), - tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + tint = MaterialTheme.colors.primary, modifier = Modifier .size(36.dp) .padding(4.dp) @@ -576,12 +579,11 @@ fun PreviewSendMsgView() { isDirectChat = true, liveMessageAlertShown = SharedPreference(get = { true }, set = { }), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, timedMessageAllowed = false, placeholder = "", @@ -612,12 +614,11 @@ fun PreviewSendMsgViewEditing() { isDirectChat = true, liveMessageAlertShown = SharedPreference(get = { true }, set = { }), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, timedMessageAllowed = false, placeholder = "", @@ -648,12 +649,11 @@ fun PreviewSendMsgViewInProgress() { isDirectChat = true, liveMessageAlertShown = SharedPreference(get = { true }, set = { }), sendMsgEnabled = true, + userCantSendReason = null, sendButtonEnabled = true, nextSendGrpInv = false, needToAllowVoiceToContact = false, allowedVoiceByPrefs = true, - userIsObserver = false, - userCanSend = true, allowVoiceToContact = {}, timedMessageAllowed = false, placeholder = "", diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 1bea4c18d4..6726009a5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -487,8 +487,6 @@ The image cannot be decoded. Please, try a different image or contact developers. The video cannot be decoded. Please, try a different video or contact developers. you are observer - You can\'t send messages! - Please contact group admin. Files and media prohibited! Only group owners can enable files and media. Send direct message to connect @@ -508,6 +506,19 @@ Report content: only group moderators will see it. Report other: only group moderators will see it. + You can\'t send messages! + contact not ready + contact deleted + not synchronized + contact disabled + you are observer + Please contact group admin. + request to join rejected + group is deleted + removed from group + you left + can\'t send messages + Image Waiting for image diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt index d0d4fb5e92..41964b7d18 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -44,10 +44,10 @@ import kotlin.text.substring actual fun PlatformTextField( composeState: MutableState, sendMsgEnabled: Boolean, + disabledText: String?, sendMsgButtonDisabled: Boolean, textStyle: MutableState, showDeleteTextButton: MutableState, - userIsObserver: Boolean, placeholder: String, showVoiceButton: Boolean, onMessageChange: (ComposeMessage) -> Unit, @@ -203,16 +203,16 @@ actual fun PlatformTextField( ) showDeleteTextButton.value = cs.message.text.split("\n").size >= 4 && !cs.inProgress if (composeState.value.preview is ComposePreview.VoicePreview) { - ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding) - } else if (userIsObserver) { - ComposeOverlay(MR.strings.you_are_observer, textStyle, padding) + ComposeOverlay(generalGetString(MR.strings.voice_message_send_text), textStyle, padding) + } else if (disabledText != null) { + ComposeOverlay(disabledText, textStyle, padding) } } @Composable -private fun ComposeOverlay(textId: StringResource, textStyle: MutableState, padding: PaddingValues) { +private fun ComposeOverlay(text: String, textStyle: MutableState, padding: PaddingValues) { Text( - generalGetString(textId), + text, Modifier.padding(padding), color = MaterialTheme.colors.secondary, style = textStyle.value.copy(fontStyle = FontStyle.Italic) From cf0639bf2851ac1aa266ef5908530a83a4505daf Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 5 Jun 2025 21:05:16 +0100 Subject: [PATCH 05/14] website: add Whonix to reviews (#5966) --- README.md | 20 ++++++++++---------- images/privacy-guides.jpg | Bin 11225 -> 11430 bytes images/whonix-logo.jpg | Bin 0 -> 8183 bytes website/src/_includes/hero.html | 8 ++++++-- website/src/img/whonix-dark.png | Bin 0 -> 36012 bytes website/src/img/whonix-light.png | Bin 0 -> 29922 bytes 6 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 images/whonix-logo.jpg create mode 100644 website/src/img/whonix-dark.png create mode 100644 website/src/img/whonix-light.png diff --git a/README.md b/README.md index 40d552b84d..554c6068d9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ # SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design! -[](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)     [](https://www.privacyguides.org/en/real-time-communication/#simplex-chat)     [](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/) +[](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html)     [](https://www.privacyguides.org/en/real-time-communication/#simplex-chat)     [](https://www.whonix.org/wiki/Chat#Recommendation)     [](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/) ## Welcome to SimpleX Chat! @@ -110,6 +110,15 @@ After you connect, you can [verify connection security code](./blog/20230103-sim Read about the app features and settings in the new [User guide](./docs/guide/README.md). +## Contribute + +We would love to have you join the development! You can help us with: + +- [share the color theme](./docs/THEMES.md) you use in Android app! +- writing a tutorial or recipes about hosting servers, chat bot automations, etc. +- contributing to SimpleX Chat knowledge-base. +- developing features - please connect to us via chat so we can help you get started. + ## Help translating SimpleX Chat Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages. @@ -141,15 +150,6 @@ Join our translators to help SimpleX grow! Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed – please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us! -## Contribute - -We would love to have you join the development! You can help us with: - -- [share the color theme](./docs/THEMES.md) you use in Android app! -- writing a tutorial or recipes about hosting servers, chat bot automations, etc. -- contributing to SimpleX Chat knowledge-base. -- developing features - please connect to us via chat so we can help you get started. - ## Please support us with your donations Huge thank you to everybody who donated to SimpleX Chat! diff --git a/images/privacy-guides.jpg b/images/privacy-guides.jpg index f15a8862f7aaf91449031a0c11e8b669bb11f11f..5876d10c02a5f1bf13a746fec056c7a94d957cef 100644 GIT binary patch literal 11430 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 + + Whonix + + Heise Online @@ -43,7 +47,7 @@ Opt Out Podcast - +
@@ -66,7 +70,7 @@ - + {#

PLAY

diff --git a/website/src/img/whonix-dark.png b/website/src/img/whonix-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f361f4d1f6c1429ca368654146e59b8f5041b0e2 GIT binary patch literal 36012 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 Date: Fri, 6 Jun 2025 11:58:44 +0100 Subject: [PATCH 06/14] core: 6.3.4.2 (simplexmq 6.4.0.3) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- tests/ChatClient.hs | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index 3e6ccab8a5..44305353f2 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: deaec3cce286e959bd594b9620c307954b510a07 + tag: f44ea0a6d8eec8abf4af177ebeb91629f7d89165 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 8d17a2ce99..0c29b47a36 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."deaec3cce286e959bd594b9620c307954b510a07" = "0b8m4czjiwsi9169plslyk2rjw0f370vv7ha6qm2hpx14bxzz7xm"; + "https://github.com/simplex-chat/simplexmq.git"."f44ea0a6d8eec8abf4af177ebeb91629f7d89165" = "1biq1kq33v7hnacbhllry9n5c6dmh9dyqnz8hc5abgsv1z38qb1a"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index a9dca273e3..f3691ab1a6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.4.1 +version: 6.3.4.2 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 2d0bde5058..e3bab5a0ec 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -17,6 +17,7 @@ import Control.Concurrent (forkIOWithUnmask, killThread, threadDelay) import Control.Concurrent.Async import Control.Concurrent.STM import Control.Exception (bracket, bracket_) +import Control.Logger.Simple import Control.Monad import Control.Monad.Except import Control.Monad.Reader @@ -519,7 +520,7 @@ smpServerCfg = allowSMPProxy = True, serverClientConcurrency = 16, information = Nothing, - startOptions = StartOptions {maintenance = False, compactLog = False, skipWarnings = False, confirmMigrations = MCYesUp} + startOptions = StartOptions {maintenance = False, logLevel = LogError, compactLog = False, skipWarnings = False, confirmMigrations = MCYesUp} } persistentServerStoreCfg :: FilePath -> AServerStoreCfg From 50dfda6c0908a9c95391c85bcac70bfbc5237354 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 8 Jun 2025 18:27:42 +0100 Subject: [PATCH 07/14] core: fix deletion queries for PostgreSQL client (#5969) * core: fix deletion queries for PostgreSQL client * disable test in posrgres * plan --- src/Simplex/Chat/Store/Messages.hs | 10 ++++++++-- .../Chat/Store/SQLite/Migrations/chat_query_plans.txt | 2 +- tests/ChatTests/Groups.hs | 7 +++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index bdbb3fe67d..752a4a2c6d 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -171,7 +171,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Util (eitherToMaybe) import UnliftIO.STM #if defined(dbPostgres) -import Database.PostgreSQL.Simple (FromRow, Only (..), Query, ToRow, (:.) (..)) +import Database.PostgreSQL.Simple (FromRow, In (..), Only (..), Query, ToRow, (:.) (..)) import Database.PostgreSQL.Simple.SqlQQ (sql) #else import Database.SQLite.Simple (FromRow, Only (..), Query, ToRow, (:.) (..)) @@ -2370,8 +2370,14 @@ updateGroupChatItemModerated db User {userId} GroupInfo {groupId} ci m@GroupMemb updateMemberCIsModerated :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> UTCTime -> IO () updateMemberCIsModerated db User {userId} GroupInfo {groupId, membership} member byGroupMember md deletedTs = do itemIds <- updateCIs =<< getCurrentTime +#if defined(dbPostgres) + let inItemIds = Only $ In (map fromOnly itemIds) + DB.execute db "DELETE FROM messages WHERE message_id IN (SELECT message_id FROM chat_item_messages WHERE chat_item_id IN ?)" inItemIds + DB.execute db "DELETE FROM chat_item_versions WHERE chat_item_id IN ?" inItemIds +#else DB.executeMany db deleteChatItemMessagesQuery itemIds DB.executeMany db "DELETE FROM chat_item_versions WHERE chat_item_id = ?" itemIds +#endif where memId = groupMemberId' member updateQuery = @@ -2887,7 +2893,7 @@ getGroupCIMentions db ciId = SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias FROM chat_item_mentions r LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id - LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) WHERE r.chat_item_id = ? |] (Only ciId) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 88c6c33b41..1f16e0fdff 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -3186,7 +3186,7 @@ Query: SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias FROM chat_item_mentions r LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id - LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) + LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) WHERE r.chat_item_id = ? Plan: diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 73a8735f63..431f19c77f 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -84,7 +84,10 @@ chatGroupTests = do describe "batch send messages" $ do it "send multiple messages api" testSendMulti it "send multiple timed messages" testSendMultiTimed +#if !defined(dbPostgres) + -- TODO [postgres] this test hangs with PostgreSQL it "send multiple messages (many chat batches)" testSendMultiManyBatches +#endif xit'' "shared message body is reused" testSharedMessageBody xit'' "shared batch body is reused" testSharedBatchBody describe "async group connections" $ do @@ -1821,7 +1824,7 @@ testDeleteMemberWithMessages = do cath <## "alice updated group #team:" cath <## "updated group preferences:" - cath <## "Full deletion: on" + cath <## "Full deletion: on" ] threadDelay 750000 bob #> "#team hello" @@ -6496,7 +6499,7 @@ testForwardQuoteMention = bob <## " hello @alice @cath", do cath <# "#team alice!> -> forwarded" - cath <## " hello @alice @cath" + cath <## " hello @alice @cath" ] -- forward mentions alice `send` "@bob <- #team hello" From 6fdd50efb941a8ffce26179397f5a69704dc36f5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 8 Jun 2025 18:28:26 +0100 Subject: [PATCH 08/14] core: 6.3.5.0 --- simplex-chat.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f3691ab1a6..7d66292ef5 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.4.2 +version: 6.3.5.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 5f6595dda931563afd736e1eaa182d0e1359e09d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 9 Jun 2025 09:32:32 +0100 Subject: [PATCH 09/14] 6.3.5: ios 280, android 292, desktop 104 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5ebc7f9b4b..0a3a42d774 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -533,8 +533,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -692,8 +692,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,8 +778,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.4.1-Cm6JGiMgJjo4088oWn41JO.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */, ); path = Libraries; sourceTree = ""; @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1996,7 +1996,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2046,7 +2046,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2063,11 +2063,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2083,11 +2083,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2123,7 +2123,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2160,7 +2160,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2208,7 +2208,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2259,7 +2259,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2299,7 +2299,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 277; + CURRENT_PROJECT_VERSION = 280; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2333,7 +2333,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.4; + MARKETING_VERSION = 6.3.5; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 18add58bcf..bf87553b56 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,11 +24,11 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.4 -android.version_code=288 +android.version_name=6.3.5 +android.version_code=292 -desktop.version_name=6.3.4 -desktop.version_code=101 +desktop.version_name=6.3.5 +desktop.version_code=104 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 07abe24e18275695959688aadf9ba40dfaa021cb Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 14 Jun 2025 14:17:34 +0100 Subject: [PATCH 10/14] core: make decoding for short link data forward compatible (#5989) * core: make decoding for short link data forward compatible * simplexmq --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 44305353f2..ccdf35d776 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: f44ea0a6d8eec8abf4af177ebeb91629f7d89165 + tag: 6ac7101f4f9ad477d967537a647b06d3b6dff547 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0c29b47a36..1454f17a5b 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."f44ea0a6d8eec8abf4af177ebeb91629f7d89165" = "1biq1kq33v7hnacbhllry9n5c6dmh9dyqnz8hc5abgsv1z38qb1a"; + "https://github.com/simplex-chat/simplexmq.git"."6ac7101f4f9ad477d967537a647b06d3b6dff547" = "0vh6jsn1sh8v39zldar0g04snijyc0fq2h678rbbmk1pprcnwrr7"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From a593557c2116310f66d7cfb1390e3c4a974d701a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 14 Jun 2025 14:46:08 +0100 Subject: [PATCH 11/14] core: 6.3.6.0 (simplexmq 6.4.0.3.1) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cabal.project b/cabal.project index ccdf35d776..f406b9820e 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 6ac7101f4f9ad477d967537a647b06d3b6dff547 + tag: 3d62a383d5dcae6529d6d866233857182bcb4d47 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 1454f17a5b..84f9d0db34 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."6ac7101f4f9ad477d967537a647b06d3b6dff547" = "0vh6jsn1sh8v39zldar0g04snijyc0fq2h678rbbmk1pprcnwrr7"; + "https://github.com/simplex-chat/simplexmq.git"."3d62a383d5dcae6529d6d866233857182bcb4d47" = "133xm8jkim7agd6drwm3lbx1z7v8nf4l3asrm46ag3n2q201yfxc"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7d66292ef5..96b2f941c7 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.3.5.0 +version: 6.3.6.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 442d9afc4b3c1b6c8a165bbd2ab51bb1000f0087 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 14 Jun 2025 19:26:46 +0100 Subject: [PATCH 12/14] android: remove Contribute link from Android bundle --- apps/multiplatform/common/build.gradle.kts | 1 + .../chat/simplex/common/views/usersettings/SettingsView.kt | 5 ++++- apps/multiplatform/gradle.properties | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 345a75b1e7..e2927e4aaf 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -155,6 +155,7 @@ buildConfig { buildConfigField("String", "DESKTOP_VERSION_NAME", "\"${extra["desktop.version_name"]}\"") buildConfigField("int", "DESKTOP_VERSION_CODE", "${extra["desktop.version_code"]}") buildConfigField("String", "DATABASE_BACKEND", "\"${extra["database.backend"]}\"") + buildConfigField("Boolean", "ANDROID_BUNDLE", "${extra["android.bundle"]}") } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 5bd45ccaab..7ea656e1e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -21,6 +21,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* @@ -127,7 +128,9 @@ fun SettingsLayout( SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_support)) { - ContributeItem(uriHandler) + if (!BuildConfigCommon.ANDROID_BUNDLE) { + ContributeItem(uriHandler) + } RateAppItem(uriHandler) StarOnGithubItem(uriHandler) } diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index bf87553b56..de82f8272f 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -27,6 +27,8 @@ kotlin.jvm.target=11 android.version_name=6.3.5 android.version_code=292 +android.bundle=false + desktop.version_name=6.3.5 desktop.version_code=104 From c08189108e1616c820606a3050f87bdebfd07a1e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 14 Jun 2025 20:12:19 +0100 Subject: [PATCH 13/14] 6.3.6: ios 282, android 295, desktop 106 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 56 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0a3a42d774..9326ae9abe 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -174,8 +174,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -533,8 +533,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -692,8 +692,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,8 +778,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.5.0-34ngpCJsgXpChvTPhV0df0.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */, ); path = Libraries; sourceTree = ""; @@ -1971,7 +1971,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1996,7 +1996,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2021,7 +2021,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2046,7 +2046,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000"; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; @@ -2063,11 +2063,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2083,11 +2083,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2108,7 +2108,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2123,7 +2123,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2145,7 +2145,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2160,7 +2160,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2182,7 +2182,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2208,7 +2208,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2233,7 +2233,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2259,7 +2259,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2284,7 +2284,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2299,7 +2299,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2318,7 +2318,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 280; + CURRENT_PROJECT_VERSION = 282; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2333,7 +2333,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.3.5; + MARKETING_VERSION = 6.3.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index de82f8272f..aa4c7a7470 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,13 +24,13 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.3.5 -android.version_code=292 +android.version_name=6.3.6 +android.version_code=295 android.bundle=false -desktop.version_name=6.3.5 -desktop.version_code=104 +desktop.version_name=6.3.6 +desktop.version_code=106 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 3d22b738d8da3f89c5f8f91130ca0e2d62646a1a Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:38:02 +0000 Subject: [PATCH 14/14] core: fix change connection user (#5992) * core: fix change connection user * plans --- src/Simplex/Chat/Library/Commands.hs | 17 +-------- src/Simplex/Chat/Store/Direct.hs | 14 ------- .../SQLite/Migrations/agent_query_plans.txt | 4 -- .../SQLite/Migrations/chat_query_plans.txt | 8 ---- src/Simplex/Chat/View.hs | 7 ++-- tests/ChatTests/Profiles.hs | 38 +++++++++++++++---- 6 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index d3b945af4f..8c475b111b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1671,25 +1671,10 @@ processChatCommand' vr = \case case (pccConnStatus, connLinkInv) of (ConnNew, Just (CCLink cReqInv _)) -> do newUser <- privateGetUser newUserId - conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser) + conn' <- recreateConn user conn newUser pure $ CRConnectionUserChanged user conn conn' newUser _ -> throwChatError CEConnectionUserChangeProhibited where - canKeepLink :: ConnReqInvitation -> User -> CM Bool - canKeepLink (CRInvitationUri crData _) newUser = do - let ConnReqUriData {crSmpQueues = q :| _} = crData - SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q - newUserServers <- - map protoServer' . L.filter (\ServerCfg {enabled} -> enabled) - <$> getKnownAgentServers SPSMP newUser - pure $ smpServer `elem` newUserServers - updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do - withAgent $ \a -> changeConnectionUser a (aUserId user) (aConnId' conn) (aUserId newUser) - withFastStore' $ \db -> do - conn' <- updatePCCUser db userId conn newUserId - forM_ customUserProfileId $ \profileId -> - deletePCCIncognitoProfile db user profileId - pure conn' recreateConn user conn@PendingContactConnection {customUserProfileId, connLinkInv} newUser = do subMode <- chatReadVar subscriptionMode let userData = shortLinkUserData $ isJust $ connShortLink =<< connLinkInv diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 4de832a8b1..9318f62f76 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -66,7 +66,6 @@ module Simplex.Chat.Store.Direct updateContactAccepted, getUserByContactRequestId, getPendingContactConnections, - updatePCCUser, getContactConnections, getConnectionById, getConnectionsContacts, @@ -440,19 +439,6 @@ updatePCCIncognito db User {userId} conn customUserProfileId = do (customUserProfileId, updatedAt, userId, pccConnId conn) pure (conn :: PendingContactConnection) {customUserProfileId, updatedAt} -updatePCCUser :: DB.Connection -> UserId -> PendingContactConnection -> UserId -> IO PendingContactConnection -updatePCCUser db userId conn newUserId = do - updatedAt <- getCurrentTime - DB.execute - db - [sql| - UPDATE connections - SET user_id = ?, custom_user_profile_id = NULL, updated_at = ? - WHERE user_id = ? AND connection_id = ? - |] - (newUserId, updatedAt, userId, pccConnId conn) - pure (conn :: PendingContactConnection) {customUserProfileId = Nothing, updatedAt} - deletePCCIncognitoProfile :: DB.Connection -> User -> ProfileId -> IO () deletePCCIncognitoProfile db User {userId} profileId = DB.execute diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt index 13215dcb75..a85ba4a4cb 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt @@ -1071,10 +1071,6 @@ Query: UPDATE connections SET smp_agent_version = ? WHERE conn_id = ? Plan: SEARCH connections USING PRIMARY KEY (conn_id=?) -Query: UPDATE connections SET user_id = ? WHERE conn_id = ? and user_id = ? -Plan: -SEARCH connections USING PRIMARY KEY (conn_id=?) - Query: UPDATE messages SET msg_body = x'' WHERE conn_id = ? AND internal_id = ? Plan: SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 1f16e0fdff..e9ade30f93 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -4215,14 +4215,6 @@ Query: Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) -Query: - UPDATE connections - SET user_id = ?, custom_user_profile_id = NULL, updated_at = ? - WHERE user_id = ? AND connection_id = ? - -Plan: -SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) - Query: UPDATE contact_profiles SET contact_link = ?, updated_at = ? diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 42d1132961..4ba5acbb43 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1747,10 +1747,9 @@ viewConnectionIncognitoUpdated PendingContactConnection {pccConnId, customUserPr | otherwise = ["connection " <> sShow pccConnId <> " changed to non incognito"] viewConnectionUserChanged :: User -> PendingContactConnection -> User -> PendingContactConnection -> [StyledString] -viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId, connLinkInv} User {localDisplayName = n'} PendingContactConnection {connLinkInv = connLinkInv'} = - case (connLinkInv, connLinkInv') of - (Just ccLink, Just ccLink') - | ccLink /= ccLink' -> [userChangedStr <> ", new link:"] <> newLink ccLink' +viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {pccConnId} User {localDisplayName = n'} PendingContactConnection {connLinkInv = connLinkInv'} = + case connLinkInv' of + Just ccLink' -> [userChangedStr <> ", new link:"] <> newLink ccLink' _ -> [userChangedStr] where userChangedStr = "connection " <> sShow pccConnId <> " changed from user " <> plain n <> " to user " <> plain n' diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 433615e62a..adff745200 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1827,7 +1827,7 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ \alice bob -> do -- Create a new invite alice ##> "/connect" - inv <- getInvitation alice + _ <- getInvitation alice -- Create new user and go back to original user alice ##> "/create user alisa" showActiveUser alice "alisa" @@ -1837,12 +1837,18 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ showActiveUser alice "alice (Alice)" -- Change connection to newly created user alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + _ <- getTermLine alice + alice <## "" alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection back to other user alice ##> "/_set conn user :1 3" - alice <## "connection 1 changed from user alisa to user alisa2" + alice <## "connection 1 changed from user alisa to user alisa2, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" alice ##> "/user alisa2" showActiveUser alice "alisa2" -- Connect @@ -1851,13 +1857,14 @@ testChangePCCUser = testChat2 aliceProfile bobProfile $ concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alisa2: contact is connected") + alice <##> bob testChangePCCUserFromIncognito :: HasCallStack => TestParams -> IO () testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ \alice bob -> do -- Create a new invite and set as incognito alice ##> "/connect" - inv <- getInvitation alice + _ <- getInvitation alice alice ##> "/_set incognito :1 on" alice <## "connection 1 changed to incognito" -- Create new user and go back to original user @@ -1867,13 +1874,19 @@ testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ showActiveUser alice "alice (Alice)" -- Change connection to newly created user alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + _ <- getTermLine alice + alice <## "" alice `hasContactProfiles` ["alice"] alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection back to initial user alice ##> "/_set conn user :1 1" - alice <## "connection 1 changed from user alisa to user alice" + alice <## "connection 1 changed from user alisa to user alice, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" alice ##> "/user alice" showActiveUser alice "alice (Alice)" -- Connect @@ -1882,13 +1895,14 @@ testChangePCCUserFromIncognito = testChat2 aliceProfile bobProfile $ concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alice (Alice): contact is connected") + alice <##> bob testChangePCCUserAndThenIncognito :: HasCallStack => TestParams -> IO () testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ \alice bob -> do -- Create a new invite and set as incognito alice ##> "/connect" - inv <- getInvitation alice + _ <- getInvitation alice -- Create new user and go back to original user alice ##> "/create user alisa" showActiveUser alice "alisa" @@ -1896,7 +1910,10 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ showActiveUser alice "alice (Alice)" -- Change connection to newly created user alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" alice ##> "/user alisa" showActiveUser alice "alisa" -- Change connection to incognito and make sure it's attached to the newly created user profile @@ -1911,6 +1928,10 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ alice <## ("bob (Bob): contact is connected, your incognito profile for this contact is " <> alisaIncognito) alice <## ("use /i bob to print out this incognito profile again") ] + alice ?#> "@bob hi" + bob <# (alisaIncognito <> "> hi") + bob #> ("@" <> alisaIncognito <> " hey") + alice ?<# "bob> hey" testChangePCCUserDiffSrv :: HasCallStack => TestParams -> IO () testChangePCCUserDiffSrv ps = do @@ -1951,6 +1972,7 @@ testChangePCCUserDiffSrv ps = do concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alisa: contact is connected") + alice <##> bob where serverCfg' = smpServerCfg