diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 6d600f33ff..e85f6a664a 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -13,6 +13,7 @@ struct ContentView: View { @EnvironmentObject var chatModel: ChatModel @ObservedObject var alertManager = AlertManager.shared @ObservedObject var callController = CallController.shared + @ObservedObject var appSheetState = AppSheetState.shared @Environment(\.colorScheme) var colorScheme @EnvironmentObject var theme: AppTheme @EnvironmentObject var sceneDelegate: SceneDelegate @@ -250,7 +251,8 @@ struct ContentView: View { private func mainView() -> some View { ZStack(alignment: .top) { - ChatListView(activeUserPickerSheet: $chatListUserPickerSheet).privacySensitive(protectScreen) + ChatListView(activeUserPickerSheet: $chatListUserPickerSheet) + .redacted(reason: appSheetState.redactionReasons(protectScreen)) .onAppear { requestNtfAuthorization() // Local Authentication notice is to be shown on next start after onboarding is complete diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 8525a141e3..e6970d4728 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -147,6 +147,7 @@ final class ChatModel: ObservableObject { @Published var chatDbEncrypted: Bool? @Published var chatDbStatus: DBMigrationResult? @Published var ctrlInitInProgress: Bool = false + @Published var notificationResponse: UNNotificationResponse? // local authentication @Published var contentViewAccessAuthenticated: Bool = false @Published var laRequest: LocalAuthRequest? diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 95063845f1..18fabc5a32 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -29,17 +29,33 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { private var granted = false private var prevNtfTime: Dictionary = [:] + override init() { + super.init() + UNUserNotificationCenter.current().delegate = self + } + // Handle notification when app is in background func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler handler: () -> Void) { logger.debug("NtfManager.userNotificationCenter: didReceive") - let content = response.notification.request.content + if appStateGroupDefault.get() == .active { + processNotificationResponse(response) + } else { + logger.debug("NtfManager.userNotificationCenter: remember response in model") + ChatModel.shared.notificationResponse = response + } + handler() + } + + func processNotificationResponse(_ ntfResponse: UNNotificationResponse) { let chatModel = ChatModel.shared - let action = response.actionIdentifier - logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") + let content = ntfResponse.notification.request.content + let action = ntfResponse.actionIdentifier + logger.debug("NtfManager.processNotificationResponse: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)") if let userId = content.userInfo["userId"] as? Int64, userId != chatModel.currentUser?.userId { + logger.debug("NtfManager.processNotificationResponse changeActiveUser") changeActiveUser(userId, viewPwd: nil) } if content.categoryIdentifier == ntfCategoryContactRequest && (action == ntfActionAcceptContact || action == ntfActionAcceptContactIncognito), @@ -61,7 +77,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { ItemsModel.shared.loadOpenChat(chatId) } } - handler() } private func ntfCallAction(_ content: UNNotificationContent, _ action: String) -> (ChatId, NtfCallAction)? { @@ -76,7 +91,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { return nil } - // Handle notification when the app is in foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, @@ -210,7 +224,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { } } } - center.delegate = self } func notifyContactRequest(_ user: any UserLike, _ contactRequest: UserContactRequest) { diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 7f2c3b5866..0dd54782b4 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -82,11 +82,17 @@ struct SimpleXApp: App { if appState != .stopped { startChatAndActivate { - if appState.inactive && chatModel.chatRunning == true { - Task { - await updateChats() - if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { - await updateCallInvitations() + if chatModel.chatRunning == true { + if let ntfResponse = chatModel.notificationResponse { + chatModel.notificationResponse = nil + NtfManager.shared.processNotificationResponse(ntfResponse) + } + if appState.inactive { + Task { + await updateChats() + if !chatModel.showCallView && !CallController.shared.hasActiveCalls() { + await updateCallInvitations() + } } } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 971d088fe2..19e2b528f1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -287,8 +287,8 @@ struct ComposeView: View { // this is a workaround to fire an explicit event in certain cases @State private var stopPlayback: Bool = false - @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true - @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true + @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial var body: some View { VStack(spacing: 0) { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 50ec8f28c1..8880023e02 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -40,7 +40,7 @@ struct SendMessageView: View { @State private var showCustomTimePicker = false @State private var selectedDisappearingMessageTime: Int? = customDisappearingMessageTimeDefault.get() @State private var progressByTimeout = false - @AppStorage(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false + @UserDefault(DEFAULT_LIVE_MESSAGE_ALERT_SHOWN) private var liveMessageAlertShown = false var body: some View { ZStack { diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index dc1e40b6b6..d93edc0575 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -94,10 +94,10 @@ struct ReverseList: UIViewControllerRepresentable { if let section = self.dataSource.sectionIdentifier(for: indexPath.section) { let itemCount = self.getTotalItemsInItemSection(indexPath: indexPath) if self.representer.sectionModel.activeSection == section { - if self.scrollDirection == .toOldest, indexPath.item > itemCount - 8, itemCount > 8 { + if self.scrollDirection == .toOldest, indexPath.item > itemCount - 8 { let lastItem = self.getLastItemInItemSection(indexPath: indexPath) self.representer.loadPage(.toOldest, section, lastItem) - } else if self.scrollDirection == .toLatest, indexPath.item < 8, itemCount > 8 { + } else if self.scrollDirection == .toLatest, indexPath.item < 8 { let firstItem = self.getFirstItemInItemSection(indexPath: indexPath) self.representer.loadPage(.toLatest, section, firstItem) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index a1b40aadbe..7b24995f62 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -121,9 +121,11 @@ struct ChatListView: View { UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet) } ) - .sheet(item: $activeUserPickerSheet) { - UserPickerSheetView(sheet: $0) - } + .appSheet( + item: $activeUserPickerSheet, + onDismiss: { chatModel.laRequest = nil }, + content: { UserPickerSheetView(sheet: $0) } + ) .onChange(of: activeUserPickerSheet) { if $0 != nil { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { diff --git a/apps/ios/Shared/Views/Helpers/AppSheet.swift b/apps/ios/Shared/Views/Helpers/AppSheet.swift index 0ade1c0d8e..1e334367e8 100644 --- a/apps/ios/Shared/Views/Helpers/AppSheet.swift +++ b/apps/ios/Shared/Views/Helpers/AppSheet.swift @@ -11,6 +11,12 @@ import SwiftUI class AppSheetState: ObservableObject { static let shared = AppSheetState() @Published var scenePhaseActive: Bool = false + + func redactionReasons(_ protectScreen: Bool) -> RedactionReasons { + !protectScreen || scenePhaseActive + ? RedactionReasons() + : RedactionReasons.placeholder + } } private struct PrivacySensitive: ViewModifier { @@ -19,11 +25,7 @@ private struct PrivacySensitive: ViewModifier { @ObservedObject var appSheetState: AppSheetState = AppSheetState.shared func body(content: Content) -> some View { - if !protectScreen { - content - } else { - content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy) - } + content.redacted(reason: appSheetState.redactionReasons(protectScreen)) } } diff --git a/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift b/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift index ae94b4685c..9d5ae2e289 100644 --- a/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift +++ b/apps/ios/Shared/Views/Helpers/ThemeModeEditor.swift @@ -16,6 +16,7 @@ struct UserWallpaperEditor: View { @State var themeModeOverride: ThemeModeOverride @State var applyToMode: DefaultThemeMode? @State var showMore: Bool = false + @State var showFileImporter: Bool = false @Binding var globalThemeUsed: Bool var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void @@ -125,24 +126,27 @@ struct UserWallpaperEditor: View { CustomizeThemeColorsSection(editColor: { name in editColor(name, theme) }) - ImportExportThemeSection(perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in - let importedFromString = imported.wallpaper?.importFromString() - let importedType = importedFromString?.toAppWallpaper().type - let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get()) - let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } - let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors - let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) - Task { - await MainActor.run { - themeModeOverride = res - } - await save(applyToMode, res) - } - } + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: ChatModel.shared.currentUser?.uiThemes) } else { AdvancedSettingsButton(theme.colors.primary) { showMore = true } } } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { imported in + let importedFromString = imported.wallpaper?.importFromString() + let importedType = importedFromString?.toAppWallpaper().type + let currentTheme = ThemeManager.currentColors(nil, nil, nil, themeOverridesDefault.get()) + let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } + let colors = ThemeManager.currentThemeOverridesForExport(type, nil, nil).colors + let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) + Task { + await MainActor.run { + themeModeOverride = res + } + await save(applyToMode, res) + } + } + ) } private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool { @@ -216,6 +220,7 @@ struct ChatWallpaperEditor: View { @State var themeModeOverride: ThemeModeOverride @State var applyToMode: DefaultThemeMode? @State var showMore: Bool = false + @State var showFileImporter: Bool = false @Binding var globalThemeUsed: Bool var save: (DefaultThemeMode?, ThemeModeOverride?) async -> Void @@ -328,24 +333,27 @@ struct ChatWallpaperEditor: View { CustomizeThemeColorsSection(editColor: editColor) - ImportExportThemeSection(perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) { imported in - let importedFromString = imported.wallpaper?.importFromString() - let importedType = importedFromString?.toAppWallpaper().type - let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) - let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } - let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors - let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) - Task { - await MainActor.run { - themeModeOverride = res - } - await save(applyToMode, res) - } - } + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: themeModeOverride, perUser: ChatModel.shared.currentUser?.uiThemes) } else { AdvancedSettingsButton(theme.colors.primary) { showMore = true } } } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { imported in + let importedFromString = imported.wallpaper?.importFromString() + let importedType = importedFromString?.toAppWallpaper().type + let currentTheme = ThemeManager.currentColors(nil, nil, ChatModel.shared.currentUser?.uiThemes, themeOverridesDefault.get()) + let type: WallpaperType? = if importedType?.sameType(currentTheme.wallpaper.type) == true { nil } else { importedType } + let colors = ThemeManager.currentThemeOverridesForExport(type, nil, ChatModel.shared.currentUser?.uiThemes).colors + let res = ThemeModeOverride(mode: imported.base.mode, colors: imported.colors, wallpaper: importedFromString).removeSameColors(imported.base, colorsToCompare: colors) + Task { + await MainActor.run { + themeModeOverride = res + } + await save(applyToMode, res) + } + } + ) } private func onTypeCopyFromSameTheme(_ type: WallpaperType?) -> Bool { diff --git a/apps/ios/Shared/Views/Helpers/UserDefault.swift b/apps/ios/Shared/Views/Helpers/UserDefault.swift new file mode 100644 index 0000000000..5f18465d20 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/UserDefault.swift @@ -0,0 +1,62 @@ +// +// UserDefault.swift +// SimpleX (iOS) +// +// Created by user on 14/10/2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import Combine + +@propertyWrapper +public struct UserDefault: DynamicProperty { + @StateObject private var observer = UserDefaultObserver() + let initialValue: Value + let key: String + let store: UserDefaults + + public init( + wrappedValue: Value, + _ key: String, + store: UserDefaults = .standard + ) { + self.initialValue = wrappedValue + self.key = key + self.store = store + } + + public var wrappedValue: Value { + get { + // Observer can only be accessed after the property wrapper is installed in view (runtime exception) + observer.subscribe(to: key) + return store.object(forKey: key) as? Value ?? initialValue + } + nonmutating set { + store.set(newValue, forKey: key) + } + } +} + +private class UserDefaultObserver: ObservableObject { + private var subscribed = false + + func subscribe(to key: String) { + if !subscribed { + NotificationCenter.default.addObserver( + self, + selector: #selector(userDefaultsDidChange), + name: UserDefaults.didChangeNotification, + object: nil + ) + subscribed = true + } + } + + @objc + private func userDefaultsDidChange(_ notification: Notification) { + Task { @MainActor in objectWillChange.send() } + } + + deinit { NotificationCenter.default.removeObserver(self) } +} diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index 36c05ed43d..23e1f783f7 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -105,7 +105,12 @@ struct TerminalView: View { } } .navigationViewStyle(.stack) - .navigationTitle("Chat console") + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Chat console").font(.headline) + } + } .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 50f0a3a7b6..4c61d592ac 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -583,11 +583,14 @@ struct CustomizeThemeView: View { } } - ImportExportThemeSection(perChat: nil, perUser: nil, save: { theme in + ImportExportThemeSection(showFileImporter: $showFileImporter, perChat: nil, perUser: nil) + } + .modifier( + ThemeImporter(isPresented: $showFileImporter) { theme in ThemeManager.saveAndApplyThemeOverrides(theme) saveThemeToDatabase(nil) - }) - } + } + ) /// When changing app theme, user overrides are hidden. User overrides will be returned back after closing Appearance screen, see ThemeDestinationPicker() .interactiveDismissDisabled(true) } @@ -595,10 +598,9 @@ struct CustomizeThemeView: View { struct ImportExportThemeSection: View { @EnvironmentObject var theme: AppTheme + @Binding var showFileImporter: Bool var perChat: ThemeModeOverride? var perUser: ThemeModeOverrides? - var save: (ThemeOverrides) -> Void - @State private var showFileImporter = false var body: some View { Section { @@ -626,39 +628,47 @@ struct ImportExportThemeSection: View { } label: { Text("Import theme").foregroundColor(theme.colors.primary) } - .fileImporter( - isPresented: $showFileImporter, - allowedContentTypes: [.data/*.plainText*/], - allowsMultipleSelection: false - ) { result in - if case let .success(files) = result, let fileURL = files.first { - do { - var fileSize: Int? = nil - if fileURL.startAccessingSecurityScopedResource() { - let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) - fileSize = resourceValues.fileSize - } - if let fileSize = fileSize, - // Same as Android/desktop - fileSize <= 5_500_000 { - if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { - save(theme) - logger.error("Saved theme from file") - } else { - logger.error("Error decoding theme file") - } - fileURL.stopAccessingSecurityScopedResource() - } else { - fileURL.stopAccessingSecurityScopedResource() - let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary) - AlertManager.shared.showAlertMsg( - title: "Large file!", - message: "Currently maximum supported file size is \(prettyMaxFileSize)." - ) - } - } catch { - logger.error("Appearance fileImporter error \(error.localizedDescription)") + } + } +} + +struct ThemeImporter: ViewModifier { + @Binding var isPresented: Bool + var save: (ThemeOverrides) -> Void + + func body(content: Content) -> some View { + content.fileImporter( + isPresented: $isPresented, + allowedContentTypes: [.data/*.plainText*/], + allowsMultipleSelection: false + ) { result in + if case let .success(files) = result, let fileURL = files.first { + do { + var fileSize: Int? = nil + if fileURL.startAccessingSecurityScopedResource() { + let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) + fileSize = resourceValues.fileSize } + if let fileSize = fileSize, + // Same as Android/desktop + fileSize <= 5_500_000 { + if let string = try? String(contentsOf: fileURL, encoding: .utf8), let theme: ThemeOverrides = decodeYAML("themeId: \(UUID().uuidString)\n" + string) { + save(theme) + logger.error("Saved theme from file") + } else { + logger.error("Error decoding theme file") + } + fileURL.stopAccessingSecurityScopedResource() + } else { + fileURL.stopAccessingSecurityScopedResource() + let prettyMaxFileSize = ByteCountFormatter.string(fromByteCount: 5_500_000, countStyle: .binary) + AlertManager.shared.showAlertMsg( + title: "Large file!", + message: "Currently maximum supported file size is \(prettyMaxFileSize)." + ) + } + } catch { + logger.error("Appearance fileImporter error \(error.localizedDescription)") } } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index a7938b86c3..12a982e76b 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -331,7 +331,12 @@ struct SettingsView: View { chatDatabaseRow() NavigationLink { MigrateFromDevice(showProgressOnSettings: $showProgress) - .navigationTitle("Migrate device") + .toolbar { + // Redaction broken for `.navigationTitle` - using a toolbar item instead. + ToolbarItem(placement: .principal) { + Text("Migrate device").font(.headline) + } + } .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) } label: { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 2903388fb9..a21d47c16c 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; + CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */; }; CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; @@ -219,15 +220,15 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; + E56F46202CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */; }; + E56F46212CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */; }; E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; }; E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; }; E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; }; E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; }; E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C42CBA891A00D7A2FA /* libgmpxx.a */; }; E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C52CBA891A00D7A2FA /* libgmp.a */; }; - E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */; }; E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C72CBA891A00D7A2FA /* libffi.a */; }; - E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -545,6 +546,7 @@ CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = ""; }; CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; + CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = ""; }; CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = ""; }; CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = ""; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -560,6 +562,8 @@ D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; + E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a"; sourceTree = ""; }; + E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a"; sourceTree = ""; }; E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; }; E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; @@ -614,9 +618,7 @@ E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = ""; }; E5E997C42CBA891A00D7A2FA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; E5E997C52CBA891A00D7A2FA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a"; sourceTree = ""; }; E5E997C72CBA891A00D7A2FA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -656,13 +658,13 @@ buildActionMask = 2147483647; files = ( E5E997C92CBA891A00D7A2FA /* libgmpxx.a in Frameworks */, - E5E997CB2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a in Frameworks */, E5E997CA2CBA891A00D7A2FA /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, E5E997CC2CBA891A00D7A2FA /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E56F46202CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5E997CD2CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a in Frameworks */, + E56F46212CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -742,8 +744,8 @@ E5E997C72CBA891A00D7A2FA /* libffi.a */, E5E997C52CBA891A00D7A2FA /* libgmp.a */, E5E997C42CBA891A00D7A2FA /* libgmpxx.a */, - E5E997C62CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv-ghc9.6.3.a */, - E5E997C82CBA891A00D7A2FA /* libHSsimplex-chat-6.1.0.9-3X73OucN19a19eYgUK66sv.a */, + E56F461F2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9-ghc9.6.3.a */, + E56F461E2CC2B2E300F1559D /* libHSsimplex-chat-6.1.1.0-KwHIA7FZqPI5ZTCAoi00n9.a */, ); path = Libraries; sourceTree = ""; @@ -802,6 +804,7 @@ CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */, CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */, + CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */, ); path = Helpers; sourceTree = ""; @@ -1482,6 +1485,7 @@ 5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */, 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */, 5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */, + CEA6E91C2CBD21B0002B5DB4 /* UserDefault.swift in Sources */, 5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */, 5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */, 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */, @@ -1899,7 +1903,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1924,7 +1928,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1948,7 +1952,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1973,7 +1977,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1989,11 +1993,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2009,11 +2013,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2034,7 +2038,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2049,7 +2053,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2071,7 +2075,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2086,7 +2090,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2108,7 +2112,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2134,7 +2138,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2159,7 +2163,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2185,7 +2189,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2210,7 +2214,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2225,7 +2229,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2244,7 +2248,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 244; + CURRENT_PROJECT_VERSION = 245; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2259,7 +2263,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.1; + MARKETING_VERSION = 6.1.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index fae6d2293f..ea567427fe 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2088,6 +2088,7 @@ public enum ProtocolErrorType: Decodable, Hashable { case AUTH case CRYPTO case QUOTA + case STORE(storeErr: String) case NO_MSG case LARGE_MSG case EXPIRED diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 9f0ed746a4..29d45a3b9b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -6054,6 +6054,7 @@ sealed class SMPErrorType { is AUTH -> "AUTH" is CRYPTO -> "CRYPTO" is QUOTA -> "QUOTA" + is STORE -> "STORE ${storeErr}" is NO_MSG -> "NO_MSG" is LARGE_MSG -> "LARGE_MSG" is EXPIRED -> "EXPIRED" @@ -6066,6 +6067,7 @@ sealed class SMPErrorType { @Serializable @SerialName("AUTH") class AUTH: SMPErrorType() @Serializable @SerialName("CRYPTO") class CRYPTO: SMPErrorType() @Serializable @SerialName("QUOTA") class QUOTA: SMPErrorType() + @Serializable @SerialName("STORE") class STORE(val storeErr: String): SMPErrorType() @Serializable @SerialName("NO_MSG") class NO_MSG: SMPErrorType() @Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType() @Serializable @SerialName("EXPIRED") class EXPIRED: SMPErrorType() diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 907b0a94ae..32bd7579c8 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.1 -android.version_code=247 +android.version_name=6.1.1 +android.version_code=249 -desktop.version_name=6.1 -desktop.version_code=73 +desktop.version_name=6.1.1 +desktop.version_code=74 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 diff --git a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md index feb2a85036..cefe8560f5 100644 --- a/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md +++ b/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.md @@ -23,7 +23,7 @@ permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-use ## SimpleX cryptographic design review by Trail of Bits - + It's been almost two years since Trail of Bits did the first security assessment of SimpleX Chat. diff --git a/blog/20241016-wired-attack-on-privacy.md b/blog/20241016-wired-attack-on-privacy.md new file mode 100644 index 0000000000..3bc69c8176 --- /dev/null +++ b/blog/20241016-wired-attack-on-privacy.md @@ -0,0 +1,39 @@ +--- +layout: layouts/article.html +title: "Wired’s Attack on Privacy" +date: 2024-10-16 +previewBody: blog_previews/20241016.html +image: images/20241016-wired-privacy.jpg +imageWide: true +permalink: "/blog/20241016-wired-attack-on-privacy.html" +--- + +# Wired’s Attack on Privacy + + + +The [Wired article](https://www.wired.com/story/neo-nazis-flee-telegram-encrypted-app-simplex/) by David Gilbert focusing on neo-Nazis moving to SimpleX Chat following the Telegram's changes in privacy policy is biased and misleading. By cherry-picking information from [the report](https://www.isdglobal.org/digital_dispatches/neo-nazi-accelerationists-seek-new-digital-refuge-amid-looming-telegram-crackdown/) by the Institute for Strategic Dialogue (ISD), Wired fails to mention that SimpleX network design prioritizes privacy in order to protect human rights defenders, journalists, and everyday users who value their privacy — many people feel safer using SimpleX than non-private apps, being protected from strangers contacting them. + +Yes, privacy-focused SimpleX network offers encryption and anonymity — that’s the point. To paint this as problematic solely because of who may use such apps misses the broader, critical context. + +SimpleX’s true strength lies in protection of [users' metadata](./20240416-dangers-of-metadata-in-messengers.md), which can reveal sensitive information about who is communicating, when, and how often. SimpleX protocols are designed to minimize metadata collection. For countless people, especially vulnerable groups, these features can be life-saving. Wired article ignores these essential protections, and overlooks the positive aspects of having such a unique design, as noted in the publication which [they link to](https://www.maargentino.com/is-telegrams-privacy-shift-driving-extremists-toward-simplex/): + +> *“SimpleX also has a significant advantage when it comes to protecting metadata — the information that can reveal who you’re talking to, when, and how often. SimpleX is designed with privacy at its core, minimizing the amount of metadata collected and ensuring that any temporary data necessary for functionality is not retained or linked to identifiable users.”* + +Both publications referenced by Wired also explore how SimpleX design actually hinders extremist groups from spreading propaganda or building large networks. SimpleX design restricts message visibility and file retention, making it far from ideal for those looking to coordinate large networks. Yet these important qualities are ignored by Wired in favor of fear-mongering about encryption — an argument we've seen before when apps like Signal [faced similar treatment](https://foreignpolicy.com/2021/03/13/telegram-signal-apps-right-wing-extremism-islamic-state-terrorism-violence-europol-encrypted/). Ironically, Wired just a month earlier encouraged its readers to [adopt encrypted messaging apps](https://www.wired.com/story/gadget-lab-podcast-657/), making its current stance even more contradictory. + +The vilification of apps that offer critically important privacy, anonymity, and encryption must stop. That a small share of users may abuse these tools doesn’t justify broad criticism. Additionally, the lobbying for client-side scanning, which Wired’s article seems to indirectly endorse, is not only dangerous but goes against fundamental principles of free speech and personal security. We strongly oppose the use of private communications for any kind of monitoring, including AI training, which would undermine the very trust encryption is designed to build. + +It’s alarming to see Wired not only criticize SimpleX for its strong privacy protections but also subtly blame the European Court of Human Rights for [upholding basic human rights](https://www.theregister.com/2024/02/15/echr_backdoor_encryption/) by rejecting laws that would force encrypted apps to scan and hand over private messages before encryption. Wired writes: + +> *…European Court of Human Rights decision in February of this year ruled that forcing encrypted messaging apps to provide a backdoor to law enforcement was illegal. This decision undermined the EU’s controversial proposal that would potentially force encrypted messaging apps to scan all user content for identifiers of child sexual abuse material.* + +This commentary is both inappropriate and misguided — it plays into the hands of anti-privacy lobbyists attempting to criminalize access to private communications. Framing privacy and anonymity as tools for criminals ignores the reality that these protections are essential for millions of legitimate users, from activists to journalists, to ordinary citizens. Client-side scanning can't have any meaningful effect on reducing CSAM distribution, instead resulting in increase of crime and abuse when criminals get access to this data. + +We need to correct this narrative. The real danger lies not in protecting communication, but in failing to do so. Privacy apps like SimpleX are crucial, not just for those resisting mass surveillance, but for everyone who values the right to communicate without fear of their conversations being monitored or misused. This is a right we must defend and incorporate into law, [as we wrote before](./20240704-future-of-privacy-enforcing-privacy-standards.md). + +Wired could have stood on the right side of this battle and helped normalize the demand for privacy, genuinely protecting people from criminals and from the exploitation of the increasingly AI-enabled mass surveillance. Instead they chose the path of spreading fear and uncertainty of encrypted messaging and tools that enable privacy and anonymity. + +Spreading misinformation about privacy and security undermines trust in the tools that protect us, making it easier to justify more invasive surveillance measures that chip away at our civil liberties. + +Wired did not respond to our request for comment. diff --git a/blog/images/20241016-wired-privacy.jpg b/blog/images/20241016-wired-privacy.jpg new file mode 100644 index 0000000000..a97e9ba79c Binary files /dev/null and b/blog/images/20241016-wired-privacy.jpg differ diff --git a/cabal.project b/cabal.project index 2df43da784..4fc8a72d45 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: c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5 + tag: 967afaf802d7ea98480eaf280bfc6f35d4d43f05 source-repository-package type: git diff --git a/package.yaml b/package.yaml index d98b470be9..94dc13ad2e 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.9 +version: 6.1.1.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 487cbfa9dd..d68731083f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."c41bfe831d6d3fdf068f7419cbfed6afa46cb5b5" = "1awqy4srdgcwmjf7q3s9w75w6wp38qk65fza2k3q1a1s2lj6h8w1"; + "https://github.com/simplex-chat/simplexmq.git"."967afaf802d7ea98480eaf280bfc6f35d4d43f05" = "0k8m07hxfgn8h8pqrfchqd8490fvv1jf8slw8qjp0vxdpxa84n3i"; "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 6913ddc14d..96d16f5004 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.1.0.9 +version: 6.1.1.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5f586e8dad..c875f9afa3 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -203,8 +203,9 @@ _defaultSMPServers = _defaultNtfServers :: [NtfServer] _defaultNtfServers = - [ "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", - "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" + [ "ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,5ex3mupcazy3zlky64ab27phjhijpemsiby33qzq3pliejipbtx5xgad.onion" + -- "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", + -- "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" ] maxImageSize :: Integer diff --git a/website/src/_includes/blog_previews/20241016.html b/website/src/_includes/blog_previews/20241016.html new file mode 100644 index 0000000000..d7bd7f5483 --- /dev/null +++ b/website/src/_includes/blog_previews/20241016.html @@ -0,0 +1,4 @@ +

The Wired article by David Gilbert focusing on neo-Nazis moving to SimpleX Chat following the Telegram's changes in + privacy policy is biased and misleading. By cherry-picking information from the report by the Institute for + Strategic Dialogue (ISD), Wired fails to mention that SimpleX network design prioritizes privacy in order to protect + human rights defenders, journalists, and everyday users who value their privacy.

\ No newline at end of file