From c48583791060de352b1b1cbf6282cfdaed870e5b Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 22 Aug 2024 15:02:32 +0100 Subject: [PATCH 001/704] ios: allow for chat profile selection on new chat screen (#4729) * ios: allow for chat profile selection on new chat screen * add api and types * initial api connection with error handling * improve incognito handling * adjustments to different server connections * loading state * simpler handling of race * smaller delay * improve error handling and messages * fix header * remove tap section footer * incognito adjustments * set UI driving vars in main thread * remove result * incognito in profile picker and footer * put incognito mask inside a circle * fix click on incognito when already selected * fix avoid users swapping position when picker is active * fix pending contact cleanup logic * icons * restore incognito help * fix updating qr code * remove info from footer * layout --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- apps/ios/Shared/Model/SimpleXAPI.swift | 7 + .../Views/NewChat/AddContactLearnMore.swift | 2 + .../Views/NewChat/NewChatMenuButton.swift | 31 +- .../Shared/Views/NewChat/NewChatView.swift | 317 +++++++++++++++--- apps/ios/SimpleXChat/APITypes.swift | 7 + 5 files changed, 306 insertions(+), 58 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ebc58c6a05..e4b5410392 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -673,6 +673,13 @@ func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> P throw r } +func apiChangeConnectionUser(connId: Int64, userId: Int64) async throws -> PendingContactConnection? { + let r = await chatSendCmd(.apiChangeConnectionUser(connId: connId, userId: userId)) + + if case let .connectionUserChanged(_, _, toConnection, _) = r {return toConnection} + throw r +} + func apiConnectPlan(connReq: String) async throws -> ConnectionPlan { let userId = try currentUserId("apiConnectPlan") let r = await chatSendCmd(.apiConnectPlan(userId: userId, connReq: connReq)) diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift index 6001dff790..3a64a955c5 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -28,7 +28,9 @@ struct AddContactLearnMore: View { Text("If you can't meet in person, show QR code in a video call, or share the link.") Text("Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).") } + .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } .modifier(ThemedBackground(grouped: true)) } diff --git a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift index bcca763a75..78dc2be7b8 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatMenuButton.swift @@ -14,9 +14,10 @@ enum ContactType: Int { } struct NewChatMenuButton: View { + @EnvironmentObject var chatModel: ChatModel @State private var showNewChatSheet = false @State private var alert: SomeAlert? = nil - @State private var globalAlert: SomeAlert? = nil + @State private var pendingConnection: PendingContactConnection? = nil var body: some View { Button { @@ -28,22 +29,14 @@ struct NewChatMenuButton: View { .frame(width: 24, height: 24) } .appSheet(isPresented: $showNewChatSheet) { - NewChatSheet(alert: $alert) + NewChatSheet(pendingConnection: $pendingConnection) .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) - .alert(item: $alert) { a in - return a.alert + .onDisappear { + alert = cleanupPendingConnection(chatModel: chatModel, contactConnection: pendingConnection) + pendingConnection = nil } } - // This is a workaround to show "Keep unused invitation" alert in both following cases: - // - on going back from NewChatView to NewChatSheet, - // - on dismissing NewChatMenuButton sheet while on NewChatView (skipping NewChatSheet) - .onChange(of: alert?.id) { a in - if !showNewChatSheet && alert != nil { - globalAlert = alert - alert = nil - } - } - .alert(item: $globalAlert) { a in + .alert(item: $alert) { a in return a.alert } } @@ -60,7 +53,8 @@ struct NewChatSheet: View { @State private var searchText = "" @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil - @Binding var alert: SomeAlert? + @State private var alert: SomeAlert? + @Binding var pendingConnection: PendingContactConnection? // Sheet height management @State private var isAddContactActive = false @@ -78,6 +72,9 @@ struct NewChatSheet: View { .navigationBarTitleDisplayMode(.large) .navigationBarHidden(searchMode) .modifier(ThemedBackground(grouped: true)) + .alert(item: $alert) { a in + return a.alert + } } if #available(iOS 16.0, *), oneHandUI { let sheetHeight: CGFloat = showArchive ? 575 : 500 @@ -112,7 +109,7 @@ struct NewChatSheet: View { if (searchText.isEmpty) { Section { NavigationLink(isActive: $isAddContactActive) { - NewChatView(selection: .invite, parentAlert: $alert) + NewChatView(selection: .invite, parentAlert: $alert, contactConnection: $pendingConnection) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) @@ -122,7 +119,7 @@ struct NewChatSheet: View { } } NavigationLink(isActive: $isScanPasteLinkActive) { - NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert) + NewChatView(selection: .connect, showQRCodeScanner: true, parentAlert: $alert, contactConnection: $pendingConnection) .navigationTitle("New chat") .modifier(ThemedBackground(grouped: true)) .navigationBarTitleDisplayMode(.large) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 6cbc65e7c9..9a52bfaa20 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -45,18 +45,47 @@ enum NewChatOption: Identifiable { var id: Self { self } } +func cleanupPendingConnection(chatModel: ChatModel, contactConnection: PendingContactConnection?) -> SomeAlert? { + var alert: SomeAlert? = nil + + if !(chatModel.showingInvitation?.connChatUsed ?? true), + let conn = contactConnection { + alert = SomeAlert( + alert: Alert( + title: Text("Keep unused invitation?"), + message: Text("You can view invitation link again in connection details."), + primaryButton: .default(Text("Keep")) {}, + secondaryButton: .destructive(Text("Delete")) { + Task { + await deleteChat(Chat( + chatInfo: .contactConnection(contactConnection: conn), + chatItems: [] + )) + } + } + ), + id: "keepUnusedInvitation" + ) + } + + chatModel.showingInvitation = nil + + return alert +} + struct NewChatView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @State var selection: NewChatOption @State var showQRCodeScanner = false @State private var invitationUsed: Bool = false - @State private var contactConnection: PendingContactConnection? = nil @State private var connReqInvitation: String = "" @State private var creatingConnReq = false + @State var choosingProfile = false @State private var pastedLink: String = "" @State private var alert: NewChatViewAlert? @Binding var parentAlert: SomeAlert? + @Binding var contactConnection: PendingContactConnection? var body: some View { VStack(alignment: .leading) { @@ -122,26 +151,10 @@ struct NewChatView: View { } } .onDisappear { - if !(m.showingInvitation?.connChatUsed ?? true), - let conn = contactConnection { - parentAlert = SomeAlert( - alert: Alert( - title: Text("Keep unused invitation?"), - message: Text("You can view invitation link again in connection details."), - primaryButton: .default(Text("Keep")) {}, - secondaryButton: .destructive(Text("Delete")) { - Task { - await deleteChat(Chat( - chatInfo: .contactConnection(contactConnection: conn), - chatItems: [] - )) - } - } - ), - id: "keepUnusedInvitation" - ) + if !choosingProfile { + parentAlert = cleanupPendingConnection(chatModel: m, contactConnection: contactConnection) + contactConnection = nil } - m.showingInvitation = nil } .alert(item: $alert) { a in switch(a) { @@ -159,7 +172,8 @@ struct NewChatView: View { InviteView( invitationUsed: $invitationUsed, contactConnection: $contactConnection, - connReqInvitation: connReqInvitation + connReqInvitation: $connReqInvitation, + choosingProfile: $choosingProfile ) } else if creatingConnReq { creatingLinkProgressView() @@ -210,13 +224,25 @@ struct NewChatView: View { } } +private func incognitoProfileImage() -> some View { + Image(systemName: "theatermasks.fill") + .resizable() + .scaledToFit() + .frame(width: 30) + .foregroundColor(.indigo) +} + private struct InviteView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @Binding var invitationUsed: Bool @Binding var contactConnection: PendingContactConnection? - var connReqInvitation: String + @Binding var connReqInvitation: String + @Binding var choosingProfile: Bool + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false + @State private var showSettings: Bool = false + @State private var showIncognitoSheet = false var body: some View { List { @@ -226,28 +252,43 @@ private struct InviteView: View { .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10)) qrCodeView() - - Section { - IncognitoToggle(incognitoEnabled: $incognitoDefault) - } footer: { - sharedProfileInfo(incognitoDefault) - .foregroundColor(theme.colors.secondary) - } - } - .onChange(of: incognitoDefault) { incognito in - Task { - do { - if let contactConn = contactConnection, - let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { - await MainActor.run { - contactConnection = conn - chatModel.updateContactConnection(conn) + if let selectedProfile = chatModel.currentUser { + Section { + NavigationLink { + ActiveProfilePicker( + contactConnection: $contactConnection, + connReqInvitation: $connReqInvitation, + incognitoEnabled: $incognitoDefault, + choosingProfile: $choosingProfile, + selectedProfile: selectedProfile + ) + } label: { + HStack { + if incognitoDefault { + incognitoProfileImage() + Text("Incognito") + } else { + ProfileImage(imageStr: chatModel.currentUser?.image, size: 30) + Text(chatModel.currentUser?.chatViewName ?? "") + } } } - } catch { - logger.error("apiSetConnectionIncognito error: \(responseError(error))") + } header: { + Text("Share profile").foregroundColor(theme.colors.secondary) + } footer: { + if incognitoDefault { + Text("A new random profile will be shared.") + } } } + } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } + .onChange(of: incognitoDefault) { incognito in + setInvitationUsed() + } + .onChange(of: chatModel.currentUser) { u in setInvitationUsed() } } @@ -270,6 +311,7 @@ private struct InviteView: View { private func qrCodeView() -> some View { Section(header: Text("Or show this code").foregroundColor(theme.colors.secondary)) { SimpleXLinkQRCode(uri: connReqInvitation, onShare: setInvitationUsed) + .id("simplex-qrcode-view-for-\(connReqInvitation)") .padding() .background( RoundedRectangle(cornerRadius: 12, style: .continuous) @@ -289,6 +331,197 @@ private struct InviteView: View { } } +private struct ActiveProfilePicker: View { + @Environment(\.dismiss) var dismiss + @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @Binding var contactConnection: PendingContactConnection? + @Binding var connReqInvitation: String + @Binding var incognitoEnabled: Bool + @Binding var choosingProfile: Bool + @State private var alert: SomeAlert? + @State private var switchingProfile = false + @State private var switchingProfileByTimeout = false + @State private var lastSwitchingProfileByTimeoutCall: Double? + @State private var profiles: [User] = [] + @State var selectedProfile: User + + var body: some View { + viewBody() + .navigationTitle("Select chat profile") + .navigationBarTitleDisplayMode(.large) + .onAppear { + profiles = chatModel.users + .map { $0.user } + .filter({ u in u.activeUser || !u.hidden }) + .sorted { u, _ in u.activeUser } + } + .onChange(of: incognitoEnabled) { incognito in + if !switchingProfile { + return + } + + Task { + do { + if let contactConn = contactConnection, + let conn = try await apiSetConnectionIncognito(connId: contactConn.pccConnId, incognito: incognito) { + await MainActor.run { + contactConnection = conn + chatModel.updateContactConnection(conn) + switchingProfile = false + dismiss() + } + } + } catch { + switchingProfile = false + incognitoEnabled = !incognito + logger.error("apiSetConnectionIncognito error: \(responseError(error))") + let err = getErrorAlert(error, "Error changing to incognito!") + + alert = SomeAlert( + alert: Alert( + title: Text(err.title), + message: Text(err.message ?? "Error: \(responseError(error))") + ), + id: "setConnectionIncognitoError" + ) + } + } + } + .onChange(of: switchingProfile) { sp in + if sp { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + switchingProfileByTimeout = switchingProfile + } + } else { + switchingProfileByTimeout = false + } + } + .onChange(of: selectedProfile) { profile in + if (profile == chatModel.currentUser) { + return + } + Task { + do { + switchingProfile = true + if let contactConn = contactConnection, + let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) { + + await MainActor.run { + contactConnection = conn + connReqInvitation = conn.connReqInv ?? "" + chatModel.updateContactConnection(conn) + } + do { + try await changeActiveUserAsync_(profile.userId, viewPwd: nil) + await MainActor.run { + switchingProfile = false + dismiss() + } + } catch { + await MainActor.run { + switchingProfile = false + alert = SomeAlert( + alert: Alert( + title: Text("Error switching profile"), + message: Text("Your connection was moved to \(profile.chatViewName) but and unexpected error ocurred while redirecting you to the profile.") + ), + id: "switchingProfileError" + ) + } + } + } + } catch { + await MainActor.run { + // TODO: discuss error handling + switchingProfile = false + if let currentUser = chatModel.currentUser { + selectedProfile = currentUser + } + let err = getErrorAlert(error, "Error changing connection profile") + alert = SomeAlert( + alert: Alert( + title: Text(err.title), + message: Text(err.message ?? "Error: \(responseError(error))") + ), + id: "changeConnectionUserError" + ) + } + } + } + } + .alert(item: $alert) { a in + a.alert + } + .onAppear { + choosingProfile = true + } + .onDisappear { + choosingProfile = false + } + } + + + @ViewBuilder private func viewBody() -> some View { + NavigationView { + if switchingProfileByTimeout { + ProgressView("Switching profile…") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .modifier(ThemedBackground(grouped: true)) + } else { + profilePicker() + .modifier(ThemedBackground(grouped: true)) + } + } + } + + @ViewBuilder private func profilePicker() -> some View { + List { + Button { + if !incognitoEnabled { + incognitoEnabled = true + switchingProfile = true + } + } label : { + HStack { + incognitoProfileImage() + Text("Incognito") + .foregroundColor(theme.colors.onBackground) + Spacer() + if incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } + } + } + ForEach(profiles) { item in + Button { + if selectedProfile != item || incognitoEnabled { + switchingProfile = true + incognitoEnabled = false + selectedProfile = item + } + } label: { + HStack { + ProfileImage(imageStr: item.image, size: 30) + .padding(.trailing, 2) + Text(item.chatViewName) + .foregroundColor(theme.colors.onBackground) + .lineLimit(1) + Spacer() + if selectedProfile == item, !incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } + } + } + } + } + } +} + private struct ConnectView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var theme: AppTheme @@ -975,10 +1208,12 @@ func connReqSentAlert(_ type: ConnReqType) -> Alert { struct NewChatView_Previews: PreviewProvider { static var previews: some View { @State var parentAlert: SomeAlert? + @State var contactConnection: PendingContactConnection? = nil NewChatView( selection: .invite, - parentAlert: $parentAlert + parentAlert: $parentAlert, + contactConnection: $contactConnection ) } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index a6409dec2f..e5de52828d 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -97,6 +97,7 @@ public enum ChatCommand { case apiVerifyGroupMember(groupId: Int64, groupMemberId: Int64, connectionCode: String?) case apiAddContact(userId: Int64, incognito: Bool) case apiSetConnectionIncognito(connId: Int64, incognito: Bool) + case apiChangeConnectionUser(connId: Int64, userId: Int64) case apiConnectPlan(userId: Int64, connReq: String) case apiConnect(userId: Int64, incognito: Bool, connReq: String) case apiConnectContactViaAddress(userId: Int64, incognito: Bool, contactId: Int64) @@ -262,6 +263,7 @@ public enum ChatCommand { case let .apiVerifyGroupMember(groupId, groupMemberId, .none): return "/_verify code #\(groupId) \(groupMemberId)" case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))" case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))" + case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)" case let .apiConnectPlan(userId, connReq): return "/_connect plan \(userId) \(connReq)" case let .apiConnect(userId, incognito, connReq): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connReq)" case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)" @@ -403,6 +405,7 @@ public enum ChatCommand { case .apiVerifyGroupMember: return "apiVerifyGroupMember" case .apiAddContact: return "apiAddContact" case .apiSetConnectionIncognito: return "apiSetConnectionIncognito" + case .apiChangeConnectionUser: return "apiChangeConnectionUser" case .apiConnectPlan: return "apiConnectPlan" case .apiConnect: return "apiConnect" case .apiDeleteChat: return "apiDeleteChat" @@ -555,6 +558,7 @@ public enum ChatResponse: Decodable, Error { case connectionVerified(user: UserRef, verified: Bool, expectedCode: String) case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection) case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection) + case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef) case connectionPlan(user: UserRef, connectionPlan: ConnectionPlan) case sentConfirmation(user: UserRef, connection: PendingContactConnection) case sentInvitation(user: UserRef, connection: PendingContactConnection) @@ -725,6 +729,7 @@ public enum ChatResponse: Decodable, Error { case .connectionVerified: return "connectionVerified" case .invitation: return "invitation" case .connectionIncognitoUpdated: return "connectionIncognitoUpdated" + case .connectionUserChanged: return "connectionUserChanged" case .connectionPlan: return "connectionPlan" case .sentConfirmation: return "sentConfirmation" case .sentInvitation: return "sentInvitation" @@ -893,6 +898,7 @@ public enum ChatResponse: Decodable, Error { case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)") case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)") case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection)) + case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\newUserId: \(String(describing: newUser.userId))") case let .connectionPlan(u, connectionPlan): return withUser(u, String(describing: connectionPlan)) case let .sentConfirmation(u, connection): return withUser(u, String(describing: connection)) case let .sentInvitation(u, connection): return withUser(u, String(describing: connection)) @@ -1857,6 +1863,7 @@ public enum ChatErrorType: Decodable, Hashable { case agentCommandError(message: String) case invalidFileDescription(message: String) case connectionIncognitoChangeProhibited + case connectionUserChangeProhibited case peerChatVRangeIncompatible case internalError(message: String) case exception(message: String) From 791489e9430fdce4a1eceef71d6f1e70512fd866 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:36:35 +0400 Subject: [PATCH 002/704] core: multi forward api (#4704) --- apps/ios/SimpleXChat/APITypes.swift | 1 - apps/simplex-bot-advanced/Main.hs | 2 +- .../src/Broadcast/Bot.hs | 2 +- .../src/Directory/Events.hs | 2 +- src/Simplex/Chat.hs | 752 +++++++++++------- src/Simplex/Chat/Bot.hs | 7 +- src/Simplex/Chat/Controller.hs | 9 +- src/Simplex/Chat/Messages/Batch.hs | 10 +- src/Simplex/Chat/Store/Files.hs | 10 +- src/Simplex/Chat/Terminal/Input.hs | 4 +- src/Simplex/Chat/Terminal/Main.hs | 2 +- src/Simplex/Chat/Terminal/Output.hs | 5 +- src/Simplex/Chat/View.hs | 6 +- tests/ChatTests/Direct.hs | 17 +- tests/ChatTests/Files.hs | 24 +- tests/ChatTests/Forward.hs | 38 +- tests/ChatTests/Groups.hs | 26 +- tests/ChatTests/Local.hs | 17 +- tests/ChatTests/Profiles.hs | 4 +- tests/MessageBatching.hs | 2 +- tests/RemoteTests.hs | 4 +- 21 files changed, 601 insertions(+), 343 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index e5de52828d..3dc9138774 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1848,7 +1848,6 @@ public enum ChatErrorType: Decodable, Hashable { case inlineFileProhibited(fileId: Int64) case invalidQuote case invalidForward - case forwardNoFile case invalidChatItemUpdate case invalidChatItemDelete case hasCurrentCall diff --git a/apps/simplex-bot-advanced/Main.hs b/apps/simplex-bot-advanced/Main.hs index 50d7005e34..4733dafb79 100644 --- a/apps/simplex-bot-advanced/Main.hs +++ b/apps/simplex-bot-advanced/Main.hs @@ -46,7 +46,7 @@ mySquaringBot _user cc = do CRContactConnected _ contact _ -> do contactConnected contact sendMessage cc contact welcomeMessage - CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc number_ = readMaybe msg :: Maybe Integer sendMessage cc contact $ case number_ of diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs index 5fa3fff0a7..da021ee0b5 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Bot.hs @@ -40,7 +40,7 @@ broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _u CRContactConnected _ ct _ -> do contactConnected ct sendMessage cc ct welcomeMessage - CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _} | publisher `elem` publishers -> if allowContent mc then do diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 33b43a239b..64e6acf1d8 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -73,7 +73,7 @@ crDirectoryEvent = \case CRGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo CRChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct CRChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct - CRNewChatItem {chatItem = AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}} -> + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} -> Just $ case (mc, itemLive) of (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t _ -> DEUnsupportedMessage ct ciId diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 5899be6445..54a7f1ef0b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -38,10 +38,11 @@ import Data.Char import Data.Constraint (Dict (..)) import Data.Either (fromRight, lefts, partitionEithers, rights) import Data.Fixed (div') +import Data.Foldable (foldr') import Data.Functor (($>)) import Data.Functor.Identity import Data.Int (Int64) -import Data.List (find, foldl', isSuffixOf, mapAccumL, partition, sortOn) +import Data.List (find, foldl', isSuffixOf, mapAccumL, partition, sortOn, zipWith4) import Data.List.NonEmpty (NonEmpty (..), nonEmpty, toList, (<|)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) @@ -763,18 +764,18 @@ processChatCommand' vr = \case Just (CIFFGroup _ _ (Just gId) (Just fwdItemId)) -> Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTGroup gId) fwdItemId) _ -> pure Nothing - APISendMessage (ChatRef cType chatId) live itemTTL cm -> withUser $ \user -> case cType of + APISendMessages (ChatRef cType chatId) live itemTTL cms -> withUser $ \user -> case cType of CTDirect -> withContactLock "sendMessage" chatId $ - sendContactContentMessage user chatId live itemTTL cm Nothing + sendContactContentMessages user chatId live itemTTL (L.map (,Nothing) cms) CTGroup -> withGroupLock "sendMessage" chatId $ - sendGroupContentMessage user chatId live itemTTL cm Nothing + sendGroupContentMessages user chatId live itemTTL (L.map (,Nothing) cms) CTLocal -> pure $ chatCmdError (Just user) "not supported" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" - APICreateChatItem folderId cm -> withUser $ \user -> - createNoteFolderContentItem user folderId cm Nothing + APICreateChatItems folderId cms -> withUser $ \user -> + createNoteFolderContentItems user folderId (L.map (,Nothing) cms) APIUpdateChatItem (ChatRef cType chatId) itemId live mc -> withUser $ \user -> case cType of CTDirect -> withContactLock "updateChatItem" chatId $ do ct@Contact {contactId} <- withFastStore $ \db -> getContact db vr user chatId @@ -813,7 +814,7 @@ processChatCommand' vr = \case let changed = mc /= oldMC if changed || fromMaybe False itemLive then do - (SndMessage {msgId}, _) <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) + SndMessage {msgId} <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) ci' <- withFastStore' $ \db -> do currentTs <- liftIO getCurrentTime when changed $ @@ -959,7 +960,7 @@ processChatCommand' vr = \case let GroupMember {memberId = itemMemberId} = chatItemMember g ci rs <- withFastStore' $ \db -> getGroupReactions db g membership itemMemberId itemSharedMId True checkReactionAllowed rs - (SndMessage {msgId}, _) <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add) + SndMessage {msgId} <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add) createdAt <- liftIO getCurrentTime reactions <- withFastStore' $ \db -> do setGroupReaction db g membership itemMemberId itemSharedMId True reaction add msgId createdAt @@ -977,55 +978,83 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIForwardChatItem (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemId itemTTL -> withUser $ \user -> case toCType of + APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of CTDirect -> do - (cm, ciff) <- prepareForward user - withContactLock "forwardChatItem, to contact" toChatId $ - sendContactContentMessage user toChatId False itemTTL cm ciff + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withContactLock "forwardChatItem, to contact" toChatId $ + sendContactContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTGroup -> do - (cm, ciff) <- prepareForward user - withGroupLock "forwardChatItem, to group" toChatId $ - sendGroupContentMessage user toChatId False itemTTL cm ciff + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withGroupLock "forwardChatItem, to group" toChatId $ + sendGroupContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTLocal -> do - (cm, ciff) <- prepareForward user - createNoteFolderContentItem user toChatId cm ciff + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + createNoteFolderContentItems user toChatId cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - prepareForward :: User -> CM (ComposedMessage, Maybe CIForwardedFrom) + prepareForward :: User -> CM [ComposeMessageReq] prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do - (ct, CChatItem _ ci) <- withFastStore $ \db -> do - ct <- getContact db vr user fromChatId - cci <- getDirectChatItem db user fromChatId itemId - pure (ct, cci) - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + ct <- withFastStore $ \db -> getContact db vr user fromChatId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + mapM (ciComposeMsgReq ct) items where - forwardName :: Contact -> ContactName - forwardName Contact {profile = LocalProfile {displayName, localAlias}} - | localAlias /= "" = localAlias - | otherwise = displayName + getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) + getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq + ciComposeMsgReq ct (CChatItem _ ci) = do + (mc, mDir) <- forwardMC ci + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) + where + forwardName :: Contact -> ContactName + forwardName Contact {profile = LocalProfile {displayName, localAlias}} + | localAlias /= "" = localAlias + | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do - (gInfo, CChatItem _ ci) <- withFastStore $ \db -> do - gInfo <- getGroupInfo db vr user fromChatId - cci <- getGroupChatItem db user fromChatId itemId - pure (gInfo, cci) - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + mapM (ciComposeMsgReq gInfo) items where - forwardName :: GroupInfo -> ContactName - forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName + getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) + getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq + ciComposeMsgReq gInfo (CChatItem _ ci) = do + (mc, mDir) <- forwardMC ci + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) + where + forwardName :: GroupInfo -> ContactName + forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - (CChatItem _ ci) <- withFastStore $ \db -> getLocalChatItem db user fromChatId itemId - (mc, _) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci Nothing - pure (ComposedMessage file Nothing mc, ciff) + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + mapM ciComposeMsgReq items + where + getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) + getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId + ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq + ciComposeMsgReq (CChatItem _ ci) = do + (mc, _) <- forwardMC ci + file <- forwardCryptoFile ci + let ciff = forwardCIFF ci Nothing + pure (ComposedMessage file Nothing mc, ciff) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where @@ -1042,27 +1071,26 @@ processChatCommand' vr = \case forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) forwardCryptoFile ChatItem {file = Nothing} = pure Nothing forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of - CIFile {fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} - | ciFileLoaded fileStatus -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Just fromCF) (throwChatError CEForwardNoFile) - Just filesFolder -> do - let fsFromPath = filesFolder filePath - ifM - (doesFileExist fsFromPath) - ( do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - -- to keep forwarded file in case original is deleted - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) - ) - (throwChatError CEForwardNoFile) - _ -> throwChatError CEForwardNoFile + CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) + Just filesFolder -> do + let fsFromPath = filesFolder filePath + ifM + (doesFileExist fsFromPath) + ( do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + -- to keep forwarded file in case original is deleted + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF + pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) + ) + (pure Nothing) + _ -> pure Nothing copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do fromSizeFull <- getFileSize fsFromPath @@ -1271,7 +1299,7 @@ processChatCommand' vr = \case let call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} call_ <- atomically $ TM.lookupInsert contactId call' calls forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] ok user else pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)) SendCallInvitation cName callType -> withUser $ \user -> do @@ -1784,17 +1812,17 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItem toChatRef (ChatRef CTDirect contactId) forwardedItemId Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItem toChatRef (ChatRef CTGroup groupId) forwardedItemId Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItem toChatRef (ChatRef CTLocal folderId) forwardedItemId Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing SendMessage (ChatName cType name) msg -> withUser $ \user -> do let mc = MCText msg case cType of @@ -1802,7 +1830,7 @@ processChatCommand' vr = \case withFastStore' (\db -> runExceptT $ getContactIdByName db user name) >>= \case Right ctId -> do let chatRef = ChatRef CTDirect ctId - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage Nothing Nothing mc :| []) Left _ -> withFastStore' (\db -> runExceptT $ getActiveMembersByName db vr user name) >>= \case Right [(gInfo, member)] -> do @@ -1816,11 +1844,11 @@ processChatCommand' vr = \case CTGroup -> do gId <- withFastStore $ \db -> getGroupIdByName db user name let chatRef = ChatRef CTGroup gId - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage Nothing Nothing mc :| []) CTLocal | name == "" -> do folderId <- withFastStore (`getUserNoteFolderId` user) - processChatCommand . APICreateChatItem folderId $ ComposedMessage Nothing Nothing mc + processChatCommand $ APICreateChatItems folderId (ComposedMessage Nothing Nothing mc :| []) | otherwise -> throwChatError $ CECommandError "not supported" _ -> throwChatError $ CECommandError "not supported" SendMemberContactMessage gName mName msg -> withUser $ \user -> do @@ -1839,11 +1867,11 @@ processChatCommand' vr = \case cr -> pure cr Just ctId -> do let chatRef = ChatRef CTDirect ctId - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage Nothing Nothing mc :| []) SendLiveMessage chatName msg -> withUser $ \user -> do chatRef <- getChatRef user chatName let mc = MCText msg - processChatCommand . APISendMessage chatRef True Nothing $ ComposedMessage Nothing Nothing mc + processChatCommand $ APISendMessages chatRef True Nothing (ComposedMessage Nothing Nothing mc :| []) SendMessageBroadcast msg -> withUser $ \user -> do contacts <- withFastStore' $ \db -> getUserContacts db vr user withChatLock "sendMessageBroadcast" . procCmd $ do @@ -1884,7 +1912,7 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user cName quotedItemId <- withFastStore $ \db -> getDirectChatItemIdByText db userId contactId msgDir quotedMsg let mc = MCText msg - processChatCommand . APISendMessage (ChatRef CTDirect contactId) False Nothing $ ComposedMessage Nothing (Just quotedItemId) mc + processChatCommand $ APISendMessages (ChatRef CTDirect contactId) False Nothing (ComposedMessage Nothing (Just quotedItemId) mc :| []) DeleteMessage chatName deletedMsg -> withUser $ \user -> do chatRef <- getChatRef user chatName deletedItemId <- getSentChatItemIdByText user chatRef deletedMsg @@ -1995,9 +2023,9 @@ processChatCommand' vr = \case (Just ct, Just cReq) -> sendGrpInvitation user ct gInfo (m :: GroupMember) {memberRole = memRole} cReq _ -> throwChatError $ CEGroupCantResendInvitation gInfo cName _ -> do - (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemRole mId memRole + msg <- sendGroupMessage user gInfo members $ XGrpMemRole mId memRole ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent gEvent) - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] pure CRMemberRoleUser {user, groupInfo = gInfo, member = m {memberRole = memRole}, fromRole = mRole, toRole = memRole} APIBlockMemberForAll groupId memberId blocked -> withUser $ \user -> do Group gInfo@GroupInfo {membership} members <- withFastStore $ \db -> getGroup db vr user groupId @@ -2014,7 +2042,7 @@ processChatCommand' vr = \case msg <- sendGroupMessage' user gInfo remainingMembers event let ciContent = CISndGroupEvent $ SGEMemberBlocked memberId (fromLocalProfile bmp) blocked ci <- saveSndChatItem user (CDGroupSnd gInfo) msg ciContent - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] bm' <- withFastStore $ \db -> do liftIO $ updateGroupMemberBlocked db user groupId memberId mrs getGroupMember db vr user groupId memberId @@ -2036,9 +2064,9 @@ processChatCommand' vr = \case deleteMemberConnection user m withFastStore' $ \db -> deleteGroupMember db user m _ -> do - (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemDel mId + msg <- sendGroupMessage user gInfo members $ XGrpMemDel mId ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent $ SGEMemberDeleted memberId (fromLocalProfile memberProfile)) - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] deleteMemberConnection' user m True -- undeleted "member connected" chat item will prevent deletion of member record deleteOrUpdateMemberRecord user m @@ -2050,7 +2078,7 @@ processChatCommand' vr = \case cancelFilesInProgress user filesInfo msg <- sendGroupMessage' user gInfo members XGrpLeave ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent SGEUserLeft) - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci] -- TODO delete direct connections that were unused deleteGroupLinkIfExists user gInfo -- member records are not deleted to keep history @@ -2149,7 +2177,7 @@ processChatCommand' vr = \case let ct' = ct {contactGrpInvSent = True} forM_ msgContent_ $ \mc -> do ci <- saveSndChatItem user (CDDirectSnd ct') sndMsg (CISndMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct') ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct') ci] pure $ CRNewMemberContactSentInv user ct' g m _ -> throwChatError CEGroupMemberNotActive CreateGroupLink gName mRole -> withUser $ \user -> do @@ -2168,7 +2196,7 @@ processChatCommand' vr = \case groupId <- withFastStore $ \db -> getGroupIdByName db user gName quotedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId cName quotedMsg let mc = MCText msg - processChatCommand . APISendMessage (ChatRef CTGroup groupId) False Nothing $ ComposedMessage Nothing (Just quotedItemId) mc + processChatCommand $ APISendMessages (ChatRef CTGroup groupId) False Nothing (ComposedMessage Nothing (Just quotedItemId) mc :| []) ClearNoteFolder -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) processChatCommand $ APIClearChat (ChatRef CTLocal folderId) @@ -2208,8 +2236,8 @@ processChatCommand' vr = \case SendFile chatName f -> withUser $ \user -> do chatRef <- getChatRef user chatName case chatRef of - ChatRef CTLocal folderId -> processChatCommand . APICreateChatItem folderId $ ComposedMessage (Just f) Nothing (MCFile "") - _ -> processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCFile "") + ChatRef CTLocal folderId -> processChatCommand $ APICreateChatItems folderId (ComposedMessage (Just f) Nothing (MCFile "") :| []) + _ -> processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage (Just f) Nothing (MCFile "") :| []) SendImage chatName f@(CryptoFile fPath _) -> withUser $ \user -> do chatRef <- getChatRef user chatName filePath <- lift $ toFSFilePath fPath @@ -2217,7 +2245,7 @@ processChatCommand' vr = \case fileSize <- getFileSize filePath unless (fileSize <= maxImageSize) $ throwChatError CEFileImageSize {filePath} -- TODO include file description for preview - processChatCommand . APISendMessage chatRef False Nothing $ ComposedMessage (Just f) Nothing (MCImage "" fixedImagePreview) + processChatCommand $ APISendMessages chatRef False Nothing (ComposedMessage (Just f) Nothing (MCImage "" fixedImagePreview) :| []) ForwardFile chatName fileId -> forwardFile chatName fileId SendFile ForwardImage chatName fileId -> forwardFile chatName fileId SendImage SendFileDescription _chatName _f -> pure $ chatCmdError Nothing "TODO" @@ -2626,11 +2654,11 @@ processChatCommand' vr = \case assertUserGroupRole g GROwner when (n /= n') $ checkValidName n' g' <- withStore $ \db -> updateGroupProfile db user g p' - (msg, _) <- sendGroupMessage user g' ms (XGrpInfo p') + msg <- sendGroupMessage user g' ms (XGrpInfo p') let cd = CDGroupSnd g' unless (sameGroupProfileInfo p p') $ do ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p') - toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat g') ci) + toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat g') ci] createGroupFeatureChangedItems user cd CISndGroupFeature g g' pure $ CRGroupUpdated user g g' Nothing checkValidName :: GroupName -> CM () @@ -2712,7 +2740,7 @@ processChatCommand' vr = \case let content = CISndGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole timed_ <- contactCITimed ct ci <- saveSndChatItem' user (CDDirectSnd ct) msg content Nothing Nothing Nothing timed_ False - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) drgRandomBytes :: Int -> CM ByteString @@ -2858,77 +2886,154 @@ processChatCommand' vr = \case forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, itemId) _ -> pure () -- prohibited - sendContactContentMessage :: User -> ContactId -> Bool -> Maybe Int -> ComposedMessage -> Maybe CIForwardedFrom -> CM ChatResponse - sendContactContentMessage user contactId live itemTTL (ComposedMessage file_ quotedItemId_ mc) itemForwarded = do + sendContactContentMessages :: User -> ContactId -> Bool -> Maybe Int -> NonEmpty ComposeMessageReq -> CM ChatResponse + sendContactContentMessages user contactId live itemTTL cmrs = do + assertMultiSendable live cmrs ct@Contact {contactUsed} <- withFastStore $ \db -> getContact db vr user contactId assertDirectAllowed user MDSnd ct XMsgNew_ + assertVoiceAllowed ct unless contactUsed $ withFastStore' $ \db -> updateContactUsed db user ct - if isVoice mc && not (featureAllowed SCFVoice forUser ct) - then pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (chatFeatureNameText CFVoice)) - else do - (fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer ct - timed_ <- sndContactCITimed live ct itemTTL - (msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ - (msg, _) <- sendDirectContactMessage user ct (XMsgNew msgContainer) - ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ itemForwarded timed_ live - forM_ (timed_ >>= timedDeleteAt') $ - startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) - pure $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + processComposedMessages ct where - setupSndFileTransfer :: Contact -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) - setupSndFileTransfer ct = forM file_ $ \file -> do - fileSize <- checkSndFile file - xftpSndFileTransfer user file fileSize 1 $ CGContact ct - prepareMsg :: Maybe FileInvitation -> Maybe CITimed -> CM (MsgContainer, Maybe (CIQuote 'CTDirect)) - prepareMsg fInv_ timed_ = case (quotedItemId_, itemForwarded) of - (Nothing, Nothing) -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) - (Nothing, Just _) -> pure (MCForward (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) - (Just quotedItemId, Nothing) -> do - CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, file} <- - withFastStore $ \db -> getDirectChatItem db user contactId quotedItemId - (origQmc, qd, sent) <- quoteData qci - let msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Nothing} - qmc = quoteContent mc origQmc file - quotedItem = CIQuote {chatDir = qd, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText} - pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem) - (Just _, Just _) -> throwChatError CEInvalidQuote + assertVoiceAllowed :: Contact -> CM () + assertVoiceAllowed ct = + when (not (featureAllowed SCFVoice forUser ct) && any (\(ComposedMessage {msgContent}, _) -> isVoice msgContent) cmrs) $ + throwChatError (CECommandError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFVoice)) + processComposedMessages :: Contact -> CM ChatResponse + processComposedMessages ct = do + (fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers + timed_ <- sndContactCITimed live ct itemTTL + (msgContainers, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_ + msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers + let itemsData = prepareSndItemsData msgs_ cmrs ciFiles_ quotedItems_ + (errs, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live + unless (null errs) $ toView $ CRChatErrors (Just user) errs + forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> + forM_ cis $ \ci -> + startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) deleteAt + pure $ CRNewChatItems user (map (AChatItem SCTDirect SMDSnd (DirectChat ct)) cis) where - quoteData :: ChatItem c d -> CM (MsgContent, CIQDirection 'CTDirect, Bool) - quoteData ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidQuote - quoteData ChatItem {content = CISndMsgContent qmc} = pure (qmc, CIQDirectSnd, True) - quoteData ChatItem {content = CIRcvMsgContent qmc} = pure (qmc, CIQDirectRcv, False) - quoteData _ = throwChatError CEInvalidQuote - sendGroupContentMessage :: User -> GroupId -> Bool -> Maybe Int -> ComposedMessage -> Maybe CIForwardedFrom -> CM ChatResponse - sendGroupContentMessage user groupId live itemTTL (ComposedMessage file_ quotedItemId_ mc) itemForwarded = do + setupSndFileTransfers :: CM (NonEmpty (Maybe FileInvitation, Maybe (CIFile 'MDSnd))) + setupSndFileTransfers = + forM cmrs $ \(ComposedMessage {fileSource = file_}, _) -> case file_ of + Just file -> do + fileSize <- checkSndFile file + (fInv, ciFile) <- xftpSndFileTransfer user file fileSize 1 $ CGContact ct + pure (Just fInv, Just ciFile) + Nothing -> pure (Nothing, Nothing) + prepareMsgs :: NonEmpty (ComposeMessageReq, Maybe FileInvitation) -> Maybe CITimed -> CM (NonEmpty (MsgContainer, Maybe (CIQuote 'CTDirect))) + prepareMsgs cmsFileInvs timed_ = + forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded), fInv_) -> + case (quotedItemId, itemForwarded) of + (Nothing, Nothing) -> pure (MCSimple (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) + (Nothing, Just _) -> pure (MCForward (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Nothing) + (Just qiId, Nothing) -> do + CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, file} <- + withFastStore $ \db -> getDirectChatItem db user contactId qiId + (origQmc, qd, sent) <- quoteData qci + let msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Nothing} + qmc = quoteContent mc origQmc file + quotedItem = CIQuote {chatDir = qd, itemId = Just qiId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText} + pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem) + (Just _, Just _) -> throwChatError CEInvalidQuote + where + quoteData :: ChatItem c d -> CM (MsgContent, CIQDirection 'CTDirect, Bool) + quoteData ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidQuote + quoteData ChatItem {content = CISndMsgContent qmc} = pure (qmc, CIQDirectSnd, True) + quoteData ChatItem {content = CIRcvMsgContent qmc} = pure (qmc, CIQDirectRcv, False) + quoteData _ = throwChatError CEInvalidQuote + sendGroupContentMessages :: User -> GroupId -> Bool -> Maybe Int -> NonEmpty ComposeMessageReq -> CM ChatResponse + sendGroupContentMessages user groupId live itemTTL cmrs = do + assertMultiSendable live cmrs g@(Group gInfo _) <- withFastStore $ \db -> getGroup db vr user groupId assertUserGroupRole gInfo GRAuthor - send g + assertGroupContentAllowed gInfo + processComposedMessages g where - send g@(Group gInfo@GroupInfo {membership} ms) = - case prohibitedGroupContent gInfo membership mc file_ of - Just f -> notAllowedError f - Nothing -> do - (fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer g (length $ filter memberCurrent ms) - timed_ <- sndGroupCITimed live gInfo itemTTL - (msgContainer, quotedItem_) <- prepareGroupMsg user gInfo mc quotedItemId_ itemForwarded fInv_ timed_ live - (msg, r) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) - ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ itemForwarded timed_ live + assertGroupContentAllowed :: GroupInfo -> CM () + assertGroupContentAllowed gInfo@GroupInfo {membership} = + case findProhibited (L.toList cmrs) of + Just f -> throwChatError (CECommandError $ "feature not allowed " <> T.unpack (groupFeatureNameText f)) + Nothing -> pure () + where + findProhibited :: [ComposeMessageReq] -> Maybe GroupFeature + findProhibited = + foldr' + (\(ComposedMessage {fileSource, msgContent = mc}, _) acc -> prohibitedGroupContent gInfo membership mc fileSource <|> acc) + Nothing + processComposedMessages :: Group -> CM ChatResponse + processComposedMessages g@(Group gInfo ms) = do + (fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers (length $ filter memberCurrent ms) + timed_ <- sndGroupCITimed live gInfo itemTTL + (msgContainers, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_ + (msgs_, gsr) <- sendGroupMessages user gInfo ms $ L.map XMsgNew msgContainers + let itemsData = prepareSndItemsData (L.toList msgs_) cmrs ciFiles_ quotedItems_ + cis_ <- saveSndChatItems user (CDGroupSnd gInfo) itemsData timed_ live + createMemberSndStatuses cis_ msgs_ gsr + let (errs, cis) = partitionEithers cis_ + unless (null errs) $ toView $ CRChatErrors (Just user) errs + forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> + forM_ cis $ \ci -> + startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) deleteAt + pure $ CRNewChatItems user (map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) cis) + where + setupSndFileTransfers :: Int -> CM (NonEmpty (Maybe FileInvitation, Maybe (CIFile 'MDSnd))) + setupSndFileTransfers n = + forM cmrs $ \(ComposedMessage {fileSource = file_}, _) -> case file_ of + Just file -> do + fileSize <- checkSndFile file + (fInv, ciFile) <- xftpSndFileTransfer user file fileSize n $ CGGroup g + pure (Just fInv, Just ciFile) + Nothing -> pure (Nothing, Nothing) + prepareMsgs :: NonEmpty (ComposeMessageReq, Maybe FileInvitation) -> Maybe CITimed -> CM (NonEmpty (MsgContainer, Maybe (CIQuote 'CTGroup))) + prepareMsgs cmsFileInvs timed_ = + forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded), fInv_) -> + prepareGroupMsg user gInfo mc quotedItemId itemForwarded fInv_ timed_ live + createMemberSndStatuses :: + [Either ChatError (ChatItem 'CTGroup 'MDSnd)] -> + NonEmpty (Either ChatError SndMessage) -> + GroupSndResult -> + CM () + createMemberSndStatuses cis_ msgs_ GroupSndResult {sentTo, pending, forwarded} = do + let msgToItem = mapMsgToItem withFastStore' $ \db -> do - let GroupSndResult {sentTo, pending, forwarded} = mkGroupSndResult r - createMemberSndStatuses db ci sentTo GSSNew - createMemberSndStatuses db ci forwarded GSSForwarded - createMemberSndStatuses db ci pending GSSInactive - forM_ (timed_ >>= timedDeleteAt') $ - startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) - pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + forM_ sentTo (processSentTo db msgToItem) + forM_ forwarded (processForwarded db) + forM_ pending (processPending db msgToItem) where - createMemberSndStatuses db ci ms' gss = - forM_ ms' $ \GroupMember {groupMemberId} -> createGroupSndStatus db (chatItemId' ci) groupMemberId gss - notAllowedError f = pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText f)) - setupSndFileTransfer :: Group -> Int -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) - setupSndFileTransfer g n = forM file_ $ \file -> do - fileSize <- checkSndFile file - xftpSndFileTransfer user file fileSize n $ CGGroup g + mapMsgToItem :: Map MessageId ChatItemId + mapMsgToItem = foldr' addItem M.empty (zip (L.toList msgs_) cis_) + where + addItem (Right SndMessage {msgId}, Right ci) m = M.insert msgId (chatItemId' ci) m + addItem _ m = m + processSentTo :: DB.Connection -> Map MessageId ChatItemId -> (GroupMemberId, Either ChatError [MessageId], Either ChatError ([Int64], PQEncryption)) -> IO () + processSentTo db msgToItem (mId, msgIds_, deliveryResult) = forM_ msgIds_ $ \msgIds -> do + let ciIds = mapMaybe (`M.lookup` msgToItem) msgIds + status = case deliveryResult of + Right _ -> GSSNew + Left e -> GSSError $ SndErrOther $ tshow e + forM_ ciIds $ \ciId -> createGroupSndStatus db ciId mId status + processForwarded :: DB.Connection -> GroupMember -> IO () + processForwarded db GroupMember {groupMemberId} = + forM_ cis_ $ \ci_ -> + forM_ ci_ $ \ci -> createGroupSndStatus db (chatItemId' ci) groupMemberId GSSForwarded + processPending :: DB.Connection -> Map MessageId ChatItemId -> (GroupMemberId, Either ChatError MessageId, Either ChatError ()) -> IO () + processPending db msgToItem (mId, msgId_, pendingResult) = forM_ msgId_ $ \msgId -> do + let ciId_ = M.lookup msgId msgToItem + status = case pendingResult of + Right _ -> GSSInactive + Left e -> GSSError $ SndErrOther $ tshow e + forM_ ciId_ $ \ciId -> createGroupSndStatus db ciId mId status + assertMultiSendable :: Bool -> NonEmpty ComposeMessageReq -> CM () + assertMultiSendable live cmrs + | length cmrs == 1 = pure () + | otherwise = + -- When sending multiple messages only single quote is allowed. + -- This is to support case of sending multiple attachments while also quoting another message. + -- UI doesn't allow composing with multiple quotes, so api prohibits it as well, and doesn't bother + -- batching retrieval of quoted messages (prepareMsgs). + when (live || length (L.filter (\(ComposedMessage {quotedItemId}, _) -> isJust quotedItemId) cmrs) > 1) $ + throwChatError (CECommandError "invalid multi send: live and more than one quote not supported") xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> CM (FileInvitation, CIFile 'MDSnd) xftpSndFileTransfer user file fileSize n contactOrGroup = do (fInv, ciFile, ft) <- xftpSndFileTransfer_ user file fileSize n $ Just contactOrGroup @@ -2944,27 +3049,58 @@ processChatCommand' vr = \case \db -> createSndFTDescrXFTP db user (Just m) conn ft dummyFileDescr saveMemberFD _ = pure () pure (fInv, ciFile) - createNoteFolderContentItem :: User -> NoteFolderId -> ComposedMessage -> Maybe CIForwardedFrom -> CM ChatResponse - createNoteFolderContentItem user folderId (ComposedMessage file_ quotedItemId_ mc) itemForwarded = do - forM_ quotedItemId_ $ \_ -> throwError $ ChatError $ CECommandError "not supported" + prepareSndItemsData :: + [Either ChatError SndMessage] -> + NonEmpty ComposeMessageReq -> + NonEmpty (Maybe (CIFile 'MDSnd)) -> + NonEmpty (Maybe (CIQuote c)) -> + [Either ChatError (NewSndChatItemData c)] + prepareSndItemsData msgs_ cmrs' ciFiles_ quotedItems_ = + [ ( case msg_ of + Right msg -> Right $ NewSndChatItemData msg (CISndMsgContent msgContent) f q itemForwarded + Left e -> Left e -- step over original error + ) + | (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <- + zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_) + ] + createNoteFolderContentItems :: User -> NoteFolderId -> NonEmpty ComposeMessageReq -> CM ChatResponse + createNoteFolderContentItems user folderId cmrs = do + assertNoQuotes nf <- withFastStore $ \db -> getNoteFolder db user folderId createdAt <- liftIO getCurrentTime - let content = CISndMsgContent mc - let cd = CDLocalSnd nf - ciId <- createLocalChatItem user cd content itemForwarded createdAt - ciFile_ <- forM file_ $ \cf@CryptoFile {filePath, cryptoArgs} -> do - fsFilePath <- lift $ toFSFilePath filePath - fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cryptoArgs - chunkSize <- asks $ fileChunkSize . config - withFastStore' $ \db -> do - fileId <- createLocalFile CIFSSndStored db user nf ciId createdAt cf fileSize chunkSize - pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal} - let ci = mkChatItem cd ciId content ciFile_ Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt - pure . CRNewChatItem user $ AChatItem SCTLocal SMDSnd (LocalChat nf) ci + ciFiles_ <- createLocalFiles nf createdAt + let itemsData = prepareLocalItemsData cmrs ciFiles_ + cis <- createLocalChatItems user (CDLocalSnd nf) itemsData createdAt + pure $ CRNewChatItems user (map (AChatItem SCTLocal SMDSnd (LocalChat nf)) cis) + where + assertNoQuotes :: CM () + assertNoQuotes = + when (any (\(ComposedMessage {quotedItemId}, _) -> isJust quotedItemId) cmrs) $ + throwChatError (CECommandError "createNoteFolderContentItems: quotes not supported") + createLocalFiles :: NoteFolder -> UTCTime -> CM (NonEmpty (Maybe (CIFile 'MDSnd))) + createLocalFiles nf createdAt = + forM cmrs $ \(ComposedMessage {fileSource = file_}, _) -> + forM file_ $ \cf@CryptoFile {filePath, cryptoArgs} -> do + fsFilePath <- lift $ toFSFilePath filePath + fileSize <- liftIO $ CF.getFileContentsSize $ CryptoFile fsFilePath cryptoArgs + chunkSize <- asks $ fileChunkSize . config + withFastStore' $ \db -> do + fileId <- createLocalFile CIFSSndStored db user nf createdAt cf fileSize chunkSize + pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal} + prepareLocalItemsData :: + NonEmpty ComposeMessageReq -> + NonEmpty (Maybe (CIFile 'MDSnd)) -> + [(CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom)] + prepareLocalItemsData cmrs' ciFiles_ = + [ (CISndMsgContent mc, f, itemForwarded) + | ((ComposedMessage {msgContent = mc}, itemForwarded), f) <- zip (L.toList cmrs') (L.toList ciFiles_) + ] getConnQueueInfo user Connection {connId, agentConnId = AgentConnId acId} = do msgInfo <- withFastStore' (`getLastRcvMsgInfo` connId) CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId) +type ComposeMessageReq = (ComposedMessage, Maybe CIForwardedFrom) + contactCITimed :: Contact -> CM (Maybe CITimed) contactCITimed ct = sndContactCITimed False ct Nothing @@ -4398,7 +4534,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Just AutoAccept {autoReply = Just mc} -> do (msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci] _ -> pure () processGroupMessage :: AEvent e -> ConnectionEntity -> Connection -> GroupInfo -> GroupMember -> CM () @@ -4732,7 +4868,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let GroupMember {memberId} = m ms = forwardedToGroupMembers (introducedMembers <> invitedMembers) forwardedMsgs' events = L.map (\cm -> XGrpMsgForward memberId cm brokerTs) forwardedMsgs' - unless (null ms) $ sendGroupMessages user gInfo ms events + unless (null ms) $ void $ sendGroupMessages user gInfo ms events RCVD msgMeta msgRcpt -> withAckMessage' "group rcvd" agentConnId msgMeta $ groupMsgReceived gInfo m conn msgMeta msgRcpt @@ -5240,7 +5376,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = newChatItem ciContent ciFile_ timed_ live = do ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs ciContent ciFile_ timed_ live reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_ - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}] autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> CM () autoAcceptFile = mapM_ $ \(ft, CIFile {fileSize}) -> do @@ -5544,7 +5680,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol} ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs (CIRcvMsgContent $ MCFile "") ciFile Nothing False - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] where brokerTs = metaBrokerTs msgMeta @@ -5711,7 +5847,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupMsgToView :: forall d. MsgDirectionI d => GroupInfo -> ChatItem 'CTGroup d -> CM () groupMsgToView gInfo ci = - toView $ CRNewChatItem user (AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci) + toView $ CRNewChatItems user [AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci] processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM () processGroupInvitation ct inv msg msgMeta = do @@ -5738,7 +5874,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let content = CIRcvGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole ci <- saveRcvChatItem user (CDDirectRcv ct) msg brokerTs content withStore' $ \db -> setGroupInvitationChatItemId db user groupId (chatItemId' ci) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] toView $ CRReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole} where brokerTs = metaBrokerTs msgMeta @@ -5765,7 +5901,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted} let ct'' = ct' {activeConn = activeConn'} :: Contact ci <- saveRcvChatItem user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct'') ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct'') ci] toView $ CRContactDeletedByContact user ct'' else do contactConns <- withStore' $ \db -> getContactConnections db vr userId c @@ -5966,14 +6102,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = call_ <- atomically (TM.lookupInsert contactId call' calls) forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing toView $ CRCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callTs = chatItemTs' ci} - toView $ CRNewChatItem user $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] else featureRejected CFCalls where brokerTs = metaBrokerTs msgMeta saveCallItem status = saveRcvChatItem user (CDDirectRcv ct) msg brokerTs (CIRcvCall status 0) featureRejected f = do ci <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs (CIRcvChatFeatureRejected f) Nothing Nothing False - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci] -- to party initiating call xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> CM () @@ -6426,7 +6562,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRNewMemberContactReceivedInv user mCt' g m' forM_ mContent_ $ \mc -> do ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat mCt') ci) + toView $ CRNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat mCt') ci] securityCodeChanged :: Contact -> CM () securityCodeChanged ct = do @@ -6813,21 +6949,23 @@ deleteOrUpdateMemberRecord user@User {userId} member = Just _ -> updateGroupMemberStatus db userId member GSMemRemoved Nothing -> deleteGroupMember db user member -sendDirectContactMessages :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM () +sendDirectContactMessages :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage] sendDirectContactMessages user ct events = do Connection {connChatVersion = v} <- liftEither $ contactSendConn_ ct if v >= batchSend2Version then sendDirectContactMessages' user ct events - else mapM_ (void . sendDirectContactMessage user ct) events + else forM (L.toList events) $ \evt -> + (Right . fst <$> sendDirectContactMessage user ct evt) `catchChatError` \e -> pure (Left e) -sendDirectContactMessages' :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM () +sendDirectContactMessages' :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage] sendDirectContactMessages' user ct events = do conn@Connection {connId} <- liftEither $ contactSendConn_ ct let idsEvts = L.map (ConnectionId connId,) events msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} - (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM_ (batchSendConnMessages user conn msgFlags) (L.nonEmpty msgs) + sndMsgs_ <- lift $ createSndMessages idsEvts + (sndMsgs', pqEnc_) <- batchSendConnMessagesB user conn msgFlags sndMsgs_ + forM_ pqEnc_ $ \pqEnc' -> void $ createContactPQSndItem user ct conn pqEnc' + pure sndMsgs' sendDirectContactMessage :: MsgEncodingI e => User -> Contact -> ChatMsgEvent e -> CM (SndMessage, Int64) sendDirectContactMessage user ct chatMsgEvent = do @@ -6887,17 +7025,30 @@ sendGroupMemberMessages user conn events groupId = do forM_ (L.nonEmpty msgs) $ \msgs' -> batchSendConnMessages user conn MsgFlags {notification = True} msgs' -batchSendConnMessages :: User -> Connection -> MsgFlags -> NonEmpty SndMessage -> CM () -batchSendConnMessages user conn msgFlags msgs = do - let batched = batchSndMessagesJSON msgs - let (errs', msgBatches) = partitionEithers batched - -- shouldn't happen, as large messages would have caused createNewSndMessage to throw SELargeMsg - unless (null errs') $ toView $ CRChatErrors (Just user) errs' - forM_ (L.nonEmpty msgBatches) $ \msgBatches' -> do - let msgReq = L.map (msgBatchReq conn msgFlags) msgBatches' - void $ deliverMessages msgReq +batchSendConnMessages :: User -> Connection -> MsgFlags -> NonEmpty SndMessage -> CM ([Either ChatError SndMessage], Maybe PQEncryption) +batchSendConnMessages user conn msgFlags msgs = + batchSendConnMessagesB user conn msgFlags $ L.map Right msgs -batchSndMessagesJSON :: NonEmpty SndMessage -> [Either ChatError MsgBatch] +batchSendConnMessagesB :: User -> Connection -> MsgFlags -> NonEmpty (Either ChatError SndMessage) -> CM ([Either ChatError SndMessage], Maybe PQEncryption) +batchSendConnMessagesB _user conn msgFlags msgs_ = do + let batched_ = batchSndMessagesJSON msgs_ + case L.nonEmpty batched_ of + Just batched' -> do + let msgReqs = L.map (fmap (msgBatchReq conn msgFlags)) batched' + delivered <- deliverMessagesB msgReqs + let msgs' = concat $ L.zipWith flattenMsgs batched' delivered + pqEnc = findLastPQEnc delivered + pure (msgs', pqEnc) + Nothing -> pure ([], Nothing) + where + flattenMsgs :: Either ChatError MsgBatch -> Either ChatError ([Int64], PQEncryption) -> [Either ChatError SndMessage] + flattenMsgs (Right (MsgBatch _ sndMsgs)) (Right _) = map Right sndMsgs + flattenMsgs (Right (MsgBatch _ sndMsgs)) (Left ce) = replicate (length sndMsgs) (Left ce) + flattenMsgs (Left ce) _ = [Left ce] -- restore original ChatError + findLastPQEnc :: NonEmpty (Either ChatError ([Int64], PQEncryption)) -> Maybe PQEncryption + findLastPQEnc = foldr' (\x acc -> case x of Right (_, pqEnc) -> Just pqEnc; Left _ -> acc) Nothing + +batchSndMessagesJSON :: NonEmpty (Either ChatError SndMessage) -> [Either ChatError MsgBatch] batchSndMessagesJSON = batchMessages maxEncodedMsgLength . L.toList msgBatchReq :: Connection -> MsgFlags -> MsgBatch -> ChatMsgReq @@ -6949,7 +7100,7 @@ deliverMessagesB msgReqs = do lift . withStoreBatch $ \db -> L.map (bindRight $ createDelivery db) sent where compressBodies = - forME msgReqs $ \mr@(conn@Connection {pqSupport, connChatVersion = v}, msgFlags, msgBody, msgId) -> + forME msgReqs $ \mr@(conn@Connection {pqSupport, connChatVersion = v}, msgFlags, msgBody, msgIds) -> runExceptT $ case pqSupport of -- we only compress messages when: -- 1) PQ support is enabled @@ -6958,7 +7109,7 @@ deliverMessagesB msgReqs = do PQSupportOn | v >= pqEncryptionCompressionVersion && B.length msgBody > maxCompressedMsgLength -> do let msgBody' = compressedBatchMsgBody_ msgBody when (B.length msgBody' > maxCompressedMsgLength) $ throwError $ ChatError $ CEException "large compressed message" - pure (conn, msgFlags, msgBody', msgId) + pure (conn, msgFlags, msgBody', msgIds) _ -> pure mr toAgent prev = \case Right (conn@Connection {connId, pqEncryption}, msgFlags, msgBody, _msgIds) -> @@ -6982,13 +7133,23 @@ deliverMessagesB msgReqs = do where updatePQ = updateConnPQSndEnabled db connId pqSndEnabled' --- TODO combine profile update and message into one batch --- Take into account that it may not fit, and that we currently don't support sending multiple messages to the same connection in one call. -sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResultData) +sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage sendGroupMessage user gInfo members chatMsgEvent = do + sendGroupMessages user gInfo members (chatMsgEvent :| []) >>= \case + ((Right msg) :| [], _) -> pure msg + _ -> throwChatError $ CEInternalError "sendGroupMessage: expected 1 message" + +sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage +sendGroupMessage' user gInfo members chatMsgEvent = + sendGroupMessages_ user gInfo members (chatMsgEvent :| []) >>= \case + ((Right msg) :| [], _) -> pure msg + _ -> throwChatError $ CEInternalError "sendGroupMessage': expected 1 message" + +sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult) +sendGroupMessages user gInfo members events = do when shouldSendProfileUpdate $ sendProfileUpdate `catchChatError` (toView . CRChatError (Just user)) - sendGroupMessage_ user gInfo members chatMsgEvent + sendGroupMessages_ user gInfo members events where User {profile = p, userMemberProfileUpdatedAt} = user GroupInfo {userMemberProfileSentAt} = gInfo @@ -7006,59 +7167,33 @@ sendGroupMessage user gInfo members chatMsgEvent = do currentTs <- liftIO getCurrentTime withStore' $ \db -> updateUserMemberProfileSentAt db user gInfo currentTs -type GroupSndResultData = (([Either ChatError ([Int64], PQEncryption)], [(GroupMember, Connection)]), ([Either ChatError ()], [GroupMember]), [GroupMember]) - data GroupSndResult = GroupSndResult - { sentTo :: [GroupMember], - pending :: [GroupMember], + { sentTo :: [(GroupMemberId, Either ChatError [MessageId], Either ChatError ([Int64], PQEncryption))], + pending :: [(GroupMemberId, Either ChatError MessageId, Either ChatError ())], forwarded :: [GroupMember] } -sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage -sendGroupMessage' user gInfo members chatMsgEvent = fst <$> sendGroupMessage_ user gInfo members chatMsgEvent - -sendGroupMessage_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResultData) -sendGroupMessage_ user gInfo members chatMsgEvent = - sendGroupMessages_ user gInfo members (chatMsgEvent :| []) >>= \case - (msg :| [], r) -> pure (msg, r) - _ -> throwChatError $ CEInternalError "sendGroupMessage': expected 1 message" - -sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM () -sendGroupMessages user gInfo members events = void $ sendGroupMessages_ user gInfo members events - -mkGroupSndResult :: GroupSndResultData -> GroupSndResult -mkGroupSndResult ((delivered, sentTo), (stored, pending), forwarded) = - GroupSndResult - { sentTo = filterSent' delivered sentTo fst, - pending = filterSent' stored pending id, - forwarded - } - where - -- TODO in theory this could deduplicate members and keep results only when ... some sent? or all sent? - -- This is not important, as it is not used in batch calls - filterSent' :: [Either ChatError a] -> [mem] -> (mem -> GroupMember) -> [GroupMember] - filterSent' rs ms mem = [mem m | (Right _, m) <- zip rs ms] - -sendGroupMessages_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty SndMessage, GroupSndResultData) -sendGroupMessages_ user gInfo@GroupInfo {groupId} members events = do +sendGroupMessages_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult) +sendGroupMessages_ _user gInfo@GroupInfo {groupId} members events = do let idsEvts = L.map (GroupId groupId,) events - (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts - unless (null errs) $ toView $ CRChatErrors (Just user) errs - case L.nonEmpty msgs of - Nothing -> throwChatError $ CEInternalError "sendGroupMessages: no messages created" - Just msgs' -> do - recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) - let msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} - (toSendSeparate, toSendBatched, pending, forwarded, _, dups) = - foldr addMember ([], [], [], [], S.empty, 0 :: Int) recipientMembers - when (dups /= 0) $ logError $ "sendGroupMessage: " <> tshow dups <> " duplicate members" - -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here - let msgReqs = prepareMsgReqs msgFlags msgs' toSendSeparate toSendBatched - delivered <- maybe (pure []) (fmap L.toList . deliverMessages) $ L.nonEmpty msgReqs - let errors = lefts delivered - unless (null errors) $ toView $ CRChatErrors (Just user) errors - stored <- lift . withStoreBatch' $ \db -> map (\m -> createPendingMsgs db m msgs') pending - pure (msgs', ((delivered, toSendSeparate <> toSendBatched), (stored, pending), forwarded)) + sndMsgs_ <- lift $ createSndMessages idsEvts + recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) + let msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events} + (toSendSeparate, toSendBatched, toPending, forwarded, _, dups) = + foldr' addMember ([], [], [], [], S.empty, 0 :: Int) recipientMembers + when (dups /= 0) $ logError $ "sendGroupMessages_: " <> tshow dups <> " duplicate members" + -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here + -- Deliver to toSend members + let (sendToMemIds, msgReqs) = prepareMsgReqs msgFlags sndMsgs_ toSendSeparate toSendBatched + delivered <- maybe (pure []) (fmap L.toList . deliverMessagesB) $ L.nonEmpty msgReqs + when (length delivered /= length sendToMemIds) $ logError "sendGroupMessages_: sendToMemIds and delivered length mismatch" + -- Save as pending for toPending members + let (pendingMemIds, pendingReqs) = preparePending sndMsgs_ toPending + stored <- lift $ withStoreBatch (\db -> map (bindRight $ createPendingMsg db) pendingReqs) + -- Zip for easier access to results + let sentTo = zipWith3 (\mId mReq r -> (mId, fmap (\(_, _, _, msgIds) -> msgIds) mReq, r)) sendToMemIds msgReqs delivered + pending = zipWith3 (\mId pReq r -> (mId, fmap snd pReq, r)) pendingMemIds pendingReqs stored + pure (sndMsgs_, GroupSndResult {sentTo, pending, forwarded}) where shuffleMembers :: [GroupMember] -> IO [GroupMember] shuffleMembers ms = do @@ -7079,22 +7214,38 @@ sendGroupMessages_ user gInfo@GroupInfo {groupId} members events = do where mId = groupMemberId' m mIds' = S.insert mId mIds - prepareMsgReqs :: MsgFlags -> NonEmpty SndMessage -> [(GroupMember, Connection)] -> [(GroupMember, Connection)] -> [ChatMsgReq] - prepareMsgReqs msgFlags msgs toSendSeparate toSendBatched = do - let msgReqsSeparate = foldr (\(_, conn) reqs -> foldr (\msg -> (sndMessageReq conn msg :)) reqs msgs) [] toSendSeparate - batched = batchSndMessagesJSON msgs - -- _errs shouldn't happen, as large messages would have caused createNewSndMessage to throw SELargeMsg - (_errs, msgBatches) = partitionEithers batched - case L.nonEmpty msgBatches of - Just msgBatches' -> do - let msgReqsBatched = foldr (\(_, conn) reqs -> foldr (\batch -> (msgBatchReq conn msgFlags batch :)) reqs msgBatches') [] toSendBatched - msgReqsSeparate <> msgReqsBatched - Nothing -> msgReqsSeparate + prepareMsgReqs :: MsgFlags -> NonEmpty (Either ChatError SndMessage) -> [(GroupMember, Connection)] -> [(GroupMember, Connection)] -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + prepareMsgReqs msgFlags msgs_ toSendSeparate toSendBatched = do + let batched_ = batchSndMessagesJSON msgs_ + case L.nonEmpty batched_ of + Just batched' -> do + let (memsSep, mreqsSep) = foldr' foldMsgs ([], []) toSendSeparate + (memsBtch, mreqsBtch) = foldr' (foldBatches batched') ([], []) toSendBatched + (memsSep <> memsBtch, mreqsSep <> mreqsBtch) + Nothing -> ([], []) where - sndMessageReq :: Connection -> SndMessage -> ChatMsgReq - sndMessageReq conn SndMessage {msgId, msgBody} = (conn, msgFlags, msgBody, [msgId]) - createPendingMsgs :: DB.Connection -> GroupMember -> NonEmpty SndMessage -> IO () - createPendingMsgs db m = mapM_ (\SndMessage {msgId} -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) + foldMsgs :: (GroupMember, Connection) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + foldMsgs (GroupMember {groupMemberId}, conn) memIdsReqs = + foldr' (\msg_ (memIds, reqs) -> (groupMemberId : memIds, fmap sndMessageReq msg_ : reqs)) memIdsReqs msgs_ + where + sndMessageReq :: SndMessage -> ChatMsgReq + sndMessageReq SndMessage {msgId, msgBody} = (conn, msgFlags, msgBody, [msgId]) + foldBatches :: NonEmpty (Either ChatError MsgBatch) -> (GroupMember, Connection) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) -> ([GroupMemberId], [Either ChatError ChatMsgReq]) + foldBatches batched' (GroupMember {groupMemberId}, conn) memIdsReqs = + foldr' (\batch_ (memIds, reqs) -> (groupMemberId : memIds, fmap (msgBatchReq conn msgFlags) batch_ : reqs)) memIdsReqs batched' + preparePending :: NonEmpty (Either ChatError SndMessage) -> [GroupMember] -> ([GroupMemberId], [Either ChatError (GroupMemberId, MessageId)]) + preparePending msgs_ = + foldr' foldMsgs ([], []) + where + foldMsgs :: GroupMember -> ([GroupMemberId], [Either ChatError (GroupMemberId, MessageId)]) -> ([GroupMemberId], [Either ChatError (GroupMemberId, MessageId)]) + foldMsgs GroupMember {groupMemberId} memIdsReqs = + foldr' (\msg_ (memIds, reqs) -> (groupMemberId : memIds, fmap pendingReq msg_ : reqs)) memIdsReqs msgs_ + where + pendingReq :: SndMessage -> (GroupMemberId, MessageId) + pendingReq SndMessage {msgId} = (groupMemberId, msgId) + createPendingMsg :: DB.Connection -> (GroupMemberId, MessageId) -> IO (Either ChatError ()) + createPendingMsg db (groupMemberId, msgId) = + createPendingGroupMessage db groupMemberId msgId Nothing $> Right () data MemberSendAction = MSASend Connection | MSASendBatched Connection | MSAPending | MSAForwarded @@ -7155,7 +7306,7 @@ sendPendingGroupMessages user GroupMember {groupMemberId} conn = do pgms <- withStore' $ \db -> getPendingGroupMessages db groupMemberId forM_ (L.nonEmpty pgms) $ \pgms' -> do let msgs = L.map (\(sndMsg, _, _) -> sndMsg) pgms' - batchSendConnMessages user conn MsgFlags {notification = True} msgs + void $ batchSendConnMessages user conn MsgFlags {notification = True} msgs lift . void . withStoreBatch' $ \db -> L.map (\SndMessage {msgId} -> deletePendingGroupMessage db groupMemberId msgId) msgs lift . void . withStoreBatch' $ \db -> L.map (\(_, tag, introId_) -> updateIntro_ db tag introId_) pgms' where @@ -7212,14 +7363,39 @@ saveSndChatItem :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage - saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothing Nothing Nothing Nothing False saveSndChatItem' :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIFile 'MDSnd) -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> CM (ChatItem c 'MDSnd) -saveSndChatItem' user cd msg@SndMessage {sharedMsgId} content ciFile quotedItem itemForwarded itemTimed live = do +saveSndChatItem' user cd msg content ciFile quotedItem itemForwarded itemTimed live = + saveSndChatItems user cd [Right NewSndChatItemData {msg, content, ciFile, quotedItem, itemForwarded}] itemTimed live >>= \case + [Right ci] -> pure ci + _ -> throwChatError $ CEInternalError "saveSndChatItem': expected 1 item" + +data NewSndChatItemData c = NewSndChatItemData + { msg :: SndMessage, + content :: CIContent 'MDSnd, + ciFile :: Maybe (CIFile 'MDSnd), + quotedItem :: Maybe (CIQuote c), + itemForwarded :: Maybe CIForwardedFrom + } + +saveSndChatItems :: + forall c. + ChatTypeI c => + User -> + ChatDirection c 'MDSnd -> + [Either ChatError (NewSndChatItemData c)] -> + Maybe CITimed -> + Bool -> + CM [Either ChatError (ChatItem c 'MDSnd)] +saveSndChatItems user cd itemsData itemTimed live = do createdAt <- liftIO getCurrentTime - ciId <- withStore' $ \db -> do - when (ciRequiresAttention content || contactChatDeleted cd) $ updateChatTs db user cd createdAt - ciId <- createNewSndChatItem db user cd msg content quotedItem itemForwarded itemTimed live createdAt - forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt - pure ciId - pure $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live createdAt Nothing createdAt + when (contactChatDeleted cd || any (\NewSndChatItemData {content} -> ciRequiresAttention content) (rights itemsData)) $ + withStore' (\db -> updateChatTs db user cd createdAt) + lift $ withStoreBatch (\db -> map (bindRight $ createItem db createdAt) itemsData) + where + createItem :: DB.Connection -> UTCTime -> NewSndChatItemData c -> IO (Either ChatError (ChatItem c 'MDSnd)) + createItem db createdAt NewSndChatItemData {msg = msg@SndMessage {sharedMsgId}, content, ciFile, quotedItem, itemForwarded} = do + ciId <- createNewSndChatItem db user cd msg content quotedItem itemForwarded itemTimed live createdAt + forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt + pure $ Right $ mkChatItem cd ciId content ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live createdAt Nothing createdAt saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv) saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} brokerTs content = @@ -7472,7 +7648,7 @@ createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do let dirsCIContents = map contactChangedFeatures cts (errs, acis) <- partitionEithers <$> createInternalItemsForChats user Nothing dirsCIContents unless (null errs) $ toView' $ CRChatErrors (Just user) errs - forM_ acis $ \aci -> toView' $ CRNewChatItem user aci + toView' $ CRNewChatItems user acis where contactChangedFeatures :: (Contact, Contact) -> (ChatDirection 'CTDirect d, [CIContent d]) contactChangedFeatures (Contact {mergedPreferences = cups}, ct'@Contact {mergedPreferences = cups'}) = do @@ -7510,7 +7686,7 @@ sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferenc createInternalChatItem :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> CM () createInternalChatItem user cd content itemTs_ = lift (createInternalItemsForChats user itemTs_ [(cd, [content])]) >>= \case - [Right aci] -> toView $ CRNewChatItem user aci + [Right aci] -> toView $ CRNewChatItems user [aci] [Left e] -> throwError e rs -> throwChatError $ CEInternalError $ "createInternalChatItem: expected 1 result, got " <> show (length rs) @@ -7537,14 +7713,23 @@ createInternalItemsForChats user itemTs_ dirsCIContents = do let ci = mkChatItem cd ciId content Nothing Nothing Nothing Nothing Nothing False itemTs Nothing createdAt pure $ AChatItem (chatTypeI @c) (msgDirection @d) (toChatInfo cd) ci -createLocalChatItem :: MsgDirectionI d => User -> ChatDirection 'CTLocal d -> CIContent d -> Maybe CIForwardedFrom -> UTCTime -> CM ChatItemId -createLocalChatItem user cd content itemForwarded createdAt = do - gVar <- asks random - withStore $ \db -> do - liftIO $ updateChatTs db user cd createdAt - createWithRandomId gVar $ \sharedMsgId -> - let smi_ = Just (SharedMsgId sharedMsgId) - in createNewChatItem_ db user cd Nothing smi_ content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False createdAt Nothing createdAt +createLocalChatItems :: + User -> + ChatDirection 'CTLocal 'MDSnd -> + [(CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom)] -> + UTCTime -> + CM [ChatItem 'CTLocal 'MDSnd] +createLocalChatItems user cd itemsData createdAt = do + withStore' $ \db -> updateChatTs db user cd createdAt + (errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) itemsData) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure items + where + createItem :: DB.Connection -> (CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom) -> IO (ChatItem 'CTLocal 'MDSnd) + createItem db (content, ciFile, itemForwarded) = do + ciId <- createNewChatItem_ db user cd Nothing Nothing content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False createdAt Nothing createdAt + forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt + pure $ mkChatItem cd ciId content ciFile Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt withUser' :: (User -> CM ChatResponse) -> CM ChatResponse withUser' action = @@ -7670,13 +7855,13 @@ chatCommandP = "/_get chat " *> (APIGetChat <$> chatRefP <* A.space <*> chatPaginationP <*> optional (" search=" *> stringP)), "/_get items " *> (APIGetChatItems <$> chatPaginationP <*> optional (" search=" *> stringP)), "/_get item info " *> (APIGetChatItemInfo <$> chatRefP <* A.space <*> A.decimal), - "/_send " *> (APISendMessage <$> chatRefP <*> liveMessageP <*> sendMessageTTLP <*> (" json " *> jsonP <|> " text " *> (ComposedMessage Nothing Nothing <$> mcTextP))), - "/_create *" *> (APICreateChatItem <$> A.decimal <*> (" json " *> jsonP <|> " text " *> (ComposedMessage Nothing Nothing <$> mcTextP))), + "/_send " *> (APISendMessages <$> chatRefP <*> liveMessageP <*> sendMessageTTLP <*> (" json " *> jsonP <|> " text " *> composedMessagesTextP)), + "/_create *" *> (APICreateChatItems <$> A.decimal <*> (" json " *> jsonP <|> " text " *> composedMessagesTextP)), "/_update item " *> (APIUpdateChatItem <$> chatRefP <* A.space <*> A.decimal <*> liveMessageP <* A.space <*> msgContentP), "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_forward " *> (APIForwardChatItem <$> chatRefP <* A.space <*> chatRefP <* A.space <*> A.decimal <*> sendMessageTTLP), + "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), @@ -7974,6 +8159,9 @@ chatCommandP = '*' -> head "ā¤ļø" '^' -> 'šŸš€' c -> c + composedMessagesTextP = do + text <- mcTextP + pure $ (ComposedMessage Nothing Nothing text) :| [] liveMessageP = " live=" *> onOffP <|> pure False sendMessageTTLP = " ttl=" *> ((Just <$> A.decimal) <|> ("default" $> Nothing)) <|> pure Nothing receiptSettings = do diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs index f3de92e1f2..66479c0ee6 100644 --- a/src/Simplex/Chat/Bot.hs +++ b/src/Simplex/Chat/Bot.hs @@ -11,6 +11,7 @@ import Control.Concurrent.Async import Control.Concurrent.STM import Control.Monad import qualified Data.ByteString.Char8 as B +import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Text as T import Simplex.Chat.Controller import Simplex.Chat.Core @@ -31,7 +32,7 @@ chatBotRepl welcome answer _user cc = do CRContactConnected _ contact _ -> do contactConnected contact void $ sendMessage cc contact welcome - CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do + CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do let msg = T.unpack $ ciContentToText mc void $ sendMessage cc contact =<< answer contact msg _ -> pure () @@ -68,8 +69,8 @@ sendComposedMessage cc = sendComposedMessage' cc . contactId' sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO () sendComposedMessage' cc ctId quotedItemId msgContent = do let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent} - sendChatCmd cc (APISendMessage (ChatRef CTDirect ctId) False Nothing cm) >>= \case - CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId + sendChatCmd cc (APISendMessages (ChatRef CTDirect ctId) False Nothing (cm :| [])) >>= \case + CRNewChatItems {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId r -> putStrLn $ "unexpected send message response: " <> show r deleteMessage :: ChatController -> Contact -> ChatItemId -> IO () diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index c4f056c778..9d92ee8193 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -292,13 +292,13 @@ data ChatCommand | APIGetChat ChatRef ChatPagination (Maybe String) | APIGetChatItems ChatPagination (Maybe String) | APIGetChatItemInfo ChatRef ChatItemId - | APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessage :: ComposedMessage} - | APICreateChatItem {noteFolderId :: NoteFolderId, composedMessage :: ComposedMessage} + | APISendMessages {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessages :: NonEmpty ComposedMessage} + | APICreateChatItems {noteFolderId :: NoteFolderId, composedMessages :: NonEmpty ComposedMessage} | APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent} | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} - | APIForwardChatItem {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemId :: ChatItemId, ttl :: Maybe Int} + | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) @@ -597,7 +597,7 @@ data ChatResponse | CRContactCode {user :: User, contact :: Contact, connectionCode :: Text} | CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text} | CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text} - | CRNewChatItem {user :: User, chatItem :: AChatItem} + | CRNewChatItems {user :: User, chatItems :: [AChatItem]} | CRChatItemStatusUpdated {user :: User, chatItem :: AChatItem} | CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem} @@ -1178,7 +1178,6 @@ data ChatErrorType | CEInlineFileProhibited {fileId :: FileTransferId} | CEInvalidQuote | CEInvalidForward - | CEForwardNoFile | CEInvalidChatItemUpdate | CEInvalidChatItemDelete | CEHasCurrentCall diff --git a/src/Simplex/Chat/Messages/Batch.hs b/src/Simplex/Chat/Messages/Batch.hs index 690ae5828f..c1c45d7b0a 100644 --- a/src/Simplex/Chat/Messages/Batch.hs +++ b/src/Simplex/Chat/Messages/Batch.hs @@ -17,16 +17,18 @@ import Simplex.Chat.Messages data MsgBatch = MsgBatch ByteString [SndMessage] --- | Batches [SndMessage] into batches of ByteStrings in form of JSON arrays. +-- | Batches SndMessages in [Either ChatError SndMessage] into batches of ByteStrings in form of JSON arrays. +-- Preserves original errors in the list. -- Does not check if the resulting batch is a valid JSON. -- If a single element is passed, it is returned as is (a JSON string). -- If an element exceeds maxLen, it is returned as ChatError. -batchMessages :: Int -> [SndMessage] -> [Either ChatError MsgBatch] +batchMessages :: Int -> [Either ChatError SndMessage] -> [Either ChatError MsgBatch] batchMessages maxLen = addBatch . foldr addToBatch ([], [], 0, 0) where msgBatch batch = Right (MsgBatch (encodeMessages batch) batch) - addToBatch :: SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) - addToBatch msg@SndMessage {msgBody} acc@(batches, batch, len, n) + addToBatch :: Either ChatError SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) + addToBatch (Left err) acc = (Left err : addBatch acc, [], 0, 0) -- step over original error + addToBatch (Right msg@SndMessage {msgBody}) acc@(batches, batch, len, n) | batchLen <= maxLen = (batches, msg : batch, len', n + 1) | msgLen <= maxLen = (addBatch acc, [msg], msgLen, 1) | otherwise = (errLarge msg : addBatch acc, [], 0, 0) diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index d1da081cee..2c02d872b1 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -966,20 +966,20 @@ lookupFileTransferRedirectMeta db User {userId} fileId = do redirects <- DB.query db "SELECT file_id FROM files WHERE user_id = ? AND redirect_file_id = ?" (userId, fileId) rights <$> mapM (runExceptT . getFileTransferMeta_ db userId . fromOnly) redirects -createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 -createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do +createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 +createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do DB.execute db [sql| INSERT INTO files - ( user_id, note_folder_id, chat_item_id, + ( user_id, note_folder_id, file_name, file_path, file_size, file_crypto_key, file_crypto_nonce, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at ) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, noteFolderId, chatItemId) + ( (userId, noteFolderId) :. (takeFileName filePath, filePath, fileSize) :. maybe (Nothing, Nothing) (\(CFArgs key nonce) -> (Just key, Just nonce)) cryptoArgs :. (fileChunkSize, Nothing :: Maybe InlineFileMode, fileStatus, FPLocal, itemTs, itemTs) diff --git a/src/Simplex/Chat/Terminal/Input.hs b/src/Simplex/Chat/Terminal/Input.hs index 2d1039e585..4f6d66d2c1 100644 --- a/src/Simplex/Chat/Terminal/Input.hs +++ b/src/Simplex/Chat/Terminal/Input.hs @@ -69,7 +69,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do Nothing -> setActive ct "" Just rhId -> updateRemoteUser ct u rhId CRChatItems u chatName_ _ -> whenCurrUser cc u $ mapM_ (setActive ct . chatActiveTo) chatName_ - CRNewChatItem u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo + CRNewChatItems u ((AChatItem _ SMDSnd cInfo _) : _) -> whenCurrUser cc u $ setActiveChat ct cInfo CRChatItemUpdated u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo CRChatItemsDeleted u ((ChatItemDeletion (AChatItem _ _ cInfo _) _) : _) _ _ -> whenCurrUser cc u $ setActiveChat ct cInfo CRContactDeleted u c -> whenCurrUser cc u $ unsetActiveContact ct c @@ -93,7 +93,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do Right SendMessageBroadcast {} -> True _ -> False startLiveMessage :: Either a ChatCommand -> ChatResponse -> IO () - startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItem _ (AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}})) = do + startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItems {chatItems = [AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}}]}) = do whenM (isNothing <$> readTVarIO liveMessageState) $ do let s = T.unpack msg int = case cType of SCTGroup -> 5000000; _ -> 3000000 :: Int diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index a946ba3483..64703a3a92 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -44,7 +44,7 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do (_, _, r') <- atomically . readTBQueue $ outputQ cc case r' of - CRNewChatItem {} -> printResponse r' + CRNewChatItems {} -> printResponse r' _ -> when (chatCmdLog == CCLAll) $ printResponse r' sendChatCmdStr cc chatCmd >>= printResponse threadDelay $ chatCmdDelay * 1000000 diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs index 40f14a10de..0ead850b86 100644 --- a/src/Simplex/Chat/Terminal/Output.hs +++ b/src/Simplex/Chat/Terminal/Output.hs @@ -147,7 +147,7 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha forever $ do (_, outputRH, r) <- atomically $ readTBQueue outputQ case r of - CRNewChatItem u ci -> when markRead $ markChatItemRead u ci + CRNewChatItems u (ci : _) -> when markRead $ markChatItemRead u ci -- At the moment of writing received items are created one at a time CRChatItemUpdated u ci -> when markRead $ markChatItemRead u ci CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_ @@ -175,7 +175,8 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha responseNotification :: ChatTerminal -> ChatController -> ChatResponse -> IO () responseNotification t@ChatTerminal {sendNotification} cc = \case - CRNewChatItem u (AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) -> + -- At the moment of writing received items are created one at a time + CRNewChatItems u ((AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) : _) -> when (chatDirNtf u cInfo chatDir $ isMention ci) $ do whenCurrUser cc u $ setActiveChat t cInfo case (cInfo, chatDir) of diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e154b5b902..dd2bf94467 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -120,7 +120,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code] CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView - CRNewChatItem u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item + CRNewChatItems u chatItems -> + concatMap + (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) + chatItems CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] @@ -2025,7 +2028,6 @@ viewChatError isCmd logLevel testView = \case CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] CEInvalidForward -> ["cannot forward this message"] - CEForwardNoFile -> ["cannot forward this message, file not found"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 09b2d7d51c..86c51f6aaa 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -52,6 +52,7 @@ chatDirectTests = do it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact it "should send multiline message" testMultilineMessage it "send large message" testLargeMessage + it "send multiple messages api" testSendMulti describe "duplicate contacts" $ do it "duplicate contacts are separate (contacts don't merge)" testDuplicateContactsSeparate it "new contact is separate with multiple duplicate contacts (contacts don't merge)" testDuplicateContactsMultipleSeparate @@ -839,6 +840,18 @@ testLargeMessage = bob <## "contact alice changed to alice2" bob <## "use @alice2 to send messages" +testSendMulti :: HasCallStack => FilePath -> IO () +testSendMulti = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice ##> "/_send @2 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "@bob test 1" + alice <# "@bob test 2" + bob <# "alice> test 1" + bob <# "alice> test 2" + testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers = testChat2 aliceProfile bobProfile $ @@ -2162,7 +2175,7 @@ testSetChatItemTTL = -- chat item with file alice #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") copyFile "./tests/fixtures/test.jpg" "./tests/tmp/app_files/test.jpg" - alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -2410,7 +2423,7 @@ setupDesynchronizedRatchet tmp alice = do (bob "/tail @alice 1" bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)" - bob ##> "@alice 1" + bob `send` "@alice 1" bob <## "error: command is prohibited, sendMessagesB: send prohibited" (alice FilePath -> IO () runTestMessageWithFile = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do connectUsers alice bob - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}]" alice <# "@bob hi, sending a file" alice <# "/f @bob ./tests/fixtures/test.jpg" alice <## "use /fc 1 to cancel sending" @@ -91,7 +91,7 @@ testSendImage = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do connectUsers alice bob - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob ./tests/fixtures/test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -122,7 +122,7 @@ testSenderMarkItemDeleted = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do connectUsers alice bob - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test_1MB.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test_1MB.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}]" alice <# "@bob hi, sending a file" alice <# "/f @bob ./tests/fixtures/test_1MB.pdf" alice <## "use /fc 1 to cancel sending" @@ -147,7 +147,7 @@ testFilesFoldersSendImage = connectUsers alice bob alice #$> ("/_files_folder ./tests/fixtures", id, "ok") bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") - alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -180,7 +180,7 @@ testFilesFoldersImageSndDelete = alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") - alice ##> "/_send @2 json {\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test_1MB.pdf" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" @@ -212,7 +212,7 @@ testFilesFoldersImageRcvDelete = connectUsers alice bob alice #$> ("/_files_folder ./tests/fixtures", id, "ok") bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") - alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -239,7 +239,7 @@ testSendImageWithTextAndQuote = connectUsers alice bob bob #> "@alice hi alice" alice <# "bob> hi alice" - alice ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") + alice ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]") alice <# "@bob > hi alice" alice <## " hey bob" alice <# "/f @bob ./tests/fixtures/test.jpg" @@ -265,7 +265,7 @@ testSendImageWithTextAndQuote = bob @@@ [("@alice", "hey bob")] -- quoting (file + text) with file uses quoted text - bob ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> itemId 2 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}") + bob ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> itemId 2 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}]") bob <# "@alice > hey bob" bob <## " test.pdf" bob <# "/f @alice ./tests/fixtures/test.pdf" @@ -287,7 +287,7 @@ testSendImageWithTextAndQuote = B.readFile "./tests/tmp/test.pdf" `shouldReturn` txtSrc -- quoting (file without text) with file uses file name - alice ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 3 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") + alice ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 3 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]") alice <# "@bob > test.pdf" alice <## " test.jpg" alice <# "/f @bob ./tests/fixtures/test.jpg" @@ -313,7 +313,7 @@ testGroupSendImage = \alice bob cath -> withXFTPServer $ do createGroup3 "team" alice bob cath threadDelay 1000000 - alice ##> "/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "/f #team ./tests/fixtures/test.jpg" alice <## "use /fc 1 to cancel sending" concurrentlyN_ @@ -361,7 +361,7 @@ testGroupSendImageWithTextAndQuote = (cath <# "#team bob> hi team") threadDelay 1000000 msgItemId <- lastItemId alice - alice ##> ("/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> msgItemId <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") + alice ##> ("/_send #1 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> msgItemId <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]") alice <# "#team > bob hi team" alice <## " hey bob" alice <# "/f #team ./tests/fixtures/test.jpg" @@ -460,7 +460,7 @@ testXFTPFileTransferEncrypted = let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs withXFTPServer $ do connectUsers alice bob - alice ##> ("/_send @2 json {\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}") + alice ##> ("/_send @2 json [{\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}]") alice <# "/f @bob ./tests/tmp/alice/test.pdf" alice <## "use /fc 1 to cancel sending" bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index d49c6df955..221a2426b1 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -33,6 +33,8 @@ chatForwardTests = do it "with relative paths: from contact to contact" testForwardFileContactToContact it "with relative paths: from group to notes" testForwardFileGroupToNotes it "with relative paths: from notes to group" testForwardFileNotesToGroup + describe "multi forward api" $ do + it "from contact to contact" testForwardContactToContactMulti testForwardContactToContact :: HasCallStack => FilePath -> IO () testForwardContactToContact = @@ -384,7 +386,7 @@ testForwardFileNoFilesFolder = connectUsers bob cath -- send original file - alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "@bob hi" alice <# "/f @bob ./tests/fixtures/test.pdf" alice <## "use /fc 1 to cancel sending" @@ -441,7 +443,7 @@ testForwardFileContactToContact = connectUsers bob cath -- send original file - alice ##> "/_send @2 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_send @2 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "@bob hi" alice <# "/f @bob test.pdf" alice <## "use /fc 1 to cancel sending" @@ -506,7 +508,7 @@ testForwardFileGroupToNotes = createCCNoteFolder cath -- send original file - alice ##> "/_send #1 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "#team hi" alice <# "/f #team test.pdf" alice <## "use /fc 1 to cancel sending" @@ -555,7 +557,7 @@ testForwardFileNotesToGroup = createGroup2 "team" alice cath -- create original file - alice ##> "/_create *1 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" + alice ##> "/_create *1 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]" alice <# "* hi" alice <# "* file 1 (test.pdf)" @@ -590,3 +592,31 @@ testForwardFileNotesToGroup = alice <## "notes: all messages are removed" fwdFileExists <- doesFileExist "./tests/tmp/alice_files/test_1.pdf" fwdFileExists `shouldBe` True + +testForwardContactToContactMulti :: HasCallStack => FilePath -> IO () +testForwardContactToContactMulti = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + connectUsers alice bob + connectUsers alice cath + connectUsers bob cath + + alice #> "@bob hi" + bob <# "alice> hi" + msgId1 <- lastItemId alice + + threadDelay 1000000 + + bob #> "@alice hey" + alice <# "bob> hey" + msgId2 <- lastItemId alice + + alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2) + alice <# "@cath <- you @bob" + alice <## " hi" + alice <# "@cath <- @bob" + alice <## " hey" + cath <# "alice> -> forwarded" + cath <## " hi" + cath <# "alice> -> forwarded" + cath <## " hey" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index d6849d3074..1ff01a911c 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -64,6 +64,7 @@ chatGroupTests = do it "moderate message of another group member (full delete)" testGroupModerateFullDelete it "moderate message that arrives after the event of moderation" testGroupDelayedModeration it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete + it "send multiple messages api" testSendMulti describe "async group connections" $ do xit "create and join group when clients go offline" testGroupAsync describe "group links" $ do @@ -1818,6 +1819,18 @@ testGroupDelayedModerationFullDelete tmp = do where cfg = testCfgCreateGroupDirect +testSendMulti :: HasCallStack => FilePath -> IO () +testSendMulti = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + createGroup2 "team" alice bob + + alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "#team test 1" + alice <# "#team test 2" + bob <# "#team alice> test 1" + bob <# "#team alice> test 2" + testGroupAsync :: HasCallStack => FilePath -> IO () testGroupAsync tmp = do withNewTestChat tmp "alice" aliceProfile $ \alice -> do @@ -3468,7 +3481,8 @@ testGroupSyncRatchet tmp = bob <## "1 contacts connected (use /cs for the list)" bob <## "#team: connected to server(s)" bob `send` "#team 1" - bob <## "error: command is prohibited, sendMessagesB: send prohibited" -- silence? + -- "send prohibited" error is not printed in group as SndMessage is created, + -- but it should be displayed in per member snd statuses bob <# "#team 1" (alice "/_send #1 json {\"filePath\": \"./tests/tmp/testfile\", \"msgContent\": {\"text\":\"hello\",\"type\":\"file\"}}" + bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile\", \"msgContent\": {\"text\":\"hello\",\"type\":\"file\"}}]" bob <# "#team hello" bob <# "/f #team ./tests/tmp/testfile" bob <## "use /fc 1 to cancel sending" @@ -4969,7 +4983,7 @@ testGroupHistoryMultipleFiles = threadDelay 1000000 - bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}" + bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}]" bob <# "#team hi alice" bob <# "/f #team ./tests/tmp/testfile_bob" bob <## "use /fc 1 to cancel sending" @@ -4981,7 +4995,7 @@ testGroupHistoryMultipleFiles = threadDelay 1000000 - alice ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}]" alice <# "#team hey bob" alice <# "/f #team ./tests/tmp/testfile_alice" alice <## "use /fc 2 to cancel sending" @@ -5047,7 +5061,7 @@ testGroupHistoryFileCancel = createGroup2 "team" alice bob - bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}" + bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}]" bob <# "#team hi alice" bob <# "/f #team ./tests/tmp/testfile_bob" bob <## "use /fc 1 to cancel sending" @@ -5063,7 +5077,7 @@ testGroupHistoryFileCancel = threadDelay 1000000 - alice ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}" + alice ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}]" alice <# "#team hey bob" alice <# "/f #team ./tests/tmp/testfile_alice" alice <## "use /fc 2 to cancel sending" diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index 5562d517ac..f097edbc0c 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -17,6 +17,7 @@ chatLocalChatsTests :: SpecWith FilePath chatLocalChatsTests = do describe "note folders" $ do it "create folders, add notes, read, search" testNotes + it "create multiple messages api" testCreateMulti it "switch users" testUserNotes it "preview pagination for notes" testPreviewsPagination it "chat pagination" testChatPagination @@ -52,6 +53,14 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/tail *" alice <# "* Greetings." +testCreateMulti :: FilePath -> IO () +testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do + createCCNoteFolder alice + + alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "* test 1" + alice <# "* test 2" + testUserNotes :: FilePath -> IO () testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice @@ -120,7 +129,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do let source = "./tests/fixtures/test.jpg" let stored = files "test.jpg" copyFile source stored - alice ##> "/_create *1 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"hi myself\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_create *1 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"hi myself\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "* hi myself" alice <# "* file 1 (test.jpg)" @@ -141,7 +150,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do -- one more file let stored2 = files "another_test.jpg" copyFile source stored2 - alice ##> "/_create *1 json {\"filePath\": \"another_test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" + alice ##> "/_create *1 json [{\"filePath\": \"another_test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]" alice <# "* file 2 (another_test.jpg)" alice ##> "/_delete item *1 2 internal" @@ -173,8 +182,8 @@ testOtherFiles = bob ##> "/fr 1" bob <### [ "saving file 1 from alice to test.jpg", - "started receiving file 1 (test.jpg) from alice" - ] + "started receiving file 1 (test.jpg) from alice" + ] bob <## "completed receiving file 1 (test.jpg) from alice" bob /* "test" diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 43ad5ba841..878546ba21 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1721,7 +1721,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ let startFeatures = [(0, e2eeInfoPQStr), (0, "Disappearing messages: allowed"), (0, "Full deletion: off"), (0, "Message reactions: enabled"), (0, "Voice messages: off"), (0, "Audio/video calls: enabled")] alice #$> ("/_get chat @2 count=100", chat, startFeatures) bob #$> ("/_get chat @2 count=100", chat, startFeatures) - let sendVoice = "/_send @2 json {\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}" + let sendVoice = "/_send @2 json [{\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}]" voiceNotAllowed = "bad chat command: feature not allowed Voice messages" alice ##> sendVoice alice <## voiceNotAllowed @@ -2227,7 +2227,7 @@ testGroupPrefsSimplexLinksForRole = testChat3 aliceProfile bobProfile cathProfil inv <- getInvitation bob bob ##> ("#team \"" <> inv <> "\\ntest\"") bob <## "bad chat command: feature not allowed SimpleX links" - bob ##> ("/_send #1 json {\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}") + bob ##> ("/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}]") bob <## "bad chat command: feature not allowed SimpleX links" (alice [SndMessage] -> [ChatError] -> [ByteString] -> IO () runBatcherTest' maxLen msgs expectedErrors expectedBatches = do - let (errors, batches) = partitionEithers $ batchMessages maxLen msgs + let (errors, batches) = partitionEithers $ batchMessages maxLen (map Right msgs) batchedStrs = map (\(MsgBatch batchBody _) -> batchBody) batches testErrors errors `shouldBe` testErrors expectedErrors batchedStrs `shouldBe` expectedBatches diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs index 3f1bad613a..e51a938252 100644 --- a/tests/RemoteTests.hs +++ b/tests/RemoteTests.hs @@ -238,7 +238,7 @@ remoteStoreFileTest = desktop ##> "/get remote file 1 {\"userId\": 1, \"fileId\": 1, \"sent\": true, \"fileSource\": {\"filePath\": \"test_1.pdf\"}}" hostError desktop "SEFileNotFound" -- send file not encrypted locally on mobile host - desktop ##> "/_send @2 json {\"filePath\": \"test_1.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"sending a file\"}}" + desktop ##> "/_send @2 json [{\"filePath\": \"test_1.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"sending a file\"}}]" desktop <# "@bob sending a file" desktop <# "/f @bob test_1.pdf" desktop <## "use /fc 1 to cancel sending" @@ -268,7 +268,7 @@ remoteStoreFileTest = B.readFile (desktopHostStore "test_1.pdf") `shouldReturn` src -- send file encrypted locally on mobile host - desktop ##> ("/_send @2 json {\"fileSource\": {\"filePath\":\"test_2.pdf\", \"cryptoArgs\": " <> LB.unpack (J.encode cfArgs) <> "}, \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}") + desktop ##> ("/_send @2 json [{\"fileSource\": {\"filePath\":\"test_2.pdf\", \"cryptoArgs\": " <> LB.unpack (J.encode cfArgs) <> "}, \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}]") desktop <# "/f @bob test_2.pdf" desktop <## "use /fc 2 to cancel sending" bob <# "alice> sends file test_2.pdf (266.0 KiB / 272376 bytes)" From f587179045bbead74b87e10eaadfe116bef7a466 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:38:22 +0400 Subject: [PATCH 003/704] ios: multi send & forward api (#4739) --- apps/ios/Shared/Model/SimpleXAPI.swift | 76 +++++++++++-------- .../Chat/ComposeMessage/ComposeView.swift | 34 +++++---- apps/ios/Shared/Views/TerminalView.swift | 2 +- .../ios/SimpleX NSE/NotificationService.swift | 25 +++--- apps/ios/SimpleX SE/ShareAPI.swift | 28 ++++--- apps/ios/SimpleX SE/ShareModel.swift | 26 ++++--- apps/ios/SimpleXChat/APITypes.swift | 46 ++++++----- 7 files changed, 134 insertions(+), 103 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index e4b5410392..a292f3d7a2 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -357,17 +357,17 @@ func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws - throw r } -func apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) async -> ChatItem? { - let cmd: ChatCommand = .apiForwardChatItem(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemId: itemId, ttl: ttl) +func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { + let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) } -func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool = false, ttl: Int? = nil) async -> ChatItem? { - let cmd: ChatCommand = .apiSendMessage(type: type, id: id, file: file, quotedItemId: quotedItemId, msg: msg, live: live, ttl: ttl) +func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages) return await processSendMessageCmd(toChatType: type, cmd: cmd) } -private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> ChatItem? { +private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async -> [ChatItem]? { let chatModel = ChatModel.shared let r: ChatResponse if toChatType == .direct { @@ -380,10 +380,13 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async } }) r = await chatSendCmd(cmd, bgTask: false) - if case let .newChatItem(_, aChatItem) = r { - cItem = aChatItem.chatItem - chatModel.messageDelivery[aChatItem.chatItem.id] = endTask - return cItem + if case let .newChatItems(_, aChatItems) = r { + let cItems = aChatItems.map { $0.chatItem } + if let cItemLast = cItems.last { + cItem = cItemLast + chatModel.messageDelivery[cItemLast.id] = endTask + } + return cItems } if let networkErrorAlert = networkErrorAlert(r) { AlertManager.shared.showAlert(networkErrorAlert) @@ -394,18 +397,18 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async return nil } else { r = await chatSendCmd(cmd, bgDelay: msgDelay) - if case let .newChatItem(_, aChatItem) = r { - return aChatItem.chatItem + if case let .newChatItems(_, aChatItems) = r { + return aChatItems.map { $0.chatItem } } sendMessageErrorAlert(r) return nil } } -func apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) async -> ChatItem? { - let r = await chatSendCmd(.apiCreateChatItem(noteFolderId: noteFolderId, file: file, msg: msg)) - if case let .newChatItem(_, aChatItem) = r { return aChatItem.chatItem } - createChatItemErrorAlert(r) +func apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) async -> [ChatItem]? { + let r = await chatSendCmd(.apiCreateChatItems(noteFolderId: noteFolderId, composedMessages: composedMessages)) + if case let .newChatItems(_, aChatItems) = r { return aChatItems.map { $0.chatItem } } + createChatItemsErrorAlert(r) return nil } @@ -417,8 +420,8 @@ private func sendMessageErrorAlert(_ r: ChatResponse) { ) } -private func createChatItemErrorAlert(_ r: ChatResponse) { - logger.error("apiCreateChatItem error: \(String(describing: r))") +private func createChatItemsErrorAlert(_ r: ChatResponse) { + logger.error("apiCreateChatItems error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error creating message", message: "Error: \(responseError(r))" @@ -1782,23 +1785,25 @@ func processReceivedMsg(_ res: ChatResponse) async { n.networkStatuses = ns } } - case let .newChatItem(user, aChatItem): - let cInfo = aChatItem.chatInfo - let cItem = aChatItem.chatItem - await MainActor.run { - if active(user) { - m.addChatItem(cInfo, cItem) - } else if cItem.isRcvNew && cInfo.ntfsEnabled { - m.increaseUnreadCounter(user: user) + case let .newChatItems(user, chatItems): + for chatItem in chatItems { + let cInfo = chatItem.chatInfo + let cItem = chatItem.chatItem + await MainActor.run { + if active(user) { + m.addChatItem(cInfo, cItem) + } else if cItem.isRcvNew && cInfo.ntfsEnabled { + m.increaseUnreadCounter(user: user) + } } - } - if let file = cItem.autoReceiveFile() { - Task { - await receiveFile(user: user, fileId: file.fileId, auto: true) + if let file = cItem.autoReceiveFile() { + Task { + await receiveFile(user: user, fileId: file.fileId, auto: true) + } + } + if cItem.showNotification { + NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } - } - if cItem.showNotification { - NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } case let .chatItemStatusUpdated(user, aChatItem): let cInfo = aChatItem.chatInfo @@ -1808,10 +1813,15 @@ func processReceivedMsg(_ res: ChatResponse) async { } if let endTask = m.messageDelivery[cItem.id] { switch cItem.meta.itemStatus { + case .sndNew: () case .sndSent: endTask() + case .sndRcvd: endTask() case .sndErrorAuth: endTask() case .sndError: endTask() - default: () + case .sndWarning: endTask() + case .rcvNew: () + case .rcvRead: () + case .invalid: () } } case let .chatItemUpdated(user, aChatItem): diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 78cae78cf5..99ab778a0e 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -751,6 +751,7 @@ struct ComposeView: View { case .linkPreview: sent = await send(checkLinkPreview(), quoted: quoted, live: live, ttl: ttl) case let .mediaPreviews(mediaPreviews: media): + // TODO batch send: batch media previews let last = media.count - 1 if last >= 0 { for i in 0.. ChatItem? { - if let chatItem = chat.chatInfo.chatType == .local - ? await apiCreateChatItem(noteFolderId: chat.chatInfo.apiId, file: file, msg: mc) - : await apiSendMessage( + if let chatItems = chat.chatInfo.chatType == .local + ? await apiCreateChatItems( + noteFolderId: chat.chatInfo.apiId, + composedMessages: [ComposedMessage(fileSource: file, msgContent: mc)] + ) + : await apiSendMessages( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, - file: file, - quotedItemId: quoted, - msg: mc, live: live, - ttl: ttl + ttl: ttl, + composedMessages: [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)] ) { await MainActor.run { chatModel.removeLiveDummy(animated: false) - chatModel.addChatItem(chat.chatInfo, chatItem) + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } } - return chatItem + // UI only supports sending one item at a time + return chatItems.first } if let file = file { removeFile(file.filePath) @@ -911,18 +916,21 @@ struct ComposeView: View { } func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> ChatItem? { - if let chatItem = await apiForwardChatItem( + if let chatItems = await apiForwardChatItems( toChatType: chat.chatInfo.chatType, toChatId: chat.chatInfo.apiId, fromChatType: fromChatInfo.chatType, fromChatId: fromChatInfo.apiId, - itemId: forwardedItem.id, + itemIds: [forwardedItem.id], ttl: ttl ) { await MainActor.run { - chatModel.addChatItem(chat.chatInfo, chatItem) + for chatItem in chatItems { + chatModel.addChatItem(chat.chatInfo, chatItem) + } } - return chatItem + // TODO batch send: forward multiple messages + return chatItems.first } return nil } diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index d209ced128..36c05ed43d 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -160,7 +160,7 @@ struct TerminalView_Previews: PreviewProvider { let chatModel = ChatModel() chatModel.terminalItems = [ .resp(.now, ChatResponse.response(type: "contactSubscribed", json: "{}")), - .resp(.now, ChatResponse.response(type: "newChatItem", json: "{}")) + .resp(.now, ChatResponse.response(type: "newChatItems", json: "{}")) ] return NavigationView { TerminalView() diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 1a2a27ba9b..7f1ad18ec2 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -571,17 +571,22 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { // TODO profile update case let .receivedContactRequest(user, contactRequest): return (UserContact(contactRequest: contactRequest).id, .nse(createContactRequestNtf(user, contactRequest))) - case let .newChatItem(user, aChatItem): - let cInfo = aChatItem.chatInfo - var cItem = aChatItem.chatItem - if !cInfo.ntfsEnabled { - ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) + case let .newChatItems(user, chatItems): + // Received items are created one at a time + if let chatItem = chatItems.first { + let cInfo = chatItem.chatInfo + var cItem = chatItem.chatItem + if !cInfo.ntfsEnabled { + ntfBadgeCountGroupDefault.set(max(0, ntfBadgeCountGroupDefault.get() - 1)) + } + if let file = cItem.autoReceiveFile() { + cItem = autoReceiveFile(file) ?? cItem + } + let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty + return cItem.showNotification ? (chatItem.chatId, ntf) : nil + } else { + return nil } - if let file = cItem.autoReceiveFile() { - cItem = autoReceiveFile(file) ?? cItem - } - let ntf: NSENotification = cInfo.ntfsEnabled ? .nse(createMessageReceivedNtf(user, cInfo, cItem)) : .empty - return cItem.showNotification ? (aChatItem.chatId, ntf) : nil case let .rcvFileSndCancelled(_, aChatItem, _): cleanupFile(aChatItem) return nil diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index 47e072ae78..fcb78c64b1 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -54,32 +54,30 @@ func apiGetChats(userId: User.ID) throws -> Array { throw r } -func apiSendMessage( +func apiSendMessages( chatInfo: ChatInfo, - cryptoFile: CryptoFile?, - msgContent: MsgContent -) throws -> AChatItem { + composedMessages: [ComposedMessage] +) throws -> [AChatItem] { let r = sendSimpleXCmd( chatInfo.chatType == .local - ? .apiCreateChatItem( + ? .apiCreateChatItems( noteFolderId: chatInfo.apiId, - file: cryptoFile, - msg: msgContent + composedMessages: composedMessages ) - : .apiSendMessage( + : .apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, - file: cryptoFile, - quotedItemId: nil, - msg: msgContent, live: false, - ttl: nil + ttl: nil, + composedMessages: composedMessages ) ) - if case let .newChatItem(_, chatItem) = r { - return chatItem + if case let .newChatItems(_, chatItems) = r { + return chatItems } else { - if let filePath = cryptoFile?.filePath { removeFile(filePath) } + for composedMessage in composedMessages { + if let filePath = composedMessage.fileSource?.filePath { removeFile(filePath) } + } throw r } } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index 5bda361126..f43548f676 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -141,23 +141,25 @@ class ShareModel: ObservableObject { do { SEChatState.shared.set(.sendingMessage) await waitForOtherProcessesToSuspend() - let ci = try apiSendMessage( + let chatItems = try apiSendMessages( chatInfo: selected.chatInfo, - cryptoFile: sharedContent.cryptoFile, - msgContent: sharedContent.msgContent(comment: self.comment) + composedMessages: [ComposedMessage(fileSource: sharedContent.cryptoFile, msgContent: sharedContent.msgContent(comment: self.comment))] ) if selected.chatInfo.chatType == .local { completion() } else { - await MainActor.run { self.bottomBar = .loadingBar(progress: 0) } - if let e = await handleEvents( - isGroupChat: ci.chatInfo.chatType == .group, - isWithoutFile: sharedContent.cryptoFile == nil, - chatItemId: ci.chatItem.id - ) { - await MainActor.run { errorAlert = e } - } else { - completion() + // TODO batch send: share multiple items + if let ci = chatItems.first { + await MainActor.run { self.bottomBar = .loadingBar(progress: 0) } + if let e = await handleEvents( + isGroupChat: ci.chatInfo.chatType == .group, + isWithoutFile: sharedContent.cryptoFile == nil, + chatItemId: ci.chatItem.id + ) { + await MainActor.run { errorAlert = e } + } else { + completion() + } } } } catch { diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3dc9138774..68f569053e 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -42,13 +42,13 @@ public enum ChatCommand { case apiGetChats(userId: Int64) case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String) case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) - case apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?) - case apiCreateChatItem(noteFolderId: Int64, file: CryptoFile?, msg: MsgContent) + case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage]) + case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]) case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool) case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) - case apiForwardChatItem(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemId: Int64, ttl: Int?) + case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) case apiGetNtfToken case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) case apiVerifyToken(token: DeviceToken, nonce: String, code: String) @@ -191,20 +191,20 @@ public enum ChatCommand { case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)") case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)" - case let .apiSendMessage(type, id, file, quotedItemId, mc, live, ttl): - let msg = encodeJSON(ComposedMessage(fileSource: file, quotedItemId: quotedItemId, msgContent: mc)) + case let .apiSendMessages(type, id, live, ttl, composedMessages): + let msgs = encodeJSON(composedMessages) let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msg)" - case let .apiCreateChatItem(noteFolderId, file, mc): - let msg = encodeJSON(ComposedMessage(fileSource: file, msgContent: mc)) - return "/_create *\(noteFolderId) json \(msg)" + return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)" + case let .apiCreateChatItems(noteFolderId, composedMessages): + let msgs = encodeJSON(composedMessages) + return "/_create *\(noteFolderId) json \(msgs)" case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)" case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" - case let .apiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl): + case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): let ttlStr = ttl != nil ? "\(ttl!)" : "default" - return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemId) ttl=\(ttlStr)" + return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" case .apiGetNtfToken: return "/_ntf get " case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)" case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)" @@ -349,14 +349,14 @@ public enum ChatCommand { case .apiGetChats: return "apiGetChats" case .apiGetChat: return "apiGetChat" case .apiGetChatItemInfo: return "apiGetChatItemInfo" - case .apiSendMessage: return "apiSendMessage" - case .apiCreateChatItem: return "apiCreateChatItem" + case .apiSendMessages: return "apiSendMessages" + case .apiCreateChatItems: return "apiCreateChatItems" case .apiUpdateChatItem: return "apiUpdateChatItem" case .apiDeleteChatItem: return "apiDeleteChatItem" case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" case .apiChatItemReaction: return "apiChatItemReaction" - case .apiForwardChatItem: return "apiForwardChatItem" + case .apiForwardChatItems: return "apiForwardChatItems" case .apiGetNtfToken: return "apiGetNtfToken" case .apiRegisterToken: return "apiRegisterToken" case .apiVerifyToken: return "apiVerifyToken" @@ -592,7 +592,7 @@ public enum ChatResponse: Decodable, Error { case memberSubErrors(user: UserRef, memberSubErrors: [MemberSubError]) case groupEmpty(user: UserRef, groupInfo: GroupInfo) case userContactLinkSubscribed - case newChatItem(user: UserRef, chatItem: AChatItem) + case newChatItems(user: UserRef, chatItems: [AChatItem]) case chatItemStatusUpdated(user: UserRef, chatItem: AChatItem) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) @@ -763,7 +763,7 @@ public enum ChatResponse: Decodable, Error { case .memberSubErrors: return "memberSubErrors" case .groupEmpty: return "groupEmpty" case .userContactLinkSubscribed: return "userContactLinkSubscribed" - case .newChatItem: return "newChatItem" + case .newChatItems: return "newChatItems" case .chatItemStatusUpdated: return "chatItemStatusUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" @@ -932,7 +932,9 @@ public enum ChatResponse: Decodable, Error { case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors)) case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo)) case .userContactLinkSubscribed: return noDetails - case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .newChatItems(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) @@ -1119,10 +1121,16 @@ public enum ChatPagination: Hashable { } } -struct ComposedMessage: Encodable { - var fileSource: CryptoFile? +public struct ComposedMessage: Encodable { + public var fileSource: CryptoFile? var quotedItemId: Int64? var msgContent: MsgContent + + public init(fileSource: CryptoFile? = nil, quotedItemId: Int64? = nil, msgContent: MsgContent) { + self.fileSource = fileSource + self.quotedItemId = quotedItemId + self.msgContent = msgContent + } } public struct ArchiveConfig: Encodable { From ef1897f865bd006dfd05fb201b8c5b410f83a6ae Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:39:13 +0400 Subject: [PATCH 004/704] multiplatform: multi send & forward api (#4745) --- .../chat/simplex/common/model/SimpleXAPI.kt | 91 ++++++++++--------- .../simplex/common/views/chat/ComposeView.kt | 46 ++++++---- 2 files changed, 74 insertions(+), 63 deletions(-) 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 fe568b5144..8dc9b55dd1 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 @@ -846,15 +846,15 @@ object ChatController { return null } - suspend fun apiSendMessage(rh: Long?, type: ChatType, id: Long, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? { - val cmd = CC.ApiSendMessage(type, id, file, quotedItemId, mc, live, ttl) + suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, live: Boolean = false, ttl: Int? = null, composedMessages: List): List? { + val cmd = CC.ApiSendMessages(type, id, live, ttl, composedMessages) return processSendMessageCmd(rh, cmd) } - private suspend fun processSendMessageCmd(rh: Long?, cmd: CC): AChatItem? { + private suspend fun processSendMessageCmd(rh: Long?, cmd: CC): List? { val r = sendCmd(rh, cmd) return when (r) { - is CR.NewChatItem -> r.chatItem + is CR.NewChatItems -> r.chatItems else -> { if (!(networkErrorAlert(r))) { apiErrorAlert("processSendMessageCmd", generalGetString(MR.strings.error_sending_message), r) @@ -863,13 +863,13 @@ object ChatController { } } } - suspend fun apiCreateChatItem(rh: Long?, noteFolderId: Long, file: CryptoFile? = null, mc: MsgContent): AChatItem? { - val cmd = CC.ApiCreateChatItem(noteFolderId, file, mc) + suspend fun apiCreateChatItems(rh: Long?, noteFolderId: Long, composedMessages: List): List? { + val cmd = CC.ApiCreateChatItems(noteFolderId, composedMessages) val r = sendCmd(rh, cmd) return when (r) { - is CR.NewChatItem -> r.chatItem + is CR.NewChatItems -> r.chatItems else -> { - apiErrorAlert("apiCreateChatItem", generalGetString(MR.strings.error_creating_message), r) + apiErrorAlert("apiCreateChatItems", generalGetString(MR.strings.error_creating_message), r) null } } @@ -885,9 +885,9 @@ object ChatController { } } - suspend fun apiForwardChatItem(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemId: Long, ttl: Int?): ChatItem? { - val cmd = CC.ApiForwardChatItem(toChatType, toChatId, fromChatType, fromChatId, itemId, ttl) - return processSendMessageCmd(rh, cmd)?.chatItem + suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemIds: List, ttl: Int?): List? { + val cmd = CC.ApiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl) + return processSendMessageCmd(rh, cmd)?.map { it.chatItem } } @@ -2132,27 +2132,30 @@ object ChatController { chatModel.networkStatuses[s.agentConnId] = s.networkStatus } } - is CR.NewChatItem -> withBGApi { - val cInfo = r.chatItem.chatInfo - val cItem = r.chatItem.chatItem - if (active(r.user)) { - withChats { - addChatItem(rhId, cInfo, cItem) + is CR.NewChatItems -> withBGApi { + r.chatItems.forEach { chatItem -> + val cInfo = chatItem.chatInfo + val cItem = chatItem.chatItem + if (active(r.user)) { + withChats { + addChatItem(rhId, cInfo, cItem) + } + } else if (cItem.isRcvNew && cInfo.ntfsEnabled) { + chatModel.increaseUnreadCounter(rhId, r.user) } - } else if (cItem.isRcvNew && cInfo.ntfsEnabled) { - chatModel.increaseUnreadCounter(rhId, r.user) - } - val file = cItem.file - val mc = cItem.content.msgContent - if (file != null && + val file = cItem.file + val mc = cItem.content.msgContent + if (file != null && appPrefs.privacyAcceptImages.get() && ((mc is MsgContent.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV) || (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV) - || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { - receiveFile(rhId, r.user, file.fileId, auto = true) - } - if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) { - ntfManager.notifyMessageReceived(r.user, cInfo, cItem) + || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted)) + ) { + receiveFile(rhId, r.user, file.fileId, auto = true) + } + if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) { + ntfManager.notifyMessageReceived(r.user, cInfo, cItem) + } } } is CR.ChatItemStatusUpdated -> { @@ -2863,13 +2866,13 @@ sealed class CC { class ApiGetChats(val userId: Long): CC() class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC() class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC() - class ApiSendMessage(val type: ChatType, val id: Long, val file: CryptoFile?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean, val ttl: Int?): CC() - class ApiCreateChatItem(val noteFolderId: Long, val file: CryptoFile?, val mc: MsgContent): CC() + class ApiSendMessages(val type: ChatType, val id: Long, val live: Boolean, val ttl: Int?, val composedMessages: List): CC() + class ApiCreateChatItems(val noteFolderId: Long, val composedMessages: List): CC() class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent, val live: Boolean): CC() class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC() class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC() class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() - class ApiForwardChatItem(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemId: Long, val ttl: Int?): CC() + class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC() class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC() class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC() class ApiJoinGroup(val groupId: Long): CC() @@ -3008,20 +3011,22 @@ sealed class CC { is ApiGetChats -> "/_get chats $userId pcc=on" is ApiGetChat -> "/_get chat ${chatRef(type, id)} ${pagination.cmdString}" + (if (search == "") "" else " search=$search") is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId" - is ApiSendMessage -> { + is ApiSendMessages -> { + val msgs = json.encodeToString(composedMessages) val ttlStr = if (ttl != null) "$ttl" else "default" - "/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}" + "/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json $msgs" } - is ApiCreateChatItem -> { - "/_create *$noteFolderId json ${json.encodeToString(ComposedMessage(file, null, mc))}" + is ApiCreateChatItems -> { + val msgs = json.encodeToString(composedMessages) + "/_create *$noteFolderId json $msgs" } is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}" is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}" is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}" is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}" - is ApiForwardChatItem -> { + is ApiForwardChatItems -> { val ttlStr = if (ttl != null) "$ttl" else "default" - "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} $itemId ttl=${ttlStr}" + "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}" } is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" @@ -3158,13 +3163,13 @@ sealed class CC { is ApiGetChats -> "apiGetChats" is ApiGetChat -> "apiGetChat" is ApiGetChatItemInfo -> "apiGetChatItemInfo" - is ApiSendMessage -> "apiSendMessage" - is ApiCreateChatItem -> "apiCreateChatItem" + is ApiSendMessages -> "apiSendMessages" + is ApiCreateChatItems -> "apiCreateChatItems" is ApiUpdateChatItem -> "apiUpdateChatItem" is ApiDeleteChatItem -> "apiDeleteChatItem" is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem" is ApiChatItemReaction -> "apiChatItemReaction" - is ApiForwardChatItem -> "apiForwardChatItem" + is ApiForwardChatItems -> "apiForwardChatItems" is ApiNewGroup -> "apiNewGroup" is ApiAddMember -> "apiAddMember" is ApiJoinGroup -> "apiJoinGroup" @@ -4790,7 +4795,7 @@ sealed class CR { @Serializable @SerialName("memberSubErrors") class MemberSubErrors(val user: UserRef, val memberSubErrors: List): CR() @Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR() @Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR() - @Serializable @SerialName("newChatItem") class NewChatItem(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemStatusUpdated") class ChatItemStatusUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @@ -4966,7 +4971,7 @@ sealed class CR { is MemberSubErrors -> "memberSubErrors" is GroupEmpty -> "groupEmpty" is UserContactLinkSubscribed -> "userContactLinkSubscribed" - is NewChatItem -> "newChatItem" + is NewChatItems -> "newChatItems" is ChatItemStatusUpdated -> "chatItemStatusUpdated" is ChatItemUpdated -> "chatItemUpdated" is ChatItemNotChanged -> "chatItemNotChanged" @@ -5134,7 +5139,7 @@ sealed class CR { is MemberSubErrors -> withUser(user, json.encodeToString(memberSubErrors)) is GroupEmpty -> withUser(user, json.encodeToString(group)) is UserContactLinkSubscribed -> noDetails() - is NewChatItem -> withUser(user, json.encodeToString(chatItem)) + is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemStatusUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) 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 372de02b41..821a449509 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 @@ -380,24 +380,28 @@ fun ComposeView( suspend fun send(chat: Chat, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? { val cInfo = chat.chatInfo - val aChatItem = if (chat.chatInfo.chatType == ChatType.Local) - chatModel.controller.apiCreateChatItem(rh = chat.remoteHostId, noteFolderId = chat.chatInfo.apiId, file = file, mc = mc) + val chatItems = if (chat.chatInfo.chatType == ChatType.Local) + chatModel.controller.apiCreateChatItems( + rh = chat.remoteHostId, + noteFolderId = chat.chatInfo.apiId, + composedMessages = listOf(ComposedMessage(file, null, mc)) + ) else - chatModel.controller.apiSendMessage( - rh = chat.remoteHostId, - type = cInfo.chatType, - id = cInfo.apiId, - file = file, - quotedItemId = quoted, - mc = mc, - live = live, - ttl = ttl - ) - if (aChatItem != null) { - withChats { - addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) + chatModel.controller.apiSendMessages( + rh = chat.remoteHostId, + type = cInfo.chatType, + id = cInfo.apiId, + live = live, + ttl = ttl, + composedMessages = listOf(ComposedMessage(file, quoted, mc)) + ) + if (!chatItems.isNullOrEmpty()) { + chatItems.forEach { aChatItem -> + withChats { + addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) + } } - return aChatItem.chatItem + return chatItems.first().chatItem } if (file != null) removeFile(file.filePath) return null @@ -414,21 +418,22 @@ fun ComposeView( } suspend fun forwardItem(rhId: Long?, forwardedItem: ChatItem, fromChatInfo: ChatInfo, ttl: Int?): ChatItem? { - val chatItem = controller.apiForwardChatItem( + val chatItems = controller.apiForwardChatItems( rh = rhId, toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, - itemId = forwardedItem.id, + itemIds = listOf(forwardedItem.id), ttl = ttl ) - if (chatItem != null) { + chatItems?.forEach { chatItem -> withChats { addChatItem(rhId, chat.chatInfo, chatItem) } } - return chatItem + // TODO batch send: forward multiple messages + return chatItems?.firstOrNull() } fun checkLinkPreview(): MsgContent { @@ -519,6 +524,7 @@ fun ComposeView( ComposePreview.NoPreview -> msgs.add(MsgContent.MCText(msgText)) is ComposePreview.CLinkPreview -> msgs.add(checkLinkPreview()) is ComposePreview.MediaPreview -> { + // TODO batch send: batch media previews preview.content.forEachIndexed { index, it -> val file = when (it) { is UploadContent.SimpleImage -> From 7b90e01b3a332b9ff6fb0f382ced69fe069198ac Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 23 Aug 2024 13:18:51 +0100 Subject: [PATCH 005/704] core: 6.1.0.0 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.yaml b/package.yaml index df23a563a2..3899ff4b68 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.2.0 +version: 6.1.0.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7acf07c497..72ece2f2d6 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.0.2.0 +version: 6.1.0.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 276baf56e8..527b87c010 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -72,11 +72,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 0, 0, 4] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 0] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 0, 0, 4] +minRemoteHostVersion = AppVersion [6, 1, 0, 0] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 04033fc0b5757d9da6618586f9c98dd378103909 Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 23 Aug 2024 13:20:07 +0100 Subject: [PATCH 006/704] ios: connection profile search, incognito info in selection list and improved loader (#4744) * remove comment * improve switching chat profile loader * add search on profile selection * disable auto correction * add incognito info in select chat profile * fix typos * layout * fix choosing hidden user * opacity back * Revert "layout" This reverts commit 10f1e5e9249237ac269d70dec5df79a97da98312. * remove padding * selected profile on top (profile or incognito) --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/NewChat/NewChatView.swift | 154 ++++++++++++------ .../Views/UserSettings/IncognitoHelp.swift | 1 + .../Views/UserSettings/UserProfilesView.swift | 7 + 3 files changed, 109 insertions(+), 53 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 9a52bfaa20..ca300d64cc 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -242,7 +242,6 @@ private struct InviteView: View { @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false @State private var showSettings: Bool = false - @State private var showIncognitoSheet = false var body: some View { List { @@ -282,9 +281,6 @@ private struct InviteView: View { } } } - .sheet(isPresented: $showIncognitoSheet) { - IncognitoHelp() - } .onChange(of: incognitoDefault) { incognito in setInvitationUsed() } @@ -344,16 +340,21 @@ private struct ActiveProfilePicker: View { @State private var switchingProfileByTimeout = false @State private var lastSwitchingProfileByTimeoutCall: Double? @State private var profiles: [User] = [] + @State private var searchTextOrPassword = "" + @State private var showIncognitoSheet = false + @State private var incognitoFirst: Bool = false @State var selectedProfile: User + var trimmedSearchTextOrPassword: String { searchTextOrPassword.trimmingCharacters(in: .whitespaces)} var body: some View { viewBody() .navigationTitle("Select chat profile") + .searchable(text: $searchTextOrPassword, placement: .navigationBarDrawer(displayMode: .always)) + .autocorrectionDisabled(true) .navigationBarTitleDisplayMode(.large) .onAppear { profiles = chatModel.users .map { $0.user } - .filter({ u in u.activeUser || !u.hidden }) .sorted { u, _ in u.activeUser } } .onChange(of: incognitoEnabled) { incognito in @@ -413,7 +414,7 @@ private struct ActiveProfilePicker: View { chatModel.updateContactConnection(conn) } do { - try await changeActiveUserAsync_(profile.userId, viewPwd: nil) + try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil ) await MainActor.run { switchingProfile = false dismiss() @@ -424,7 +425,7 @@ private struct ActiveProfilePicker: View { alert = SomeAlert( alert: Alert( title: Text("Error switching profile"), - message: Text("Your connection was moved to \(profile.chatViewName) but and unexpected error ocurred while redirecting you to the profile.") + message: Text("Your connection was moved to \(profile.chatViewName) but an unexpected error occurred while redirecting you to the profile.") ), id: "switchingProfileError" ) @@ -433,7 +434,6 @@ private struct ActiveProfilePicker: View { } } catch { await MainActor.run { - // TODO: discuss error handling switchingProfile = false if let currentUser = chatModel.currentUser { selectedProfile = currentUser @@ -454,71 +454,119 @@ private struct ActiveProfilePicker: View { a.alert } .onAppear { + incognitoFirst = incognitoEnabled choosingProfile = true } .onDisappear { choosingProfile = false } + .sheet(isPresented: $showIncognitoSheet) { + IncognitoHelp() + } } @ViewBuilder private func viewBody() -> some View { - NavigationView { - if switchingProfileByTimeout { - ProgressView("Switching profile…") + profilePicker() + .allowsHitTesting(!switchingProfileByTimeout) + .modifier(ThemedBackground(grouped: true)) + .overlay { + if switchingProfileByTimeout { + ProgressView() + .scaleEffect(2) .frame(maxWidth: .infinity, maxHeight: .infinity) - .modifier(ThemedBackground(grouped: true)) - } else { - profilePicker() - .modifier(ThemedBackground(grouped: true)) + } + } + } + + private func filteredProfiles() -> [User] { + let s = trimmedSearchTextOrPassword + let lower = s.localizedLowercase + + return profiles.filter { u in + if (u.activeUser || !u.hidden) && (s == "" || u.chatViewName.localizedLowercase.contains(lower)) { + return true + } + return correctPassword(u, s) + } + } + + @ViewBuilder private func profilerPickerUserOption(_ user: User) -> some View { + Button { + if selectedProfile != user || incognitoEnabled { + switchingProfile = true + incognitoEnabled = false + selectedProfile = user + } + } label: { + HStack { + ProfileImage(imageStr: user.image, size: 30) + .padding(.trailing, 2) + Text(user.chatViewName) + .foregroundColor(theme.colors.onBackground) + .lineLimit(1) + Spacer() + if selectedProfile == user, !incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) + } } } } @ViewBuilder private func profilePicker() -> some View { - List { - Button { - if !incognitoEnabled { - incognitoEnabled = true - switchingProfile = true - } - } label : { - HStack { - incognitoProfileImage() - Text("Incognito") - .foregroundColor(theme.colors.onBackground) - Spacer() - if incognitoEnabled { - Image(systemName: "checkmark") - .resizable().scaledToFit().frame(width: 16) - .foregroundColor(theme.colors.primary) - } - } + let incognitoOption = Button { + if !incognitoEnabled { + incognitoEnabled = true + switchingProfile = true } - ForEach(profiles) { item in - Button { - if selectedProfile != item || incognitoEnabled { - switchingProfile = true - incognitoEnabled = false - selectedProfile = item - } - } label: { - HStack { - ProfileImage(imageStr: item.image, size: 30) - .padding(.trailing, 2) - Text(item.chatViewName) - .foregroundColor(theme.colors.onBackground) - .lineLimit(1) - Spacer() - if selectedProfile == item, !incognitoEnabled { - Image(systemName: "checkmark") - .resizable().scaledToFit().frame(width: 16) - .foregroundColor(theme.colors.primary) - } + } label : { + HStack { + incognitoProfileImage() + Text("Incognito") + .foregroundColor(theme.colors.onBackground) + Image(systemName: "info.circle") + .foregroundColor(theme.colors.primary) + .font(.system(size: 14)) + .onTapGesture { + showIncognitoSheet = true } + Spacer() + if incognitoEnabled { + Image(systemName: "checkmark") + .resizable().scaledToFit().frame(width: 16) + .foregroundColor(theme.colors.primary) } } } + + List { + let filteredProfiles = filteredProfiles() + let activeProfile = filteredProfiles.first { u in u.activeUser } + + if let selectedProfile = activeProfile { + let otherProfiles = filteredProfiles.filter { u in u.userId != activeProfile?.userId } + + if incognitoFirst { + incognitoOption + profilerPickerUserOption(selectedProfile) + } else { + profilerPickerUserOption(selectedProfile) + incognitoOption + } + + ForEach(otherProfiles) { p in + profilerPickerUserOption(p) + } + } else { + incognitoOption + ForEach(filteredProfiles) { p in + profilerPickerUserOption(p) + } + } + } + .opacity(switchingProfileByTimeout ? 0.4 : 1) } } diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift index a0250afddf..d9862aaac8 100644 --- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift @@ -26,6 +26,7 @@ struct IncognitoHelp: View { Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).") } .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 160130bccc..06342db529 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -406,6 +406,13 @@ public func chatPasswordHash(_ pwd: String, _ salt: String) -> String { return hash } +public func correctPassword(_ user: User, _ pwd: String) -> Bool { + if let ph = user.viewPwdHash { + return pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash + } + return false +} + struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { UserProfilesView(showSettings: Binding.constant(true)) From 7b48c59f9f72637d32015e21cb1eeb53212da644 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 23 Aug 2024 14:32:16 +0100 Subject: [PATCH 007/704] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b59b1aeab3..4e46d80a5d 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ 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 */; }; - E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */; }; - E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5722C7691A2009F2C7C /* libgmp.a */; }; - E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */; }; - E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5742C7691A2009F2C7C /* libgmpxx.a */; }; - E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5752C7691A2009F2C7C /* libffi.a */; }; + E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57B2C78BF95009F2C7C /* libgmpxx.a */; }; + E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57C2C78BF95009F2C7C /* libgmp.a */; }; + E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */; }; + E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */; }; + E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57F2C78BF95009F2C7C /* libffi.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 */; }; @@ -550,11 +550,11 @@ 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 = ""; }; - E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a"; sourceTree = ""; }; - E51ED5722C7691A2009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a"; sourceTree = ""; }; - E51ED5742C7691A2009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5752C7691A2009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED57B2C78BF95009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED57C2C78BF95009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a"; sourceTree = ""; }; + E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a"; sourceTree = ""; }; + E51ED57F2C78BF95009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.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 = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */, + E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */, + E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */, + E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E51ED5772C7691A2009F2C7C /* libgmp.a in Frameworks */, - E51ED5762C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a in Frameworks */, - E51ED57A2C7691A2009F2C7C /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED5782C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a in Frameworks */, - E51ED5792C7691A2009F2C7C /* libgmpxx.a in Frameworks */, + E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5752C7691A2009F2C7C /* libffi.a */, - E51ED5722C7691A2009F2C7C /* libgmp.a */, - E51ED5742C7691A2009F2C7C /* libgmpxx.a */, - E51ED5732C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy-ghc9.6.3.a */, - E51ED5712C7691A2009F2C7C /* libHSsimplex-chat-6.0.2.0-CUdJmkNQ3mRF4AChEwYJvy.a */, + E51ED57F2C78BF95009F2C7C /* libffi.a */, + E51ED57C2C78BF95009F2C7C /* libgmp.a */, + E51ED57B2C78BF95009F2C7C /* libgmpxx.a */, + E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */, + E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */, ); path = Libraries; sourceTree = ""; From bcd50019be034ac62824cbe2200302cf4b11a0e8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:05:37 +0400 Subject: [PATCH 008/704] core: add more multi send api tests (#4750) --- src/Simplex/Chat.hs | 4 + src/Simplex/Chat/Messages.hs | 3 + src/Simplex/Chat/View.hs | 15 ++- tests/ChatTests/Direct.hs | 123 +++++++++++++++++++++-- tests/ChatTests/Files.hs | 163 ++++++++++++++++++++++++++++++ tests/ChatTests/Forward.hs | 187 +++++++++++++++++++++++++++++++++++ tests/ChatTests/Groups.hs | 105 +++++++++++++++++--- tests/ChatTests/Local.hs | 45 +++++++-- 8 files changed, 611 insertions(+), 34 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 54a7f1ef0b..dc3b4b2e54 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2906,6 +2906,7 @@ processChatCommand' vr = \case (msgContainers, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_ msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers let itemsData = prepareSndItemsData msgs_ cmrs ciFiles_ quotedItems_ + when (length itemsData /= length cmrs) $ logError "sendContactContentMessages: cmrs and itemsData length mismatch" (errs, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live unless (null errs) $ toView $ CRChatErrors (Just user) errs forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> @@ -2969,6 +2970,7 @@ processChatCommand' vr = \case (msgs_, gsr) <- sendGroupMessages user gInfo ms $ L.map XMsgNew msgContainers let itemsData = prepareSndItemsData (L.toList msgs_) cmrs ciFiles_ quotedItems_ cis_ <- saveSndChatItems user (CDGroupSnd gInfo) itemsData timed_ live + when (length itemsData /= length cmrs) $ logError "sendGroupContentMessages: cmrs and cis_ length mismatch" createMemberSndStatuses cis_ msgs_ gsr let (errs, cis) = partitionEithers cis_ unless (null errs) $ toView $ CRChatErrors (Just user) errs @@ -7038,6 +7040,7 @@ batchSendConnMessagesB _user conn msgFlags msgs_ = do delivered <- deliverMessagesB msgReqs let msgs' = concat $ L.zipWith flattenMsgs batched' delivered pqEnc = findLastPQEnc delivered + when (length msgs' /= length msgs_) $ logError "batchSendConnMessagesB: msgs_ and msgs' length mismatch" pure (msgs', pqEnc) Nothing -> pure ([], Nothing) where @@ -7190,6 +7193,7 @@ sendGroupMessages_ _user gInfo@GroupInfo {groupId} members events = do -- Save as pending for toPending members let (pendingMemIds, pendingReqs) = preparePending sndMsgs_ toPending stored <- lift $ withStoreBatch (\db -> map (bindRight $ createPendingMsg db) pendingReqs) + when (length stored /= length pendingMemIds) $ logError "sendGroupMessages_: pendingMemIds and stored length mismatch" -- Zip for easier access to results let sentTo = zipWith3 (\mId mReq r -> (mId, fmap (\(_, _, _, msgIds) -> msgIds) mReq, r)) sendToMemIds msgReqs delivered pending = zipWith3 (\mId pReq r -> (mId, fmap snd pReq, r)) pendingMemIds pendingReqs stored diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 78e3b4c640..f6c59cbcb5 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -336,6 +336,9 @@ aChatItemId (AChatItem _ _ _ ci) = chatItemId' ci aChatItemTs :: AChatItem -> UTCTime aChatItemTs (AChatItem _ _ _ ci) = chatItemTs' ci +aChatItemDir :: AChatItem -> MsgDirection +aChatItemDir (AChatItem _ sMsgDir _ _) = toMsgDirection sMsgDir + updateFileStatus :: forall c d. ChatItem c d -> CIFileStatus d -> ChatItem c d updateFileStatus ci@ChatItem {file} status = case file of Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index dd2bf94467..2158599c4b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} @@ -120,10 +121,16 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code] CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView - CRNewChatItems u chatItems -> - concatMap - (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) - chatItems + CRNewChatItems u chatItems + | length chatItems > 20 -> + if + | all (\aci -> aChatItemDir aci == MDRcv) chatItems -> ttyUser u [sShow (length chatItems) <> " new messages"] + | all (\aci -> aChatItemDir aci == MDSnd) chatItems -> ttyUser u [sShow (length chatItems) <> " messages sent"] + | otherwise -> ttyUser u [sShow (length chatItems) <> " new messages created"] + | otherwise -> + concatMap + (\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item) + chatItems CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 86c51f6aaa..9980b3b723 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -17,6 +17,7 @@ import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.List (intercalate) import qualified Data.Text as T +import Database.SQLite.Simple (Only (..)) import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call @@ -25,6 +26,7 @@ import Simplex.Chat.Options (ChatOpts (..)) import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat) +import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version @@ -52,7 +54,11 @@ chatDirectTests = do it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact it "should send multiline message" testMultilineMessage it "send large message" testLargeMessage + describe "batch send messages" $ do it "send multiple messages api" testSendMulti + it "send multiple timed messages" testSendMultiTimed + it "send multiple messages, including quote" testSendMultiWithQuote + it "send multiple messages (many chat batches)" testSendMultiManyBatches describe "duplicate contacts" $ do it "duplicate contacts are separate (contacts don't merge)" testDuplicateContactsSeparate it "new contact is separate with multiple duplicate contacts (contacts don't merge)" testDuplicateContactsMultipleSeparate @@ -716,22 +722,27 @@ testDirectMessageDeleteMultipleManyBatches = \alice bob -> do connectUsers alice bob - alice #> "@bob message 0" - bob <# "alice> message 0" - msgIdFirst <- lastItemId alice + msgIdZero <- lastItemId alice - forM_ [(1 :: Int) .. 300] $ \i -> do - alice #> ("@bob message " <> show i) - bob <# ("alice> message " <> show i) + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send @2 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" msgIdLast <- lastItemId alice - let mIdFirst = read msgIdFirst :: Int + forM_ [(1 :: Int) .. 300] $ \i -> do + bob <# ("alice> message " <> show i) + + let mIdFirst = (read msgIdZero :: Int) + 1 mIdLast = read msgIdLast :: Int deleteIds = intercalate "," (map show [mIdFirst .. mIdLast]) alice `send` ("/_delete item @2 " <> deleteIds <> " broadcast") _ <- getTermLine alice - alice <## "301 messages deleted" - forM_ [(0 :: Int) .. 300] $ \i -> do + alice <## "300 messages deleted" + forM_ [(1 :: Int) .. 300] $ \i -> do bob <# ("alice> [marked deleted] message " <> show i) testDirectLiveMessage :: HasCallStack => FilePath -> IO () @@ -852,6 +863,100 @@ testSendMulti = bob <# "alice> test 1" bob <# "alice> test 2" +testSendMultiTimed :: HasCallStack => FilePath -> IO () +testSendMultiTimed = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice ##> "/_send @2 ttl=1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "@bob test 1" + alice <# "@bob test 2" + bob <# "alice> test 1" + bob <# "alice> test 2" + + alice + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + bob + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + +testSendMultiWithQuote :: HasCallStack => FilePath -> IO () +testSendMultiWithQuote = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + alice #> "@bob hello" + bob <# "alice> hello" + msgId1 <- lastItemId alice + + threadDelay 1000000 + + bob #> "@alice hi" + alice <# "bob> hi" + msgId2 <- lastItemId alice + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message 1\"}}" + cm2 = "{\"quotedItemId\": " <> msgId1 <> ", \"msgContent\": {\"type\": \"text\", \"text\": \"message 2\"}}" + cm3 = "{\"quotedItemId\": " <> msgId2 <> ", \"msgContent\": {\"type\": \"text\", \"text\": \"message 3\"}}" + + alice ##> ("/_send @2 json [" <> cm1 <> ", " <> cm2 <> ", " <> cm3 <> "]") + alice <## "bad chat command: invalid multi send: live and more than one quote not supported" + + alice ##> ("/_send @2 json [" <> cm1 <> ", " <> cm2 <> "]") + + alice <# "@bob message 1" + alice <# "@bob >> hello" + alice <## " message 2" + + bob <# "alice> message 1" + bob <# "alice> >> hello" + bob <## " message 2" + + alice ##> ("/_send @2 json [" <> cm3 <> ", " <> cm1 <> "]") + + alice <# "@bob > hi" + alice <## " message 3" + alice <# "@bob message 1" + + bob <# "alice> > hi" + bob <## " message 3" + bob <# "alice> message 1" + +testSendMultiManyBatches :: HasCallStack => FilePath -> IO () +testSendMultiManyBatches = + testChat2 aliceProfile bobProfile $ + \alice bob -> do + connectUsers alice bob + + threadDelay 1000000 + + msgIdAlice <- lastItemId alice + msgIdBob <- lastItemId bob + + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send @2 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" + + forM_ [(1 :: Int) .. 300] $ \i -> + bob <# ("alice> message " <> show i) + + aliceItemsCount <- withCCTransaction alice $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdAlice) :: IO [[Int]] + aliceItemsCount `shouldBe` [[300]] + + bobItemsCount <- withCCTransaction bob $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdBob) :: IO [[Int]] + bobItemsCount `shouldBe` [[300]] + testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers = testChat2 aliceProfile bobProfile $ diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 32f3c02c29..c97193186b 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -36,6 +36,9 @@ chatFileTests = do it "send and receive image with text and quote" testSendImageWithTextAndQuote it "send and receive image to group" testGroupSendImage it "send and receive image with text and quote to group" testGroupSendImageWithTextAndQuote + describe "batch send messages with files" $ do + it "with files folder: send multiple files to contact" testSendMultiFilesDirect + it "with files folder: send multiple files to group" testSendMultiFilesGroup describe "file transfer over XFTP" $ do it "round file description count" $ const testXFTPRoundFDCount it "send and receive file" testXFTPFileTransfer @@ -406,6 +409,166 @@ testGroupSendImageWithTextAndQuote = cath #$> ("/_get chat #1 count=2", chat'', [((0, "hi team"), Nothing, Nothing), ((0, "hey bob"), Just (0, "hi team"), Just "./tests/tmp/test_1.jpg")]) cath @@@ [("#team", "hey bob"), ("@alice", "received invitation to join group team as admin")] +testSendMultiFilesDirect :: HasCallStack => FilePath -> IO () +testSendMultiFilesDirect = + testChat2 aliceProfile bobProfile $ \alice bob -> do + withXFTPServer $ do + connectUsers alice bob + + alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "@bob message without file" + + alice <# "@bob sending file 1" + alice <# "/f @bob test.jpg" + alice <## "use /fc 1 to cancel sending" + + alice <# "@bob sending file 2" + alice <# "/f @bob test.pdf" + alice <## "use /fc 2 to cancel sending" + + bob <# "alice> message without file" + + bob <# "alice> sending file 1" + bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [/ | ] to receive it" + + bob <# "alice> sending file 2" + bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 2 [/ | ] to receive it" + + alice <## "completed uploading file 1 (test.jpg) for bob" + alice <## "completed uploading file 2 (test.pdf) for bob" + + bob ##> "/fr 1" + bob + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + bob <## "completed receiving file 1 (test.jpg) from alice" + + bob ##> "/fr 2" + bob + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + bob <## "completed receiving file 2 (test.pdf) from alice" + + src1 <- B.readFile "./tests/tmp/alice_app_files/test.jpg" + dest1 <- B.readFile "./tests/tmp/bob_app_files/test.jpg" + dest1 `shouldBe` src1 + + src2 <- B.readFile "./tests/tmp/alice_app_files/test.pdf" + dest2 <- B.readFile "./tests/tmp/bob_app_files/test.pdf" + dest2 `shouldBe` src2 + + alice #$> ("/_get chat @2 count=3", chatF, [((1, "message without file"), Nothing), ((1, "sending file 1"), Just "test.jpg"), ((1, "sending file 2"), Just "test.pdf")]) + bob #$> ("/_get chat @2 count=3", chatF, [((0, "message without file"), Nothing), ((0, "sending file 1"), Just "test.jpg"), ((0, "sending file 2"), Just "test.pdf")]) + +testSendMultiFilesGroup :: HasCallStack => FilePath -> IO () +testSendMultiFilesGroup = + testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do + withXFTPServer $ do + createGroup3 "team" alice bob cath + + threadDelay 1000000 + + alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") + cath #$> ("/_files_folder ./tests/tmp/cath_app_files", id, "ok") + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_send #1 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "#team message without file" + + alice <# "#team sending file 1" + alice <# "/f #team test.jpg" + alice <## "use /fc 1 to cancel sending" + + alice <# "#team sending file 2" + alice <# "/f #team test.pdf" + alice <## "use /fc 2 to cancel sending" + + bob <# "#team alice> message without file" + + bob <# "#team alice> sending file 1" + bob <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [/ | ] to receive it" + + bob <# "#team alice> sending file 2" + bob <# "#team alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 2 [/ | ] to receive it" + + cath <# "#team alice> message without file" + + cath <# "#team alice> sending file 1" + cath <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + cath <## "use /fr 1 [/ | ] to receive it" + + cath <# "#team alice> sending file 2" + cath <# "#team alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + cath <## "use /fr 2 [/ | ] to receive it" + + alice <## "completed uploading file 1 (test.jpg) for #team" + alice <## "completed uploading file 2 (test.pdf) for #team" + + bob ##> "/fr 1" + bob + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + bob <## "completed receiving file 1 (test.jpg) from alice" + + bob ##> "/fr 2" + bob + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + bob <## "completed receiving file 2 (test.pdf) from alice" + + cath ##> "/fr 1" + cath + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + cath <## "completed receiving file 1 (test.jpg) from alice" + + cath ##> "/fr 2" + cath + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + cath <## "completed receiving file 2 (test.pdf) from alice" + + src1 <- B.readFile "./tests/tmp/alice_app_files/test.jpg" + dest1_1 <- B.readFile "./tests/tmp/bob_app_files/test.jpg" + dest1_2 <- B.readFile "./tests/tmp/cath_app_files/test.jpg" + dest1_1 `shouldBe` src1 + dest1_2 `shouldBe` src1 + + src2 <- B.readFile "./tests/tmp/alice_app_files/test.pdf" + dest2_1 <- B.readFile "./tests/tmp/bob_app_files/test.pdf" + dest2_2 <- B.readFile "./tests/tmp/cath_app_files/test.pdf" + dest2_1 `shouldBe` src2 + dest2_2 `shouldBe` src2 + + alice #$> ("/_get chat #1 count=3", chatF, [((1, "message without file"), Nothing), ((1, "sending file 1"), Just "test.jpg"), ((1, "sending file 2"), Just "test.pdf")]) + bob #$> ("/_get chat #1 count=3", chatF, [((0, "message without file"), Nothing), ((0, "sending file 1"), Just "test.jpg"), ((0, "sending file 2"), Just "test.pdf")]) + cath #$> ("/_get chat #1 count=3", chatF, [((0, "message without file"), Nothing), ((0, "sending file 1"), Just "test.jpg"), ((0, "sending file 2"), Just "test.pdf")]) + testXFTPRoundFDCount :: Expectation testXFTPRoundFDCount = do roundedFDCount (-100) `shouldBe` 4 diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index 221a2426b1..b339053abf 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -35,6 +35,8 @@ chatForwardTests = do it "with relative paths: from notes to group" testForwardFileNotesToGroup describe "multi forward api" $ do it "from contact to contact" testForwardContactToContactMulti + it "from group to group" testForwardGroupToGroupMulti + it "with relative paths: multiple files from contact to contact" testMultiForwardFiles testForwardContactToContact :: HasCallStack => FilePath -> IO () testForwardContactToContact = @@ -620,3 +622,188 @@ testForwardContactToContactMulti = cath <## " hi" cath <# "alice> -> forwarded" cath <## " hey" + +testForwardGroupToGroupMulti :: HasCallStack => FilePath -> IO () +testForwardGroupToGroupMulti = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup2 "team" alice bob + createGroup2 "club" alice cath + + threadDelay 1000000 + + alice #> "#team hi" + bob <# "#team alice> hi" + msgId1 <- lastItemId alice + + threadDelay 1000000 + + bob #> "#team hey" + alice <# "#team bob> hey" + msgId2 <- lastItemId alice + + alice ##> ("/_forward #2 #1 " <> msgId1 <> "," <> msgId2) + alice <# "#club <- you #team" + alice <## " hi" + alice <# "#club <- #team" + alice <## " hey" + cath <# "#club alice> -> forwarded" + cath <## " hi" + cath <# "#club alice> -> forwarded" + cath <## " hey" + + -- read chat + alice ##> "/tail #club 2" + alice <# "#club <- you #team" + alice <## " hi" + alice <# "#club <- #team" + alice <## " hey" + + cath ##> "/tail #club 2" + cath <# "#club alice> -> forwarded" + cath <## " hi" + cath <# "#club alice> -> forwarded" + cath <## " hey" + +testMultiForwardFiles :: HasCallStack => FilePath -> IO () +testMultiForwardFiles = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> withXFTPServer $ do + setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" + setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" + connectUsers alice bob + connectUsers bob cath + + threadDelay 1000000 + + msgIdZero <- lastItemId bob + + bob #> "@alice hi" + alice <# "bob> hi" + + -- send original files + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "@bob message without file" + + alice <# "@bob sending file 1" + alice <# "/f @bob test.jpg" + alice <## "use /fc 1 to cancel sending" + + alice <# "@bob sending file 2" + alice <# "/f @bob test.pdf" + alice <## "use /fc 2 to cancel sending" + + bob <# "alice> message without file" + + bob <# "alice> sending file 1" + bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 1 [/ | ] to receive it" + + bob <# "alice> sending file 2" + bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" + bob <## "use /fr 2 [/ | ] to receive it" + + alice <## "completed uploading file 1 (test.jpg) for bob" + alice <## "completed uploading file 2 (test.pdf) for bob" + + bob ##> "/fr 1" + bob + <### [ "saving file 1 from alice to test.jpg", + "started receiving file 1 (test.jpg) from alice" + ] + bob <## "completed receiving file 1 (test.jpg) from alice" + + bob ##> "/fr 2" + bob + <### [ "saving file 2 from alice to test.pdf", + "started receiving file 2 (test.pdf) from alice" + ] + bob <## "completed receiving file 2 (test.pdf) from alice" + + src1 <- B.readFile "./tests/tmp/alice_app_files/test.jpg" + dest1 <- B.readFile "./tests/tmp/bob_app_files/test.jpg" + dest1 `shouldBe` src1 + + src2 <- B.readFile "./tests/tmp/alice_app_files/test.pdf" + dest2 <- B.readFile "./tests/tmp/bob_app_files/test.pdf" + dest2 `shouldBe` src2 + + -- forward file + let msgId1 = (read msgIdZero :: Int) + 1 + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) + + -- messages printed for bob + bob <# "@cath <- you @alice" + bob <## " hi" + + bob <# "@cath <- @alice" + bob <## " message without file" + + bob <# "@cath <- @alice" + bob <## " sending file 1" + bob <# "/f @cath test_1.jpg" + bob <## "use /fc 3 to cancel sending" + + bob <# "@cath <- @alice" + bob <## " sending file 2" + bob <# "/f @cath test_1.pdf" + bob <## "use /fc 4 to cancel sending" + + -- messages printed for cath + cath <# "bob> -> forwarded" + cath <## " hi" + + cath <# "bob> -> forwarded" + cath <## " message without file" + + cath <# "bob> -> forwarded" + cath <## " sending file 1" + cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)" + cath <## "use /fr 1 [/ | ] to receive it" + + cath <# "bob> -> forwarded" + cath <## " sending file 2" + cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" + cath <## "use /fr 2 [/ | ] to receive it" + + -- file transfer + bob <## "completed uploading file 3 (test_1.jpg) for cath" + bob <## "completed uploading file 4 (test_1.pdf) for cath" + + cath ##> "/fr 1" + cath + <### [ "saving file 1 from bob to test_1.jpg", + "started receiving file 1 (test_1.jpg) from bob" + ] + cath <## "completed receiving file 1 (test_1.jpg) from bob" + + cath ##> "/fr 2" + cath + <### [ "saving file 2 from bob to test_1.pdf", + "started receiving file 2 (test_1.pdf) from bob" + ] + cath <## "completed receiving file 2 (test_1.pdf) from bob" + + src1B <- B.readFile "./tests/tmp/bob_app_files/test_1.jpg" + src1B `shouldBe` dest1 + dest1C <- B.readFile "./tests/tmp/cath_app_files/test_1.jpg" + dest1C `shouldBe` src1B + + src2B <- B.readFile "./tests/tmp/bob_app_files/test_1.pdf" + src2B `shouldBe` dest2 + dest2C <- B.readFile "./tests/tmp/cath_app_files/test_1.pdf" + dest2C `shouldBe` src2B + + -- deleting original file doesn't delete forwarded file + checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do + bob ##> "/clear alice" + bob <## "alice: all messages are removed locally ONLY" + fwdFileExists <- doesFileExist "./tests/tmp/bob_app_files/test_1.jpg" + fwdFileExists `shouldBe` True diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 1ff01a911c..d3e65ce5df 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -14,6 +14,7 @@ import Control.Monad (forM_, void, when) import qualified Data.ByteString.Char8 as B import Data.List (intercalate, isInfixOf) import qualified Data.Text as T +import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) @@ -64,7 +65,10 @@ chatGroupTests = do it "moderate message of another group member (full delete)" testGroupModerateFullDelete it "moderate message that arrives after the event of moderation" testGroupDelayedModeration it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete + describe "batch send messages" $ do it "send multiple messages api" testSendMulti + it "send multiple timed messages" testSendMultiTimed + it "send multiple messages (many chat batches)" testSendMultiManyBatches describe "async group connections" $ do xit "create and join group when clients go offline" testGroupAsync describe "group links" $ do @@ -1305,26 +1309,29 @@ testGroupMessageDeleteMultipleManyBatches = cath ##> "/set receipts all off" cath <## "ok" - alice #> "#team message 0" - concurrently_ - (bob <# "#team alice> message 0") - (cath <# "#team alice> message 0") - msgIdFirst <- lastItemId alice + msgIdZero <- lastItemId alice + + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send #1 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" forM_ [(1 :: Int) .. 300] $ \i -> do - alice #> ("#team message " <> show i) concurrently_ (bob <# ("#team alice> message " <> show i)) (cath <# ("#team alice> message " <> show i)) msgIdLast <- lastItemId alice - let mIdFirst = read msgIdFirst :: Int + let mIdFirst = (read msgIdZero :: Int) + 1 mIdLast = read msgIdLast :: Int deleteIds = intercalate "," (map show [mIdFirst .. mIdLast]) alice `send` ("/_delete item #1 " <> deleteIds <> " broadcast") _ <- getTermLine alice - alice <## "301 messages deleted" - forM_ [(0 :: Int) .. 300] $ \i -> + alice <## "300 messages deleted" + forM_ [(1 :: Int) .. 300] $ \i -> concurrently_ (bob <# ("#team alice> [marked deleted] message " <> show i)) (cath <# ("#team alice> [marked deleted] message " <> show i)) @@ -1821,15 +1828,89 @@ testGroupDelayedModerationFullDelete tmp = do testSendMulti :: HasCallStack => FilePath -> IO () testSendMulti = - testChat2 aliceProfile bobProfile $ - \alice bob -> do - createGroup2 "team" alice bob + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" alice <# "#team test 1" alice <# "#team test 2" bob <# "#team alice> test 1" bob <# "#team alice> test 2" + cath <# "#team alice> test 1" + cath <# "#team alice> test 2" + +testSendMultiTimed :: HasCallStack => FilePath -> IO () +testSendMultiTimed = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath + + alice ##> "/set disappear #team on 1" + alice <## "updated group preferences:" + alice <## "Disappearing messages: on (1 sec)" + bob <## "alice updated group #team:" + bob <## "updated group preferences:" + bob <## "Disappearing messages: on (1 sec)" + cath <## "alice updated group #team:" + cath <## "updated group preferences:" + cath <## "Disappearing messages: on (1 sec)" + + alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "#team test 1" + alice <# "#team test 2" + bob <# "#team alice> test 1" + bob <# "#team alice> test 2" + cath <# "#team alice> test 1" + cath <# "#team alice> test 2" + + alice + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + bob + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + cath + <### [ "timed message deleted: test 1", + "timed message deleted: test 2" + ] + +testSendMultiManyBatches :: HasCallStack => FilePath -> IO () +testSendMultiManyBatches = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath + + msgIdAlice <- lastItemId alice + msgIdBob <- lastItemId bob + msgIdCath <- lastItemId cath + + let cm i = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message " <> show i <> "\"}}" + cms = intercalate ", " (map cm [1 .. 300 :: Int]) + + alice `send` ("/_send #1 json [" <> cms <> "]") + _ <- getTermLine alice + + alice <## "300 messages sent" + + forM_ [(1 :: Int) .. 300] $ \i -> do + concurrently_ + (bob <# ("#team alice> message " <> show i)) + (cath <# ("#team alice> message " <> show i)) + + aliceItemsCount <- withCCTransaction alice $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdAlice) :: IO [[Int]] + aliceItemsCount `shouldBe` [[300]] + + bobItemsCount <- withCCTransaction bob $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdBob) :: IO [[Int]] + bobItemsCount `shouldBe` [[300]] + + cathItemsCount <- withCCTransaction cath $ \db -> + DB.query db "SELECT count(1) FROM chat_items WHERE chat_item_id > ?" (Only msgIdCath) :: IO [[Int]] + cathItemsCount `shouldBe` [[300]] testGroupAsync :: HasCallStack => FilePath -> IO () testGroupAsync tmp = do diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index f097edbc0c..da9c043648 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -17,12 +17,14 @@ chatLocalChatsTests :: SpecWith FilePath chatLocalChatsTests = do describe "note folders" $ do it "create folders, add notes, read, search" testNotes - it "create multiple messages api" testCreateMulti it "switch users" testUserNotes it "preview pagination for notes" testPreviewsPagination it "chat pagination" testChatPagination it "stores files" testFiles it "deleting files does not interfere with other chat types" testOtherFiles + describe "batch create messages" $ do + it "create multiple messages api" testCreateMulti + it "create multiple messages with files" testCreateMultiFiles testNotes :: FilePath -> IO () testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do @@ -53,14 +55,6 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/tail *" alice <# "* Greetings." -testCreateMulti :: FilePath -> IO () -testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do - createCCNoteFolder alice - - alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" - alice <# "* test 1" - alice <# "* test 2" - testUserNotes :: FilePath -> IO () testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do createCCNoteFolder alice @@ -197,3 +191,36 @@ testOtherFiles = doesFileExist "./tests/tmp/test.jpg" `shouldReturn` True where cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}} + +testCreateMulti :: FilePath -> IO () +testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do + createCCNoteFolder alice + + alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]" + alice <# "* test 1" + alice <# "* test 2" + +testCreateMultiFiles :: FilePath -> IO () +testCreateMultiFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do + createCCNoteFolder alice + alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + + let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" + alice ##> ("/_create *1 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + + alice <# "* message without file" + alice <# "* sending file 1" + alice <# "* file 1 (test.jpg)" + alice <# "* sending file 2" + alice <# "* file 2 (test.pdf)" + + doesFileExist "./tests/tmp/alice_app_files/test.jpg" `shouldReturn` True + doesFileExist "./tests/tmp/alice_app_files/test.pdf" `shouldReturn` True + + alice ##> "/_get chat *1 count=3" + r <- chatF <$> getTermLine alice + r `shouldBe` [((1, "message without file"), Nothing), ((1, "sending file 1"), Just "test.jpg"), ((1, "sending file 2"), Just "test.pdf")] From efe8ed17393109bcb2e799068455d0cdaeb31892 Mon Sep 17 00:00:00 2001 From: Diogo Date: Sat, 24 Aug 2024 14:59:50 +0100 Subject: [PATCH 009/704] ios: fix possible race between incognito set and profile change in conn profile picker (#4752) * ios: fix possible race between incognito set and profile change in conn profile picker * typo * fix swithcing incognito on same profile --- .../Shared/Views/NewChat/NewChatView.swift | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index ca300d64cc..c8cfc84230 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -327,6 +327,12 @@ private struct InviteView: View { } } +private enum ProfileSwitchStatus { + case switchingUser + case switchingIncognito + case idle +} + private struct ActiveProfilePicker: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var chatModel: ChatModel @@ -336,7 +342,7 @@ private struct ActiveProfilePicker: View { @Binding var incognitoEnabled: Bool @Binding var choosingProfile: Bool @State private var alert: SomeAlert? - @State private var switchingProfile = false + @State private var profileSwitchStatus: ProfileSwitchStatus = .idle @State private var switchingProfileByTimeout = false @State private var lastSwitchingProfileByTimeoutCall: Double? @State private var profiles: [User] = [] @@ -358,7 +364,7 @@ private struct ActiveProfilePicker: View { .sorted { u, _ in u.activeUser } } .onChange(of: incognitoEnabled) { incognito in - if !switchingProfile { + if profileSwitchStatus != .switchingIncognito { return } @@ -369,12 +375,12 @@ private struct ActiveProfilePicker: View { await MainActor.run { contactConnection = conn chatModel.updateContactConnection(conn) - switchingProfile = false + profileSwitchStatus = .idle dismiss() } } } catch { - switchingProfile = false + profileSwitchStatus = .idle incognitoEnabled = !incognito logger.error("apiSetConnectionIncognito error: \(responseError(error))") let err = getErrorAlert(error, "Error changing to incognito!") @@ -389,39 +395,39 @@ private struct ActiveProfilePicker: View { } } } - .onChange(of: switchingProfile) { sp in - if sp { + .onChange(of: profileSwitchStatus) { sp in + if sp != .idle { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - switchingProfileByTimeout = switchingProfile + switchingProfileByTimeout = profileSwitchStatus != .idle } } else { switchingProfileByTimeout = false } } .onChange(of: selectedProfile) { profile in - if (profile == chatModel.currentUser) { + if (profileSwitchStatus != .switchingUser) { return } Task { do { - switchingProfile = true if let contactConn = contactConnection, let conn = try await apiChangeConnectionUser(connId: contactConn.pccConnId, userId: profile.userId) { await MainActor.run { contactConnection = conn connReqInvitation = conn.connReqInv ?? "" + incognitoEnabled = false chatModel.updateContactConnection(conn) } do { try await changeActiveUserAsync_(profile.userId, viewPwd: profile.hidden ? trimmedSearchTextOrPassword : nil ) await MainActor.run { - switchingProfile = false + profileSwitchStatus = .idle dismiss() } } catch { await MainActor.run { - switchingProfile = false + profileSwitchStatus = .idle alert = SomeAlert( alert: Alert( title: Text("Error switching profile"), @@ -434,7 +440,7 @@ private struct ActiveProfilePicker: View { } } catch { await MainActor.run { - switchingProfile = false + profileSwitchStatus = .idle if let currentUser = chatModel.currentUser { selectedProfile = currentUser } @@ -493,10 +499,12 @@ private struct ActiveProfilePicker: View { @ViewBuilder private func profilerPickerUserOption(_ user: User) -> some View { Button { - if selectedProfile != user || incognitoEnabled { - switchingProfile = true + if selectedProfile == user && incognitoEnabled { incognitoEnabled = false + profileSwitchStatus = .switchingIncognito + } else if selectedProfile != user { selectedProfile = user + profileSwitchStatus = .switchingUser } } label: { HStack { @@ -519,7 +527,7 @@ private struct ActiveProfilePicker: View { let incognitoOption = Button { if !incognitoEnabled { incognitoEnabled = true - switchingProfile = true + profileSwitchStatus = .switchingIncognito } } label : { HStack { From c07df9e05ff3214261c04834e746901e8513b9a2 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:00:56 +0000 Subject: [PATCH 010/704] android: target API level 34 (Android 14) (#4697) --- apps/multiplatform/android/build.gradle.kts | 22 +++++----- .../android/src/main/AndroidManifest.xml | 14 +++++- .../main/java/chat/simplex/app/CallService.kt | 44 ++++++++++++++++--- .../java/chat/simplex/app/MainActivity.kt | 2 +- .../main/java/chat/simplex/app/SimplexApp.kt | 5 ++- .../java/chat/simplex/app/SimplexService.kt | 36 ++++++++++++--- .../simplex/app/model/NtfManager.android.kt | 4 +- .../simplex/app/views/call/CallActivity.kt | 10 ++--- .../android/src/main/res/values/colors.xml | 1 - apps/multiplatform/common/build.gradle.kts | 16 +++---- .../res/drawable/edit_text_cursor.xml | 2 +- 11 files changed, 112 insertions(+), 44 deletions(-) diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index 5c2c786a21..250616ea5c 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -15,7 +15,7 @@ android { namespace = "chat.simplex.app" minSdk = 26 //noinspection OldTargetApi - targetSdk = 33 + targetSdk = 34 // !!! // skip version code after release to F-Droid, as it uses two version codes versionCode = (extra["android.version_code"] as String).toInt() @@ -126,29 +126,29 @@ android { dependencies { implementation(project(":common")) - implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.core:core-ktx:1.13.1") //implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}") //implementation("androidx.compose.material:material:$compose_version") //implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") - implementation("androidx.lifecycle:lifecycle-process:2.7.0") - implementation("androidx.activity:activity-compose:1.8.2") - val workVersion = "2.9.0" + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") + implementation("androidx.lifecycle:lifecycle-process:2.8.4") + implementation("androidx.activity:activity-compose:1.9.1") + val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") implementation("androidx.work:work-multiprocess:$workVersion") - implementation("com.jakewharton:process-phoenix:2.2.0") + implementation("com.jakewharton:process-phoenix:3.0.0") //Camera Permission - implementation("com.google.accompanist:accompanist-permissions:0.23.0") + implementation("com.google.accompanist:accompanist-permissions:0.34.0") //implementation("androidx.compose.material:material-icons-extended:$compose_version") //implementation("androidx.compose.ui:ui-util:$compose_version") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") //androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version") debugImplementation("androidx.compose.ui:ui-tooling:1.6.4") } diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index 073f1bf8c8..deb5d83e5f 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -21,6 +21,12 @@ + + + + + + + android:stopWithTask="false" + android:foregroundServiceType="remoteMessaging" + /> @@ -141,7 +149,9 @@ android:name=".CallService" android:enabled="true" android:exported="false" - android:stopWithTask="false"/> + android:stopWithTask="false" + android:foregroundServiceType="mediaPlayback|microphone|camera|remoteMessaging" + /> = 34) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING + } else { + 0 + } + } else if (Build.VERSION.SDK_INT >= 30) { + if (call.supportsVideo()) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA + } else { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE + } + } else if (Build.VERSION.SDK_INT >= 29) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + } else { + 0 + } } private fun createNotificationChannel(): NotificationManager? { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 5a69d282b4..c63b6cb497 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -54,7 +54,7 @@ class MainActivity: FragmentActivity() { SimplexApp.context.schedulePeriodicWakeUp() } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) processIntent(intent) processExternalIntent(intent) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 9206b5be89..a8b91e261b 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -120,7 +120,10 @@ class SimplexApp: Application(), LifecycleEventObserver { * */ if (chatModel.chatRunning.value != false && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete && - appPrefs.notificationsMode.get() == NotificationsMode.SERVICE + appPrefs.notificationsMode.get() == NotificationsMode.SERVICE && + // New installation passes all checks above and tries to start the service which is not needed at all + // because preferred notification type is not yet chosen. So, check that the user has initialized db already + appPrefs.newDatabaseInitialized.get() ) { SimplexService.start() } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index a5f5d84ec2..004d2bc7f1 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.* import android.content.* import android.content.pm.PackageManager +import android.content.pm.ServiceInfo import android.net.Uri import android.os.* import android.os.SystemClock @@ -15,8 +16,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat import androidx.work.* +import chat.simplex.app.model.NtfManager import chat.simplex.common.AppLock import chat.simplex.common.helpers.requiresIgnoringBattery import chat.simplex.common.model.ChatController @@ -52,18 +55,15 @@ class SimplexService: Service() { } else { Log.d(TAG, "null intent. Probably restarted by the system.") } - startForeground(SIMPLEX_SERVICE_ID, serviceNotification) + ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType()) return START_STICKY // to restart if killed } override fun onCreate() { super.onCreate() Log.d(TAG, "Simplex service created") - val title = generalGetString(MR.strings.simplex_service_notification_title) - val text = generalGetString(MR.strings.simplex_service_notification_text) - notificationManager = createNotificationChannel() - serviceNotification = createNotification(title, text) - startForeground(SIMPLEX_SERVICE_ID, serviceNotification) + createNotificationIfNeeded() + ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType()) /** * The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and * we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown. @@ -103,6 +103,26 @@ class SimplexService: Service() { super.onDestroy() } + private fun createNotificationIfNeeded(): Notification { + val ntf = serviceNotification + if (ntf != null) return ntf + + val title = generalGetString(MR.strings.simplex_service_notification_title) + val text = generalGetString(MR.strings.simplex_service_notification_text) + notificationManager = createNotificationChannel() + val newNtf = createNotification(title, text) + serviceNotification = newNtf + return newNtf + } + + private fun foregroundServiceType(): Int { + return if (Build.VERSION.SDK_INT >= 34) { + ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING + } else { + 0 + } + } + private fun startService() { Log.d(TAG, "SimplexService startService") if (wakeLock != null || isCheckingNewMessages) return @@ -292,6 +312,10 @@ class SimplexService: Service() { } private suspend fun serviceAction(action: Action) { + if (!NtfManager.areNotificationsEnabledInSystem()) { + Log.d(TAG, "SimplexService serviceAction: ${action.name}. Notifications are not enabled in OS yet, not starting service") + return + } Log.d(TAG, "SimplexService serviceAction: ${action.name}") withContext(Dispatchers.IO) { Intent(androidAppContext, SimplexService::class.java).also { diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt index 417a81a953..cf19589d4a 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt @@ -53,7 +53,7 @@ object NtfManager { private val msgNtfTimeoutMs = 30000L init { - if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert() + if (areNotificationsEnabledInSystem()) createNtfChannelsMaybeShowAlert() } private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel { @@ -287,6 +287,8 @@ object NtfManager { } } + fun areNotificationsEnabledInSystem() = manager.areNotificationsEnabled() + /** * This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert, * The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index b78f3ac518..3d29737128 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -120,6 +120,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { return grantedAudio && grantedCamera } + @Deprecated("Was deprecated in OS") override fun onBackPressed() { if (isOnLockScreenNow()) { super.onBackPressed() @@ -139,6 +140,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { } override fun onUserLeaveHint() { + super.onUserLeaveHint() // On Android 12+ PiP is enabled automatically when a user hides the app if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) { enterPictureInPictureMode() @@ -248,6 +250,9 @@ fun CallActivityView() { ) if (permissionsState.allPermissionsGranted) { ActiveCallView() + LaunchedEffect(Unit) { + activity.startServiceAndBind() + } } else { CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) { withBGApi { chatModel.callManager.endCall(call) } @@ -285,11 +290,6 @@ fun CallActivityView() { AlertManager.shared.showInView() } } - LaunchedEffect(call == null) { - if (call != null) { - activity.startServiceAndBind() - } - } LaunchedEffect(invitation, call, switchingCall, showCallView) { if (!switchingCall && invitation == null && (!showCallView || call == null)) { Log.d(TAG, "CallActivityView: finishing activity") diff --git a/apps/multiplatform/android/src/main/res/values/colors.xml b/apps/multiplatform/android/src/main/res/values/colors.xml index e1a994e57f..1833a6d9a3 100644 --- a/apps/multiplatform/android/src/main/res/values/colors.xml +++ b/apps/multiplatform/android/src/main/res/values/colors.xml @@ -2,6 +2,5 @@ #FF000000 #FFFFFFFF - #8b8786 #121212 \ No newline at end of file diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 7e97ea3414..a1afb655e2 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -61,8 +61,8 @@ kotlin { val androidMain by getting { kotlin.srcDir("build/generated/moko/androidMain/src") dependencies { - implementation("androidx.activity:activity-compose:1.8.2") - val workVersion = "2.9.0" + implementation("androidx.activity:activity-compose:1.9.1") + val workVersion = "2.9.1" implementation("androidx.work:work-runtime-ktx:$workVersion") implementation("com.google.accompanist:accompanist-insets:0.30.1") @@ -78,22 +78,22 @@ kotlin { //Camera Permission implementation("com.google.accompanist:accompanist-permissions:0.34.0") - implementation("androidx.webkit:webkit:1.10.0") + implementation("androidx.webkit:webkit:1.11.0") // GIFs support implementation("io.coil-kt:coil-compose:2.6.0") implementation("io.coil-kt:coil-gif:2.6.0") - implementation("com.jakewharton:process-phoenix:2.2.0") + implementation("com.jakewharton:process-phoenix:3.0.0") - val cameraXVersion = "1.3.2" + val cameraXVersion = "1.3.4" implementation("androidx.camera:camera-core:${cameraXVersion}") implementation("androidx.camera:camera-camera2:${cameraXVersion}") implementation("androidx.camera:camera-lifecycle:${cameraXVersion}") implementation("androidx.camera:camera-view:${cameraXVersion}") // Calls lifecycle listener - implementation("androidx.lifecycle:lifecycle-process:2.4.1") + implementation("androidx.lifecycle:lifecycle-process:2.8.4") } } val desktopMain by getting { @@ -119,8 +119,8 @@ android { defaultConfig { minSdk = 26 } - testOptions.targetSdk = 33 - lint.targetSdk = 33 + testOptions.targetSdk = 34 + lint.targetSdk = 34 val isAndroid = gradle.startParameter.taskNames.find { val lower = it.lowercase() lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install") diff --git a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml index 683c3a4dd4..948ae4d4bf 100644 --- a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml +++ b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml @@ -1,5 +1,5 @@ - + From a1579810bb31f9b7bf8ae3a3a0920fcbb7fc75f1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 25 Aug 2024 14:31:26 +0100 Subject: [PATCH 011/704] ios: remove unnecessary protocols (#4763) --- apps/ios/SimpleXChat/APITypes.swift | 78 ++++++++++++++--------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 13661dc74a..d4998762d7 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1102,12 +1102,12 @@ public enum GroupLinkPlan: Decodable, Hashable { case known(groupInfo: GroupInfo) } -struct NewUser: Encodable, Hashable { +struct NewUser: Encodable { var profile: Profile? var pastTimestamp: Bool } -public enum ChatPagination: Hashable { +public enum ChatPagination { case last(count: Int) case after(chatItemId: Int64, count: Int) case before(chatItemId: Int64, count: Int) @@ -1329,7 +1329,7 @@ public struct ServerAddress: Decodable { ) } -public struct NetCfg: Codable, Equatable, Hashable { +public struct NetCfg: Codable, Equatable { public var socksProxy: String? = nil var socksMode: SocksMode = .always public var hostMode: HostMode = .publicHost @@ -1383,18 +1383,18 @@ public struct NetCfg: Codable, Equatable, Hashable { public var enableKeepAlive: Bool { tcpKeepAlive != nil } } -public enum HostMode: String, Codable, Hashable { +public enum HostMode: String, Codable { case onionViaSocks case onionHost = "onion" case publicHost = "public" } -public enum SocksMode: String, Codable, Hashable { +public enum SocksMode: String, Codable { case always = "always" case onion = "onion" } -public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { +public enum SMPProxyMode: String, Codable, SelectableItem { case always = "always" case unknown = "unknown" case unprotected = "unprotected" @@ -1414,7 +1414,7 @@ public enum SMPProxyMode: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyMode] = [.always, .unknown, .unprotected, .never] } -public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { +public enum SMPProxyFallback: String, Codable, SelectableItem { case allow = "allow" case allowProtected = "allowProtected" case prohibit = "prohibit" @@ -1432,7 +1432,7 @@ public enum SMPProxyFallback: String, Codable, Hashable, SelectableItem { public static let values: [SMPProxyFallback] = [.allow, .allowProtected, .prohibit] } -public enum OnionHosts: String, Identifiable, Hashable { +public enum OnionHosts: String, Identifiable { case no case prefer case require @@ -1466,7 +1466,7 @@ public enum OnionHosts: String, Identifiable, Hashable { public static let values: [OnionHosts] = [.no, .prefer, .require] } -public enum TransportSessionMode: String, Codable, Identifiable, Hashable { +public enum TransportSessionMode: String, Codable, Identifiable { case user case entity @@ -1482,7 +1482,7 @@ public enum TransportSessionMode: String, Codable, Identifiable, Hashable { public static let values: [TransportSessionMode] = [.user, .entity] } -public struct KeepAliveOpts: Codable, Equatable, Hashable { +public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds public var keepCnt: Int // times @@ -1490,7 +1490,7 @@ public struct KeepAliveOpts: Codable, Equatable, Hashable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } -public enum NetworkStatus: Decodable, Equatable, Hashable { +public enum NetworkStatus: Decodable, Equatable { case unknown case connected case disconnected @@ -1528,7 +1528,7 @@ public enum NetworkStatus: Decodable, Equatable, Hashable { } } -public struct ConnNetworkStatus: Decodable, Hashable { +public struct ConnNetworkStatus: Decodable { public var agentConnId: String public var networkStatus: NetworkStatus } @@ -1553,7 +1553,7 @@ public enum MsgFilter: String, Codable, Hashable { case mentions } -public struct UserMsgReceiptSettings: Codable, Hashable { +public struct UserMsgReceiptSettings: Codable { public var enable: Bool public var clearOverrides: Bool @@ -1602,7 +1602,7 @@ public enum SndSwitchStatus: String, Codable, Hashable { case sendingQTEST = "sending_qtest" } -public enum QueueDirection: String, Decodable, Hashable { +public enum QueueDirection: String, Decodable { case rcv case snd } @@ -1657,12 +1657,12 @@ public struct AutoAccept: Codable, Hashable { } } -public protocol SelectableItem: Hashable, Identifiable { +public protocol SelectableItem: Identifiable, Equatable { var label: LocalizedStringKey { get } static var values: [Self] { get } } -public struct DeviceToken: Decodable, Hashable { +public struct DeviceToken: Decodable { var pushProvider: PushProvider var token: String @@ -1676,12 +1676,12 @@ public struct DeviceToken: Decodable, Hashable { } } -public enum PushEnvironment: String, Hashable { +public enum PushEnvironment: String { case development case production } -public enum PushProvider: String, Decodable, Hashable { +public enum PushProvider: String, Decodable { case apns_dev case apns_prod @@ -1695,7 +1695,7 @@ public enum PushProvider: String, Decodable, Hashable { // This notification mode is for app core, UI uses AppNotificationsMode.off to mean completely disable, // and .local for periodic background checks -public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { +public enum NotificationsMode: String, Decodable, SelectableItem { case off = "OFF" case periodic = "PERIODIC" case instant = "INSTANT" @@ -1713,7 +1713,7 @@ public enum NotificationsMode: String, Decodable, SelectableItem, Hashable { public static var values: [NotificationsMode] = [.instant, .periodic, .off] } -public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { +public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact case message @@ -1731,7 +1731,7 @@ public enum NotificationPreviewMode: String, SelectableItem, Codable, Hashable { public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden] } -public struct RemoteCtrlInfo: Decodable, Hashable { +public struct RemoteCtrlInfo: Decodable { public var remoteCtrlId: Int64 public var ctrlDeviceName: String public var sessionState: RemoteCtrlSessionState? @@ -1741,7 +1741,7 @@ public struct RemoteCtrlInfo: Decodable, Hashable { } } -public enum RemoteCtrlSessionState: Decodable, Hashable { +public enum RemoteCtrlSessionState: Decodable { case starting case searching case connecting @@ -1756,17 +1756,17 @@ public enum RemoteCtrlStopReason: Decodable { case disconnected } -public struct CtrlAppInfo: Decodable, Hashable { +public struct CtrlAppInfo: Decodable { public var appVersionRange: AppVersionRange public var deviceName: String } -public struct AppVersionRange: Decodable, Hashable { +public struct AppVersionRange: Decodable { public var minVersion: String public var maxVersion: String } -public struct CoreVersionInfo: Decodable, Hashable { +public struct CoreVersionInfo: Decodable { public var version: String public var simplexmqVersion: String public var simplexmqCommit: String @@ -2104,14 +2104,14 @@ public enum RemoteCtrlError: Decodable, Hashable { case protocolError } -public struct MigrationFileLinkData: Codable, Hashable { +public struct MigrationFileLinkData: Codable { let networkConfig: NetworkConfig? public init(networkConfig: NetworkConfig) { self.networkConfig = networkConfig } - public struct NetworkConfig: Codable, Hashable { + public struct NetworkConfig: Codable { let socksProxy: String? let hostMode: HostMode? let requiredHostMode: Bool? @@ -2143,7 +2143,7 @@ public struct MigrationFileLinkData: Codable, Hashable { } } -public struct AppSettings: Codable, Equatable, Hashable { +public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil @@ -2238,7 +2238,7 @@ public struct AppSettings: Codable, Equatable, Hashable { } } -public enum AppSettingsNotificationMode: String, Codable, Hashable { +public enum AppSettingsNotificationMode: String, Codable { case off case periodic case instant @@ -2266,13 +2266,13 @@ public enum AppSettingsNotificationMode: String, Codable, Hashable { // case message //} -public enum AppSettingsLockScreenCalls: String, Codable, Hashable { +public enum AppSettingsLockScreenCalls: String, Codable { case disable case show case accept } -public struct UserNetworkInfo: Codable, Equatable, Hashable { +public struct UserNetworkInfo: Codable, Equatable { public let networkType: UserNetworkType public let online: Bool @@ -2282,7 +2282,7 @@ public struct UserNetworkInfo: Codable, Equatable, Hashable { } } -public enum UserNetworkType: String, Codable, Hashable { +public enum UserNetworkType: String, Codable { case none case cellular case wifi @@ -2300,7 +2300,7 @@ public enum UserNetworkType: String, Codable, Hashable { } } -public struct RcvMsgInfo: Codable, Hashable { +public struct RcvMsgInfo: Codable { var msgId: Int64 var msgDeliveryId: Int64 var msgDeliveryStatus: String @@ -2308,7 +2308,7 @@ public struct RcvMsgInfo: Codable, Hashable { var agentMsgMeta: String } -public struct ServerQueueInfo: Codable, Hashable { +public struct ServerQueueInfo: Codable { var server: String var rcvId: String var sndId: String @@ -2317,7 +2317,7 @@ public struct ServerQueueInfo: Codable, Hashable { var info: QueueInfo } -public struct QueueInfo: Codable, Hashable { +public struct QueueInfo: Codable { var qiSnd: Bool var qiNtf: Bool var qiSub: QSub? @@ -2325,25 +2325,25 @@ public struct QueueInfo: Codable, Hashable { var qiMsg: MsgInfo? } -public struct QSub: Codable, Hashable { +public struct QSub: Codable { var qSubThread: QSubThread var qDelivered: String? } -public enum QSubThread: String, Codable, Hashable { +public enum QSubThread: String, Codable { case noSub case subPending case subThread case prohibitSub } -public struct MsgInfo: Codable, Hashable { +public struct MsgInfo: Codable { var msgId: String var msgTs: Date var msgType: MsgType } -public enum MsgType: String, Codable, Hashable { +public enum MsgType: String, Codable { case message case quota } From ae850c8ce8367eb967618b7b0a1953f64178dd83 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 25 Aug 2024 18:00:46 +0100 Subject: [PATCH 012/704] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 89b8817fd4..9a34227f46 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ 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 */; }; - E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57B2C78BF95009F2C7C /* libgmpxx.a */; }; - E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57C2C78BF95009F2C7C /* libgmp.a */; }; - E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */; }; - E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */; }; - E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED57F2C78BF95009F2C7C /* libffi.a */; }; + E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED58F2C7B9983009F2C7C /* libgmpxx.a */; }; + E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */; }; + E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5912C7B9983009F2C7C /* libgmp.a */; }; + E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */; }; + E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5932C7B9983009F2C7C /* libffi.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 */; }; @@ -550,11 +550,11 @@ 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 = ""; }; - E51ED57B2C78BF95009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED57C2C78BF95009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a"; sourceTree = ""; }; - E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a"; sourceTree = ""; }; - E51ED57F2C78BF95009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E51ED58F2C7B9983009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a"; sourceTree = ""; }; + E51ED5912C7B9983009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a"; sourceTree = ""; }; + E51ED5932C7B9983009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.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 = ""; }; @@ -645,14 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED5802C78BF95009F2C7C /* libgmpxx.a in Frameworks */, - E51ED5812C78BF95009F2C7C /* libgmp.a in Frameworks */, - E51ED5822C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a in Frameworks */, - E51ED5842C78BF95009F2C7C /* libffi.a in Frameworks */, + E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */, + E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */, + E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */, + E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E51ED5832C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -729,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED57F2C78BF95009F2C7C /* libffi.a */, - E51ED57C2C78BF95009F2C7C /* libgmp.a */, - E51ED57B2C78BF95009F2C7C /* libgmpxx.a */, - E51ED57E2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA-ghc9.6.3.a */, - E51ED57D2C78BF95009F2C7C /* libHSsimplex-chat-6.1.0.0-29ba1DCA1ro2ZCUgGLJUlA.a */, + E51ED5932C7B9983009F2C7C /* libffi.a */, + E51ED5912C7B9983009F2C7C /* libgmp.a */, + E51ED58F2C7B9983009F2C7C /* libgmpxx.a */, + E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */, + E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */, ); path = Libraries; sourceTree = ""; From 0477b1aad37b939be89e171a98cc94b6c58286d3 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sun, 25 Aug 2024 21:21:24 +0300 Subject: [PATCH 013/704] ios: time based message grouping (#4743) * ios: time based message grouping * cleanup * hide timestamp * fix chat item not getting updated * round to minute * separate by minute * chat dir * time separation struct * add date logic * cleanup * fix groups * simplify timestamp logic; remove shape * cleanup * cleanup * refactor, add type --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 26 +++++--- .../Chat/ChatItem/CIGroupInvitationView.swift | 5 +- .../Views/Chat/ChatItem/CIMetaView.swift | 22 ++++--- .../Chat/ChatItem/CIRcvDecryptionError.swift | 5 +- .../Views/Chat/ChatItem/MsgContentView.swift | 3 +- apps/ios/Shared/Views/Chat/ChatItemView.swift | 12 ++++ apps/ios/Shared/Views/Chat/ChatView.swift | 64 +++++++++++++------ apps/ios/SimpleXChat/ChatTypes.swift | 9 ++- 8 files changed, 100 insertions(+), 46 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 0ac1a9cacb..07a0d19a55 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -123,6 +123,14 @@ class NetworkModel: ObservableObject { } } +/// ChatItemWithMenu can depend on previous or next item for it's appearance +/// This dummy model is used to force an update of all chat items, +/// when they might have changed appearance. +class ChatItemDummyModel: ObservableObject { + static let shared = ChatItemDummyModel() + func sendUpdate() { objectWillChange.send() } +} + final class ChatModel: ObservableObject { @Published var onboardingStage: OnboardingStage? @Published var setDeliveryReceipts = false @@ -428,19 +436,17 @@ final class ChatModel: ObservableObject { private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool { if let i = getChatItemIndex(cItem) { - withConditionalAnimation { - _updateChatItem(at: i, with: cItem) - } + _updateChatItem(at: i, with: cItem) + ChatItemDummyModel.shared.sendUpdate() return false } else { - withConditionalAnimation(itemAnimation()) { - var ci = cItem - if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { - ci.meta.itemStatus = status - } - im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) - im.itemAdded = true + var ci = cItem + if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus { + ci.meta.itemStatus = status } + im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0) + im.itemAdded = true + ChatItemDummyModel.shared.sendUpdate() return true } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index ef0fec5dfe..3c6da34ae5 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CIGroupInvitationView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool @ObservedObject var chat: Chat var chatItem: ChatItem var groupInvitation: CIGroupInvitation @@ -45,7 +46,7 @@ struct CIGroupInvitationView: View { .foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } @@ -53,7 +54,7 @@ struct CIGroupInvitationView: View { ( groupInvitationText() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 66b810cf2f..3f2ec8f38e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CIMetaView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool var chatItem: ChatItem var metaColor: Color var paleMetaColor = Color(UIColor.tertiaryLabel) @@ -30,24 +31,24 @@ struct CIMetaView: View { switch meta.itemStatus { case let .sndSent(sndProgress): switch sndProgress { - case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } case let .sndRcvd(_, sndProgress): switch sndProgress { case .complete: ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } case .partial: ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } default: - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy) + ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } } @@ -69,7 +70,8 @@ func ciMetaText( sent: SentCheckmark? = nil, showStatus: Bool = true, showEdited: Bool = true, - showViaProxy: Bool + showViaProxy: Bool, + showTimesamp: Bool ) -> Text { var r = Text("") if showEdited, meta.itemEdited { @@ -105,7 +107,9 @@ func ciMetaText( if let enc = encrypted { r = r + statusIconText(enc ? "lock" : "lock.open", color) + Text(" ") } - r = r + meta.timestampText.foregroundColor(color) + if showTimesamp { + r = r + meta.timestampText.foregroundColor(color) + } return r.font(.caption) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 1f2e16448d..915af3f479 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -15,6 +15,7 @@ struct CIRcvDecryptionError: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat + @Environment(\.showTimestamp) var showTimestamp: Bool var msgDecryptError: MsgDecryptError var msgCount: UInt32 var chatItem: ChatItem @@ -122,7 +123,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) } .padding(.horizontal, 12) @@ -142,7 +143,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(.red) .italic() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } .padding(.horizontal, 12) CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 999f99b294..9cc4179723 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -26,6 +26,7 @@ private func typing(_ w: Font.Weight = .light) -> Text { struct MsgContentView: View { @ObservedObject var chat: Chat + @Environment(\.showTimestamp) var showTimestamp: Bool @EnvironmentObject var theme: AppTheme var text: String var formattedText: [FormattedText]? = nil @@ -84,7 +85,7 @@ struct MsgContentView: View { } private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { - (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy) + (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 870fe30108..d444ce0735 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -9,9 +9,21 @@ import SwiftUI import SimpleXChat +extension EnvironmentValues { + struct ShowTimestamp: EnvironmentKey { + static let defaultValue: Bool = true + } + + var showTimestamp: Bool { + get { self[ShowTimestamp.self] } + set { self[ShowTimestamp.self] = newValue } + } +} + struct ChatItemView: View { @ObservedObject var chat: Chat @EnvironmentObject var theme: AppTheme + @Environment(\.showTimestamp) var showTimestamp: Bool var chatItem: ChatItem var maxWidth: CGFloat = .infinity @Binding var revealed: Bool diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 655dd8aaed..e9e86c31d7 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -696,6 +696,7 @@ struct ChatView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @Binding @ObservedObject var chat: Chat + @ObservedObject var dummyModel: ChatItemDummyModel = .shared let chatItem: ChatItem let maxWidth: CGFloat @Binding var composeState: ComposeState @@ -716,31 +717,50 @@ struct ChatView: View { var revealed: Bool { chatItem == revealedChatItem } + typealias ItemSeparation = (timestamp: Bool, largeGap: Bool) + + func getItemSeparation(_ chatItem: ChatItem, at i: Int?) -> ItemSeparation { + let im = ItemsModel.shared + if let i, i > 0 && im.reversedChatItems.count >= i { + let nextItem = im.reversedChatItems[i - 1] + let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.createdAt.timeIntervalSince(chatItem.meta.createdAt) > 60 + return ( + timestamp: largeGap || formatTimestampText(chatItem.meta.createdAt) != formatTimestampText(nextItem.meta.createdAt), + largeGap: largeGap + ) + } else { + return (timestamp: true, largeGap: true) + } + } + var body: some View { - let (currIndex, _) = m.getNextChatItem(chatItem) + let currIndex = m.getChatItemIndex(chatItem) let ciCategory = chatItem.mergeCategory let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory) let range = itemsRange(currIndex, prevHidden) + let timeSeparation = getItemSeparation(chatItem, at: currIndex) let im = ItemsModel.shared Group { if revealed, let range = range { let items = Array(zip(Array(range), im.reversedChatItems[range])) - ForEach(items.reversed(), id: \.1.viewId) { (i, ci) in - let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1] - chatItemView(ci, nil, prev) - .overlay { - if let selected = selectedChatItems, ci.canBeDeletedForSelf { - Color.clear - .contentShape(Rectangle()) - .onTapGesture { - let checked = selected.contains(ci.id) - selectUnselectChatItem(select: !checked, ci) + VStack(spacing: 0) { + ForEach(items.reversed(), id: \.1.viewId) { (i: Int, ci: ChatItem) in + let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1] + chatItemView(ci, nil, prev, getItemSeparation(ci, at: i)) + .overlay { + if let selected = selectedChatItems, ci.canBeDeletedForSelf { + Color.clear + .contentShape(Rectangle()) + .onTapGesture { + let checked = selected.contains(ci.id) + selectUnselectChatItem(select: !checked, ci) + } } - } + } } } } else { - chatItemView(chatItem, range, prevItem) + chatItemView(chatItem, range, prevItem, timeSeparation) .overlay { if let selected = selectedChatItems, chatItem.canBeDeletedForSelf { Color.clear @@ -791,7 +811,8 @@ struct ChatView: View { } } - @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?) -> some View { + @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?, _ itemSeparation: ItemSeparation) -> some View { + let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2 if case let .groupRcv(member) = ci.chatDir, case let .group(groupInfo) = chat.chatInfo { let (prevMember, memCount): (GroupMember?, Int) = @@ -833,11 +854,11 @@ struct ChatView: View { } } } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) } } } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) .padding(.trailing) .padding(.leading, 12) } else { @@ -846,11 +867,11 @@ struct ChatView: View { SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems) .padding(.leading, 12) } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.trailing) .padding(.leading, memberImageSize + 8 + 12) } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) } } else { HStack(alignment: .center, spacing: 0) { @@ -863,10 +884,10 @@ struct ChatView: View { .padding(.leading) } } - chatItemWithMenu(ci, range, maxWidth) + chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.horizontal) } - .padding(.bottom, 5) + .padding(.bottom, bottomPadding) } } @@ -881,7 +902,7 @@ struct ChatView: View { } } - @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat) -> some View { + @ViewBuilder func chatItemWithMenu(_ ci: ChatItem, _ range: ClosedRange?, _ maxWidth: CGFloat, _ itemSeparation: ItemSeparation) -> some View { let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading VStack(alignment: alignment.horizontal, spacing: 3) { ChatItemView( @@ -891,6 +912,7 @@ struct ChatView: View { revealed: .constant(revealed), allowMenu: $allowMenu ) + .environment(\.showTimestamp, itemSeparation.timestamp) .modifier(ChatItemClipped(ci)) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .accessibilityLabel("") diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 1a9cf4a216..d51e5a7cc3 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2688,6 +2688,13 @@ public enum CIDirection: Decodable, Hashable { } } } + + public func sameDirection(_ dir: CIDirection) -> Bool { + switch (self, dir) { + case let (.groupRcv(m1), .groupRcv(m2)): m1.groupMemberId == m2.groupMemberId + default: sent == dir.sent + } + } } public struct CIMeta: Decodable, Hashable { @@ -2762,7 +2769,7 @@ let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) public func formatTimestampText(_ date: Date) -> Text { - return Text(date, format: recent(date) ? msgTimeFormat : msgDateFormat) + Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) } private func recent(_ date: Date) -> Bool { From 043a4ed9159a89f7e12026348d2bed8a8ccceb24 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 27 Aug 2024 16:30:07 +0300 Subject: [PATCH 014/704] ios: add chat message tail and roundness settings; date separators (#4764) * ios: add chat message tail and roundness settings * cleanup * minor * rename * date separator * revert max roundness to pills * increase default roundness to 1 * minor * out of bounds tails, style date separator * formatting * hardcode tail growth * revert * different shape (WIP) * tail * rename * square * only show tail for the last message * remove func * capture less * variable tail height * export localizations --------- Co-authored-by: Evgeny Poberezkin --- .../Chat/ChatItem/CIGroupInvitationView.swift | 2 +- .../Chat/ChatItem/CIRcvDecryptionError.swift | 55 +++--- .../Views/Chat/ChatItem/FramedItemView.swift | 4 +- .../ChatItem/IntegrityErrorItemView.swift | 2 +- .../Chat/ChatItem/MarkedDeletedItemView.swift | 2 +- apps/ios/Shared/Views/Chat/ChatView.swift | 32 +++- .../Views/Helpers/ChatItemClipShape.swift | 174 ++++++++++++++---- .../UserSettings/AppearanceSettings.swift | 17 +- .../Views/UserSettings/SettingsView.swift | 6 + .../bg.xcloc/Localized Contents/bg.xliff | 40 ++++ .../cs.xcloc/Localized Contents/cs.xliff | 40 ++++ .../de.xcloc/Localized Contents/de.xliff | 40 ++++ .../en.xcloc/Localized Contents/en.xliff | 50 +++++ .../es.xcloc/Localized Contents/es.xliff | 40 ++++ .../fi.xcloc/Localized Contents/fi.xliff | 40 ++++ .../fr.xcloc/Localized Contents/fr.xliff | 40 ++++ .../hu.xcloc/Localized Contents/hu.xliff | 40 ++++ .../it.xcloc/Localized Contents/it.xliff | 40 ++++ .../ja.xcloc/Localized Contents/ja.xliff | 40 ++++ .../nl.xcloc/Localized Contents/nl.xliff | 40 ++++ .../pl.xcloc/Localized Contents/pl.xliff | 40 ++++ .../ru.xcloc/Localized Contents/ru.xliff | 40 ++++ .../th.xcloc/Localized Contents/th.xliff | 40 ++++ .../tr.xcloc/Localized Contents/tr.xliff | 40 ++++ .../uk.xcloc/Localized Contents/uk.xliff | 40 ++++ .../Localized Contents/zh-Hans.xliff | 40 ++++ 26 files changed, 909 insertions(+), 75 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index 3c6da34ae5..da859c1606 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -70,7 +70,7 @@ struct CIGroupInvitationView: View { } .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(chatItem, theme)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } .onChange(of: inProgress) { inProgress in diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 915af3f479..9f721f83b7 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -69,37 +69,40 @@ struct CIRcvDecryptionError: View { } @ViewBuilder private func viewBody() -> some View { - if case let .direct(contact) = chat.chatInfo, - let contactStats = contact.activeConn?.connectionStats { - if contactStats.ratchetSyncAllowed { - decryptionErrorItemFixButton(syncSupported: true) { - alert = .syncAllowedAlert { syncContactConnection(contact) } + Group { + if case let .direct(contact) = chat.chatInfo, + let contactStats = contact.activeConn?.connectionStats { + if contactStats.ratchetSyncAllowed { + decryptionErrorItemFixButton(syncSupported: true) { + alert = .syncAllowedAlert { syncContactConnection(contact) } + } + } else if !contactStats.ratchetSyncSupported { + decryptionErrorItemFixButton(syncSupported: false) { + alert = .syncNotSupportedContactAlert + } + } else { + basicDecryptionErrorItem() } - } else if !contactStats.ratchetSyncSupported { - decryptionErrorItemFixButton(syncSupported: false) { - alert = .syncNotSupportedContactAlert + } else if case let .group(groupInfo) = chat.chatInfo, + case let .groupRcv(groupMember) = chatItem.chatDir, + let mem = m.getGroupMember(groupMember.groupMemberId), + let memberStats = mem.wrapped.activeConn?.connectionStats { + if memberStats.ratchetSyncAllowed { + decryptionErrorItemFixButton(syncSupported: true) { + alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) } + } + } else if !memberStats.ratchetSyncSupported { + decryptionErrorItemFixButton(syncSupported: false) { + alert = .syncNotSupportedMemberAlert + } + } else { + basicDecryptionErrorItem() } } else { basicDecryptionErrorItem() } - } else if case let .group(groupInfo) = chat.chatInfo, - case let .groupRcv(groupMember) = chatItem.chatDir, - let mem = m.getGroupMember(groupMember.groupMemberId), - let memberStats = mem.wrapped.activeConn?.connectionStats { - if memberStats.ratchetSyncAllowed { - decryptionErrorItemFixButton(syncSupported: true) { - alert = .syncAllowedAlert { syncMemberConnection(groupInfo, groupMember) } - } - } else if !memberStats.ratchetSyncSupported { - decryptionErrorItemFixButton(syncSupported: false) { - alert = .syncNotSupportedMemberAlert - } - } else { - basicDecryptionErrorItem() - } - } else { - basicDecryptionErrorItem() } + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } } private func basicDecryptionErrorItem() -> some View { @@ -132,7 +135,6 @@ struct CIRcvDecryptionError: View { } .onTapGesture(perform: { onClick() }) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) } @@ -151,7 +153,6 @@ struct CIRcvDecryptionError: View { } .onTapGesture(perform: { onClick() }) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 313ec0d419..e70f891302 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -71,8 +71,8 @@ struct FramedItemView: View { .overlay(DetermineWidth()) .accessibilityLabel("") } - } - .background(chatItemFrameColorMaybeImageOrVideo(chatItem, theme)) + } + .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } if let (title, text) = chatItem.meta.itemStatus.statusInfo { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift index 822dda4d06..afeb88b05d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift @@ -69,7 +69,7 @@ struct CIMsgError: View { } .padding(.leading, 12) .padding(.vertical, 6) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) .onTapGesture(perform: onTap) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift index 25e06b9ea4..afd817357c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift @@ -22,7 +22,7 @@ struct MarkedDeletedItemView: View { .foregroundColor(theme.colors.secondary) .padding(.horizontal, 12) .padding(.vertical, 6) - .background(chatItemFrameColor(chatItem, theme)) + .background { chatItemFrameColor(chatItem, theme).modifier(ChatTailPadding()) } .textSelection(.disabled) } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e9e86c31d7..d94be2bb81 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -717,8 +717,8 @@ struct ChatView: View { var revealed: Bool { chatItem == revealedChatItem } - typealias ItemSeparation = (timestamp: Bool, largeGap: Bool) - + typealias ItemSeparation = (timestamp: Bool, largeGap: Bool, date: Date?) + func getItemSeparation(_ chatItem: ChatItem, at i: Int?) -> ItemSeparation { let im = ItemsModel.shared if let i, i > 0 && im.reversedChatItems.count >= i { @@ -726,10 +726,11 @@ struct ChatView: View { let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.createdAt.timeIntervalSince(chatItem.meta.createdAt) > 60 return ( timestamp: largeGap || formatTimestampText(chatItem.meta.createdAt) != formatTimestampText(nextItem.meta.createdAt), - largeGap: largeGap + largeGap: largeGap, + date: Calendar.current.isDate(chatItem.meta.createdAt, inSameDayAs: nextItem.meta.createdAt) ? nil : nextItem.meta.createdAt ) } else { - return (timestamp: true, largeGap: true) + return (timestamp: true, largeGap: true, date: nil) } } @@ -760,7 +761,20 @@ struct ChatView: View { } } } else { - chatItemView(chatItem, range, prevItem, timeSeparation) + VStack(spacing: 0) { + chatItemView(chatItem, range, prevItem, timeSeparation) + if let date = timeSeparation.date { + Text(String.localizedStringWithFormat( + NSLocalizedString("%@, %@", comment: "format for date separator in chat"), + date.formatted(.dateTime.weekday(.abbreviated)), + date.formatted(.dateTime.day().month(.abbreviated)) + )) + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .padding(8) + } + } .overlay { if let selected = selectedChatItems, chatItem.canBeDeletedForSelf { Color.clear @@ -834,14 +848,14 @@ struct ChatView: View { .foregroundStyle(.secondary) .lineLimit(2) .padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0)) - .padding(.top, 7) + .padding(.top, 3) // this is in addition to message sequence gap } HStack(alignment: .center, spacing: 0) { if selectedChatItems != nil && ci.canBeDeletedForSelf { SelectedChatItem(ciId: ci.id, selectedChatItems: $selectedChatItems) .padding(.trailing, 12) } - HStack(alignment: .top, spacing: 8) { + HStack(alignment: .top, spacing: 10) { MemberProfileImage(member, size: memberImageSize, backgroundColor: theme.colors.background) .onTapGesture { if let member = m.getGroupMember(member.groupMemberId) { @@ -869,7 +883,7 @@ struct ChatView: View { } chatItemWithMenu(ci, range, maxWidth, itemSeparation) .padding(.trailing) - .padding(.leading, memberImageSize + 8 + 12) + .padding(.leading, 10 + memberImageSize + 12) } .padding(.bottom, bottomPadding) } @@ -913,7 +927,7 @@ struct ChatView: View { allowMenu: $allowMenu ) .environment(\.showTimestamp, itemSeparation.timestamp) - .modifier(ChatItemClipped(ci)) + .modifier(ChatItemClipped(ci, tailVisible: itemSeparation.largeGap)) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } .accessibilityLabel("") if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 { diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index 477dc567eb..ddae6a5f6d 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -14,50 +14,60 @@ import SimpleXChat /// Supports [Dynamic Type](https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically) /// by retaining pill shape, even when ``ChatItem``'s height is less that twice its corner radius struct ChatItemClipped: ViewModifier { - struct ClipShape: Shape { - let maxCornerRadius: Double - - func path(in rect: CGRect) -> Path { - Path( - roundedRect: rect, - cornerRadius: min((rect.height / 2), maxCornerRadius), - style: .circular - ) - } - } - + @AppStorage(DEFAULT_CHAT_ITEM_ROUNDNESS) private var roundness = defaultChatItemRoundness + @AppStorage(DEFAULT_CHAT_ITEM_TAIL) private var tailEnabled = true + private let chatItem: (content: CIContent, chatDir: CIDirection)? + private let tailVisible: Bool + init() { - clipShape = ClipShape( - maxCornerRadius: 18 - ) + self.chatItem = nil + self.tailVisible = false + } + + init(_ ci: ChatItem, tailVisible: Bool) { + self.chatItem = (ci.content, ci.chatDir) + self.tailVisible = tailVisible } - init(_ chatItem: ChatItem) { - clipShape = ClipShape( - maxCornerRadius: { - switch chatItem.content { - case - .sndMsgContent, + private func shapeStyle() -> ChatItemShape.Style { + if let ci = chatItem { + switch ci.content { + case + .sndMsgContent, .rcvMsgContent, .rcvDecryptionError, .rcvGroupInvitation, .sndGroupInvitation, - .sndDeleted, + .sndDeleted, .rcvDeleted, .rcvIntegrityError, - .sndModerated, - .rcvModerated, + .sndModerated, + .rcvModerated, .rcvBlocked, - .invalidJSON: 18 - default: 8 + .invalidJSON: + let tail = if let mc = ci.content.msgContent, mc.isImageOrVideo && mc.text.isEmpty { + false + } else { + tailVisible } - }() - ) + return tailEnabled + ? .bubble( + padding: ci.chatDir.sent ? .trailing : .leading, + tailVisible: tail + ) + : .roundRect(radius: msgRectMaxRadius) + default: return .roundRect(radius: 8) + } + } else { + return.roundRect(radius: msgRectMaxRadius) + } } - - private let clipShape: ClipShape - + func body(content: Content) -> some View { + let clipShape = ChatItemShape( + roundness: roundness, + style: shapeStyle() + ) content .contentShape(.dragPreview, clipShape) .contentShape(.contextMenuPreview, clipShape) @@ -65,4 +75,106 @@ struct ChatItemClipped: ViewModifier { } } +struct ChatTailPadding: ViewModifier { + func body(content: Content) -> some View { + content.padding(.horizontal, -msgTailWidth) + } +} +private let msgRectMaxRadius: Double = 18 +private let msgBubbleMaxRadius: Double = msgRectMaxRadius * 1.2 +private let msgTailWidth: Double = 9 +private let msgTailMinHeight: Double = msgTailWidth * 1.254 // ~56deg +private let msgTailMaxHeight: Double = msgTailWidth * 1.732 // 60deg + +struct ChatItemShape: Shape { + fileprivate enum Style { + case bubble(padding: HorizontalEdge, tailVisible: Bool) + case roundRect(radius: Double) + } + + fileprivate let roundness: Double + fileprivate let style: Style + + func path(in rect: CGRect) -> Path { + switch style { + case let .bubble(padding, tailVisible): + let w = rect.width + let h = rect.height + let rxMax = min(msgBubbleMaxRadius, w / 2) + let ryMax = min(msgBubbleMaxRadius, h / 2) + let rx = roundness * rxMax + let ry = roundness * ryMax + let tailHeight = min(msgTailMinHeight + roundness * (msgTailMaxHeight - msgTailMinHeight), h / 2) + var path = Path() + // top side + path.move(to: CGPoint(x: rx, y: 0)) + path.addLine(to: CGPoint(x: w - rx, y: 0)) + if roundness > 0 { + // top-right corner + path.addQuadCurve(to: CGPoint(x: w, y: ry), control: CGPoint(x: w, y: 0)) + } + if rect.height > 2 * ry { + // right side + path.addLine(to: CGPoint(x: w, y: h - ry)) + } + if roundness > 0 { + // bottom-right corner + path.addQuadCurve(to: CGPoint(x: w - rx, y: h), control: CGPoint(x: w, y: h)) + } + // bottom side + if tailVisible { + path.addLine(to: CGPoint(x: -msgTailWidth, y: h)) + if roundness > 0 { + // bottom-left tail + // distance of control point from touch point, calculated via ratios + let d = tailHeight - msgTailWidth * msgTailWidth / tailHeight + // tail control point + let tc = CGPoint(x: 0, y: h - tailHeight + d * sqrt(roundness)) + // bottom-left tail curve + path.addQuadCurve(to: CGPoint(x: 0, y: h - tailHeight), control: tc) + } else { + path.addLine(to: CGPoint(x: 0, y: h - tailHeight)) + } + if rect.height > ry + tailHeight { + // left side + path.addLine(to: CGPoint(x: 0, y: ry)) + } + } else { + path.addLine(to: CGPoint(x: rx, y: h)) + path.addQuadCurve(to: CGPoint(x: 0, y: h - ry), control: CGPoint(x: 0 , y: h)) + if rect.height > 2 * ry { + // left side + path.addLine(to: CGPoint(x: 0, y: ry)) + } + } + if roundness > 0 { + // top-left corner + path.addQuadCurve(to: CGPoint(x: rx, y: 0), control: CGPoint(x: 0, y: 0)) + } + path.closeSubpath() + return switch padding { + case .leading: path + case .trailing: path + .scale(x: -1, y: 1, anchor: .center) + .path(in: rect) + } + case let .roundRect(radius): + return Path(roundedRect: rect, cornerRadius: radius * roundness) + } + } + + var offset: Double? { + switch style { + case let .bubble(padding, isTailVisible): + if isTailVisible { + switch padding { + case .leading: -msgTailWidth + case .trailing: msgTailWidth + } + } else { 0 } + case .roundRect: 0 + } + } + +} diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 73a789f108..70c33329b1 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -33,6 +33,8 @@ struct AppearanceSettings: View { }() @State private var darkModeTheme: String = UserDefaults.standard.string(forKey: DEFAULT_SYSTEM_DARK_THEME) ?? DefaultTheme.DARK.themeName @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileImageCornerRadius = defaultProfileImageCorner + @AppStorage(DEFAULT_CHAT_ITEM_ROUNDNESS) private var chatItemRoundness = defaultChatItemRoundness + @AppStorage(DEFAULT_CHAT_ITEM_TAIL) private var chatItemTail = true @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial @@ -179,6 +181,14 @@ struct AppearanceSettings: View { } } + Section(header: Text("Message shape").foregroundColor(theme.colors.secondary)) { + HStack { + Text("Corner") + Slider(value: $chatItemRoundness, in: 0...1, step: 0.05) + } + Toggle("Tail", isOn: $chatItemTail) + } + Section(header: Text("Profile images").foregroundColor(theme.colors.secondary)) { HStack(spacing: 16) { if let img = m.currentUser?.image, img != "" { @@ -358,20 +368,21 @@ struct ChatThemePreview: View { let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir)) HStack { ChatItemView(chat: Chat.sampleData, chatItem: alice, revealed: Binding.constant(false)) - .modifier(ChatItemClipped()) + .modifier(ChatItemClipped(alice, tailVisible: true)) Spacer() } HStack { Spacer() ChatItemView(chat: Chat.sampleData, chatItem: bob, revealed: Binding.constant(false)) - .modifier(ChatItemClipped()) + .modifier(ChatItemClipped(bob, tailVisible: true)) .frame(alignment: .trailing) } } else { Rectangle().fill(.clear) } } - .padding(10) + .padding(.vertical, 10) + .padding(.horizontal, 16) .frame(maxWidth: .infinity) if let wallpaperType, let wallpaperImage = wallpaperType.image, let backgroundColor, let tintColor { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index a4908f628f..d9c83803dd 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -47,6 +47,8 @@ let DEFAULT_ACCENT_COLOR_GREEN = "accentColorGreen" // deprecated, only used for let DEFAULT_ACCENT_COLOR_BLUE = "accentColorBlue" // deprecated, only used for migration let DEFAULT_USER_INTERFACE_STYLE = "userInterfaceStyle" // deprecated, only used for migration let DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" +let DEFAULT_CHAT_ITEM_ROUNDNESS = "chatItemRoundness" +let DEFAULT_CHAT_ITEM_TAIL = "chatItemTail" let DEFAULT_ONE_HAND_UI_CARD_SHOWN = "oneHandUICardShown" let DEFAULT_TOOLBAR_MATERIAL = "toolbarMaterial" let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab" @@ -75,6 +77,8 @@ let DEFAULT_THEME_OVERRIDES = "themeOverrides" let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" +let defaultChatItemRoundness: Double = 0.75 + let appDefaults: [String: Any] = [ DEFAULT_SHOW_LA_NOTICE: false, DEFAULT_LA_NOTICE_SHOWN: false, @@ -98,6 +102,8 @@ let appDefaults: [String: Any] = [ DEFAULT_DEVELOPER_TOOLS: false, DEFAULT_ENCRYPTION_STARTED: false, DEFAULT_PROFILE_IMAGE_CORNER_RADIUS: defaultProfileImageCorner, + DEFAULT_CHAT_ITEM_ROUNDNESS: defaultChatItemRoundness, + DEFAULT_CHAT_ITEM_TAIL: true, DEFAULT_ONE_HAND_UI_CARD_SHOWN: false, DEFAULT_TOOLBAR_MATERIAL: ToolbarMaterial.defaultMaterial, DEFAULT_CONNECT_VIA_LINK_TAB: ConnectViaLinkTab.scan.rawValue, 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 419f0ae864..89e628e2b6 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -137,6 +137,10 @@ %@ иска Га се ŃŠ²ŃŠŃ€Š¶Šµ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ Šø %lld членове @@ -1685,6 +1689,10 @@ This is your own one-time link! Š’ŠµŃ€ŃŠøŃ на ŃŠ“Ń€Š¾Ń‚Š¾: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? ŠŸŠ¾ŠæŃ€Š°Š²Šø име на %@? @@ -2645,6 +2653,10 @@ This is your own one-time link! Š“Ń€ŠµŃˆŠŗŠ° при ŠæŃ€Š¾Š¼ŃŠ½Š° на аГреса No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Š“Ń€ŠµŃˆŠŗŠ° при ŠæŃ€Š¾Š¼ŃŠ½Š° на Ń€Š¾Š»ŃŃ‚Š° @@ -2655,6 +2667,10 @@ This is your own one-time link! Š“Ń€ŠµŃˆŠŗŠ° при ŠæŃ€Š¾Š¼ŃŠ½Š° на настройката No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2870,6 +2886,10 @@ This is your own one-time link! Š“Ń€ŠµŃˆŠŗŠ° при спиране на чата No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Š“Ń€ŠµŃˆŠŗŠ° при ŃŠ¼ŃŠ½Š° на профил! @@ -4069,6 +4089,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Š˜Š·Ń‚Š¾Ń‡Š½ŠøŠŗŃŠŃ‚ на ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŠµŃ‚Š¾ остава скрит. @@ -5562,6 +5586,10 @@ Enable in *Network & servers* settings. Š˜Š·Š±ŠµŃ€Šø chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5911,6 +5939,10 @@ Enable in *Network & servers* settings. ДпоГели линк No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link ДпоГели този еГнократен линк за Š²Ń€ŃŠŠ·ŠŗŠ° @@ -6235,6 +6267,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture ŠŠ°ŠæŃ€Š°Š²Šø снимка @@ -7480,6 +7516,10 @@ Repeat connection request? Š’Š°ŃˆŠøŃ‚Šµ чат профили No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Š’Š°ŃˆŠøŃŃ‚ контакт изпрати файл, който е по-Š³Š¾Š»ŃŠ¼ от ŠæŠ¾Š“Š“ŃŠŃ€Š¶Š°Š½ŠøŃ в момента максимален размер (%@). 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 a02203e630..65bc83d096 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -135,6 +135,10 @@ %@ se chce připojit! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -1623,6 +1627,10 @@ This is your own one-time link! Verze jĆ”dra: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2552,6 +2560,10 @@ This is your own one-time link! Chuba změny adresy No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Chyba při změně role @@ -2562,6 +2574,10 @@ This is your own one-time link! Chyba změny nastavenĆ­ No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2772,6 +2788,10 @@ This is your own one-time link! Chyba při zastavenĆ­ chatu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Chyba při přepĆ­nĆ”nĆ­ profilu! @@ -3928,6 +3948,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5363,6 +5387,10 @@ Enable in *Network & servers* settings. Vybrat chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5708,6 +5736,10 @@ Enable in *Network & servers* settings. SdĆ­let odkaz No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -6024,6 +6056,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Vyfotit @@ -7209,6 +7245,10 @@ Repeat connection request? VaÅ”e chat profily No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kontakt odeslal soubor, který je větŔí než aktuĆ”lně podporovanĆ” maximĆ”lnĆ­ velikost (%@). 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 29adfbabf0..357b1c3c47 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -137,6 +137,10 @@ %@ will sich mit Ihnen verbinden! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ und %lld Mitglieder @@ -1740,6 +1744,10 @@ Das ist Ihr eigener Einmal-Link! Core Version: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Richtiger Name für %@? @@ -2724,6 +2732,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Wechseln der EmpfƤngeradresse No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Fehler beim Ƅndern der Rolle @@ -2734,6 +2746,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Ƅndern der Einstellung No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es spƤter erneut. @@ -2954,6 +2970,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Beenden des Chats No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Fehler beim Umschalten des Profils! @@ -4184,6 +4204,10 @@ Das ist Ihr Link für die Gruppe %@! Nachrichten-Server No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Die Nachrichtenquelle bleibt privat. @@ -5729,6 +5753,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. AuswƤhlen chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld ausgewƤhlt @@ -6099,6 +6127,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Link teilen No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Teilen Sie diesen Einmal-Einladungslink @@ -6439,6 +6471,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Machen Sie ein Foto @@ -7719,6 +7755,10 @@ Verbindungsanfrage wiederholen? Ihre Chat-Profile No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@). 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 c217793f03..006996c8d9 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -137,6 +137,11 @@ %@ wants to connect! notification title + + %1$@, %2$@ + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ and %lld members @@ -1740,6 +1745,11 @@ This is your own one-time link! Core version: v%@ No comment provided by engineer. + + Corner + Corner + No comment provided by engineer. + Correct name to %@? Correct name to %@? @@ -2724,6 +2734,11 @@ This is your own one-time link! Error changing address No comment provided by engineer. + + Error changing connection profile + Error changing connection profile + No comment provided by engineer. + Error changing role Error changing role @@ -2734,6 +2749,11 @@ This is your own one-time link! Error changing setting No comment provided by engineer. + + Error changing to incognito! + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Error connecting to forwarding server %@. Please try later. @@ -2954,6 +2974,11 @@ This is your own one-time link! Error stopping chat No comment provided by engineer. + + Error switching profile + Error switching profile + No comment provided by engineer. + Error switching profile! Error switching profile! @@ -4184,6 +4209,11 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + Message shape + No comment provided by engineer. + Message source remains private. Message source remains private. @@ -5729,6 +5759,11 @@ Enable in *Network & servers* settings. Select chat item action + + Select chat profile + Select chat profile + No comment provided by engineer. + Selected %lld Selected %lld @@ -6099,6 +6134,11 @@ Enable in *Network & servers* settings. Share link No comment provided by engineer. + + Share profile + Share profile + No comment provided by engineer. + Share this 1-time invite link Share this 1-time invite link @@ -6439,6 +6479,11 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + Tail + No comment provided by engineer. + Take picture Take picture @@ -7719,6 +7764,11 @@ Repeat connection request? Your chat profiles No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Your contact sent a file that is larger than currently supported maximum size (%@). 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 6e5ec0e85a..529b185e25 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -137,6 +137,10 @@ Ā” %@ quiere contactar! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ y %lld miembro(s) mĆ”s @@ -1740,6 +1744,10 @@ This is your own one-time link! Versión Core: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? ĀæCorregir el nombre a %@? @@ -2724,6 +2732,10 @@ This is your own one-time link! Error al cambiar servidor No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Error al cambiar rol @@ -2734,6 +2746,10 @@ This is your own one-time link! Error cambiando configuración No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Error al conectar con el servidor de reenvĆ­o %@. Por favor, intĆ©ntalo mĆ”s tarde. @@ -2954,6 +2970,10 @@ This is your own one-time link! Error al parar SimpleX No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Ā”Error al cambiar perfil! @@ -4184,6 +4204,10 @@ This is your link for group %@! Servidores de mensajes No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. El autor del mensaje se mantiene privado. @@ -5729,6 +5753,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Seleccionar chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Seleccionados %lld @@ -6099,6 +6127,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Compartir enlace No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Comparte este enlace de un solo uso @@ -6439,6 +6471,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Tomar foto @@ -7719,6 +7755,10 @@ Repeat connection request? Mis perfiles No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). El contacto ha enviado un archivo mayor al mĆ”ximo admitido (%@). 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 211e512a1e..fc31e4f2df 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -133,6 +133,10 @@ %@ haluaa muodostaa yhteyden! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -1616,6 +1620,10 @@ This is your own one-time link! Ydinversio: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2544,6 +2552,10 @@ This is your own one-time link! Virhe osoitteenvaihdossa No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Virhe roolin vaihdossa @@ -2554,6 +2566,10 @@ This is your own one-time link! Virhe asetuksen muuttamisessa No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2762,6 +2778,10 @@ This is your own one-time link! Virhe keskustelun lopettamisessa No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Virhe profiilin vaihdossa! @@ -3918,6 +3938,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5351,6 +5375,10 @@ Enable in *Network & servers* settings. Valitse chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5695,6 +5723,10 @@ Enable in *Network & servers* settings. Jaa linkki No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -6010,6 +6042,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Ota kuva @@ -7194,6 +7230,10 @@ Repeat connection request? Keskusteluprofiilisi No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Yhteyshenkilƶsi lƤhetti tiedoston, joka on suurempi kuin tƤllƤ hetkellƤ tuettu enimmƤiskoko (%@). 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 c05098980e..6970039315 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -137,6 +137,10 @@ %@ veut se connecter ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ et %lld membres @@ -1740,6 +1744,10 @@ Il s'agit de votre propre lien unique ! Version du cœur : v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Corriger le nom pour %@ ? @@ -2724,6 +2732,10 @@ Il s'agit de votre propre lien unique ! Erreur de changement d'adresse No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Erreur lors du changement de rĆ“le @@ -2734,6 +2746,10 @@ Il s'agit de votre propre lien unique ! Erreur de changement de paramĆØtre No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Erreur de connexion au serveur de redirection %@. Veuillez rĆ©essayer plus tard. @@ -2954,6 +2970,10 @@ Il s'agit de votre propre lien unique ! Erreur lors de l'arrĆŖt du chat No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Erreur lors du changement de profil ! @@ -4184,6 +4204,10 @@ Voici votre lien pour le groupe %@ ! Serveurs de messages No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. La source du message reste privĆ©e. @@ -5729,6 +5753,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Choisir chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld sĆ©lectionnĆ©(s) @@ -6099,6 +6127,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Partager le lien No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Partager ce lien d'invitation unique @@ -6439,6 +6471,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Prendre une photo @@ -7719,6 +7755,10 @@ RĆ©pĆ©ter la demande de connexion ? Vos profils de chat No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Votre contact a envoyĆ© un fichier plus grand que la taille maximale supportĆ©e actuellement(%@). 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 f7328eed91..de95de3421 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -137,6 +137,10 @@ %@ kapcsolódni szeretne! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ Ć©s tovĆ”bbi %lld tag @@ -1740,6 +1744,10 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! AlapverziószĆ”m: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? NĆ©v javĆ­tĆ”sa erre: %@? @@ -2724,6 +2732,10 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Hiba a cĆ­m megvĆ”ltoztatĆ”sakor No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Hiba a szerepkƶr megvĆ”ltoztatĆ”sakor @@ -2734,6 +2746,10 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Hiba a beĆ”llĆ­tĆ”s megvĆ”ltoztatĆ”sakor No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Hiba a(z) %@ tovĆ”bbĆ­tó kiszolgĆ”lóhoz való kapcsolódĆ”skor. PróbĆ”lja meg kĆ©sőbb. @@ -2954,6 +2970,10 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Hiba a csevegĆ©s megĆ”llĆ­tĆ”sakor No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Hiba a profil vĆ”ltĆ”sakor! @@ -4184,6 +4204,10 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! ÜzenetkiszolgĆ”lók No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Az üzenet forrĆ”sa titokban marad. @@ -5729,6 +5753,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. VĆ”lasztĆ”s chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld kivĆ”lasztva @@ -6099,6 +6127,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. HivatkozĆ”s megosztĆ”sa No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s megosztĆ”sa @@ -6439,6 +6471,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture KĆ©p kĆ©szĆ­tĆ©se @@ -7719,6 +7755,10 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? CsevegĆ©si profilok No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Ismerőse olyan fĆ”jlt küldƶtt, amely meghaladja a jelenleg tĆ”mogatott maximĆ”lis mĆ©retet (%@). 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 72eb3561e3..700d181aab 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -137,6 +137,10 @@ %@ si vuole connettere! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ e %lld membri @@ -1740,6 +1744,10 @@ Questo ĆØ il tuo link una tantum! Versione core: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Correggere il nome a %@? @@ -2724,6 +2732,10 @@ Questo ĆØ il tuo link una tantum! Errore nella modifica dell'indirizzo No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Errore nel cambio di ruolo @@ -2734,6 +2746,10 @@ Questo ĆØ il tuo link una tantum! Errore nella modifica dell'impostazione No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Errore di connessione al server di inoltro %@. Riprova più tardi. @@ -2954,6 +2970,10 @@ Questo ĆØ il tuo link una tantum! Errore nell'interruzione della chat No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Errore nel cambio di profilo! @@ -4184,6 +4204,10 @@ Questo ĆØ il tuo link per il gruppo %@! Server dei messaggi No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. La fonte del messaggio resta privata. @@ -5729,6 +5753,10 @@ Attivalo nelle impostazioni *Rete e server*. Seleziona chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld selezionato @@ -6099,6 +6127,10 @@ Attivalo nelle impostazioni *Rete e server*. Condividi link No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Condividi questo link di invito una tantum @@ -6439,6 +6471,10 @@ Attivalo nelle impostazioni *Rete e server*. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Scatta foto @@ -7719,6 +7755,10 @@ Ripetere la richiesta di connessione? I tuoi profili di chat No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@). 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 a545f3ba05..0b9b431c8b 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -137,6 +137,10 @@ %@ ćŒęŽ„ē¶šć‚’åøŒęœ›ć—ć¦ć„ć¾ć™! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@悄%@など%lld人のピンバー @@ -1640,6 +1644,10 @@ This is your own one-time link! ć‚³ć‚¢ć®ćƒćƒ¼ć‚øćƒ§ćƒ³: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2569,6 +2577,10 @@ This is your own one-time link! ć‚¢ćƒ‰ćƒ¬ć‚¹å¤‰ę›“ć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿ No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role å½¹å‰²å¤‰ę›“ć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿ @@ -2579,6 +2591,10 @@ This is your own one-time link! čØ­å®šå¤‰ę›“ć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿ No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2787,6 +2803,10 @@ This is your own one-time link! ćƒćƒ£ćƒƒćƒˆåœę­¢ć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿ No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«åˆ‡ć‚Šę›æćˆć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿļ¼ @@ -3942,6 +3962,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5376,6 +5400,10 @@ Enable in *Network & servers* settings. éøęŠž chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5713,6 +5741,10 @@ Enable in *Network & servers* settings. ćƒŖćƒ³ć‚Æć‚’é€ć‚‹ No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -6029,6 +6061,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture å†™ēœŸć‚’ę’®å½± @@ -7212,6 +7248,10 @@ Repeat connection request? ć‚ćŖćŸć®ćƒćƒ£ćƒƒćƒˆćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ« No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). é€£ēµ”å…ˆćŒē¾åœØć‚µćƒćƒ¼ćƒˆć•ć‚Œć¦ć„ć‚‹ęœ€å¤§ć‚µć‚¤ć‚ŗ (%@) ć‚ˆć‚Šå¤§ćć„ćƒ•ć‚”ć‚¤ćƒ«ć‚’é€äæ”ć—ć¾ć—ćŸć€‚ 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 15a8c01a64..cd52825882 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -137,6 +137,10 @@ %@ wil verbinding maken! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ en %lld leden @@ -1740,6 +1744,10 @@ Dit is uw eigen eenmalige link! Core versie: v% @ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Juiste naam voor %@? @@ -2724,6 +2732,10 @@ Dit is uw eigen eenmalige link! Fout bij wijzigen van adres No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Fout bij wisselen van rol @@ -2734,6 +2746,10 @@ Dit is uw eigen eenmalige link! Fout bij wijzigen van instelling No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw. @@ -2954,6 +2970,10 @@ Dit is uw eigen eenmalige link! Fout bij het stoppen van de chat No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Fout bij wisselen van profiel! @@ -4184,6 +4204,10 @@ Dit is jouw link voor groep %@! Berichtservers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Berichtbron blijft privĆ©. @@ -5729,6 +5753,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Selecteer chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld %lld geselecteerd @@ -6099,6 +6127,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Deel link No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Deel deze eenmalige uitnodigingslink @@ -6439,6 +6471,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Foto nemen @@ -7719,6 +7755,10 @@ Verbindingsverzoek herhalen? Uw chat profielen No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@). 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 525d30daa6..a474f30768 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -137,6 +137,10 @@ %@ chce się połączyć! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ i %lld członków @@ -1740,6 +1744,10 @@ To jest twój jednorazowy link! Wersja rdzenia: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Poprawić imię na %@? @@ -2724,6 +2732,10 @@ To jest twój jednorazowy link! Błąd zmiany adresu No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Błąd zmiany roli @@ -2734,6 +2746,10 @@ To jest twój jednorazowy link! Błąd zmiany ustawienia No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później. @@ -2954,6 +2970,10 @@ To jest twój jednorazowy link! Błąd zatrzymania czatu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Błąd przełączania profilu! @@ -4184,6 +4204,10 @@ To jest twój link do grupy %@! Serwery wiadomości No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Źródło wiadomości pozostaje prywatne. @@ -5729,6 +5753,10 @@ Włącz w ustawianiach *Sieć i serwery* . Wybierz chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Zaznaczono %lld @@ -6099,6 +6127,10 @@ Włącz w ustawianiach *Sieć i serwery* . Udostępnij link No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Udostępnij ten jednorazowy link @@ -6439,6 +6471,10 @@ Włącz w ustawianiach *Sieć i serwery* . TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Zrób zdjęcie @@ -7719,6 +7755,10 @@ Powtórzyć prośbę połączenia? Twoje profile czatu No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@). 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 969a7d68e0..957e599e85 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -137,6 +137,10 @@ %@ хочет ŃŠ¾ŠµŠ“ŠøŠ½ŠøŃ‚ŃŒŃŃ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ Šø %lld членов Š³Ń€ŃƒŠæŠæŃ‹ @@ -1740,6 +1744,10 @@ This is your own one-time link! Š’ŠµŃ€ŃŠøŃ ŃŠ“Ń€Š°: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Š˜ŃŠæŃ€Š°Š²ŠøŃ‚ŃŒ ŠøŠ¼Ń на %@? @@ -2724,6 +2732,10 @@ This is your own one-time link! ŠžŃˆŠøŠ±ŠŗŠ° при изменении аГреса No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role ŠžŃˆŠøŠ±ŠŗŠ° при изменении роли @@ -2734,6 +2746,10 @@ This is your own one-time link! ŠžŃˆŠøŠ±ŠŗŠ° при изменении настройки No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. ŠžŃˆŠøŠ±ŠŗŠ° ŠæŠ¾Š“ŠŗŠ»ŃŽŃ‡ŠµŠ½ŠøŃ Šŗ ŠæŠµŃ€ŠµŃŃ‹Š»Š°ŃŽŃ‰ŠµŠ¼Ńƒ ŃŠµŃ€Š²ŠµŃ€Ńƒ %@. ŠŸŠ¾ŠæŃ€Š¾Š±ŃƒŠ¹Ń‚Šµ позже. @@ -2954,6 +2970,10 @@ This is your own one-time link! ŠžŃˆŠøŠ±ŠŗŠ° при остановке чата No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! ŠžŃˆŠøŠ±ŠŗŠ° выбора ŠæŃ€Š¾Ń„ŠøŠ»Ń! @@ -4184,6 +4204,10 @@ This is your link for group %@! Дерверы сообщений No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Š˜ŃŃ‚Š¾Ń‡Š½ŠøŠŗ ŃŠ¾Š¾Š±Ń‰ŠµŠ½ŠøŃ Š¾ŃŃ‚Š°Ń‘Ń‚ŃŃ ŠŗŠ¾Š½Ń„ŠøŠ“ŠµŠ½Ń†ŠøŠ°Š»ŃŒŠ½Ń‹Š¼. @@ -5729,6 +5753,10 @@ Enable in *Network & servers* settings. Š’Ń‹Š±Ń€Š°Ń‚ŃŒ chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Выбрано %lld @@ -6099,6 +6127,10 @@ Enable in *Network & servers* settings. ŠŸŠ¾Š“ŠµŠ»ŠøŃ‚ŃŒŃŃ ссылкой No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link ŠŸŠ¾Š“ŠµŠ»ŠøŃ‚ŃŒŃŃ оГноразовой ссылкой-ŠæŃ€ŠøŠ³Š»Š°ŃˆŠµŠ½ŠøŠµŠ¼ @@ -6439,6 +6471,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Š”Š“ŠµŠ»Š°Ń‚ŃŒ фото @@ -7719,6 +7755,10 @@ Repeat connection request? Š’Š°ŃˆŠø профили чата No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Š’Š°Ńˆ контакт отправил файл, размер которого ŠæŃ€ŠµŠ²Ń‹ŃˆŠ°ŠµŃ‚ Š¼Š°ŠŗŃŠøŠ¼Š°Š»ŃŒŠ½Ń‹Š¹ размер (%@). 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 646a94a337..366f67d0cd 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -129,6 +129,10 @@ %@ ąø­ąø¢ąø²ąøą¹€ąøŠąø·ą¹ˆąø­ąø”ąø•ą¹ˆąø­! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members No comment provided by engineer. @@ -1606,6 +1610,10 @@ This is your own one-time link! ąø£ąøøą¹ˆąø™ąø«ąø„ąø±ąø: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2530,6 +2538,10 @@ This is your own one-time link! ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ąø—ąøµą¹ˆąø­ąø¢ąø¹ą¹ˆ No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ąøšąø—ąøšąø²ąø— @@ -2540,6 +2552,10 @@ This is your own one-time link! ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø² No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2747,6 +2763,10 @@ This is your own one-time link! ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ąø«ąø¢ąøøąø”ą¹ąøŠąø— No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ą¹€ąø›ąø„ąøµą¹ˆąø¢ąø™ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œ! @@ -3901,6 +3921,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -5328,6 +5352,10 @@ Enable in *Network & servers* settings. เคือก chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5670,6 +5698,10 @@ Enable in *Network & servers* settings. ą¹ąøŠąø£ą¹Œąø„ąø“ąø‡ąøą¹Œ No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link No comment provided by engineer. @@ -5983,6 +6015,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture ąø–ą¹ˆąø²ąø¢ąø ąø²ąøž @@ -7163,6 +7199,10 @@ Repeat connection request? ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œą¹ąøŠąø—ąø‚ąø­ąø‡ąø„ąøøąø“ No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). ąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ąø‚ąø­ąø‡ąø„ąøøąø“ąøŖą¹ˆąø‡ą¹„ąøŸąø„ą¹Œąø—ąøµą¹ˆą¹ƒąø«ąøą¹ˆąøąø§ą¹ˆąø²ąø‚ąø™ąø²ąø”ąøŖąø¹ąø‡ąøŖąøøąø”ąø—ąøµą¹ˆąø£ąø­ąø‡ąø£ąø±ąøšą¹ƒąø™ąø›ąø±ąøˆąøˆąøøąøšąø±ąø™ (%@) 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 054f65110f..13fa3c8a84 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -137,6 +137,10 @@ %@ bağlanmak istiyor! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ ve %lld üyeleri @@ -1689,6 +1693,10 @@ Bu senin kendi tek kullanımlık bağlantın! Ƈekirdek sürümü: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? İsim %@ olarak düzeltilsin mi? @@ -2653,6 +2661,10 @@ Bu senin kendi tek kullanımlık bağlantın! Adres değiştirilirken hata oluştu No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Rol değiştirilirken hata oluştu @@ -2663,6 +2675,10 @@ Bu senin kendi tek kullanımlık bağlantın! Ayar değiştirilirken hata oluştu No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2878,6 +2894,10 @@ Bu senin kendi tek kullanımlık bağlantın! Sohbet durdurulurken hata oluştu No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Profil değiştirilirken hata oluştu! @@ -4084,6 +4104,10 @@ Bu senin grup iƧin bağlantın %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Mesaj kaynağı gizli kalır. @@ -5585,6 +5609,10 @@ Enable in *Network & servers* settings. SeƧ chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5938,6 +5966,10 @@ Enable in *Network & servers* settings. Bağlantıyı paylaş No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link Bu tek kullanımlık bağlantı davetini paylaş @@ -6264,6 +6296,10 @@ Enable in *Network & servers* settings. TCP_TVLDEKAL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Fotoğraf Ƨek @@ -7517,6 +7553,10 @@ Bağlantı isteği tekrarlansın mı? Sohbet profillerin No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Kişiniz şu anda desteklenen maksimum boyuttan (%@) daha büyük bir dosya gƶnderdi. 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 7bcb30c1db..69685620ba 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -137,6 +137,10 @@ %@ хоче ŠæŃ–Š“ŠŗŠ»ŃŽŃ‡ŠøŃ‚ŠøŃŃ! notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ та %lld ŃƒŃ‡Š°ŃŠ½ŠøŠŗŃ–Š² @@ -1740,6 +1744,10 @@ This is your own one-time link! ŠžŃŠ½Š¾Š²Š½Š° Š²ŠµŃ€ŃŃ–Ń: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? Виправити ім'я на %@? @@ -2724,6 +2732,10 @@ This is your own one-time link! Помилка зміни аГреси No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role Помилка зміни ролі @@ -2734,6 +2746,10 @@ This is your own one-time link! Помилка зміни Š½Š°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. Помилка ŠæŃ–Š“ŠŗŠ»ŃŽŃ‡ŠµŠ½Š½Ń Го сервера переаГресації %@. Š”ŠæŃ€Š¾Š±ŃƒŠ¹Ń‚Šµ ŠæŃ–Š·Š½Ń–ŃˆŠµ. @@ -2954,6 +2970,10 @@ This is your own one-time link! Помилка зупинки Ń‡Š°Ń‚Ńƒ No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! Помилка ŠæŠµŃ€ŠµŠ¼ŠøŠŗŠ°Š½Š½Ń ŠæŃ€Š¾Ń„Ń–Š»ŃŽ! @@ -4184,6 +4204,10 @@ This is your link for group %@! Дервери ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½ŃŒ No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. Джерело ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń Š·Š°Š»ŠøŃˆŠ°Ń”Ń‚ŃŒŃŃ приватним. @@ -5729,6 +5753,10 @@ Enable in *Network & servers* settings. Š’ŠøŠ±ŠµŃ€Ń–Ń‚ŃŒ chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld Вибрано %lld @@ -6099,6 +6127,10 @@ Enable in *Network & servers* settings. ŠŸŠ¾Š“Ń–Š»Ń–Ń‚ŃŒŃŃ ŠæŠ¾ŃŠøŠ»Š°Š½Š½ŃŠ¼ No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link ŠŸŠ¾Š“Ń–Š»Ń–Ń‚ŃŒŃŃ цим оГноразовим ŠæŠ¾ŃŠøŠ»Š°Š½Š½ŃŠ¼-Š·Š°ŠæŃ€Š¾ŃˆŠµŠ½Š½ŃŠ¼ @@ -6439,6 +6471,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture Š”Ń„Š¾Ń‚Š¾Š³Ń€Š°Ń„ŃƒŠ¹Ń‚Šµ @@ -7719,6 +7755,10 @@ Repeat connection request? Š’Š°ŃˆŃ– профілі Ń‡Š°Ń‚Ńƒ No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). Š’Š°Ńˆ контакт наГіслав файл, розмір ŃŠŗŠ¾Š³Š¾ ŠæŠµŃ€ŠµŠ²ŠøŃ‰ŃƒŃ” ŠæŃ–Š“Ń‚Ń€ŠøŠ¼ŃƒŠ²Š°Š½ŠøŠ¹ на цей момент максимальний розмір (%@). 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 8c3641549d..b524846ffd 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 @@ -135,6 +135,10 @@ %@ č¦čæžęŽ„ļ¼ notification title + + %1$@, %2$@ + format for date separator in chat + %@, %@ and %lld members %@, %@ 和 %lld ęˆå‘˜ @@ -1665,6 +1669,10 @@ This is your own one-time link! ę øåæƒē‰ˆęœ¬: v%@ No comment provided by engineer. + + Corner + No comment provided by engineer. + Correct name to %@? No comment provided by engineer. @@ -2618,6 +2626,10 @@ This is your own one-time link! ę›“ę”¹åœ°å€é”™čÆÆ No comment provided by engineer. + + Error changing connection profile + No comment provided by engineer. + Error changing role 曓改角色错误 @@ -2628,6 +2640,10 @@ This is your own one-time link! 曓改设置错误 No comment provided by engineer. + + Error changing to incognito! + No comment provided by engineer. + Error connecting to forwarding server %@. Please try later. No comment provided by engineer. @@ -2841,6 +2857,10 @@ This is your own one-time link! åœę­¢čŠå¤©é”™čÆÆ No comment provided by engineer. + + Error switching profile + No comment provided by engineer. + Error switching profile! åˆ‡ę¢čµ„ę–™é”™čÆÆļ¼ @@ -4033,6 +4053,10 @@ This is your link for group %@! Message servers No comment provided by engineer. + + Message shape + No comment provided by engineer. + Message source remains private. ę¶ˆęÆę„ęŗäæęŒē§åÆ†ć€‚ @@ -5512,6 +5536,10 @@ Enable in *Network & servers* settings. 选ꋩ chat item action + + Select chat profile + No comment provided by engineer. + Selected %lld No comment provided by engineer. @@ -5861,6 +5889,10 @@ Enable in *Network & servers* settings. åˆ†äŗ«é“¾ęŽ„ No comment provided by engineer. + + Share profile + No comment provided by engineer. + Share this 1-time invite link åˆ†äŗ«ę­¤äø€ę¬”ę€§é‚€čÆ·é“¾ęŽ„ @@ -6185,6 +6217,10 @@ Enable in *Network & servers* settings. TCP_KEEPINTVL No comment provided by engineer. + + Tail + No comment provided by engineer. + Take picture ę‹ē…§ @@ -7417,6 +7453,10 @@ Repeat connection request? ę‚Øēš„čŠå¤©čµ„ę–™ No comment provided by engineer. + + Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + No comment provided by engineer. + Your contact sent a file that is larger than currently supported maximum size (%@). ę‚Øēš„č”ē³»äŗŗå‘é€ēš„ę–‡ä»¶å¤§äŗŽå½“å‰ę”ÆęŒēš„ęœ€å¤§å¤§å° (%@)怂 From e582d2d742120ccc208b208f70f9d95878f91551 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 27 Aug 2024 14:32:54 +0100 Subject: [PATCH 015/704] android, desktop: allow for chat profile selection on new chat screen (#4741) * add api and types * basic ui * add search on profiles * profile images on select chat profile * incognito adjustments * basic api connection * handling errors * add loading state * header to scroll * selected profile on top (profile or incognito) * adjust share profile copy * avoid list moving around on selection commit * bigger profile pick * info icon interactive area * thumbs to match contacts list size * incognito sizes matching icons * title to section padding * add chevron * align borders and other chevron icon * prevent click on self * only prevent selection * update * selectable item area * no need for oninfo to be composable * simplify * wrap apis in try * remove redundant derivedStateOf * closure fns capital naming * simplify current user null check --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/ChatModel.kt | 3 +- .../chat/simplex/common/model/SimpleXAPI.kt | 35 +- .../newchat/ContactConnectionInfoView.kt | 2 +- .../common/views/newchat/NewChatView.kt | 338 +++++++++++++++++- .../views/usersettings/UserProfilesView.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 4 + 6 files changed, 363 insertions(+), 23 deletions(-) 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 e92b3d714a..de13f05dbd 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 @@ -784,7 +784,8 @@ object ChatModel { data class ShowingInvitation( val connId: String, val connReq: String, - val connChatUsed: Boolean + val connChatUsed: Boolean, + val conn: PendingContactConnection ) enum class ChatType(val type: String) { 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 060e75a9a1..c621b9eacf 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 @@ -1132,9 +1132,30 @@ object ChatController { suspend fun apiSetConnectionIncognito(rh: Long?, connId: Long, incognito: Boolean): PendingContactConnection? { val r = sendCmd(rh, CC.ApiSetConnectionIncognito(connId, incognito)) - if (r is CR.ConnectionIncognitoUpdated) return r.toConnection - Log.e(TAG, "apiSetConnectionIncognito bad response: ${r.responseType} ${r.details}") - return null + + return when (r) { + is CR.ConnectionIncognitoUpdated -> r.toConnection + else -> { + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiSetConnectionIncognito", generalGetString(MR.strings.error_sending_message), r) + } + null + } + } + } + + suspend fun apiChangeConnectionUser(rh: Long?, connId: Long, userId: Long): PendingContactConnection? { + val r = sendCmd(rh, CC.ApiChangeConnectionUser(connId, userId)) + + return when (r) { + is CR.ConnectionUserChanged -> r.toConnection + else -> { + if (!(networkErrorAlert(r))) { + apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r) + } + null + } + } } suspend fun apiConnectPlan(rh: Long?, connReq: String): ConnectionPlan? { @@ -2916,6 +2937,7 @@ sealed class CC { class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC() class APIAddContact(val userId: Long, val incognito: Boolean): CC() class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC() + class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC() class APIConnectPlan(val userId: Long, val connReq: String): CC() class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC() class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC() @@ -3071,6 +3093,7 @@ sealed class CC { is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else "" is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}" is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}" + is ApiChangeConnectionUser -> "/_set conn user :$connId $userId" is APIConnectPlan -> "/_connect plan $userId $connReq" is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq" is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId" @@ -3213,6 +3236,7 @@ sealed class CC { is APIVerifyGroupMember -> "apiVerifyGroupMember" is APIAddContact -> "apiAddContact" is ApiSetConnectionIncognito -> "apiSetConnectionIncognito" + is ApiChangeConnectionUser -> "apiChangeConnectionUser" is APIConnectPlan -> "apiConnectPlan" is APIConnect -> "apiConnect" is ApiConnectContactViaAddress -> "apiConnectContactViaAddress" @@ -4757,6 +4781,7 @@ sealed class CR { @Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR() @Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR() @Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR() + @Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR() @Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR() @Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR() @Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR() @@ -4935,6 +4960,7 @@ sealed class CR { is ConnectionVerified -> "connectionVerified" is Invitation -> "invitation" is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated" + is ConnectionUserChanged -> "ConnectionUserChanged" is CRConnectionPlan -> "connectionPlan" is SentConfirmation -> "sentConfirmation" is SentInvitation -> "sentInvitation" @@ -5103,6 +5129,7 @@ sealed class CR { is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode") is Invitation -> withUser(user, "connReqInvitation: $connReqInvitation\nconnection: $connection") is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection)) + is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" ) is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan)) is SentConfirmation -> withUser(user, json.encodeToString(connection)) is SentInvitation -> withUser(user, json.encodeToString(connection)) @@ -5553,6 +5580,7 @@ sealed class ChatErrorType { is AgentCommandError -> "agentCommandError" is InvalidFileDescription -> "invalidFileDescription" is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited" + is ConnectionUserChangeProhibited -> "connectionUserChangeProhibited" is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible" is InternalError -> "internalError" is CEException -> "exception $message" @@ -5630,6 +5658,7 @@ sealed class ChatErrorType { @Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType() @Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType() @Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType() + @Serializable @SerialName("connectionUserChangeProhibited") object ConnectionUserChangeProhibited: ChatErrorType() @Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType() @Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType() @Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 88e483e92d..64ff7e4f40 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -39,7 +39,7 @@ fun ContactConnectionInfoView( ) { LaunchedEffect(connReqInvitation) { if (connReqInvitation != null) { - chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false) + chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false, conn = contactConnection) } } /** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation]. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index d2e8ac7a6c..544f5f72bb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -2,20 +2,25 @@ package chat.simplex.common.views.newchat import SectionBottomSpacer import SectionItemView +import SectionSpacer import SectionTextFooter import SectionView +import TextIconSpaced import androidx.compose.foundation.* -import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.TextStyle @@ -32,6 +37,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.net.URI @@ -43,7 +49,7 @@ enum class NewChatOption { fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRCodeScanner: Boolean = false, close: () -> Unit) { val selection = remember { stateGetOrPut("selection") { selection } } val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } } - val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) } + val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) } val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } } val creatingConnReq = rememberSaveable { mutableStateOf(false) } val pastedLink = rememberSaveable { mutableStateOf("") } @@ -177,6 +183,15 @@ private fun CreatingLinkProgressView() { DefaultProgressView(stringResource(MR.strings.creating_link)) } +private fun updateShownConnection(conn: PendingContactConnection) { + chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy( + conn = conn, + connId = conn.id, + connReq = conn.connReqInv ?: "", + connChatUsed = true + ) +} + @Composable private fun RetryButton(onClick: () -> Unit) { Column( @@ -192,6 +207,248 @@ private fun RetryButton(onClick: () -> Unit) { } } +@Composable +private fun ProfilePickerOption( + title: String, + selected: Boolean, + disabled: Boolean, + onSelected: () -> Unit, + image: @Composable () -> Unit, + onInfo: (() -> Unit)? = null +) { + Row( + Modifier + .fillMaxWidth() + .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp) + .clickable(enabled = !disabled, onClick = onSelected) + .padding(horizontal = DEFAULT_PADDING, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + image() + TextIconSpaced(false) + Text(title, modifier = Modifier.align(Alignment.CenterVertically)) + if (onInfo != null) { + Spacer(Modifier.padding(6.dp)) + Column(Modifier + .size(48.dp) + .clip(CircleShape) + .clickable( + enabled = !disabled, + onClick = { ModalManager.start.showModal { IncognitoView() } } + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painterResource(MR.images.ic_info), + stringResource(MR.strings.incognito), + tint = MaterialTheme.colors.primary + ) + } + } + Spacer(Modifier.weight(1f)) + if (selected) { + Icon( + painterResource( + MR.images.ic_check + ), + title, + Modifier.size(20.dp), + tint = MaterialTheme.colors.primary, + ) + } + } + Divider( + Modifier.padding( + start = DEFAULT_PADDING_HALF, + end = DEFAULT_PADDING_HALF, + ) + ) +} + +private fun filteredProfiles(users: List, searchTextOrPassword: String): List { + val s = searchTextOrPassword.trim() + val lower = s.lowercase() + return users.filter { u -> + if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) { + true + } else { + correctPassword(u, s) + } + } +} + +@Composable +private fun ActiveProfilePicker( + search: MutableState, + contactConnection: PendingContactConnection?, + close: () -> Unit, + rhId: Long? +) { + val switchingProfile = remember { mutableStateOf(false) } + val incognito = remember { + chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get() + } + val selectedProfile by remember { chatModel.currentUser } + val searchTextOrPassword = rememberSaveable { search } + val profiles = remember { + chatModel.users.map { it.user }.sortedBy { !it.activeUser } + } + val filteredProfiles by remember { + derivedStateOf { filteredProfiles(profiles, searchTextOrPassword.value) } + } + + var progressByTimeout by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(switchingProfile.value) { + progressByTimeout = if (switchingProfile.value) { + delay(500) + switchingProfile.value + } else { + false + } + } + + @Composable + fun ProfilePickerUserOption(user: User) { + val selected = selectedProfile?.userId == user.userId && !incognito + + ProfilePickerOption( + title = user.chatViewName, + disabled = switchingProfile.value || selected, + selected = selected, + onSelected = { + switchingProfile.value = true + withApi { + try { + if (contactConnection != null) { + val conn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) + if (conn != null) { + withChats { + updateContactConnection(rhId, conn) + updateShownConnection(conn) + } + controller.changeActiveUser_( + rhId = user.remoteHostId, + toUserId = user.userId, + viewPwd = if (user.hidden) searchTextOrPassword.value else null + ) + + if (chatModel.currentUser.value?.userId != user.userId) { + AlertManager.shared.showAlertMsg(generalGetString( + MR.strings.switching_profile_error_title), + String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) + ) + } + + withChats { + updateContactConnection(user.remoteHostId, conn) + } + close.invoke() + } + } + } finally { + switchingProfile.value = false + } + } + }, + image = { ProfileImage(size = 42.dp, image = user.image) } + ) + } + + @Composable + fun IncognitoUserOption() { + ProfilePickerOption( + disabled = switchingProfile.value, + title = stringResource(MR.strings.incognito), + selected = incognito, + onSelected = { + if (!incognito) { + switchingProfile.value = true + withApi { + try { + if (contactConnection != null) { + val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) + + if (conn != null) { + withChats { + updateContactConnection(rhId, conn) + updateShownConnection(conn) + } + close.invoke() + } + } + } finally { + switchingProfile.value = false + } + } + } + }, + image = { + Spacer(Modifier.width(8.dp)) + Icon( + painterResource(MR.images.ic_theater_comedy_filled), + contentDescription = stringResource(MR.strings.incognito), + Modifier.size(32.dp), + tint = Indigo, + ) + Spacer(Modifier.width(2.dp)) + }, + onInfo = { ModalManager.start.showModal { IncognitoView() } }, + ) + } + + BoxWithConstraints { + Column( + Modifier + .fillMaxSize() + .alpha(if (progressByTimeout) 0.6f else 1f) + ) { + LazyColumnWithScrollBar(userScrollEnabled = !switchingProfile.value) { + item { + AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(rhId), bottomPadding = DEFAULT_PADDING) + } + val activeProfile = filteredProfiles.firstOrNull { it.activeUser } + + if (activeProfile != null) { + val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId } + + if (incognito) { + item { + IncognitoUserOption() + } + item { + ProfilePickerUserOption(activeProfile) + } + } else { + item { + ProfilePickerUserOption(activeProfile) + } + item { + IncognitoUserOption() + } + } + + itemsIndexed(otherProfiles) { _, p -> + ProfilePickerUserOption(p) + } + } else { + item { + IncognitoUserOption() + } + itemsIndexed(filteredProfiles) { _, p -> + ProfilePickerUserOption(p) + } + } + } + } + if (progressByTimeout) { + DefaultProgressView("") + } + } +} + @Composable private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState) { SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) { @@ -204,23 +461,72 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() }) } - Spacer(Modifier.height(10.dp)) - val incognito = remember { mutableStateOf(controller.appPrefs.incognito.get()) } - IncognitoToggle(controller.appPrefs.incognito, incognito) { - ModalManager.start.showModal { IncognitoView() } + Spacer(Modifier.height(DEFAULT_PADDING)) + val incognito by remember(chatModel.showingInvitation.value?.conn?.incognito, controller.appPrefs.incognito.get()) { + derivedStateOf { + chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get() + } } - KeyChangeEffect(incognito.value) { - withBGApi { - val contactConn = contactConnection.value ?: return@withBGApi - val conn = controller.apiSetConnectionIncognito(rhId, contactConn.pccConnId, incognito.value) ?: return@withBGApi - withChats { - contactConnection.value = conn - updateContactConnection(rhId, conn) + val currentUser = remember { chatModel.currentUser }.value + + if (currentUser != null) { + SectionView(stringResource(MR.strings.new_chat_share_profile).uppercase(), headerBottomPadding = 5.dp) { + SectionItemView( + padding = PaddingValues( + top = 0.dp, + bottom = 0.dp, + start = 16.dp, + end = 16.dp + ), + click = { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { + ActiveProfilePicker( + search = search, + close = close, + rhId = rhId, + contactConnection = contactConnection.value + ) + }) + } + } + ) { + if (incognito) { + Spacer(Modifier.width(8.dp)) + Icon( + painterResource(MR.images.ic_theater_comedy_filled), + contentDescription = stringResource(MR.strings.incognito), + tint = Indigo, + modifier = Modifier.size(32.dp) + ) + Spacer(Modifier.width(2.dp)) + } else { + ProfileImage(size = 42.dp, image = currentUser.image) + } + TextIconSpaced(false) + Text( + text = if (incognito) stringResource(MR.strings.incognito) else currentUser.chatViewName, + color = MaterialTheme.colors.onBackground + ) + Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.End) { + Icon( + painter = painterResource(MR.images.ic_arrow_forward_ios), + contentDescription = stringResource(MR.strings.new_chat_share_profile), + tint = MaterialTheme.colors.secondary, + ) + } } } - chatModel.markShowingInvitationUsed() + if (incognito) { + SectionTextFooter(generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared)) + } } - SectionTextFooter(sharedProfileInfo(chatModel, incognito.value)) } @Composable @@ -366,7 +672,7 @@ private fun createInvitation( if (r != null) { withChats { updateContactConnection(rhId, r.second) - chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false) + chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second) contactConnection.value = r.second } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index a7bf5920e4..d4334dfed2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -303,7 +303,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: ( } } -private fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { +fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List { val s = searchTextOrPassword.trim() val lower = s.lowercase() return m.users.filter { u -> @@ -317,7 +317,7 @@ private fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List !u.user.hidden }.size -private fun correctPassword(user: User, pwd: String): Boolean { +fun correctPassword(user: User, pwd: String): Boolean { val ph = user.viewPwdHash return ph != null && pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash } 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 f5272ce7fc..9e9beef86b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -665,6 +665,10 @@ 1-time link SimpleX address Or show this code + Share profile + Select chat profile + Error switching profile + Your connection was moved to %s but an unexpected error occurred while redirecting you to the profile. Or scan QR code Keep unused invitation? You can view invitation link again in connection details. From 05e7f350378b01c700b934adf9850f3d00291df3 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 27 Aug 2024 22:12:55 +0100 Subject: [PATCH 016/704] core: fix associated agent user for recreated connections (#4771) * core: fix associated user for recreated connections * fix test for connection recreation --- src/Simplex/Chat.hs | 2 +- tests/ChatTests/Profiles.hs | 67 +++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index dc3b4b2e54..796a128abe 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1709,7 +1709,7 @@ processChatCommand' vr = \case pure conn' recreateConn user conn@PendingContactConnection {customUserProfileId} newUser = do subMode <- chatReadVar subscriptionMode - (agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOn subMode + (agConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId newUser) True SCMInvitation Nothing IKPQOn subMode conn' <- withFastStore' $ \db -> do deleteConnectionRecord db user connId forM_ customUserProfileId $ \profileId -> diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 878546ba21..a36eef8ca9 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PostfixOperators #-} +{-# LANGUAGE TypeApplications #-} module ChatTests.Profiles where @@ -18,6 +19,8 @@ import Simplex.Chat.Types (ConnStatus (..), Profile (..)) import Simplex.Chat.Types.Shared (GroupMemberRole (..)) import Simplex.Chat.Types.UITheme import Simplex.Messaging.Encoding.String (StrEncoding (..)) +import Simplex.Messaging.Server.Env.STM hiding (subscriptions) +import Simplex.Messaging.Transport import Simplex.Messaging.Util (encodeJSON) import System.Directory (copyFile, createDirectoryIfMissing) import Test.Hspec hiding (it) @@ -1653,34 +1656,42 @@ testChangePCCUserAndThenIncognito = testChat2 aliceProfile bobProfile $ ] testChangePCCUserDiffSrv :: HasCallStack => FilePath -> IO () -testChangePCCUserDiffSrv = testChat2 aliceProfile bobProfile $ - \alice bob -> do - -- Create a new invite - alice ##> "/connect" - _ <- getInvitation alice - alice ##> "/_set incognito :1 on" - alice <## "connection 1 changed to incognito" - -- Create new user with different servers - alice ##> "/create user alisa" - showActiveUser alice "alisa" - alice #$> ("/smp smp://2345-w==@smp2.example.im smp://3456-w==@smp3.example.im:5224", id, "ok") - alice ##> "/user alice" - showActiveUser alice "alice (Alice)" - -- Change connection to newly created user and use the newly created connection - alice ##> "/_set conn user :1 2" - alice <## "connection 1 changed from user alice to user alisa, new link:" - alice <## "" - inv <- getTermLine alice - alice <## "" - alice `hasContactProfiles` ["alice"] - alice ##> "/user alisa" - showActiveUser alice "alisa" - -- Connect - bob ##> ("/connect " <> inv) - bob <## "confirmation sent!" - concurrently_ - (alice <## "bob (Bob): contact is connected") - (bob <## "alisa: contact is connected") +testChangePCCUserDiffSrv tmp = do + withSmpServer' serverCfg' $ do + withNewTestChatCfgOpts tmp testCfg testOpts "alice" aliceProfile $ \alice -> do + withNewTestChatCfgOpts tmp testCfg testOpts "bob" bobProfile $ \bob -> do + -- Create a new invite + alice ##> "/connect" + _ <- getInvitation alice + alice ##> "/_set incognito :1 on" + alice <## "connection 1 changed to incognito" + -- Create new user with different servers + alice ##> "/create user alisa" + showActiveUser alice "alisa" + alice #$> ("/smp smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003", id, "ok") + alice ##> "/user alice" + showActiveUser alice "alice (Alice)" + -- Change connection to newly created user and use the newly created connection + alice ##> "/_set conn user :1 2" + alice <## "connection 1 changed from user alice to user alisa, new link:" + alice <## "" + inv <- getTermLine alice + alice <## "" + alice `hasContactProfiles` ["alice"] + alice ##> "/user alisa" + showActiveUser alice "alisa" + -- Connect + bob ##> ("/connect " <> inv) + bob <## "confirmation sent!" + concurrently_ + (alice <## "bob (Bob): contact is connected") + (bob <## "alisa: contact is connected") + where + serverCfg' = + smpServerCfg + { transports = [("7003", transport @TLS), ("7002", transport @TLS)], + msgQueueQuota = 2 + } testSetConnectionAlias :: HasCallStack => FilePath -> IO () testSetConnectionAlias = testChat2 aliceProfile bobProfile $ From 8cc075eda8a958ccfe1629a9d9f253c3083c6851 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 27 Aug 2024 22:13:20 +0100 Subject: [PATCH 017/704] ios: show correct message times (#4779) --- apps/ios/Shared/Views/Chat/ChatView.swift | 6 +++--- apps/ios/SimpleXChat/ChatTypes.swift | 20 +------------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index d94be2bb81..acdcabd7e2 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -723,11 +723,11 @@ struct ChatView: View { let im = ItemsModel.shared if let i, i > 0 && im.reversedChatItems.count >= i { let nextItem = im.reversedChatItems[i - 1] - let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.createdAt.timeIntervalSince(chatItem.meta.createdAt) > 60 + let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.itemTs.timeIntervalSince(chatItem.meta.itemTs) > 60 return ( - timestamp: largeGap || formatTimestampText(chatItem.meta.createdAt) != formatTimestampText(nextItem.meta.createdAt), + timestamp: largeGap || formatTimestampText(chatItem.meta.itemTs) != formatTimestampText(nextItem.meta.itemTs), largeGap: largeGap, - date: Calendar.current.isDate(chatItem.meta.createdAt, inSameDayAs: nextItem.meta.createdAt) ? nil : nextItem.meta.createdAt + date: Calendar.current.isDate(chatItem.meta.itemTs, inSameDayAs: nextItem.meta.itemTs) ? nil : nextItem.meta.itemTs ) } else { return (timestamp: true, largeGap: true, date: nil) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index d51e5a7cc3..07340bb963 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2765,26 +2765,8 @@ public struct CITimed: Decodable, Hashable { public var deleteAt: Date? } -let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() -let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) - public func formatTimestampText(_ date: Date) -> Text { - Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) -} - -private func recent(_ date: Date) -> Bool { - let now = Date() - let calendar = Calendar.current - - guard let previousDay = calendar.date(byAdding: DateComponents(day: -1), to: now), - let previousDay18 = calendar.date(bySettingHour: 18, minute: 0, second: 0, of: previousDay), - let currentDay00 = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: now), - let currentDay12 = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: now) else { - return false - } - - let isSameDay = calendar.isDate(date, inSameDayAs: now) - return isSameDay || (now < currentDay12 && date >= previousDay18 && date < currentDay00) + Text(verbatim: date.formatted(date: .omitted, time: .shortened)) } public enum CIStatus: Decodable, Hashable { From 700918f0cabc4812474470deac3d3a6cd0a1eea3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Aug 2024 20:55:54 +0100 Subject: [PATCH 018/704] ios: show member role on the right (#4783) * ios: show member role on the right * member layout --------- Co-authored-by: Levitating Pineapple --- apps/ios/Shared/Views/Chat/ChatView.swift | 86 +++++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index d74b7b88e6..dde63b6511 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -710,7 +710,8 @@ struct ChatView: View { @State private var showChatItemInfoSheet: Bool = false @State private var chatItemInfo: ChatItemInfo? @State private var showForwardingSheet: Bool = false - + @State private var msgWidth: CGFloat = 0 + @Binding var selectedChatItems: Set? @State private var allowMenu: Bool = true @@ -824,6 +825,51 @@ struct ChatView: View { } } } + + + @available(iOS 16.0, *) + struct MemberLayout: Layout { + let spacing: Double + let msgWidth: Double + + private func sizes(subviews: Subviews, proposal: ProposedViewSize) -> (CGSize, CGSize) { + assert(subviews.count == 2, "member layout must contain exactly two subviews") + let roleSize = subviews[1].sizeThatFits(proposal) + let memberSize = subviews[0].sizeThatFits( + ProposedViewSize( + width: (proposal.width ?? msgWidth) - roleSize.width, + height: proposal.height + ) + ) + return (memberSize, roleSize) + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize { + let (memberSize, roleSize) = sizes(subviews: subviews, proposal: proposal) + return CGSize( + width: min( + proposal.width ?? msgWidth, + max(msgWidth, roleSize.width + spacing + memberSize.width) + ), + height: max(memberSize.height, roleSize.height) + ) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) { + let (memberSize, roleSize) = sizes(subviews: subviews, proposal: proposal) + subviews[0].place( + at: CGPoint(x: bounds.minX, y: bounds.midY - memberSize.height / 2), + proposal: ProposedViewSize(memberSize) + ) + subviews[1].place( + at: CGPoint( + x: bounds.minX + max(memberSize.width + spacing, msgWidth - roleSize.width), + y: bounds.midY - roleSize.height / 2 + ), + proposal: ProposedViewSize(roleSize) + ) + } + } @ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange?, _ prevItem: ChatItem?, _ itemSeparation: ItemSeparation) -> some View { let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2 @@ -838,15 +884,40 @@ struct ChatView: View { if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil { VStack(alignment: .leading, spacing: 4) { if ci.content.showMemberName { - let t = if memCount == 1 && member.memberRole > .member { - Text(member.memberRole.text + " ").fontWeight(.semibold) + Text(member.displayName) - } else { - Text(memberNames(member, prevMember, memCount)) + Group { + if memCount == 1 && member.memberRole > .member { + Group { + if #available(iOS 16.0, *) { + MemberLayout(spacing: 16, msgWidth: msgWidth) { + Text(member.chatViewName) + .lineLimit(1) + Text(member.memberRole.text) + .fontWeight(.semibold) + .lineLimit(1) + .padding(.trailing, 8) + } + } else { + HStack(spacing: 16) { + Text(member.chatViewName) + .lineLimit(1) + Text(member.memberRole.text) + .fontWeight(.semibold) + .lineLimit(1) + .layoutPriority(1) + } + } + } + .frame( + maxWidth: maxWidth, + alignment: chatItem.chatDir.sent ? .trailing : .leading + ) + } else { + Text(memberNames(member, prevMember, memCount)) + .lineLimit(2) + } } - t .font(.caption) .foregroundStyle(.secondary) - .lineLimit(2) .padding(.leading, memberImageSize + 14 + (selectedChatItems != nil && ci.canBeDeletedForSelf ? 12 + 24 : 0)) .padding(.top, 3) // this is in addition to message sequence gap } @@ -869,6 +940,7 @@ struct ChatView: View { } } chatItemWithMenu(ci, range, maxWidth, itemSeparation) + .onPreferenceChange(DetermineWidth.Key.self) { msgWidth = $0 } } } } From 1c64b17545a884764e734fc0fa5fb353696aba51 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 29 Aug 2024 13:19:41 +0300 Subject: [PATCH 019/704] ios: remove tails from group invitations (#4792) --- apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift index ddae6a5f6d..e1e0911e4d 100644 --- a/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift +++ b/apps/ios/Shared/Views/Helpers/ChatItemClipShape.swift @@ -36,8 +36,6 @@ struct ChatItemClipped: ViewModifier { .sndMsgContent, .rcvMsgContent, .rcvDecryptionError, - .rcvGroupInvitation, - .sndGroupInvitation, .sndDeleted, .rcvDeleted, .rcvIntegrityError, @@ -56,10 +54,12 @@ struct ChatItemClipped: ViewModifier { tailVisible: tail ) : .roundRect(radius: msgRectMaxRadius) + case .rcvGroupInvitation, .sndGroupInvitation: + return .roundRect(radius: msgRectMaxRadius) default: return .roundRect(radius: 8) } } else { - return.roundRect(radius: msgRectMaxRadius) + return .roundRect(radius: msgRectMaxRadius) } } From eef1e97ecc9ce68f1b3b28fa85d3e107e4251a5c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 29 Aug 2024 13:40:55 +0100 Subject: [PATCH 020/704] ci: dont build when files in core do not change (#4797) --- .github/workflows/build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c41fb4646a..6ad4f12ef9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,22 @@ on: branches: - master - stable - - users tags: - "v*" - "!*-fdroid" - "!*-armv7a" pull_request: + paths-ignore: + - "apps/ios" + - "apps/multiplatform" + - "blog" + - "docs" + - "fastlane" + - "images" + - "packages" + - "website" + - "README.md" + - "PRIVACY.md" jobs: prepare-release: From 0b0b78293f5cf2a6f196aee52f43e14d986c4e77 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 29 Aug 2024 19:25:08 +0300 Subject: [PATCH 021/704] ios: fix inaccurate floating unread counters in chat message view (#4781) * ios: fix inaccurate floating unread counters in chat message view * account for inset; remove old on appear/disappear blocks * revert id * first visible * remove UnreadChatItemCounts * cleanup * revert duplicates * add todo * throttle first * cleanup * lines --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 35 -------- .../Views/Chat/ChatItem/FramedItemView.swift | 2 +- .../Chat/ChatItem/FullScreenMediaView.swift | 2 +- apps/ios/Shared/Views/Chat/ChatView.swift | 88 +++++++------------ apps/ios/Shared/Views/Chat/ReverseList.swift | 58 +++++++++--- .../Views/ChatList/ChatPreviewView.swift | 4 +- 6 files changed, 83 insertions(+), 106 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 07a0d19a55..2be3191f4f 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -888,35 +888,6 @@ final class ChatModel: ObservableObject { _ = upsertGroupMember(groupInfo, updatedMember) } } - - func unreadChatItemCounts(itemsInView: Set) -> UnreadChatItemCounts { - var i = 0 - var totalBelow = 0 - var unreadBelow = 0 - while i < im.reversedChatItems.count - 1 && !itemsInView.contains(im.reversedChatItems[i].viewId) { - totalBelow += 1 - if im.reversedChatItems[i].isRcvNew { - unreadBelow += 1 - } - i += 1 - } - return UnreadChatItemCounts( - // TODO these thresholds account for the fact that items are still "visible" while - // covered by compose area, they should be replaced with the actual height in pixels below the screen. - isNearBottom: totalBelow < 15, - isReallyNearBottom: totalBelow < 2, - unreadBelow: unreadBelow - ) - } - - func topItemInView(itemsInView: Set) -> ChatItem? { - let maxIx = im.reversedChatItems.count - 1 - var i = 0 - let inView = { itemsInView.contains(self.im.reversedChatItems[$0].viewId) } - while i < maxIx && !inView(i) { i += 1 } - while i < maxIx && inView(i) { i += 1 } - return im.reversedChatItems[min(i - 1, maxIx)] - } } struct ShowingInvitation { @@ -929,12 +900,6 @@ struct NTFContactRequest { var chatId: String } -struct UnreadChatItemCounts: Equatable { - var isNearBottom: Bool - var isReallyNearBottom: Bool - var unreadBelow: Int -} - final class Chat: ObservableObject, Identifiable, ChatLike { @Published var chatInfo: ChatInfo @Published var chatItems: [ChatItem] diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index e70f891302..260ac64e43 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct FramedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var scrollModel: ReverseListScrollModel @ObservedObject var chat: Chat var chatItem: ChatItem var preview: UIImage? diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift index a80c5412b6..044ee2a26d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift @@ -13,7 +13,7 @@ import AVKit struct FullScreenMediaView: View { @EnvironmentObject var m: ChatModel - @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var scrollModel: ReverseListScrollModel @State var chatItem: ChatItem @State var image: UIImage? @State var player: AVPlayer? = nil diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index dde63b6511..a5ad7ce456 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -22,8 +22,8 @@ struct ChatView: View { @Environment(\.presentationMode) var presentationMode @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat - @StateObject private var scrollModel = ReverseListScrollModel() - @StateObject private var floatingButtonModel = FloatingButtonModel() + @StateObject private var scrollModel = ReverseListScrollModel() + @StateObject private var floatingButtonModel: FloatingButtonModel = .shared @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -76,7 +76,8 @@ struct ChatView: View { VStack(spacing: 0) { ZStack(alignment: .bottomTrailing) { chatItemsList() - floatingButtons(counts: floatingButtonModel.unreadChatItemCounts) + // TODO: Extract into a separate view, to reduce the scope of `FloatingButtonModel` updates + floatingButtons(unreadBelow: floatingButtonModel.unreadBelow, isNearBottom: floatingButtonModel.isNearBottom) } connectingText() if selectedChatItems == nil { @@ -413,12 +414,6 @@ struct ChatView: View { revealedChatItem: $revealedChatItem, selectedChatItems: $selectedChatItems ) - .onAppear { - floatingButtonModel.appeared(viewId: ci.viewId) - } - .onDisappear { - floatingButtonModel.disappeared(viewId: ci.viewId) - } .id(ci.id) // Required to trigger `onAppear` on iOS15 } loadPage: { loadChatItems(cInfo) @@ -429,13 +424,10 @@ struct ChatView: View { .onChange(of: searchText) { _ in Task { await loadChat(chat: chat, search: searchText) } } - .onChange(of: im.reversedChatItems) { _ in - floatingButtonModel.chatItemsChanged() - } .onChange(of: im.itemAdded) { added in if added { im.itemAdded = false - if floatingButtonModel.unreadChatItemCounts.isReallyNearBottom { + if floatingButtonModel.isReallyNearBottom { scrollModel.scrollToBottom() } } @@ -458,57 +450,43 @@ struct ChatView: View { } class FloatingButtonModel: ObservableObject { - private enum Event { - case appeared(String) - case disappeared(String) - case chatItemsChanged - } - - @Published var unreadChatItemCounts: UnreadChatItemCounts - - private let events = PassthroughSubject() + static let shared = FloatingButtonModel() + @Published var unreadBelow: Int = 0 + @Published var isNearBottom: Bool = true + var isReallyNearBottom: Bool { scrollOffset.value > 0 && scrollOffset.value < 500 } + let visibleItems = PassthroughSubject<[String], Never>() + let scrollOffset = CurrentValueSubject(0) private var bag = Set() init() { - unreadChatItemCounts = UnreadChatItemCounts( - isNearBottom: true, - isReallyNearBottom: true, - unreadBelow: 0 - ) - events + visibleItems .receive(on: DispatchQueue.global(qos: .background)) - .scan(Set()) { itemsInView, event in - var updated = itemsInView - switch event { - case let .appeared(viewId): updated.insert(viewId) - case let .disappeared(viewId): updated.remove(viewId) - case .chatItemsChanged: () - } - return updated + .map { itemIds in + if let viewId = itemIds.first, + let index = ItemsModel.shared.reversedChatItems.firstIndex(where: { $0.viewId == viewId }) { + ItemsModel.shared.reversedChatItems[.. some View { + private func floatingButtons(unreadBelow: Int, isNearBottom: Bool) -> some View { VStack { - let unreadAbove = chat.chatStats.unreadCount - counts.unreadBelow + let unreadAbove = chat.chatStats.unreadCount - unreadBelow if unreadAbove > 0 { circleButton { unreadCountText(unreadAbove) @@ -529,16 +507,16 @@ struct ChatView: View { } } Spacer() - if counts.unreadBelow > 0 { + if unreadBelow > 0 { circleButton { - unreadCountText(counts.unreadBelow) + unreadCountText(unreadBelow) .font(.callout) .foregroundColor(theme.colors.primary) } .onTapGesture { scrollModel.scrollToBottom() } - } else if !counts.isNearBottom { + } else if !isNearBottom { circleButton { Image(systemName: "chevron.down") .foregroundColor(theme.colors.primary) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 94d160e1b4..bff0774926 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -8,15 +8,16 @@ import SwiftUI import Combine +import SimpleXChat /// A List, which displays it's items in reverse order - from bottom to top -struct ReverseList: UIViewControllerRepresentable { - let items: Array +struct ReverseList: UIViewControllerRepresentable { + let items: Array - @Binding var scrollState: ReverseListScrollModel.State + @Binding var scrollState: ReverseListScrollModel.State /// Closure, that returns user interface for a given item - let content: (Item) -> Content + let content: (ChatItem) -> Content let loadPage: () -> Void @@ -25,7 +26,9 @@ struct ReverseList: UIV } func updateUIViewController(_ controller: Controller, context: Context) { + controller.representer = self if case let .scrollingTo(destination) = scrollState, !items.isEmpty { + controller.view.layer.removeAllAnimations() switch destination { case .nextPage: controller.scrollToNextPage() @@ -42,9 +45,10 @@ struct ReverseList: UIV /// Controller, which hosts SwiftUI cells class Controller: UITableViewController { private enum Section { case main } - private let representer: ReverseList - private var dataSource: UITableViewDiffableDataSource! + var representer: ReverseList + private var dataSource: UITableViewDiffableDataSource! private var itemCount: Int = 0 + private let updateFloatingButtons = PassthroughSubject() private var bag = Set() init(representer: ReverseList) { @@ -71,7 +75,7 @@ struct ReverseList: UIV } // 3. Configure data source - self.dataSource = UITableViewDiffableDataSource( + self.dataSource = UITableViewDiffableDataSource( tableView: tableView ) { (tableView, indexPath, item) -> UITableViewCell? in if indexPath.item > self.itemCount - 8, self.itemCount > 8 { @@ -103,6 +107,10 @@ struct ReverseList: UIV name: notificationName, object: nil ) + updateFloatingButtons + .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) + .sink { self.updateVisibleItems() } + .store(in: &bag) } @available(*, unavailable) @@ -171,8 +179,8 @@ struct ReverseList: UIV Task { representer.scrollState = .atDestination } } - func update(items: Array) { - var snapshot = NSDiffableDataSourceSnapshot() + func update(items: [ChatItem]) { + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(items) dataSource.defaultRowAnimation = .none @@ -188,6 +196,32 @@ struct ReverseList: UIV ) } itemCount = items.count + updateFloatingButtons.send() + } + + override func scrollViewDidScroll(_ scrollView: UIScrollView) { + updateFloatingButtons.send() + } + + private func updateVisibleItems() { + let fbm = ChatView.FloatingButtonModel.shared + fbm.scrollOffset.send(tableView.contentOffset.y + InvertedTableView.inset) + fbm.visibleItems.send( + (tableView.indexPathsForVisibleRows ?? []) + .compactMap { indexPath -> String? in + let relativeFrame = tableView.superview!.convert( + tableView.rectForRow(at: indexPath), + from: tableView + ) + // Checks that the cell is visible accounting for the added insets + let isVisible = + relativeFrame.maxY > InvertedTableView.inset && + relativeFrame.minY < tableView.frame.height - InvertedTableView.inset + return indexPath.item < representer.items.count && isVisible + ? representer.items[indexPath.item].viewId + : nil + } + ) } } @@ -232,12 +266,12 @@ struct ReverseList: UIV } /// Manages ``ReverseList`` scrolling -class ReverseListScrollModel: ObservableObject { +class ReverseListScrollModel: ObservableObject { /// Represents Scroll State of ``ReverseList`` enum State: Equatable { enum Destination: Equatable { case nextPage - case item(Item.ID) + case item(ChatItem.ID) case bottom } @@ -255,7 +289,7 @@ class ReverseListScrollModel: ObservableObject { state = .scrollingTo(.bottom) } - func scrollToItem(id: Item.ID) { + func scrollToItem(id: ChatItem.ID) { state = .scrollingTo(.item(id)) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 9e6d3005b6..43892ec469 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -324,12 +324,12 @@ struct ChatPreviewView: View { case let .image(_, image): smallContentPreview(size: dynamicMediaSize) { CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) - .environmentObject(ReverseListScrollModel()) + .environmentObject(ReverseListScrollModel()) } case let .video(_,image, duration): smallContentPreview(size: dynamicMediaSize) { CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) - .environmentObject(ReverseListScrollModel()) + .environmentObject(ReverseListScrollModel()) } case let .voice(_, duration): smallContentPreviewVoice(size: dynamicMediaSize) { From 23f54c1022669af5fbe0f33ee2a0fb0a614bf8b6 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 29 Aug 2024 20:33:48 +0300 Subject: [PATCH 022/704] ios: fix crash regression (#4800) --- apps/ios/Shared/Views/Chat/ReverseList.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index bff0774926..f64189f95f 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -209,10 +209,10 @@ struct ReverseList: UIViewControllerRepresentable { fbm.visibleItems.send( (tableView.indexPathsForVisibleRows ?? []) .compactMap { indexPath -> String? in - let relativeFrame = tableView.superview!.convert( + guard let relativeFrame = tableView.superview?.convert( tableView.rectForRow(at: indexPath), from: tableView - ) + ) else { return nil } // Checks that the cell is visible accounting for the added insets let isVisible = relativeFrame.maxY > InvertedTableView.inset && From 41cb734d5681e6bff70a59bb100edc9e566334b1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 30 Aug 2024 21:31:57 +0100 Subject: [PATCH 023/704] docs: FAQ on deletion of sent messages and read receipts (#4470) * docs: FAQ on deletion of sent messages and read receipts * update --- .../xcshareddata/swiftpm/Package.resolved | 3 +- docs/FAQ.md | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c8623a95cb..22312bf5a1 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,7 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" + "branch" : "master", + "revision" : "7c50eb60ca4b90043c6ad719d595803488496212" } }, { diff --git a/docs/FAQ.md b/docs/FAQ.md index a9f94b49eb..774054e58c 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -15,7 +15,9 @@ revision: 23.04.2024 - [How to configure and delete groups?](#how-to-configure-and-delete-groups) - [Are there any reactions to messages? Can I answer specific messages directly?](#are-there-any-reactions-to-messages-can-i-answer-specific-messages-directly) - [What do checkmarks mean?](#what-do-checkmarks-mean) +- [I want to see when my contacts read my messages](#i-want-to-see-when-my-contacts-read-my-messages) - [Can I use the same profile on desktop? Do messages sync cross-platform?](#can-i-use-the-same-profile-on-desktop-do-messages-sync-cross-platform) +- [Why cannot I delete messages I sent from my contact's device?](#why-cannot-i-delete-messages-i-sent-from-my-contacts-device) [Troubleshooting](#troubleshooting) - [I do not receive messages or message notifications](#i-do-not-receive-messages-or-message-notifications) @@ -79,12 +81,43 @@ It's quite simple: - two checkmarks - message is delivered to the recipient's device. "sent" means accepted by the relay for delivery, "delivered" - stored on the recipient device. -Also see [ ](#i-do-not-see-the-second-tick-on-the-messages-i-sent) +Also see: [I do not see the second tick on the messages I sent](#i-do-not-see-the-second-tick-on-the-messages-i-sent) + +### I want to see when my contacts read my messages + +To know when your contact read your messages, your contact's app has to send you a confirmation message. And vice versa, for your contact to know when you read the message, your app has to send a confirmation message. + +The important questions for this feature: +- do you always want that your contacts can see when you read all their messages? Probably, even with your close friends, sometimes you would prefer to have time before you answer their message, and also have a plausible deniability that you have not seen the message. And this should be ok - in the end, this is your device, and it should be for you to decide whether this confirmation message is sent or not, and when it is sent. +- what practical problems an automatic notification sent to your contacts when you read the message solves for you compared with you simply adding a reaction to a message or sending a quick reply? + +Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addicitve patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. + +We think that delivery receipts are important and equally benefit both sides as the conversation, as they confirm that communication network functions properly. But we strongly believe that read receipts is an anti-feature that only benefits the app developers, and hurts the relations between the app users. So we are not planning to add it even as opt-in. In case you want your contact to know you've read the message put a reaction to it. And if you don't want them to know it - it is also ok, what your device sends should be under your control. ### Can I use the same profile on desktop? Do messages sync cross-platform? You can use your profile from mobile device on desktop. However, to do so you need to be on the same network, both on your mobile and desktop. More about it: [Release info](../blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md#link-mobile-and-desktop-apps-via-secure-quantum-resistant-protocol). +### Why cannot I delete messages I sent from my contact's device? + +In SimpleX Chat, you and your contacts can delete the messages you send from recipients' devices if you both agree to that within 24 hours of sending it. To be able to do that you both have to enable "Delete for everyone" option in Contact preferences - tap on the contact's name above the conversation to get there. + +You can also revoke the files you send. If the recipients did not yet receive the file, they will not be able to receive it after the file is revoked. + +This is different from most other messengers that allow deleting messages from the recipients' devices without any agreement with the recipients. + +We believe that allowing deleting information from your device to your contacts is a very wrong design decision for several reasons: +1) it violates your data sovereignty as the device owner - once your are in possession of any information, you have the rights to retain it, and any deletion should be agreed with you. And security and privacy is not possible if users don't have sovereignty over their devices. +2) it may be a business communication, and either your organisation policy or a compliance requirement is that every message you receive must be preserved for some time. +3) the message can contain a legally binding promise, effectively a contract between you and your contact, in which case you both need to keep it. +4) the messages may contain threat or abuse and you may want to keep them as a proof. +5) you may have paid for the the message (e.g., it can be a design project or consulting report), and you don't want it to suddenly disappear before you had a chance to store it outside of the conversation. + +It is also important to remember, that even if your contact enabled "Delete for everyone", you cannot really see it as a strong guarantee that the message will be deleted. Your contact's app can have a very simple modification (a one-line code change), that would prevent this deletion from happening when you request it. So you cannot see it as something that guarantees your security from your contacts. + +When "Delete for everyone" is not enabled, you can still mark the sent message as deleted within 24 hours of sending it. In this case the recipient will see it as "deleted message", and will be able to reveal the original message. + ## Troubleshooting ### I do not receive messages or message notifications From 7a5b04d523f3cede54919eb93be0df50a169bc8d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 31 Aug 2024 11:39:43 +0100 Subject: [PATCH 024/704] faq: private message routing (#4807) * faq: private message routing * readme * corrections --- README.md | 34 ++++++----- ...5.8-private-message-routing-chat-themes.md | 3 +- docs/FAQ.md | 60 +++++++++++++++++-- 3 files changed, 74 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1a500187c0..e62fb8c8b3 100644 --- a/README.md +++ b/README.md @@ -300,25 +300,27 @@ What is already implemented: 1. Instead of user profile identifiers used by all other platforms, even the most private ones, SimpleX uses [pairwise per-queue identifiers](./docs/GLOSSARY.md#pairwise-pseudonymous-identifier) (2 addresses for each unidirectional message queue, with an optional 3rd address for push notifications on iOS, 2 queues in each connection between the users). It makes observing the network graph on the application level more difficult, as for `n` users there can be up to `n * (n-1)` message queues. 2. [End-to-end encryption](./docs/GLOSSARY.md#end-to-end-encryption) in each message queue using [NaCl cryptobox](https://nacl.cr.yp.to/box.html). This is added to allow redundancy in the future (passing each message via several servers), to avoid having the same ciphertext in different queues (that would only be visible to the attacker if TLS is compromised). The encryption keys used for this encryption are not rotated, instead we are planning to rotate the queues. Curve25519 keys are used for key negotiation. 3. [Double ratchet](./docs/GLOSSARY.md#double-ratchet-algorithm) end-to-end encryption in each conversation between two users (or group members). This is the same algorithm that is used in Signal and many other messaging apps; it provides OTR messaging with [forward secrecy](./docs/GLOSSARY.md#forward-secrecy) (each message is encrypted by its own ephemeral key) and [break-in recovery](./docs/GLOSSARY.md#post-compromise-security) (the keys are frequently re-negotiated as part of the message exchange). Two pairs of Curve448 keys are used for the initial [key agreement](./docs/GLOSSARY.md#key-agreement-protocol), initiating party passes these keys via the connection link, accepting side - in the header of the confirmation message. -4. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). -5. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. -6. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. -7. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. -8. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. -9. To protect your IP address all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. -10. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. -11. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. -12. Manual messaging queue rotations to move conversation to another SMP relay. -13. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). -14. Local files encryption. +4. [Post-quantum resistant key exchange](./docs/GLOSSARY.md#post-quantum-cryptography) in double ratchet protocol *on every ratchet step*. Read more in [this post](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) and also see this [publication by Apple]( https://security.apple.com/blog/imessage-pq3/) explaining the need for post-quantum key rotation. +5. Additional layer of encryption using NaCL cryptobox for the messages delivered from the server to the recipient. This layer avoids having any ciphertext in common between sent and received traffic of the server inside TLS (and there are no identifiers in common as well). +6. Several levels of [content padding](./docs/GLOSSARY.md#message-padding) to frustrate message size attacks. +7. All message metadata, including the time when the message was received by the server (rounded to a second) is sent to the recipients inside an encrypted envelope, so even if TLS is compromised it cannot be observed. +8. Only TLS 1.2/1.3 are allowed for client-server connections, limited to cryptographic algorithms: CHACHA20POLY1305_SHA256, Ed25519/Ed448, Curve25519/Curve448. +9. To protect against replay attacks SimpleX servers require [tlsunique channel binding](https://www.rfc-editor.org/rfc/rfc5929.html) as session ID in each client command signed with per-queue ephemeral key. +10. To protect your IP address from unknown messaging relays, and for per-message transport anonymity (compared with Tor/VPN per-connection anonymity), from v6.0 all SimpleX Chat clients use private message routing by default. Read more in [this post](./blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing). +11. To protect your IP address from unknown file relays, when SOCKS proxy is not enabled SimpleX Chat clients ask for a confirmation before downloading the files from unknown servers. +12. To protect your IP address from known servers all SimpleX Chat clients support accessing messaging servers via Tor - see [v3.1 release announcement](./blog/20220808-simplex-chat-v3.1-chat-groups.md) for more details. +13. Local database encryption with passphrase - your contacts, groups and all sent and received messages are stored encrypted. If you used SimpleX Chat before v4.0 you need to enable the encryption via the app settings. +14. Transport isolation - different TCP connections and Tor circuits are used for traffic of different user profiles, optionally - for different contacts and group member connections. +15. Manual messaging queue rotations to move conversation to another SMP relay. +16. Sending end-to-end encrypted files using [XFTP protocol](https://simplex.chat/blog/20230301-simplex-file-transfer-protocol.html). +17. Local files encryption. We plan to add: -1. Senders' SMP relays and recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. -2. Post-quantum resistant key exchange in double ratchet protocol. -3. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). -4. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. -5. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +1. Automatic message queue rotation and redundancy. Currently the queues created between two users are used until the queue is manually changed by the user or contact is deleted. We are planning to add automatic queue rotation to make these identifiers temporary and rotate based on some schedule TBC (e.g., every X messages, or every X hours/days). +2. Message "mixing" - adding latency to message delivery, to protect against traffic correlation by message time. +3. Reproducible builds – this is the limitation of the development stack, but we will be investing into solving this problem. Users can still build all applications and services from the source code. +4. Recipients' XFTP relays to reduce traffic and conceal IP addresses from the relays chosen, and potentially controlled, by another party. ## For developers diff --git a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md index 9e915bd3c4..0519e78e7b 100644 --- a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md +++ b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md @@ -96,8 +96,9 @@ The diagram below shows all the encryption layers used in private message routin For private routing to work, both the forwardig and the destination relays should support the updated messaging protocol - it is supported from v5.8 of the messaging relays. It is already released to all relays preset in the app, and available as a self-hosted server. We updated [the guide](../docs/SERVER.md) about how to host your own messaging relays. Because many self-hosted relays did not upgrade yet, private routing is not enabled by default. To enable it, you can open *Network & servers* settings in the app and change the settings in *Private message routing* section. We recommend setting *Private routing* option to *Unprotected* (to use it only with unknown relays and when not connecting via Tor) and *Allow downgrade* to *Yes* (so messages can still be delivered to the messaging relays that didn't upgrade yet) or to *When IP hidden* (in which case the messages will fail to deliver to unknown relays that didn't upgrade yet unless you connect to them via Tor). +See [F.A.Q. section](../docs/FAQ.md#does-simplex-protect-my-ip-address) for answers about private message routing. -Read more about the technical design of the private message routing in [this document](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2023-09-12-second-relays.md). +Read more about the technical design of the private message routing in [this document](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/done/2023-09-12-second-relays.md) and in [the messaging protocol specification](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/simplex-messaging.md#proxying-sender-commands). ## Server transparency diff --git a/docs/FAQ.md b/docs/FAQ.md index 774054e58c..d8a8d5938f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -32,6 +32,8 @@ revision: 23.04.2024 - [Does SimpleX support post quantum cryptography?](#does-simplex-support-post-quantum-cryptography) - [What user data can be provided on request?](#what-user-data-can-be-provided-on-request) - [Does SimpleX protect my IP address?](#does-simplex-protect-my-ip-address) +- [Doesn't private message routing reinvent Tor?](#doesnt-private-message-routing-reinvent-tor) +- [Why don't you embed Tor in SimpleX Chat app?](#why-dont-you-embed-tor-in-simplex-chat-app) - [Can I host my own relays?](#can-i-host-my-own-relays) [Funding and business model](#funding-and-business-model) @@ -91,7 +93,7 @@ The important questions for this feature: - do you always want that your contacts can see when you read all their messages? Probably, even with your close friends, sometimes you would prefer to have time before you answer their message, and also have a plausible deniability that you have not seen the message. And this should be ok - in the end, this is your device, and it should be for you to decide whether this confirmation message is sent or not, and when it is sent. - what practical problems an automatic notification sent to your contacts when you read the message solves for you compared with you simply adding a reaction to a message or sending a quick reply? -Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addicitve patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. +Overall, it seems that this feature is more damaging to your communications with your contacts than it is helpful. It keeps senders longer in the app, nervously waiting for read receipts, exploiting addictive patterns - having you spend more time in the app is the reason why it is usually present in most messaging apps. It also creates a pressure on the recipients to reply sooner, and if read receipts are opt-in, it creates a pressure to enable it, that can be particularly damaging in any relationships with power imbalance. We think that delivery receipts are important and equally benefit both sides as the conversation, as they confirm that communication network functions properly. But we strongly believe that read receipts is an anti-feature that only benefits the app developers, and hurts the relations between the app users. So we are not planning to add it even as opt-in. In case you want your contact to know you've read the message put a reaction to it. And if you don't want them to know it - it is also ok, what your device sends should be under your control. @@ -109,7 +111,7 @@ This is different from most other messengers that allow deleting messages from t We believe that allowing deleting information from your device to your contacts is a very wrong design decision for several reasons: 1) it violates your data sovereignty as the device owner - once your are in possession of any information, you have the rights to retain it, and any deletion should be agreed with you. And security and privacy is not possible if users don't have sovereignty over their devices. -2) it may be a business communication, and either your organisation policy or a compliance requirement is that every message you receive must be preserved for some time. +2) it may be a business communication, and either your organization policy or a compliance requirement is that every message you receive must be preserved for some time. 3) the message can contain a legally binding promise, effectively a contract between you and your contact, in which case you both need to keep it. 4) the messages may contain threat or abuse and you may want to keep them as a proof. 5) you may have paid for the the message (e.g., it can be a design project or consulting report), and you don't want it to suddenly disappear before you had a chance to store it outside of the conversation. @@ -208,7 +210,7 @@ To determine whether it is the limitation of your, your contact's or both device - if it is shown on your screen as soon as you start the call, then your device does not support call encryption. - if in the beginning of the call your device shows "e2e encryption" but when your contact accepts the call it changes to "no e2e encryption", then it is only your contact's device that does not support it. -You need to upgrade webview (some Android systems allow it), Android system or the device to have support for e2e encryption in the calls - all modern webviews (and browsers) support it. +You need to upgrade webview (some Android systems allow it), Android system or the device to have support for e2e encryption in the calls - all modern WebViews (and browsers) support it. ### I clicked the link to connect, but could not connect @@ -232,9 +234,55 @@ Please see our [Privacy Policy](../PRIVACY.md) and [Transparency Reports](./TRAN ### Does SimpleX protect my IP address? -Not fully yet, it is a work in progress. While your device does not connect to your contacts' devices directly, as it happens in p2p networks, your contacts can self-host their relays, and you will connect to them when sending messages. A modified relay can record IP addresses connecting devices, as is the case with any other server, including Tor entry nodes, VPN providers, etc. - IP address is fundamental to Internet functioning, and there will always be some server that can observe your IP address. +Yes! -We are currently working on the next version of message routing protocol that will protect your IP address from the relays chosen by your contacts, so it will only be visible to the relays chosen by you. Read about technical details here: [RFC](https://github.com/simplex-chat/simplexmq/blob/stable/rfcs/2023-09-12-second-relays.md). +SimpleX Chat from version 6.0 uses *private message routing* whenever you send messages to unknown servers (all servers in app network settings, both enabled and not, are considered "known"). + +For private routing to work, the servers chosen by your contacts (and by the group members in your groups) must be upgraded to the recent versions. Messaging servers include support for private routing from v5.8, but we recommend using the latest versions. + +If the servers didn't upgrade, the messages would temporarily fail to deliver. You will see an orange warning icon on the message, and you can decide if you want to deliver them by connecting to these servers directly (it would require changing network settings). At the time of writing (August 2024), all preset servers and absolute majority of self-hosted servers we can see on the network support private message routing. + +With private routing enabled, instead of connecting to your contact's server directly, your client would "instruct" one of the known servers to forward the message, preventing the destination server from observing your IP address. + +Your messages are additionally end-to-end encrypted between your client and the destination server, so that the forwarding server cannot observe the destination addresses and server responses – similarly to how onion routing work. Private message routing is, effectively, a two-hop onion packet routing. + +Also, this connection is protected from man-in-the-middle attack by the forwarding server, as your client will validate destination server certificate using its fingerprint in the server address. + +You can optionally enable private message routing for all servers in Advanced network settings to complicate traffic correlation for known servers too. This will be default once the clients are improved to "know about" and to take into account network server operators. + +See [this post](../blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md#private-message-routing) for more details about how private message routing works. + +### Doesn't private message routing reinvent Tor? + +No, it does not! + +It provides better privacy for messaging than Tor, and it can be used with and without Tor or other means to additionally protect your traffic from known servers as well. + +Tor, VPN and other transport overlay networks route sockets, by creating long-lived TCP circuits between you and the destination server. While it protects your IP address, it does not protect your activity within this circuit. E.g., if you visit a website via Tor, it can still observe all pages you view within a session. Likewise, if you were connecting directly to a messaging server via Tor, this server would be able to list all message queues you send messages to. + +Private message routing routes packets (each message is one 16kb packet), not sockets. Unlike Tor and VPN, it does not create circuits between your client and destination servers. The forwarding server creates one shared session between itself and the destination, and forwards all messages from you and other clients to that destination server, mixing messages from many clients into a single TCP session. + +As each message uses its own random encryption key and random (non-sequential) identifier, the destination server cannot link multiple message queue addresses to the same client. At the same time, the forwarding server cannot observe which (and how many) addresses on the destination server your client sends messages to, thanks to e2e encryption between the client and destination server. In that regard, this design is similar to onion routing, but with per-packet anonymity, not per-circuit. + +This design is similar to mixnets (e.g. [Nym network](https://nymtech.net)), and it is tailored to the needs of message routing, providing better transport anonymity that general purpose networks, like Tor or VPN. You still can use Tor or VPN to connect to known servers, to protect your IP address from them. + +### Why don't you embed Tor in SimpleX Chat app? + +[Tor](https://www.torproject.org) is a fantastic transport overlay network - we believe it might be the best there is right now. If its [threat model](https://support.torproject.org/about/attacks-on-onion-routing/) works for you, you absolutely should use it - SimpleX Chat app supports Tor via SOCKS proxy [since v3.1](https://simplex.chat/blog/20220808-simplex-chat-v3.1-chat-groups.html#access-messaging-servers-via-tor), and SimpleX network servers can be available on both public and onion address at the same time [since v3.2](https://simplex.chat/blog/20220901-simplex-chat-v3.2-incognito-mode.html#using-onion-server-addresses-with-tor), improving anonymity of the users who use Tor. + +If you host your messaging server on the onion address only, the users who don't use Tor would still be able to message you via private message routing - all preset servers are configured to forward messages to onion-only servers. + +But there are many reasons not to embed Tor in the app: +- it increases response latency, error rate, and battery usage, and we believe that for most users enabling Tor by default would be a bad trade-off. +- it would require us regularly updating Tor library in the app, and your Tor integrity would depend on us – you would be "putting too many eggs in one basket". +- some networks restrict Tor traffic, so the app UI would have to support advanced Tor configuration, diverting our limited resources from the core app features that benefit all users. +- some countries have legislative restrictions on Tor usage, so we would have to support multiple app versions, also increasing our costs and slowing down the progress. + +The last, but not the least, it would create an unfair competitive advantage to Tor. We believe in competition, and we want our users to be able to choose which transport overlay network to use, based on what network threat model works best for them. + +If you want to use Tor or any other overlay network, such as i2p, [Nym network](https://nymtech.net), [Katzenpost](https://katzenpost.network), etc., you need to research their limitations, because none of them provides absolute anonymity against all possible attackers. + +And if after that research you decide to use Tor, it takes about 2 minutes to install and start [Orbot app](https://guardianproject.info/apps/org.torproject.android/). We believe that if it seems complex, then you *should not* be using Tor - it is an advanced technology that can only improve your privacy and anonymity if you understand its limitations and know how to configure it. ### Can I host my own relays? @@ -244,7 +292,7 @@ Of course! Please check these tutorials: [SMP server](./SERVER.md) and [XFTP ser ### How are you funded? -SimpleX Chat Ltd is funded by private investors and venture capital. As an open-source project, it is also being generously supported by donations as well. Read [more details](../blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md#how-is-it-funded-and-what-is-the-business-model). +SimpleX Chat Ltd is funded by private investors and venture capital. As an open-source project, it is also being generously supported by donations as well. Read the posts [from 2023](../blog/20230422-simplex-chat-vision-funding-v5-videos-files-passcode.md#how-is-it-funded-and-what-is-the-business-model) and [from 2024](../blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.md) for more details. ### Why VCs? From d68a3ba80d356815c53d1910be434c3d5f3a995c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 31 Aug 2024 11:58:49 +0100 Subject: [PATCH 025/704] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 43 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 3 +- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 03c1108cd0..3700fbbf16 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -214,11 +214,11 @@ 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 */; }; - E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED58F2C7B9983009F2C7C /* libgmpxx.a */; }; - E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */; }; - E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5912C7B9983009F2C7C /* libgmp.a */; }; - E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */; }; - E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E51ED5932C7B9983009F2C7C /* libffi.a */; }; + E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84522C832BF9008C24D1 /* libgmpxx.a */; }; + E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */; }; + E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */; }; + E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84552C832BF9008C24D1 /* libgmp.a */; }; + E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84562C832BF9008C24D1 /* libffi.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 */; }; @@ -550,11 +550,11 @@ 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 = ""; }; - E51ED58F2C7B9983009F2C7C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a"; sourceTree = ""; }; - E51ED5912C7B9983009F2C7C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a"; sourceTree = ""; }; - E51ED5932C7B9983009F2C7C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E5BD84522C832BF9008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a"; sourceTree = ""; }; + E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a"; sourceTree = ""; }; + E5BD84552C832BF9008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E5BD84562C832BF9008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.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 = ""; }; @@ -645,17 +645,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51ED5942C7B9983009F2C7C /* libgmpxx.a in Frameworks */, + E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */, + E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */, + E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */, + E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E51ED5982C7B9983009F2C7C /* libffi.a in Frameworks */, - E51ED5972C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a in Frameworks */, - E51ED5952C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a in Frameworks */, - E51ED5962C7B9983009F2C7C /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */, - E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */, - E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -732,11 +729,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E51ED5932C7B9983009F2C7C /* libffi.a */, - E51ED5912C7B9983009F2C7C /* libgmp.a */, - E51ED58F2C7B9983009F2C7C /* libgmpxx.a */, - E51ED5902C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx-ghc9.6.3.a */, - E51ED5922C7B9983009F2C7C /* libHSsimplex-chat-6.1.0.0-2HbUlAtNXgRGMjFy4vK7lx.a */, + E5BD84562C832BF9008C24D1 /* libffi.a */, + E5BD84552C832BF9008C24D1 /* libgmp.a */, + E5BD84522C832BF9008C24D1 /* libgmpxx.a */, + E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */, + E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 22312bf5a1..c8623a95cb 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "branch" : "master", - "revision" : "7c50eb60ca4b90043c6ad719d595803488496212" + "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" } }, { From f94f0dea082bea18a8c4152a38066e47c731bb81 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 31 Aug 2024 14:34:46 +0100 Subject: [PATCH 026/704] website: fix links --- docs/TRANSPARENCY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TRANSPARENCY.md b/docs/TRANSPARENCY.md index 43fdd12ac5..bae7a4f781 100644 --- a/docs/TRANSPARENCY.md +++ b/docs/TRANSPARENCY.md @@ -17,8 +17,8 @@ This page will include any and all reports on requests for user data. Our objective is to consistently ensure that no user data and absolute minimum of the metadata required for the network to function is available for disclosure by any infrastructure operators, under any circumstances. **Helpful resources**: -- [Privacy policy](../PRIVACY.md) -- [Privacy and security: technical details and limitations](../README.md#privacy-and-security-technical-details-and-limitations) +- [Privacy policy](/PRIVACY.md) +- [Privacy and security: technical details and limitations](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#privacy-and-security-technical-details-and-limitations) - Whitepaper: - [Trust in servers](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#trust-in-servers) - [Encryption Primitives Used](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#encryption-primitives-used) From 3b49817f173a0de33638c4f6ed703545420f9f0a Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sat, 31 Aug 2024 17:24:50 +0000 Subject: [PATCH 027/704] desktop: fix vlc dependency (#4809) --- apps/multiplatform/common/build.gradle.kts | 2 +- apps/multiplatform/desktop/build.gradle.kts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index a1afb655e2..1670672753 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -102,7 +102,7 @@ kotlin { implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT") implementation("org.slf4j:slf4j-simple:2.0.12") - implementation("uk.co.caprica:vlcj:4.8.2") + implementation("uk.co.caprica:vlcj:4.8.3") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index 401c2938d5..e39ba48a0b 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -18,7 +18,6 @@ kotlin { dependencies { implementation(project(":common")) implementation(compose.desktop.currentOs) - implementation("net.java.dev.jna:jna:5.14.0") } } val jvmTest by getting From 33895b0330fd9e32dc9bc1e69bc15d54a9207cb2 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Tue, 3 Sep 2024 09:59:40 +0300 Subject: [PATCH 028/704] ios: show received messages using checkmark with slash (#4816) * ios: show received messages using checkmark with slash * update message info view * cleanup * remove dead arguments * Revert "remove dead arguments" This reverts commit 1fc07669c785dde960c77ee54026004871706acf. * remove status icon * cleanup * update assets * tweak checkmark * fix space, rename --------- Co-authored-by: Evgeny Poberezkin --- .../checkmark.2.symbolset/Contents.json | 12 + .../checkmark.2.symbolset/checkmark.2.svg | 227 ++++++++++++++++++ .../checkmark.wide.symbolset/Contents.json | 12 + .../checkmark.wide.svg | 218 +++++++++++++++++ .../Views/Chat/ChatItem/CIMetaView.swift | 57 ++--- .../Shared/Views/Chat/ChatItemInfoView.swift | 16 +- apps/ios/SimpleXChat/ChatTypes.swift | 54 +++-- 7 files changed, 514 insertions(+), 82 deletions(-) create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json create mode 100644 apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json new file mode 100644 index 0000000000..8e38b499dd --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info": { + "author": "xcode", + "version": 1 + }, + "symbols": [ + { + "filename": "checkmark.2.svg", + "idiom": "universal" + } + ] +} \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg new file mode 100644 index 0000000000..577fa1db76 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.2.symbolset/checkmark.2.svg @@ -0,0 +1,227 @@ + + + checkmark.2 + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + + can be adjusted by modifying the x-location of the margin guidelines. + + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.5.0 + + + Requires Xcode 15 or greater + + + Generated from double.checkmark + + + Typeset at 100.0 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json new file mode 100644 index 0000000000..11a91cb811 --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info": { + "author": "xcode", + "version": 1 + }, + "symbols": [ + { + "filename": "checkmark.wide.svg", + "idiom": "universal" + } + ] +} \ No newline at end of file diff --git a/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg new file mode 100644 index 0000000000..b5dfc6b3de --- /dev/null +++ b/apps/ios/Shared/Assets.xcassets/checkmark.wide.symbolset/checkmark.wide.svg @@ -0,0 +1,218 @@ + + + checkmark.wide + + + + + + + Weight/Scale Variations + + + Ultralight + + + Thin + + + Light + + + Regular + + + Medium + + + Semibold + + + Bold + + + Heavy + + + Black + + + + + + + + + + + + + Design Variations + + + Symbols are supported in up to nine weights and three scales. + + + For optimal layout with text and other symbols, vertically align + + + symbols with the adjacent text. + + + + + + + + + Margins + + + Leading and trailing margins on the left and right side of each symbol + + + + can be adjusted by modifying the x-location of the margin guidelines. + + + + Modifications are automatically applied proportionally to all + + + scales and weights. + + + + + + Exporting + + + Symbols should be outlined when exporting to ensure the + + + design is preserved when submitting to Xcode. + + + Template v.5.0 + + + Requires Xcode 15 or greater + + + Generated from double.checkmark + + + Typeset at 100.0 points + + + Small + + + Medium + + + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 3f2ec8f38e..719c1cabd0 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -25,41 +25,22 @@ struct CIMetaView: View { if chatItem.isDeletedContent { chatItem.timestampText.font(.caption).foregroundColor(metaColor) } else { - let meta = chatItem.meta - let ttl = chat.chatInfo.timedMessagesTTL - let encrypted = chatItem.encryptedFile - switch meta.itemStatus { - case let .sndSent(sndProgress): - switch sndProgress { - case .complete: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - case .partial: ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .sent, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } - case let .sndRcvd(_, sndProgress): - switch sndProgress { - case .complete: - ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } - case .partial: - ZStack { - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd1, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: paleMetaColor, sent: .rcvd2, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } - } - default: - ciMetaText(meta, chatTTL: ttl, encrypted: encrypted, color: metaColor, showStatus: showStatus, showEdited: showEdited, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) - } + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + color: chatItem.meta.itemStatus.sndProgress == .partial + ? paleMetaColor + : metaColor, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ) } } } -enum SentCheckmark { - case sent - case rcvd1 - case rcvd2 -} - func ciMetaText( _ meta: CIMeta, chatTTL: Int?, @@ -67,7 +48,6 @@ func ciMetaText( color: Color = .clear, primaryColor: Color = .accentColor, transparent: Bool = false, - sent: SentCheckmark? = nil, showStatus: Bool = true, showEdited: Bool = true, showViaProxy: Bool, @@ -89,17 +69,8 @@ func ciMetaText( r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2) } if showStatus { - if let (icon, statusColor) = meta.statusIcon(color, primaryColor) { - let t = Text(Image(systemName: icon)).font(.caption2) - let gap = Text(" ").kerning(-1.25) - let t1 = t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) - switch sent { - case nil: r = r + t1 - case .sent: r = r + t1 + gap - case .rcvd1: r = r + t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67)) + gap - case .rcvd2: r = r + gap + t1 - } - r = r + Text(" ") + if let (image, statusColor) = meta.itemStatus.statusIcon(color, primaryColor) { + r = r + Text(image).foregroundColor(transparent ? .clear : statusColor) + Text(" ") } else if !meta.disappearing { r = r + statusIconText("circlebadge.fill", .clear) + Text(" ") } diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index f6a856dad1..62ea607d27 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -450,20 +450,8 @@ struct ChatItemInfoView: View { .foregroundColor(theme.colors.secondary).opacity(0.67) } let v = Group { - let (icon, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) - switch status { - case .rcvd: - ZStack(alignment: .trailing) { - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - .padding(.trailing, 6) - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - } - default: - Image(systemName: icon) - .foregroundColor(statusColor) - } + let (image, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) + image.foregroundColor(statusColor) } if let (title, text) = status.statusInfo { diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 07340bb963..105edd725f 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2723,10 +2723,6 @@ public struct CIMeta: Decodable, Hashable { return false } - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color)? { - itemStatus.statusIcon(metaColor, primaryColor) - } - public static func getSample(_ id: Int64, _ ts: Date, _ text: String, _ status: CIStatus = .sndNew, itemDeleted: CIDeleted? = nil, itemEdited: Bool = false, itemLive: Bool = false, deletable: Bool = true, editable: Bool = true) -> CIMeta { CIMeta( itemId: id, @@ -2794,21 +2790,29 @@ public enum CIStatus: Decodable, Hashable { } } - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color)? { + public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { - case .sndNew: return nil - case .sndSent: return ("checkmark", metaColor) + case .sndNew: nil + case .sndSent: (Image("checkmark.wide"), metaColor) case let .sndRcvd(msgRcptStatus, _): switch msgRcptStatus { - case .ok: return ("checkmark", metaColor) - case .badMsgHash: return ("checkmark", .red) + case .ok: (Image("checkmark.2"), metaColor) + case .badMsgHash: (Image("checkmark.2"), .red) } - case .sndErrorAuth: return ("multiply", .red) - case .sndError: return ("multiply", .red) - case .sndWarning: return ("exclamationmark.triangle.fill", .orange) - case .rcvNew: return ("circlebadge.fill", primaryColor) - case .rcvRead: return nil - case .invalid: return ("questionmark", metaColor) + case .sndErrorAuth: (Image(systemName: "multiply"), .red) + case .sndError: (Image(systemName: "multiply"), .red) + case .sndWarning: (Image(systemName: "exclamationmark.triangle.fill"), .orange) + case .rcvNew: (Image(systemName: "circlebadge.fill"), primaryColor) + case .rcvRead: nil + case .invalid: (Image(systemName: "questionmark"), metaColor) + } + } + + public var sndProgress: SndCIStatusProgress? { + switch self { + case let .sndSent(sndProgress): sndProgress + case let .sndRcvd(_ , sndProgress): sndProgress + default: nil } } @@ -2903,20 +2907,20 @@ public enum GroupSndStatus: Decodable, Hashable { case warning(agentError: SndError) case invalid(text: String) - public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color) { + public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color) { switch self { - case .new: return ("ellipsis", metaColor) - case .forwarded: return ("chevron.forward.2", metaColor) - case .inactive: return ("person.badge.minus", metaColor) - case .sent: return ("checkmark", metaColor) + case .new: (Image(systemName: "ellipsis"), metaColor) + case .forwarded: (Image(systemName: "chevron.forward.2"), metaColor) + case .inactive: (Image(systemName: "person.badge.minus"), metaColor) + case .sent: (Image("checkmark.wide"), metaColor) case let .rcvd(msgRcptStatus): switch msgRcptStatus { - case .ok: return ("checkmark", metaColor) - case .badMsgHash: return ("checkmark", .red) + case .ok: (Image("checkmark.2"), metaColor) + case .badMsgHash: (Image("checkmark.2"), .red) } - case .error: return ("multiply", .red) - case .warning: return ("exclamationmark.triangle.fill", .orange) - case .invalid: return ("questionmark", metaColor) + case .error: (Image(systemName: "multiply"), .red) + case .warning: (Image(systemName: "exclamationmark.triangle.fill"), .orange) + case .invalid: (Image(systemName: "questionmark"), metaColor) } } From 71bea947a5ce2a194ceda050742901712ed26e61 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 4 Sep 2024 14:49:01 +0100 Subject: [PATCH 029/704] ios: cache base64 images (#4827) --- apps/ios/Shared/Theme/Theme.swift | 2 +- .../Views/Chat/ChatItem/CILinkView.swift | 2 +- .../Views/Chat/ChatItem/FramedItemView.swift | 4 ++-- apps/ios/Shared/Views/Chat/ChatItemView.swift | 2 +- .../ComposeMessage/ComposeImageView.swift | 2 +- .../Chat/ComposeMessage/ComposeLinkView.swift | 2 +- .../Views/ChatList/ChatPreviewView.swift | 6 +++--- .../Shared/Views/Helpers/ProfileImage.swift | 2 +- apps/ios/SimpleX SE/ShareModel.swift | 2 +- apps/ios/SimpleX SE/ShareView.swift | 6 +++--- apps/ios/SimpleXChat/ImageUtils.swift | 20 ++++++++++++++++--- 11 files changed, 32 insertions(+), 18 deletions(-) diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift index e2641eb8dd..53f2931d16 100644 --- a/apps/ios/Shared/Theme/Theme.swift +++ b/apps/ios/Shared/Theme/Theme.swift @@ -102,7 +102,7 @@ extension ThemeWallpaper { public func importFromString() -> ThemeWallpaper { if preset == nil, let image { // Need to save image from string and to save its path - if let parsed = UIImage(base64Encoded: image), + if let parsed = imageFromBase64(image), let filename = saveWallpaperFile(image: parsed) { var copy = self copy.image = nil diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 3c864ab172..692e6bb8a6 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -16,7 +16,7 @@ struct CILinkView: View { var body: some View { VStack(alignment: .center, spacing: 6) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 260ac64e43..d657d79a4f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -185,7 +185,7 @@ struct FramedItemView: View { let v = ZStack(alignment: .topTrailing) { switch (qi.content) { case let .image(_, image): - if let uiImage = UIImage(base64Encoded: image) { + if let uiImage = imageFromBase64(image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) @@ -197,7 +197,7 @@ struct FramedItemView: View { ciQuotedMsgView(qi) } case let .video(_, image, _): - if let uiImage = UIImage(base64Encoded: image) { + if let uiImage = imageFromBase64(image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index d444ce0735..bf09d15ff1 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -72,7 +72,7 @@ struct ChatItemView: View { default: nil } } - .flatMap { UIImage(base64Encoded: $0) } + .flatMap { imageFromBase64($0) } let adjustedMaxWidth = { if let preview, preview.size.width <= preview.size.height { maxWidth * 0.75 diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift index df3a8caf55..14026d79d1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift @@ -18,7 +18,7 @@ struct ComposeImageView: View { var body: some View { HStack(alignment: .center, spacing: 8) { let imgs: [UIImage] = images.compactMap { image in - UIImage(base64Encoded: image) + imageFromBase64(image) } if imgs.count == 0 { ProgressView() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index f7f1a89299..6c44aeea83 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -40,7 +40,7 @@ struct ComposeLinkView: View { private func linkPreviewView(_ linkPreview: LinkPreview) -> some View { HStack(alignment: .center, spacing: 8) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 43892ec469..cf9977860d 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -302,7 +302,7 @@ struct ChatPreviewView: View { case let .link(_, preview): smallContentPreview(size: dynamicMediaSize) { ZStack(alignment: .topTrailing) { - Image(uiImage: UIImage(base64Encoded: preview.image) ?? UIImage(systemName: "arrow.up.right")!) + Image(uiImage: imageFromBase64(preview.image) ?? UIImage(systemName: "arrow.up.right")!) .resizable() .aspectRatio(contentMode: .fill) .frame(width: dynamicMediaSize, height: dynamicMediaSize) @@ -323,12 +323,12 @@ struct ChatPreviewView: View { } case let .image(_, image): smallContentPreview(size: dynamicMediaSize) { - CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) + CIImageView(chatItem: ci, preview: imageFromBase64(image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) .environmentObject(ReverseListScrollModel()) } case let .video(_,image, duration): smallContentPreview(size: dynamicMediaSize) { - CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) + CIVideoView(chatItem: ci, preview: imageFromBase64(image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) .environmentObject(ReverseListScrollModel()) } case let .voice(_, duration): diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift index 248504c59b..3eedd56441 100644 --- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift +++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift @@ -20,7 +20,7 @@ struct ProfileImage: View { @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner var body: some View { - if let uiImage = UIImage(base64Encoded: imageStr) { + if let uiImage = imageFromBase64(imageStr) { clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred) } else { let c = color.asAnotherColorFromSecondaryVariant(theme) diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index f43548f676..e73aeee13c 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -104,7 +104,7 @@ class ShareModel: ObservableObject { // Decode base64 images on background thread let profileImages = chats.reduce(into: Dictionary()) { dict, chatData in if let profileImage = chatData.chatInfo.image, - let uiImage = UIImage(base64Encoded: profileImage) { + let uiImage = imageFromBase64(profileImage) { dict[chatData.id] = uiImage } } diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift index 1f502ffcff..f2b9de9f72 100644 --- a/apps/ios/SimpleX SE/ShareView.swift +++ b/apps/ios/SimpleX SE/ShareView.swift @@ -147,8 +147,8 @@ struct ShareView: View { } } - @ViewBuilder private func imagePreview(_ img: String) -> some View { - if let img = UIImage(base64Encoded: img) { + @ViewBuilder private func imagePreview(_ imgStr: String) -> some View { + if let img = imageFromBase64(imgStr) { previewArea { Image(uiImage: img) .resizable() @@ -163,7 +163,7 @@ struct ShareView: View { @ViewBuilder private func linkPreview(_ linkPreview: LinkPreview) -> some View { previewArea { HStack(alignment: .center, spacing: 8) { - if let uiImage = UIImage(base64Encoded: linkPreview.image) { + if let uiImage = imageFromBase64(linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index 67218a781e..2eb747c6a5 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -383,16 +383,30 @@ extension UIImage { } return self } +} - public convenience init?(base64Encoded: String?) { - if let base64Encoded, let data = Data(base64Encoded: dropImagePrefix(base64Encoded)) { - self.init(data: data) +public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { + if let base64Encoded { + if let img = imageCache.object(forKey: base64Encoded as NSString) { + return img + } else if let data = Data(base64Encoded: dropImagePrefix(base64Encoded)), + let img = UIImage(data: data) { + imageCache.setObject(img, forKey: base64Encoded as NSString) + return img } else { return nil } + } else { + return nil } } +private var imageCache: NSCache = { + var cache = NSCache() + cache.countLimit = 1000 + return cache +}() + public func getLinkPreview(url: URL, cb: @escaping (LinkPreview?) -> Void) { logger.debug("getLinkMetadata: fetching URL preview") LPMetadataProvider().startFetchingMetadata(for: url){ metadata, error in From ebb5d629e70d7d91d2e1a0f7b86075a294a40d56 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 4 Sep 2024 23:12:05 +0100 Subject: [PATCH 030/704] ios: attach share sheet to the topmost view controller (to support sharing from nested sheets) (#4830) --- apps/ios/Shared/Views/Helpers/ShareSheet.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 936c6cb3ab..88e4bffe9f 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -11,12 +11,18 @@ import SwiftUI func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { let keyWindowScene = UIApplication.shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene if let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first, - let presentedViewController = keyWindow.rootViewController?.presentedViewController ?? keyWindow.rootViewController { + let rootViewController = keyWindow.rootViewController { + // Find the top-most presented view controller + var topController = rootViewController + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) if let completed = completed { - let handler: UIActivityViewController.CompletionWithItemsHandler = { _,_,_,_ in completed() } - activityViewController.completionWithItemsHandler = handler - } - presentedViewController.present(activityViewController, animated: true) + activityViewController.completionWithItemsHandler = { _, _, _, _ in + completed() + } + } + topController.present(activityViewController, animated: true) } } From 1f3355921c1c186b11fc0032b57faab1ec886bc7 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 6 Sep 2024 14:36:54 +0300 Subject: [PATCH 031/704] ios: make message information on media readable (#4820) * ios: ensure legibility of elements rendered over media * reduce diff * match meta padding * material play background * remove circlebadge * progress circle * meta color modes * refactor * conditional space * fix * fix2 * fix3 * revert video buttons --------- Co-authored-by: Evgeny Poberezkin --- .../Chat/ChatItem/CIGroupInvitationView.swift | 4 +- .../Views/Chat/ChatItem/CIImageView.swift | 2 +- .../Views/Chat/ChatItem/CIMetaView.swift | 124 ++++++++++++++---- .../Chat/ChatItem/CIRcvDecryptionError.swift | 4 +- .../Views/Chat/ChatItem/CIVideoView.swift | 42 +++--- .../Views/Chat/ChatItem/FramedItemView.swift | 17 ++- .../Views/Chat/ChatItem/MsgContentView.swift | 2 +- .../Helpers/InvertedForegroundStyle.swift | 21 +++ apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/SimpleXChat/ChatTypes.swift | 17 +-- 10 files changed, 161 insertions(+), 76 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift index da859c1606..1a77b36d6f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift @@ -46,7 +46,7 @@ struct CIGroupInvitationView: View { .foregroundColor(inProgress ? theme.colors.secondary : chatIncognito ? .indigo : theme.colors.primary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } @@ -54,7 +54,7 @@ struct CIGroupInvitationView: View { ( groupInvitationText() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showStatus: false, showEdited: false, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) .overlay(DetermineWidth()) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index 3966d7e258..b06c6df48c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -165,9 +165,9 @@ struct CIImageView: View { private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View { Image(systemName: icon) .resizable() + .invertedForegroundStyle() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(.white) .padding(padding) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 719c1cabd0..9840b22fc8 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -18,6 +18,7 @@ struct CIMetaView: View { var paleMetaColor = Color(UIColor.tertiaryLabel) var showStatus = true var showEdited = true + var invertedMaterial = false @AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false @@ -25,18 +26,59 @@ struct CIMetaView: View { if chatItem.isDeletedContent { chatItem.timestampText.font(.caption).foregroundColor(metaColor) } else { - ciMetaText( - chatItem.meta, - chatTTL: chat.chatInfo.timedMessagesTTL, - encrypted: chatItem.encryptedFile, - color: chatItem.meta.itemStatus.sndProgress == .partial - ? paleMetaColor - : metaColor, - showStatus: showStatus, - showEdited: showEdited, - showViaProxy: showSentViaProxy, - showTimesamp: showTimestamp - ) + ZStack { + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + color: metaColor, + paleColor: paleMetaColor, + colorMode: invertedMaterial + ? .invertedMaterial + : .normal, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ).invertedForegroundStyle(enabled: invertedMaterial) + if invertedMaterial { + ciMetaText( + chatItem.meta, + chatTTL: chat.chatInfo.timedMessagesTTL, + encrypted: chatItem.encryptedFile, + colorMode: .normal, + onlyOverrides: true, + showStatus: showStatus, + showEdited: showEdited, + showViaProxy: showSentViaProxy, + showTimesamp: showTimestamp + ) + } + } + } + } +} + +enum MetaColorMode { + // Renders provided colours + case normal + // Fully transparent meta - used for reserving space + case transparent + // Renders white on dark backgrounds and black on light ones + case invertedMaterial + + func resolve(_ c: Color?) -> Color? { + switch self { + case .normal: c + case .transparent: .clear + case .invertedMaterial: nil + } + } + + var statusSpacer: Text { + switch self { + case .normal, .transparent: Text(Image(systemName: "circlebadge.fill")).foregroundColor(.clear) + case .invertedMaterial: Text(" ").kerning(13) } } } @@ -45,47 +87,77 @@ func ciMetaText( _ meta: CIMeta, chatTTL: Int?, encrypted: Bool?, - color: Color = .clear, + color: Color = .clear, // we use this function to reserve space without rendering meta + paleColor: Color? = nil, primaryColor: Color = .accentColor, - transparent: Bool = false, + colorMode: MetaColorMode = .normal, + onlyOverrides: Bool = false, // only render colors that differ from base showStatus: Bool = true, showEdited: Bool = true, showViaProxy: Bool, showTimesamp: Bool ) -> Text { var r = Text("") + var space: Text? = nil + let appendSpace = { + if let sp = space { + r = r + sp + space = nil + } + } + let resolved = colorMode.resolve(color) if showEdited, meta.itemEdited { - r = r + statusIconText("pencil", color) + r = r + statusIconText("pencil", resolved) } if meta.disappearing { - r = r + statusIconText("timer", color).font(.caption2) + r = r + statusIconText("timer", resolved).font(.caption2) let ttl = meta.itemTimed?.ttl if ttl != chatTTL { - r = r + Text(shortTimeText(ttl)).foregroundColor(color) + r = r + colored(Text(shortTimeText(ttl)), resolved) } - r = r + Text(" ") + space = Text(" ") } if showViaProxy, meta.sentViaProxy == true { - r = r + statusIconText("arrow.forward", color.opacity(0.67)).font(.caption2) + appendSpace() + r = r + statusIconText("arrow.forward", resolved?.opacity(0.67)).font(.caption2) } if showStatus { - if let (image, statusColor) = meta.itemStatus.statusIcon(color, primaryColor) { - r = r + Text(image).foregroundColor(transparent ? .clear : statusColor) + Text(" ") + appendSpace() + if let (image, statusColor) = meta.itemStatus.statusIcon(color, paleColor ?? color, primaryColor) { + let metaColor = if onlyOverrides && statusColor == color { + Color.clear + } else { + colorMode.resolve(statusColor) + } + r = r + colored(Text(image), metaColor) + space = Text(" ") } else if !meta.disappearing { - r = r + statusIconText("circlebadge.fill", .clear) + Text(" ") + space = colorMode.statusSpacer + Text(" ") } } if let enc = encrypted { - r = r + statusIconText(enc ? "lock" : "lock.open", color) + Text(" ") + appendSpace() + r = r + statusIconText(enc ? "lock" : "lock.open", resolved) + space = Text(" ") } if showTimesamp { - r = r + meta.timestampText.foregroundColor(color) + appendSpace() + r = r + colored(meta.timestampText, resolved) } return r.font(.caption) } -private func statusIconText(_ icon: String, _ color: Color) -> Text { - Text(Image(systemName: icon)).foregroundColor(color) +private func statusIconText(_ icon: String, _ color: Color?) -> Text { + colored(Text(Image(systemName: icon)), color) +} + +// Applying `foregroundColor(nil)` breaks `.invertedForegroundStyle` modifier +private func colored(_ t: Text, _ color: Color?) -> Text { + if let color { + t.foregroundColor(color) + } else { + t + } } struct CIMetaView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index 9f721f83b7..c76ffe8c05 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -126,7 +126,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(syncSupported ? theme.colors.primary : theme.colors.secondary) .font(.callout) + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) ) } .padding(.horizontal, 12) @@ -145,7 +145,7 @@ struct CIRcvDecryptionError: View { .foregroundColor(.red) .italic() + Text(" ") - + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + + ciMetaText(chatItem.meta, chatTTL: nil, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } .padding(.horizontal, 12) CIMetaView(chat: chat, chatItem: chatItem, metaColor: theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 4670fc685f..851b90bc3d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -292,30 +292,22 @@ struct CIVideoView: View { .clipShape(Circle()) } - private func durationProgress() -> some View { - HStack { - Text("\(durationText(videoPlaying ? progress : duration))") - .foregroundColor(.white) - .font(.caption) - .padding(.vertical, 3) - .padding(.horizontal, 6) - .background(Color.black.opacity(0.35)) - .cornerRadius(10) - .padding([.top, .leading], 6) - - if let file = chatItem.file, !videoPlaying { - Text("\(ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary))") - .foregroundColor(.white) - .font(.caption) - .padding(.vertical, 3) - .padding(.horizontal, 6) - .background(Color.black.opacity(0.35)) - .cornerRadius(10) - .padding(.top, 6) - } + private var fileSizeString: String { + if let file = chatItem.file, !videoPlaying { + " " + ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) + } else { + "" } } + private func durationProgress() -> some View { + Text((durationText(videoPlaying ? progress : duration)) + fileSizeString) + .invertedForegroundStyle() + .font(.caption) + .padding(.vertical, 6) + .padding(.horizontal, 12) + } + private func imageView(_ img: UIImage) -> some View { let w = img.size.width <= img.size.height ? maxWidth * 0.75 : maxWidth return ZStack(alignment: .topTrailing) { @@ -411,9 +403,9 @@ struct CIVideoView: View { private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View { Image(systemName: icon) .resizable() + .invertedForegroundStyle() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(.white) .padding(smallView ? 0 : padding) } @@ -428,10 +420,8 @@ struct CIVideoView: View { private func progressCircle(_ progress: Int64, _ total: Int64) -> some View { Circle() .trim(from: 0, to: Double(progress) / Double(total)) - .stroke( - Color(uiColor: .white), - style: StrokeStyle(lineWidth: 2) - ) + .stroke(style: StrokeStyle(lineWidth: 2)) + .invertedForegroundStyle() .rotationEffect(.degrees(-90)) .frame(width: 16, height: 16) .padding([.trailing, .top], smallView ? 0 : 11) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index d657d79a4f..5f2930951f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -64,12 +64,17 @@ struct FramedItemView: View { .overlay(DetermineWidth()) } - if chatItem.content.msgContent != nil { - CIMetaView(chat: chat, chatItem: chatItem, metaColor: useWhiteMetaColor ? Color.white : theme.colors.secondary) - .padding(.horizontal, 12) - .padding(.bottom, 6) - .overlay(DetermineWidth()) - .accessibilityLabel("") + if let content = chatItem.content.msgContent { + CIMetaView( + chat: chat, + chatItem: chatItem, + metaColor: theme.colors.secondary, + invertedMaterial: useWhiteMetaColor + ) + .padding(.horizontal, 12) + .padding(.bottom, 6) + .overlay(DetermineWidth()) + .accessibilityLabel("") } } .background { chatItemFrameColorMaybeImageOrVideo(chatItem, theme).modifier(ChatTailPadding()) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 9cc4179723..63d5dc30dc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -85,7 +85,7 @@ struct MsgContentView: View { } private func reserveSpaceForMeta(_ mt: CIMeta) -> Text { - (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, transparent: true, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) + (rightToLeft ? Text("\n") : Text(" ")) + ciMetaText(mt, chatTTL: chat.chatInfo.timedMessagesTTL, encrypted: nil, colorMode: .transparent, showViaProxy: showSentViaProxy, showTimesamp: showTimestamp) } } diff --git a/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift b/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift new file mode 100644 index 0000000000..dca413dafe --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/InvertedForegroundStyle.swift @@ -0,0 +1,21 @@ +// +// Test.swift +// SimpleX (iOS) +// +// Created by Levitating Pineapple on 31/08/2024. +// Copyright Ā© 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +extension View { + @ViewBuilder + func invertedForegroundStyle(enabled: Bool = true) -> some View { + if enabled { + foregroundStyle(Material.ultraThin) + .environment(\.colorScheme, .dark) + .grayscale(1) + .contrast(-20) + } else { self } + } +} diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3700fbbf16..b4226d567b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -194,6 +194,7 @@ 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; 8CE848A32C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */; }; B76E6C312C5C41D900EC11AA /* ContactListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */; }; + CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */; }; CE1EB0E42C459A660099D896 /* ShareAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1EB0E32C459A660099D896 /* ShareAPI.swift */; }; CE2AD9CE2C452A4D00E844E3 /* ChatUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */; }; CE3097FB2C4C0C9F00180898 /* ErrorAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */; }; @@ -532,6 +533,7 @@ 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; 8CE848A22C5A0FA000D5C7C8 /* SelectableChatItemToolbars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableChatItemToolbars.swift; sourceTree = ""; }; B76E6C302C5C41D900EC11AA /* ContactListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListNavLink.swift; sourceTree = ""; }; + CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvertedForegroundStyle.swift; sourceTree = ""; }; CE1EB0E32C459A660099D896 /* ShareAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAPI.swift; sourceTree = ""; }; CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatUtils.swift; sourceTree = ""; }; CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; @@ -789,6 +791,7 @@ 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, CE7548092C622630009579B7 /* SwipeLabel.swift */, + CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, ); path = Helpers; sourceTree = ""; @@ -1489,6 +1492,7 @@ 5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */, 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */, + CE176F202C87014C00145DBC /* InvertedForegroundStyle.swift in Sources */, 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */, 5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */, 5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */, diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 105edd725f..c60e6e8f64 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2790,13 +2790,14 @@ public enum CIStatus: Decodable, Hashable { } } - public func statusIcon(_ metaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { + public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { case .sndNew: nil - case .sndSent: (Image("checkmark.wide"), metaColor) - case let .sndRcvd(msgRcptStatus, _): + case let .sndSent(sndProgress): + (Image("checkmark.wide"), sndProgress == .partial ? paleMetaColor : metaColor) + case let .sndRcvd(msgRcptStatus, sndProgress): switch msgRcptStatus { - case .ok: (Image("checkmark.2"), metaColor) + case .ok: (Image("checkmark.2"), sndProgress == .partial ? paleMetaColor : metaColor) case .badMsgHash: (Image("checkmark.2"), .red) } case .sndErrorAuth: (Image(systemName: "multiply"), .red) @@ -2808,14 +2809,6 @@ public enum CIStatus: Decodable, Hashable { } } - public var sndProgress: SndCIStatusProgress? { - switch self { - case let .sndSent(sndProgress): sndProgress - case let .sndRcvd(_ , sndProgress): sndProgress - default: nil - } - } - public var statusInfo: (String, String)? { switch self { case .sndNew: return nil From 06939343a1a55c850fc992c9764584f83fff10fc Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Fri, 6 Sep 2024 15:32:41 +0300 Subject: [PATCH 032/704] ios: revert showing date in chat list timestamp (#4834) --- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 26 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index a5ad7ce456..f3d1cc92a5 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -704,7 +704,7 @@ struct ChatView: View { let nextItem = im.reversedChatItems[i - 1] let largeGap = !nextItem.chatDir.sameDirection(chatItem.chatDir) || nextItem.meta.itemTs.timeIntervalSince(chatItem.meta.itemTs) > 60 return ( - timestamp: largeGap || formatTimestampText(chatItem.meta.itemTs) != formatTimestampText(nextItem.meta.itemTs), + timestamp: largeGap || formatTimestampMeta(chatItem.meta.itemTs) != formatTimestampMeta(nextItem.meta.itemTs), largeGap: largeGap, date: Calendar.current.isDate(chatItem.meta.itemTs, inSameDayAs: nextItem.meta.itemTs) ? nil : nextItem.meta.itemTs ) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index c60e6e8f64..84bf445601 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2713,7 +2713,7 @@ public struct CIMeta: Decodable, Hashable { public var deletable: Bool public var editable: Bool - public var timestampText: Text { get { formatTimestampText(itemTs) } } + public var timestampText: Text { Text(formatTimestampMeta(itemTs)) } public var recent: Bool { updatedAt + 10 > .now } public var isLive: Bool { itemLive == true } public var disappearing: Bool { !isRcvNew && itemTimed?.deleteAt != nil } @@ -2761,8 +2761,30 @@ public struct CITimed: Decodable, Hashable { public var deleteAt: Date? } +let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() +let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) + public func formatTimestampText(_ date: Date) -> Text { - Text(verbatim: date.formatted(date: .omitted, time: .shortened)) + Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) +} + +public func formatTimestampMeta(_ date: Date) -> String { + date.formatted(date: .omitted, time: .shortened) +} + +private func recent(_ date: Date) -> Bool { + let now = Date() + let calendar = Calendar.current + + guard let previousDay = calendar.date(byAdding: DateComponents(day: -1), to: now), + let previousDay18 = calendar.date(bySettingHour: 18, minute: 0, second: 0, of: previousDay), + let currentDay00 = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: now), + let currentDay12 = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: now) else { + return false + } + + let isSameDay = calendar.isDate(date, inSameDayAs: now) + return isSameDay || (now < currentDay12 && date >= previousDay18 && date < currentDay00) } public enum CIStatus: Decodable, Hashable { From 1839dab17bc389201c31735930e8d688f7a53bca Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 6 Sep 2024 22:09:55 +0100 Subject: [PATCH 033/704] ios: move caching images to background thread, dont use main thread scheduler for marking items read (#4840) --- apps/ios/Shared/Views/Chat/ChatView.swift | 5 +++-- apps/ios/SimpleXChat/ImageUtils.swift | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index f3d1cc92a5..e22b4aa328 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -797,9 +797,10 @@ struct ChatView: View { } private func waitToMarkRead(_ op: @Sendable @escaping () async -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { + Task { + _ = try? await Task.sleep(nanoseconds: 600_000000) if m.chatId == chat.chatInfo.id { - Task(operation: op) + await op() } } } diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift index 2eb747c6a5..36148d4324 100644 --- a/apps/ios/SimpleXChat/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -391,7 +391,9 @@ public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { return img } else if let data = Data(base64Encoded: dropImagePrefix(base64Encoded)), let img = UIImage(data: data) { - imageCache.setObject(img, forKey: base64Encoded as NSString) + imageCacheQueue.async { + imageCache.setObject(img, forKey: base64Encoded as NSString) + } return img } else { return nil @@ -401,6 +403,8 @@ public func imageFromBase64(_ base64Encoded: String?) -> UIImage? { } } +private let imageCacheQueue = DispatchQueue.global(qos: .background) + private var imageCache: NSCache = { var cache = NSCache() cache.countLimit = 1000 From 5ed701402b82377a59c657fd8aac6f1ccc137e6d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 7 Sep 2024 19:40:10 +0100 Subject: [PATCH 034/704] core: optimize marking chat items as read, batch API (#4843) * core: optimize marking chat items as read * tests, ui types * ios: fix api * refactor --- apps/ios/Shared/Model/SimpleXAPI.swift | 4 ++ apps/ios/SimpleXChat/APITypes.swift | 7 +++ .../chat/simplex/common/model/SimpleXAPI.kt | 10 ++++ src/Simplex/Chat.hs | 49 +++++++++++++------ src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/Store/Messages.hs | 46 +++++++++++++---- tests/ChatTests/Direct.hs | 19 +++++++ tests/ChatTests/Groups.hs | 18 +++++++ 8 files changed, 129 insertions(+), 25 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 020b40f73e..9e77316725 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1000,6 +1000,10 @@ func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async thr try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange)) } +func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws { + try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, itemIds: itemIds)) +} + func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat)) } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index d4998762d7..7f030cb838 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -129,6 +129,7 @@ public enum ChatCommand { // WebRTC calls / case apiGetNetworkStatuses case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) + case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) @@ -293,6 +294,7 @@ public enum ChatCommand { case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" case .apiGetNetworkStatuses: return "/_network_statuses" case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" + case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))" case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" @@ -434,6 +436,7 @@ public enum ChatCommand { case .apiCallStatus: return "apiCallStatus" case .apiGetNetworkStatuses: return "apiGetNetworkStatuses" case .apiChatRead: return "apiChatRead" + case .apiChatItemsRead: return "apiChatItemsRead" case .apiChatUnread: return "apiChatUnread" case .receiveFile: return "receiveFile" case .setFileToReceive: return "setFileToReceive" @@ -462,6 +465,10 @@ public enum ChatCommand { "\(type.rawValue)\(id)" } + func joinedIds(_ ids: [Int64]) -> String { + ids.map { "\($0)" }.joined(separator: ",") + } + func protoServersStr(_ servers: [ServerCfg]) -> String { encodeJSON(ProtoServersConfig(servers: servers)) } 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 1c30e706d8..452e8a704b 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 @@ -1480,6 +1480,13 @@ object ChatController { return false } + suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, itemIds: List): Boolean { + val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, itemIds)) + if (r is CR.CmdOk) return true + Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}") + return false + } + suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean { val r = sendCmd(rh, CC.ApiChatUnread(type, id, unreadChat)) if (r is CR.CmdOk) return true @@ -2967,6 +2974,7 @@ sealed class CC { class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC() class ApiRejectContact(val contactReqId: Long): CC() class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC() + class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List): CC() class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC() class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC() class CancelFile(val fileId: Long): CC() @@ -3123,6 +3131,7 @@ sealed class CC { is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}" is ApiGetNetworkStatuses -> "/_network_statuses" is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}" + is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}" is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" is ReceiveFile -> "/freceive $fileId" + @@ -3266,6 +3275,7 @@ sealed class CC { is ApiCallStatus -> "apiCallStatus" is ApiGetNetworkStatuses -> "apiGetNetworkStatuses" is ApiChatRead -> "apiChatRead" + is ApiChatItemsRead -> "apiChatItemsRead" is ApiChatUnread -> "apiChatUnread" is ReceiveFile -> "receiveFile" is CancelFile -> "cancelFile" diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d9f4ce0db3..d348a56389 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -55,9 +55,9 @@ import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Time (NominalDiffTime, addUTCTime, defaultTimeLocale, formatTime) import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime, nominalDay, nominalDiffTimeToSeconds) import Data.Time.Clock.System (systemToUTCTime) -import Data.Word (Word32) import qualified Data.UUID as UUID import qualified Data.UUID.V4 as V4 +import Data.Word (Word32) import qualified Database.SQLite.Simple as SQL import Simplex.Chat.Archive import Simplex.Chat.Call @@ -115,7 +115,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM @@ -1114,26 +1114,24 @@ processChatCommand' vr = \case when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId - APIChatRead (ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of + APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of CTDirect -> do user <- withFastStore $ \db -> getUserByContactId db chatId - timedItems <- withFastStore' $ \db -> getDirectUnreadTimedItems db user chatId fromToIds ts <- liftIO getCurrentTime - forM_ timedItems $ \(itemId, ttl) -> do - let deleteAt = addUTCTime (realToFrac ttl) ts - withFastStore' $ \db -> setDirectChatItemDeleteAt db user chatId itemId deleteAt - startProximateTimedItemThread user (ChatRef CTDirect chatId, itemId) deleteAt - withFastStore' $ \db -> updateDirectChatItemsRead db user chatId fromToIds + timedItems <- withFastStore' $ \db -> do + timedItems <- getDirectUnreadTimedItems db user chatId fromToIds + updateDirectChatItemsRead db user chatId fromToIds + setDirectChatItemsDeleteAt db user chatId timedItems ts + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user CTGroup -> do - user@User {userId} <- withFastStore $ \db -> getUserByGroupId db chatId - timedItems <- withFastStore' $ \db -> getGroupUnreadTimedItems db user chatId fromToIds + user <- withFastStore $ \db -> getUserByGroupId db chatId ts <- liftIO getCurrentTime - forM_ timedItems $ \(itemId, ttl) -> do - let deleteAt = addUTCTime (realToFrac ttl) ts - withFastStore' $ \db -> setGroupChatItemDeleteAt db user chatId itemId deleteAt - startProximateTimedItemThread user (ChatRef CTGroup chatId, itemId) deleteAt - withFastStore' $ \db -> updateGroupChatItemsRead db userId chatId fromToIds + timedItems <- withFastStore' $ \db -> do + timedItems <- getGroupUnreadTimedItems db user chatId fromToIds + updateGroupChatItemsRead db user chatId fromToIds + setGroupChatItemsDeleteAt db user chatId timedItems ts + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt ok user CTLocal -> do user <- withFastStore $ \db -> getUserByNoteFolderId db chatId @@ -1141,6 +1139,24 @@ processChatCommand' vr = \case ok user CTContactRequest -> pure $ chatCmdError Nothing "not supported" CTContactConnection -> pure $ chatCmdError Nothing "not supported" + APIChatItemsRead chatRef@(ChatRef cType chatId) itemIds -> withUser $ \_ -> case cType of + CTDirect -> do + user <- withFastStore $ \db -> getUserByContactId db chatId + timedItems <- withFastStore' $ \db -> do + timedItems <- updateDirectChatItemsReadList db user chatId itemIds + setDirectChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt + ok user + CTGroup -> do + user <- withFastStore $ \db -> getUserByGroupId db chatId + timedItems <- withFastStore' $ \db -> do + timedItems <- updateGroupChatItemsReadList db user chatId itemIds + setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt + ok user + CTLocal -> pure $ chatCmdError Nothing "not supported" + CTContactRequest -> pure $ chatCmdError Nothing "not supported" + CTContactConnection -> pure $ chatCmdError Nothing "not supported" APIChatUnread (ChatRef cType chatId) unreadChat -> withUser $ \user -> case cType of CTDirect -> do withFastStore $ \db -> do @@ -7873,6 +7889,7 @@ chatCommandP = "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), + "/_read chat items " *> (APIChatItemsRead <$> chatRefP <*> _strP), "/_unread chat " *> (APIChatUnread <$> chatRefP <* A.space <*> onOffP), "/_delete " *> (APIDeleteChat <$> chatRefP <*> chatDeleteMode), "/_clear chat " *> (APIClearChat <$> chatRefP), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 9d92ee8193..7268d0734a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -302,6 +302,7 @@ data ChatCommand | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) + | APIChatItemsRead ChatRef (NonEmpty ChatItemId) | APIChatUnread ChatRef Bool | APIDeleteChat ChatRef ChatDeleteMode -- currently delete mode settings are only applied to direct chats | APIClearChat ChatRef diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 6dbd9124c5..f6f9588f66 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -60,10 +60,12 @@ module Simplex.Chat.Store.Messages deleteLocalChatItem, updateDirectChatItemsRead, getDirectUnreadTimedItems, - setDirectChatItemDeleteAt, + updateDirectChatItemsReadList, + setDirectChatItemsDeleteAt, updateGroupChatItemsRead, getGroupUnreadTimedItems, - setGroupChatItemDeleteAt, + updateGroupChatItemsReadList, + setGroupChatItemsDeleteAt, updateLocalChatItemsRead, getChatRefViaItemId, getChatItemVersions, @@ -126,7 +128,9 @@ import Data.ByteString.Char8 (ByteString) import Data.Either (fromRight, rights) import Data.Int (Int64) import Data.List (sortBy) -import Data.Maybe (fromMaybe, isJust, mapMaybe) +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as L +import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe) import Data.Ord (Down (..), comparing) import Data.Text (Text) import qualified Data.Text as T @@ -1339,15 +1343,27 @@ getDirectUnreadTimedItems db User {userId} contactId itemsRange_ = case itemsRan |] (userId, contactId, CISRcvNew) -setDirectChatItemDeleteAt :: DB.Connection -> User -> ContactId -> ChatItemId -> UTCTime -> IO () -setDirectChatItemDeleteAt db User {userId} contactId chatItemId deleteAt = +updateDirectChatItemsReadList :: DB.Connection -> User -> ContactId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)] +updateDirectChatItemsReadList db user contactId itemIds = do + catMaybes . L.toList <$> mapM getUpdateDirectItem itemIds + where + getUpdateDirectItem chatItemId = do + let itemsRange = Just (chatItemId, chatItemId) + timedItem <- maybeFirstRow id $ getDirectUnreadTimedItems db user contactId itemsRange + updateDirectChatItemsRead db user contactId itemsRange + pure timedItem + +setDirectChatItemsDeleteAt :: DB.Connection -> User -> ContactId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] +setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do + let deleteAt = addUTCTime (realToFrac ttl) currentTs DB.execute db "UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?" (deleteAt, userId, contactId, chatItemId) + pure (chatItemId, deleteAt) -updateGroupChatItemsRead :: DB.Connection -> UserId -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO () -updateGroupChatItemsRead db userId groupId itemsRange_ = do +updateGroupChatItemsRead :: DB.Connection -> User -> GroupId -> Maybe (ChatItemId, ChatItemId) -> IO () +updateGroupChatItemsRead db User {userId} groupId itemsRange_ = do currentTs <- getCurrentTime case itemsRange_ of Just (fromItemId, toItemId) -> @@ -1392,12 +1408,24 @@ getGroupUnreadTimedItems db User {userId} groupId itemsRange_ = case itemsRange_ |] (userId, groupId, CISRcvNew) -setGroupChatItemDeleteAt :: DB.Connection -> User -> GroupId -> ChatItemId -> UTCTime -> IO () -setGroupChatItemDeleteAt db User {userId} groupId chatItemId deleteAt = +updateGroupChatItemsReadList :: DB.Connection -> User -> GroupId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)] +updateGroupChatItemsReadList db user groupId itemIds = do + catMaybes . L.toList <$> mapM getUpdateGroupItem itemIds + where + getUpdateGroupItem chatItemId = do + let itemsRange = Just (chatItemId, chatItemId) + timedItem <- maybeFirstRow id $ getGroupUnreadTimedItems db user groupId itemsRange + updateGroupChatItemsRead db user groupId itemsRange + pure timedItem + +setGroupChatItemsDeleteAt :: DB.Connection -> User -> GroupId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] +setGroupChatItemsDeleteAt db User {userId} groupId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do + let deleteAt = addUTCTime (realToFrac ttl) currentTs DB.execute db "UPDATE chat_items SET timed_delete_at = ? WHERE user_id = ? AND group_id = ? AND chat_item_id = ?" (deleteAt, userId, groupId, chatItemId) + pure (chatItemId, deleteAt) updateLocalChatItemsRead :: DB.Connection -> User -> NoteFolderId -> Maybe (ChatItemId, ChatItemId) -> IO () updateLocalChatItemsRead db User {userId} noteFolderId itemsRange_ = do diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 9980b3b723..97a9d89200 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -3,6 +3,7 @@ {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PostfixOperators #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} module ChatTests.Direct where @@ -22,6 +23,7 @@ import Simplex.Chat.AppSettings (defaultAppSettings) import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options (ChatOpts (..)) import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) @@ -38,6 +40,7 @@ chatDirectTests :: SpecWith FilePath chatDirectTests = do describe "direct messages" $ do describe "add contact and send/receive messages" testAddContact + it "mark multiple messages as read" testMarkReadDirect it "clear chat with contact" testContactClear it "deleting contact deletes profile" testDeleteContactDeletesProfile it "delete contact keeping conversation" testDeleteContactKeepConversation @@ -212,6 +215,22 @@ testAddContact = versionTestMatrix2 runTestAddContact then chatFeatures else (0, e2eeInfoNoPQStr) : tail chatFeatures +testMarkReadDirect :: HasCallStack => FilePath -> IO () +testMarkReadDirect = testChat2 aliceProfile bobProfile $ \alice bob -> do + connectUsers alice bob + alice #> "@bob 1" + alice #> "@bob 2" + alice #> "@bob 3" + alice #> "@bob 4" + bob <# "alice> 1" + bob <# "alice> 2" + bob <# "alice> 3" + bob <# "alice> 4" + bob ##> "/last_item_id" + i :: ChatItemId <- read <$> getTermLine bob + let itemIds = intercalate "," $ map show [i - 3 .. i] + bob #$> ("/_read chat items @2 " <> itemIds, id, "ok") + testDuplicateContactsSeparate :: HasCallStack => FilePath -> IO () testDuplicateContactsSeparate = testChat2 aliceProfile bobProfile $ diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index d3e65ce5df..c65c7b8085 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -16,6 +16,7 @@ import Data.List (intercalate, isInfixOf) import qualified Data.Text as T import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..)) +import Simplex.Chat.Messages (ChatItemId) import Simplex.Chat.Options import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) @@ -34,6 +35,7 @@ chatGroupTests :: SpecWith FilePath chatGroupTests = do describe "chat groups" $ do describe "add contacts, create group and send/receive messages" testGroupMatrix + it "mark multiple messages as read" testMarkReadGroup it "v1: add contacts, create group and send/receive messages" testGroup it "v1: add contacts, create group and send/receive messages, check messages" testGroupCheckMessages it "send large message" testGroupLargeMessage @@ -355,6 +357,22 @@ testGroupShared alice bob cath checkMessages directConnections = do alice #$> ("/_unread chat #1 on", id, "ok") alice #$> ("/_unread chat #1 off", id, "ok") +testMarkReadGroup :: HasCallStack => FilePath -> IO () +testMarkReadGroup = testChat2 aliceProfile bobProfile $ \alice bob -> do + createGroup2 "team" alice bob + alice #> "#team 1" + alice #> "#team 2" + alice #> "#team 3" + alice #> "#team 4" + bob <# "#team alice> 1" + bob <# "#team alice> 2" + bob <# "#team alice> 3" + bob <# "#team alice> 4" + bob ##> "/last_item_id" + i :: ChatItemId <- read <$> getTermLine bob + let itemIds = intercalate "," $ map show [i - 3 .. i] + bob #$> ("/_read chat items #1 " <> itemIds, id, "ok") + testGroupLargeMessage :: HasCallStack => FilePath -> IO () testGroupLargeMessage = testChat2 aliceProfile bobProfile $ From 74b837bf9a39f974347ffebc730c2d72d42a136b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 8 Sep 2024 20:02:38 +0100 Subject: [PATCH 035/704] core: allow deleting user when user record in agent database was deleted (#4851) --- apps/simplex-chat/Server.hs | 4 ++-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/simplex-chat/Server.hs b/apps/simplex-chat/Server.hs index 58a167e4f5..5351565946 100644 --- a/apps/simplex-chat/Server.hs +++ b/apps/simplex-chat/Server.hs @@ -23,7 +23,7 @@ import Simplex.Chat import Simplex.Chat.Controller import Simplex.Chat.Core import Simplex.Chat.Options -import Simplex.Messaging.Transport.Server (runTCPServer) +import Simplex.Messaging.Transport.Server (runLocalTCPServer) import Simplex.Messaging.Util (raceAny_) import UnliftIO.Exception import UnliftIO.STM @@ -68,7 +68,7 @@ newChatServerClient qSize = do runChatServer :: ChatServerConfig -> ChatController -> IO () runChatServer ChatServerConfig {chatPort, clientQSize} cc = do started <- newEmptyTMVarIO - runTCPServer started chatPort $ \sock -> do + runLocalTCPServer started chatPort $ \sock -> do ws <- liftIO $ getConnection sock c <- atomically $ newChatServerClient clientQSize putStrLn "client connected" diff --git a/cabal.project b/cabal.project index d7f6b67eb1..26a25613d6 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: d559a66145cf7b4cd367c09974ed1ce8393940b2 + tag: 344a295845ceea6a8a926e3f4c10fe79bcf05abe source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 0f6592086f..5b16072588 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."d559a66145cf7b4cd367c09974ed1ce8393940b2" = "1jav7jmriims6vlkxg8gmal03f9mbgrwc8v6g0rp95ivkx8gfjyw"; + "https://github.com/simplex-chat/simplexmq.git"."344a295845ceea6a8a926e3f4c10fe79bcf05abe" = "13l8qmzx0bfvs089hb68x25nfh5v0ik0gq1iyv3y3qnffw8601cf"; "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/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d348a56389..c140025648 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2807,7 +2807,10 @@ processChatCommand' vr = \case filesInfo <- withFastStore' (`getUserFileInfo` user) cancelFilesInProgress user filesInfo deleteFilesLocally filesInfo - withAgent $ \a -> deleteUser a (aUserId user) delSMPQueues + withAgent (\a -> deleteUser a (aUserId user) delSMPQueues) + `catchChatError` \case + e@(ChatErrorAgent NO_USER _) -> toView $ CRChatError (Just user) e + e -> throwError e withFastStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing ok_ From 8f6e9741e79a049b55e917c2376b09ca7c67b83f Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Mon, 9 Sep 2024 16:58:22 +0300 Subject: [PATCH 036/704] ios: add floating date separator (#4801) * ios: add floating date separator * floating date separator * revert formatTimestampText * send tuple, reduce lookups * background date visibility * whitespace * streamline * visible date * move pipeline to ReverseList * space * remove ViewUpdate * cleanup * refactor * combine unread items model updates * split publisher * remove readItemPublisher * revert markChatItemRead_ change * use single item api * comment test buttons * style * update top floating button instantly * cleanup * cleanup * minor * remove task * prevent concurrent updates * fix mark chat read --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 31 ++- apps/ios/Shared/Model/SimpleXAPI.swift | 18 +- apps/ios/Shared/Views/Chat/ChatView.swift | 245 ++++++++++++------- apps/ios/Shared/Views/Chat/ReverseList.swift | 61 +++-- 4 files changed, 232 insertions(+), 123 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 2be3191f4f..af394d11aa 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -61,7 +61,7 @@ class ItemsModel: ObservableObject { init() { publisher - .throttle(for: 0.25, scheduler: DispatchQueue.main, latest: true) + .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) .sink { self.objectWillChange.send() } .store(in: &bag) } @@ -563,6 +563,7 @@ final class ChatModel: ObservableObject { // update preview _updateChat(cInfo.id) { chat in self.decreaseUnreadCounter(user: self.currentUser!, by: chat.chatStats.unreadCount) + self.updateFloatingButtons(unreadCount: 0) chat.chatStats = ChatStats() } // update current chat @@ -579,6 +580,12 @@ final class ChatModel: ObservableObject { } } + private func updateFloatingButtons(unreadCount: Int) { + let fbm = ChatView.FloatingButtonModel.shared + fbm.totalUnread = unreadCount + fbm.objectWillChange.send() + } + func markChatItemsRead(_ cInfo: ChatInfo, aboveItem: ChatItem? = nil) { if let cItem = aboveItem { if chatId == cInfo.id, let i = getChatItemIndex(cItem) { @@ -597,6 +604,7 @@ final class ChatModel: ObservableObject { if markedCount > 0 { chat.chatStats.unreadCount -= markedCount self.decreaseUnreadCounter(user: self.currentUser!, by: markedCount) + self.updateFloatingButtons(unreadCount: chat.chatStats.unreadCount) } } } @@ -626,19 +634,15 @@ final class ChatModel: ObservableObject { } } - func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { - if chatId == cInfo.id, - let itemIndex = getChatItemIndex(cItem), - im.reversedChatItems[itemIndex].isRcvNew { - await MainActor.run { - withTransaction(Transaction()) { - // update current chat - markChatItemRead_(itemIndex) - // update preview - unreadCollector.changeUnreadCounter(cInfo.id, by: -1) + func markChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID]) { + if self.chatId == cInfo.id { + for itemId in itemIds { + if let i = im.reversedChatItems.firstIndex(where: { $0.id == itemId }) { + markChatItemRead_(i) } } } + self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count) } private let unreadCollector = UnreadCollector() @@ -664,9 +668,10 @@ final class ChatModel: ObservableObject { } func changeUnreadCounter(_ chatId: ChatId, by count: Int) { - DispatchQueue.main.async { - self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count + if chatId == ChatModel.shared.chatId { + ChatView.FloatingButtonModel.shared.totalUnread += count } + self.unreadCounts[chatId] = (self.unreadCounts[chatId] ?? 0) + count subject.send() } } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 9e77316725..7312b42b1b 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1291,11 +1291,23 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async { func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { do { - logger.debug("apiMarkChatItemRead: \(cItem.id)") try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) - await ChatModel.shared.markChatItemRead(cInfo, cItem) + DispatchQueue.main.async { + ChatModel.shared.markChatItemsRead(cInfo, [cItem.id]) + } } catch { - logger.error("apiMarkChatItemRead apiChatRead error: \(responseError(error))") + logger.error("apiChatRead error: \(responseError(error))") + } +} + +func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID]) async { + do { + try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds) + DispatchQueue.main.async { + ChatModel.shared.markChatItemsRead(cInfo, itemIds) + } + } catch { + logger.error("apiChatItemsRead error: \(responseError(error))") } } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e22b4aa328..5c11cdc3df 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -23,7 +23,6 @@ struct ChatView: View { @Environment(\.scenePhase) var scenePhase @State @ObservedObject var chat: Chat @StateObject private var scrollModel = ReverseListScrollModel() - @StateObject private var floatingButtonModel: FloatingButtonModel = .shared @State private var showChatInfoSheet: Bool = false @State private var showAddMembersSheet: Bool = false @State private var composeState = ComposeState() @@ -76,8 +75,7 @@ struct ChatView: View { VStack(spacing: 0) { ZStack(alignment: .bottomTrailing) { chatItemsList() - // TODO: Extract into a separate view, to reduce the scope of `FloatingButtonModel` updates - floatingButtons(unreadBelow: floatingButtonModel.unreadBelow, isNearBottom: floatingButtonModel.isNearBottom) + FloatingButtons(theme: theme, scrollModel: scrollModel, chat: chat) } connectingText() if selectedChatItems == nil { @@ -341,6 +339,7 @@ struct ChatView: View { await markChatUnread(chat, unreadChat: false) } } + ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount } private func searchToolbar() -> some View { @@ -427,7 +426,7 @@ struct ChatView: View { .onChange(of: im.itemAdded) { added in if added { im.itemAdded = false - if floatingButtonModel.isReallyNearBottom { + if FloatingButtonModel.shared.isReallyNearBottom { scrollModel.scrollToBottom() } } @@ -453,86 +452,162 @@ struct ChatView: View { static let shared = FloatingButtonModel() @Published var unreadBelow: Int = 0 @Published var isNearBottom: Bool = true - var isReallyNearBottom: Bool { scrollOffset.value > 0 && scrollOffset.value < 500 } - let visibleItems = PassthroughSubject<[String], Never>() - let scrollOffset = CurrentValueSubject(0) - private var bag = Set() + @Published var date: Date? + @Published var isDateVisible: Bool = false + var totalUnread: Int = 0 + var isReallyNearBottom: Bool = true + var hideDateWorkItem: DispatchWorkItem? - init() { - visibleItems - .receive(on: DispatchQueue.global(qos: .background)) - .map { itemIds in - if let viewId = itemIds.first, - let index = ItemsModel.shared.reversedChatItems.firstIndex(where: { $0.viewId == viewId }) { - ItemsModel.shared.reversedChatItems[.. 0 && listState.scrollOffset < 500 + } + + // set floating button indication mode + let nearBottom = listState.scrollOffset < 800 + if nearBottom != self.isNearBottom { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in + self?.isNearBottom = nearBottom + } + } + + // hide Date indicator after 1 second of no scrolling + hideDateWorkItem?.cancel() + let workItem = DispatchWorkItem { [weak self] in + guard let it = self else { return } + it.setDate(visibility: false) + it.hideDateWorkItem = nil + } + DispatchQueue.main.async { [weak self] in + self?.hideDateWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem) + } } + + func resetDate() { + date = nil + isDateVisible = false + } + + private func setDate(visibility isVisible: Bool) { + if isVisible { + if !isNearBottom, + !isDateVisible, + let date, !Calendar.current.isDateInToday(date) { + withAnimation { self.isDateVisible = true } + } + } else if isDateVisible { + withAnimation { self.isDateVisible = false } + } + } + } - private func floatingButtons(unreadBelow: Int, isNearBottom: Bool) -> some View { - VStack { - let unreadAbove = chat.chatStats.unreadCount - unreadBelow - if unreadAbove > 0 { - circleButton { - unreadCountText(unreadAbove) - .font(.callout) - .foregroundColor(theme.colors.primary) + private struct FloatingButtons: View { + let theme: AppTheme + let scrollModel: ReverseListScrollModel + let chat: Chat + @ObservedObject var model = FloatingButtonModel.shared + + var body: some View { + ZStack(alignment: .top) { + if let date = model.date { + DateSeparator(date: date) + .padding(.vertical, 4).padding(.horizontal, 8) + .background(.thinMaterial) + .clipShape(Capsule()) + .opacity(model.isDateVisible ? 1 : 0) } - .onTapGesture { - scrollModel.scrollToNextPage() - } - .contextMenu { - Button { - Task { - await markChatRead(chat) + VStack { + let unreadAbove = model.totalUnread - model.unreadBelow + if unreadAbove > 0 { + circleButton { + unreadCountText(unreadAbove) + .font(.callout) + .foregroundColor(theme.colors.primary) } - } label: { - Label("Mark read", systemImage: "checkmark") + .onTapGesture { + scrollModel.scrollToNextPage() + } + .contextMenu { + Button { + Task { + await markChatRead(chat) + } + } label: { + Label("Mark read", systemImage: "checkmark") + } + } + } + Spacer() + if model.unreadBelow > 0 { + circleButton { + unreadCountText(model.unreadBelow) + .font(.callout) + .foregroundColor(theme.colors.primary) + } + .onTapGesture { + scrollModel.scrollToBottom() + } + } else if !model.isNearBottom { + circleButton { + Image(systemName: "chevron.down") + .foregroundColor(theme.colors.primary) + } + .onTapGesture { scrollModel.scrollToBottom() } } } + .padding() + .frame(maxWidth: .infinity, alignment: .trailing) } - Spacer() - if unreadBelow > 0 { - circleButton { - unreadCountText(unreadBelow) - .font(.callout) - .foregroundColor(theme.colors.primary) - } - .onTapGesture { - scrollModel.scrollToBottom() - } - } else if !isNearBottom { - circleButton { - Image(systemName: "chevron.down") - .foregroundColor(theme.colors.primary) - } - .onTapGesture { scrollModel.scrollToBottom() } + .onDisappear(perform: model.resetDate) + } + + private func circleButton(_ content: @escaping () -> Content) -> some View { + ZStack { + Circle() + .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground)) + .frame(width: 44, height: 44) + content() } } - .padding() } - private func circleButton(_ content: @escaping () -> Content) -> some View { - ZStack { - Circle() - .foregroundColor(Color(uiColor: .tertiarySystemGroupedBackground)) - .frame(width: 44, height: 44) - content() + private struct DateSeparator: View { + let date: Date + + var body: some View { + Text(String.localizedStringWithFormat( + NSLocalizedString("%@, %@", comment: "format for date separator in chat"), + date.formatted(.dateTime.weekday(.abbreviated)), + date.formatted(.dateTime.day().month(.abbreviated)) + )) + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) } } @@ -693,6 +768,7 @@ struct ChatView: View { @Binding var selectedChatItems: Set? @State private var allowMenu: Bool = true + @State private var markedRead = false var revealed: Bool { chatItem == revealedChatItem } @@ -743,15 +819,7 @@ struct ChatView: View { VStack(spacing: 0) { chatItemView(chatItem, range, prevItem, timeSeparation) if let date = timeSeparation.date { - Text(String.localizedStringWithFormat( - NSLocalizedString("%@, %@", comment: "format for date separator in chat"), - date.formatted(.dateTime.weekday(.abbreviated)), - date.formatted(.dateTime.day().month(.abbreviated)) - )) - .font(.callout) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .padding(8) + DateSeparator(date: date).padding(8) } } .overlay { @@ -767,12 +835,16 @@ struct ChatView: View { } } .onAppear { + if markedRead { + return + } else { + markedRead = true + } if let range { - if let items = unreadItems(range) { + let itemIds = unreadItemIds(range) + if !itemIds.isEmpty { waitToMarkRead { - for ci in items { - await apiMarkChatItemRead(chat.chatInfo, ci) - } + await apiMarkChatItemsRead(chat.chatInfo, itemIds) } } } else if chatItem.isRcvNew { @@ -782,18 +854,17 @@ struct ChatView: View { } } } - - private func unreadItems(_ range: ClosedRange) -> [ChatItem]? { + + private func unreadItemIds(_ range: ClosedRange) -> [ChatItem.ID] { let im = ItemsModel.shared - let items = range.compactMap { i in + return range.compactMap { i in if i >= 0 && i < im.reversedChatItems.count { let ci = im.reversedChatItems[i] - return if ci.isRcvNew { ci } else { nil } + return if ci.isRcvNew { ci.id } else { nil } } else { return nil } } - return if items.isEmpty { nil } else { items } } private func waitToMarkRead(_ op: @Sendable @escaping () async -> Void) { diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index f64189f95f..2e09909c5e 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -107,9 +107,14 @@ struct ReverseList: UIViewControllerRepresentable { name: notificationName, object: nil ) + updateFloatingButtons - .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true) - .sink { self.updateVisibleItems() } + .throttle(for: 0.2, scheduler: DispatchQueue.global(qos: .background), latest: true) + .sink { + if let listState = DispatchQueue.main.sync(execute: { [weak self] in self?.getListState() }) { + ChatView.FloatingButtonModel.shared.updateOnListChange(listState) + } + } .store(in: &bag) } @@ -203,25 +208,35 @@ struct ReverseList: UIViewControllerRepresentable { updateFloatingButtons.send() } - private func updateVisibleItems() { - let fbm = ChatView.FloatingButtonModel.shared - fbm.scrollOffset.send(tableView.contentOffset.y + InvertedTableView.inset) - fbm.visibleItems.send( - (tableView.indexPathsForVisibleRows ?? []) - .compactMap { indexPath -> String? in - guard let relativeFrame = tableView.superview?.convert( - tableView.rectForRow(at: indexPath), - from: tableView - ) else { return nil } - // Checks that the cell is visible accounting for the added insets - let isVisible = - relativeFrame.maxY > InvertedTableView.inset && - relativeFrame.minY < tableView.frame.height - InvertedTableView.inset - return indexPath.item < representer.items.count && isVisible - ? representer.items[indexPath.item].viewId - : nil + func getListState() -> ListState? { + if let visibleRows = tableView.indexPathsForVisibleRows, + visibleRows.last?.item ?? 0 < representer.items.count { + let scrollOffset: Double = tableView.contentOffset.y + InvertedTableView.inset + let topItemDate: Date? = + if let lastVisible = visibleRows.last(where: { isVisible(indexPath: $0) }) { + representer.items[lastVisible.item].meta.itemTs + } else { + nil } - ) + let bottomItemId: ChatItem.ID? = + if let firstVisible = visibleRows.first(where: { isVisible(indexPath: $0) }) { + representer.items[firstVisible.item].id + } else { + nil + } + return (scrollOffset: scrollOffset, topItemDate: topItemDate, bottomItemId: bottomItemId) + } + return nil + } + + private func isVisible(indexPath: IndexPath) -> Bool { + if let relativeFrame = tableView.superview?.convert( + tableView.rectForRow(at: indexPath), + from: tableView + ) { + relativeFrame.maxY > InvertedTableView.inset && + relativeFrame.minY < tableView.frame.height - InvertedTableView.inset + } else { false } } } @@ -265,6 +280,12 @@ struct ReverseList: UIViewControllerRepresentable { } } +typealias ListState = ( + scrollOffset: Double, + topItemDate: Date?, + bottomItemId: ChatItem.ID? +) + /// Manages ``ReverseList`` scrolling class ReverseListScrollModel: ObservableObject { /// Represents Scroll State of ``ReverseList`` From 388609563daf16891c8c04798117d7d282ea0765 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:22:14 +0400 Subject: [PATCH 037/704] core: update simplexmq (ntf encoding) (#4853) --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b4226d567b..a8a02b7579 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,6 +167,11 @@ 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; + 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */; }; + 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085E2C8F2CCB00AE9D01 /* libffi.a */; }; + 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */; }; + 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */; }; + 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; @@ -215,11 +220,6 @@ 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 */; }; - E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84522C832BF9008C24D1 /* libgmpxx.a */; }; - E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */; }; - E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */; }; - E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84552C832BF9008C24D1 /* libgmp.a */; }; - E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84562C832BF9008C24D1 /* libffi.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 */; }; @@ -506,6 +506,11 @@ 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; + 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 64A2085E2C8F2CCB00AE9D01 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a"; sourceTree = ""; }; + 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a"; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; @@ -552,11 +557,6 @@ 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 = ""; }; - E5BD84522C832BF9008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a"; sourceTree = ""; }; - E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a"; sourceTree = ""; }; - E5BD84552C832BF9008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E5BD84562C832BF9008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.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 = ""; }; @@ -647,13 +647,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E5BD84572C832BF9008C24D1 /* libgmpxx.a in Frameworks */, - E5BD845A2C832BF9008C24D1 /* libgmp.a in Frameworks */, + 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */, + 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */, + 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E5BD845B2C832BF9008C24D1 /* libffi.a in Frameworks */, - E5BD84582C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a in Frameworks */, - E5BD84592C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */, + 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -731,11 +731,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E5BD84562C832BF9008C24D1 /* libffi.a */, - E5BD84552C832BF9008C24D1 /* libgmp.a */, - E5BD84522C832BF9008C24D1 /* libgmpxx.a */, - E5BD84542C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N-ghc9.6.3.a */, - E5BD84532C832BF9008C24D1 /* libHSsimplex-chat-6.1.0.0-1tXK6wuT4H71iMwoWdPa4N.a */, + 64A2085E2C8F2CCB00AE9D01 /* libffi.a */, + 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */, + 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */, + 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */, + 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */, ); path = Libraries; sourceTree = ""; diff --git a/cabal.project b/cabal.project index 26a25613d6..fc958929af 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: 344a295845ceea6a8a926e3f4c10fe79bcf05abe + tag: 946e16339e16e026f51185ebfb48c3a0c5a5b2e1 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 5b16072588..fa3c6d4839 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."344a295845ceea6a8a926e3f4c10fe79bcf05abe" = "13l8qmzx0bfvs089hb68x25nfh5v0ik0gq1iyv3y3qnffw8601cf"; + "https://github.com/simplex-chat/simplexmq.git"."946e16339e16e026f51185ebfb48c3a0c5a5b2e1" = "1jkx2f14h5krmy467zyifsc58dys89pkpn08cyf1q9v78in7nwfd"; "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 fb4475027d811308d38d32b9ec0b50a1e7006cfa Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 10 Sep 2024 09:31:53 +0100 Subject: [PATCH 038/704] ios: new user picker (#4821) * ios: new user picker (#4770) * current user picker progress * one hand picker * thin bullet icon * more user picker buttons * button clickable areas * divider padding * extra space after sun * send current user option to address view * add unread count badge * with anim for apperance close * edit current profile from picker * remove you section from settings * remove help and support * simplify * move settings and sun to same row * remove redundant vstack * long press on sun/moon switches to system setting * remove back button from migrate device * smooth profile transitions * close user picker on list profiles * fix dismiss on migrate from device * fix dismiss when deleting last visible user while having hidden users * picker visibility toggle tweaks * remove strange square from profile switcher click * dirty way to save auto accept settings on dismiss * Revert "dirty way to save auto accept settings on dismiss" This reverts commit e7b19ee8aa185b767941d12d5d4534d81f9ea6f5. * consistent animation on user picker toggle * change space between profiles * remove result * ignore result * unread badge * move to sheet * half sheet on one hand ui * fix dismiss on device migration * fix desktop connect * sun to meet other action icons * fill bullet list button * fix tap in settings to take full width * icon sizings and paddings * open settings in same sheet * apply same trick as other buttons for ligth toggle * layout * open profiles sheet large when +3 users * layout * layout * paddings * paddings * remove show progress * always small user picker * fixed height * open all actions as sheets * type, color * simpler and more effective way of avoid moving around on user select * dismiss user profiles sheet on user change * connect desktop back button remove * remove back buttons from user address view * remove porgress * header inside list * alert on auto accept unsaved changes * Cancel -> Discard * revert * fix connect to desktop * remove extra space * fix share inside multi sheet * user picker and options as separate sheet * revert showShareSheet * fix current profile and all profiles selection * change alert * update * cleanup user address * remove func * alert on unsaved changes in chat prefs * fix layout * cleanup --------- Co-authored-by: Evgeny Poberezkin * ios: fix switching profiles (#4822) * ios: different user picker layout (#4826) * ios: different user picker layout * remove section * layout, color * color * remove activeUser * fix gradient * recursive sheets * gradient padding * share sheet * layout * dismiss sheets --------- Co-authored-by: Levitating Pineapple * ios: use the same way to share from all sheets (#4829) * ios: close user picker before opening other sheets * Revert "share sheet" This reverts commit 006415582515f85fb6f634c7727e74295d4447ad. * dismiss/show via callback * Revert "ios: close user picker before opening other sheets" This reverts commit 19110398f8b566d3e925b91074c3c19d97b35f17. * ios: show alerts from sheets (#4839) * padding * remove gradient * cleanup * simplify settings * padding --------- Co-authored-by: Diogo Co-authored-by: Levitating Pineapple Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .gitignore | 1 + .../Shared/Views/Call/IncomingCallView.swift | 1 + .../Shared/Views/ChatList/ChatListView.swift | 78 +++-- .../Shared/Views/ChatList/UserPicker.swift | 327 ++++++++++-------- .../ios/Shared/Views/Helpers/ShareSheet.swift | 30 +- .../Views/Migration/MigrateFromDevice.swift | 13 +- .../RemoteAccess/ConnectDesktopView.swift | 7 - .../Views/UserSettings/PreferencesView.swift | 11 + .../Views/UserSettings/SettingsView.swift | 102 ++---- .../Views/UserSettings/UserAddressView.swift | 39 +-- .../Views/UserSettings/UserProfilesView.swift | 12 +- 11 files changed, 335 insertions(+), 286 deletions(-) diff --git a/.gitignore b/.gitignore index e3ea5d267b..645b55ec9d 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ website/package/generated* # Ignore build tool output, e.g. code coverage website/.nyc_output/ website/coverage/ +result # Ignore API documentation website/api-docs/ diff --git a/apps/ios/Shared/Views/Call/IncomingCallView.swift b/apps/ios/Shared/Views/Call/IncomingCallView.swift index 4960281d72..5479a9fada 100644 --- a/apps/ios/Shared/Views/Call/IncomingCallView.swift +++ b/apps/ios/Shared/Views/Call/IncomingCallView.swift @@ -38,6 +38,7 @@ struct IncomingCallView: View { } HStack { ProfilePreview(profileOf: invitation.contact, color: .white) + .padding(.vertical, 6) Spacer() callButton("Reject", "phone.down.fill", .red) { diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 8ad03236f1..156b8694c4 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -9,6 +9,17 @@ import SwiftUI import SimpleXChat +enum UserPickerSheet: Identifiable { + case address + case chatPreferences + case chatProfiles + case currentProfile + case useFromDesktop + case settings + + var id: Self { self } +} + struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme @@ -18,9 +29,9 @@ struct ChatListView: View { @State private var searchText = "" @State private var searchShowingSimplexLink = false @State private var searchChatFilteredBySimplexLink: String? = nil - @State private var userPickerVisible = false - @State private var showConnectDesktop = false @State private var scrollToSearchBar = false + @State private var activeUserPickerSheet: UserPickerSheet? = nil + @State private var userPickerShown: Bool = false @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true @@ -46,21 +57,44 @@ struct ChatListView: View { ), destination: chatView ) { chatListView } - if userPickerVisible { - Rectangle().fill(.white.opacity(0.001)).onTapGesture { - withAnimation { - userPickerVisible.toggle() + } + .sheet(isPresented: $userPickerShown) { + UserPicker(activeSheet: $activeUserPickerSheet) + .sheet(item: $activeUserPickerSheet) { sheet in + if let currentUser = chatModel.currentUser { + switch sheet { + case .address: + NavigationView { + UserAddressView(shareViaProfile: currentUser.addressShared) + .navigationTitle("Public address") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + case .chatProfiles: + NavigationView { + UserProfilesView() + } + case .currentProfile: + NavigationView { + UserProfile() + .navigationTitle("Your current profile") + .modifier(ThemedBackground()) + } + case .chatPreferences: + NavigationView { + PreferencesView(profile: currentUser.profile, preferences: currentUser.fullPreferences, currentPreferences: currentUser.fullPreferences) + .navigationTitle("Your preferences") + .navigationBarTitleDisplayMode(.large) + .modifier(ThemedBackground(grouped: true)) + } + case .useFromDesktop: + ConnectDesktopView(viaSettings: false) + case .settings: + SettingsView(showSettings: $showSettings) + .navigationBarTitleDisplayMode(.large) + } } } - } - UserPicker( - showSettings: $showSettings, - showConnectDesktop: $showConnectDesktop, - userPickerVisible: $userPickerVisible - ) - } - .sheet(isPresented: $showConnectDesktop) { - ConnectDesktopView() } } @@ -73,7 +107,7 @@ struct ChatListView: View { .navigationBarHidden(searchMode || oneHandUI) } .scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center) - .onDisappear() { withAnimation { userPickerVisible = false } } + .onDisappear() { activeUserPickerSheet = nil } .refreshable { AlertManager.shared.showAlert(Alert( title: Text("Reconnect servers?"), @@ -164,7 +198,7 @@ struct ChatListView: View { let user = chatModel.currentUser ?? User.sampleData ZStack(alignment: .topTrailing) { ProfileImage(imageStr: user.image, size: 32, color: Color(uiColor: .quaternaryLabel)) - .padding(.trailing, 4) + .padding([.top, .trailing], 3) let allRead = chatModel.users .filter { u in !u.user.activeUser && !u.user.hidden } .allSatisfy { u in u.unreadCount == 0 } @@ -173,13 +207,7 @@ struct ChatListView: View { } } .onTapGesture { - if chatModel.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 { - withAnimation { - userPickerVisible.toggle() - } - } else { - showSettings = true - } + userPickerShown = true } } @@ -269,7 +297,7 @@ struct ChatListView: View { } } - private func unreadBadge(_ text: Text? = Text(" "), size: CGFloat = 18) -> some View { + private func unreadBadge(size: CGFloat = 18) -> some View { Circle() .frame(width: size, height: size) .foregroundColor(theme.colors.primary) diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index 5041e093db..9d7f6bbd9c 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -8,179 +8,228 @@ import SimpleXChat struct UserPicker: View { @EnvironmentObject var m: ChatModel - @Environment(\.scenePhase) var scenePhase @EnvironmentObject var theme: AppTheme - @Binding var showSettings: Bool - @Binding var showConnectDesktop: Bool - @Binding var userPickerVisible: Bool - @State var scrollViewContentSize: CGSize = .zero - @State var disableScrolling: Bool = true - private let menuButtonHeight: CGFloat = 68 - @State var chatViewNameWidth: CGFloat = 0 - + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize + @Environment(\.scenePhase) private var scenePhase: ScenePhase + @Environment(\.colorScheme) private var colorScheme: ColorScheme + @Environment(\.dismiss) private var dismiss: DismissAction + @Binding var activeSheet: UserPickerSheet? + @State private var switchingProfile = false var body: some View { - VStack { - Spacer().frame(height: 1) - VStack(spacing: 0) { - ScrollView { - ScrollViewReader { sp in - let users = m.users - .filter({ u in u.user.activeUser || !u.user.hidden }) - .sorted { u, _ in u.user.activeUser } - VStack(spacing: 0) { - ForEach(users) { u in - userView(u) - Divider() - if u.user.activeUser { Divider() } - } - } - .overlay { - GeometryReader { geo -> Color in - DispatchQueue.main.async { - scrollViewContentSize = geo.size - let scenes = UIApplication.shared.connectedScenes - if let windowScene = scenes.first as? UIWindowScene { - let layoutFrame = windowScene.windows[0].safeAreaLayoutGuide.layoutFrame - disableScrolling = scrollViewContentSize.height + menuButtonHeight + 10 < layoutFrame.height - } - } - return Color.clear - } - } - .onChange(of: userPickerVisible) { visible in - if visible, let u = users.first { - sp.scrollTo(u.id) + if #available(iOS 16.0, *) { + let v = viewBody.presentationDetents([.height(420)]) + if #available(iOS 16.4, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } + } else { + viewBody + } + } + + private var viewBody: some View { + let otherUsers = m.users.filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } + return List { + Section(header: Text("You").foregroundColor(theme.colors.secondary)) { + if let user = m.currentUser { + openSheetOnTap(label: { + ZStack { + let v = ProfilePreview(profileOf: user) + .foregroundColor(.primary) + .padding(.leading, -8) + if #available(iOS 16.0, *) { + v + } else { + v.padding(.vertical, 4) } } + }) { + activeSheet = .currentProfile } - } - .simultaneousGesture(DragGesture(minimumDistance: disableScrolling ? 0 : 10000000)) - .frame(maxHeight: scrollViewContentSize.height) - menuButton("Use from desktop", icon: "desktopcomputer") { - showConnectDesktop = true - withAnimation { - userPickerVisible.toggle() + openSheetOnTap(title: m.userAddress == nil ? "Create public address" : "Your public address", icon: "qrcode") { + activeSheet = .address + } + + openSheetOnTap(title: "Chat preferences", icon: "switch.2") { + activeSheet = .chatPreferences } } - Divider() - menuButton("Settings", icon: "gearshape") { - showSettings = true - withAnimation { - userPickerVisible.toggle() + } + + Section { + if otherUsers.isEmpty { + openSheetOnTap(title: "Your chat profiles", icon: "person.crop.rectangle.stack") { + activeSheet = .chatProfiles + } + } else { + let v = userPickerRow(otherUsers, size: 44) + .padding(.leading, -11) + if #available(iOS 16.0, *) { + v + } else { + v.padding(.vertical, 4) + } + } + + openSheetOnTap(title: "Use from desktop", icon: "desktopcomputer") { + activeSheet = .useFromDesktop + } + + ZStack(alignment: .trailing) { + openSheetOnTap(title: "Settings", icon: "gearshape") { + activeSheet = .settings + } + Label {} icon: { + Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") + .resizable() + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(maxWidth: 20, maxHeight: 20) + } + .onTapGesture { + if (colorScheme == .light) { + ThemeManager.applyTheme(systemDarkThemeDefault.get()) + } else { + ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) + } + } + .onLongPressGesture { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } } } } - .clipShape(RoundedRectangle(cornerRadius: 16)) - .background( - Rectangle() - .fill(theme.colors.surface) - .cornerRadius(16) - .shadow(color: .black.opacity(0.12), radius: 24, x: 0, y: 0) - ) - .onPreferenceChange(DetermineWidth.Key.self) { chatViewNameWidth = $0 } - .frame(maxWidth: chatViewNameWidth > 0 ? min(300, chatViewNameWidth + 130) : 300) - .padding(8) - .opacity(userPickerVisible ? 1.0 : 0.0) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear { - // This check prevents the call of listUsers after the app is suspended, and the database is closed. - if case .active = scenePhase { - Task { - do { - let users = try await listUsersAsync() - await MainActor.run { m.users = users } - } catch { - logger.error("Error loading users \(responseError(error))") - } - } - } - } - } - - private func userView(_ u: UserInfo) -> some View { - let user = u.user - return Button(action: { - if user.activeUser { - showSettings = true - withAnimation { - userPickerVisible.toggle() - } - } else { + // This check prevents the call of listUsers after the app is suspended, and the database is closed. + if case .active = scenePhase { Task { do { - try await changeActiveUserAsync_(user.userId, viewPwd: nil) - await MainActor.run { userPickerVisible = false } + let users = try await listUsersAsync() + await MainActor.run { m.users = users } } catch { - await MainActor.run { - AlertManager.shared.showAlertMsg( - title: "Error switching profile!", - message: "Error: \(responseError(error))" - ) - } + logger.error("Error loading users \(responseError(error))") } } } - }, label: { - HStack(spacing: 0) { - ProfileImage(imageStr: user.image, size: 44, color: Color(uiColor: .tertiarySystemFill)) - .padding(.trailing, 12) - Text(user.chatViewName) - .fontWeight(user.activeUser ? .medium : .regular) - .foregroundColor(theme.colors.onBackground) - .overlay(DetermineWidth()) - Spacer() - if user.activeUser { - Image(systemName: "checkmark") - } else if u.unreadCount > 0 { - unreadCounter(u.unreadCount, color: user.showNtfs ? theme.colors.primary : theme.colors.secondary) - } else if !user.showNtfs { - Image(systemName: "speaker.slash") + } + .modifier(ThemedBackground(grouped: true)) + .disabled(switchingProfile) + } + + private func userPickerRow(_ users: [UserInfo], size: CGFloat) -> some View { + HStack(spacing: 6) { + let s = ScrollView(.horizontal) { + HStack(spacing: 27) { + ForEach(users) { u in + if !u.user.hidden && u.user.userId != m.currentUser?.userId { + userView(u, size: size) + } + } + } + .padding(.leading, 4) + .padding(.trailing, 22) + } + ZStack(alignment: .trailing) { + if #available(iOS 16.0, *) { + s.scrollIndicators(.hidden) + } else { + s + } + LinearGradient( + colors: [.clear, .black], + startPoint: .leading, + endPoint: .trailing + ) + .frame(width: size, height: size + 3) + .blendMode(.destinationOut) + .allowsHitTesting(false) + } + .compositingGroup() + .padding(.top, -3) // to fit unread badge + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(theme.colors.secondary) + .padding(.trailing, 4) + .onTapGesture { + activeSheet = .chatProfiles + } + } + } + + private func userView(_ u: UserInfo, size: CGFloat) -> some View { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) + .padding([.top, .trailing], 3) + if (u.unreadCount > 0) { + unreadBadge(u) + } + } + .frame(width: size) + .onTapGesture { + switchingProfile = true + Task { + do { + try await changeActiveUserAsync_(u.user.userId, viewPwd: nil) + await MainActor.run { + switchingProfile = false + dismiss() + } + } catch { + await MainActor.run { + switchingProfile = false + AlertManager.shared.showAlertMsg( + title: "Error switching profile!", + message: "Error: \(responseError(error))" + ) + } } } - .padding(.trailing) - .padding([.leading, .vertical], 12) - }) - .buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill))) + } } - - private func menuButton(_ title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { - Button(action: action) { - HStack(spacing: 0) { - Text(title) - .overlay(DetermineWidth()) - Spacer() - Image(systemName: icon) + + private func openSheetOnTap(title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { + openSheetOnTap(label: { + ZStack(alignment: .leading) { + Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center) .symbolRenderingMode(.monochrome) .foregroundColor(theme.colors.secondary) + Text(title) + .foregroundColor(.primary) + .padding(.leading, 36) } - .padding(.horizontal) - .padding(.vertical, 22) - .frame(height: menuButtonHeight) - } - .buttonStyle(PressedButtonStyle(defaultColor: theme.colors.surface, pressedColor: Color(uiColor: .secondarySystemFill))) + }, action: action) + } + + private func openSheetOnTap(label: () -> V, action: @escaping () -> Void) -> some View { + Button(action: action, label: label) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + } + + private func unreadBadge(_ u: UserInfo) -> some View { + let size = dynamicSize(userFont).chatInfoSize + return unreadCountText(u.unreadCount) + .font(userFont <= .xxxLarge ? .caption : .caption2) + .foregroundColor(.white) + .padding(.horizontal, dynamicSize(userFont).unreadPadding) + .frame(minWidth: size, minHeight: size) + .background(u.user.showNtfs ? theme.colors.primary : theme.colors.secondary) + .cornerRadius(dynamicSize(userFont).unreadCorner) } -} - -private func unreadCounter(_ unread: Int, color: Color) -> some View { - unreadCountText(unread) - .font(.caption) - .foregroundColor(.white) - .padding(.horizontal, 4) - .frame(minWidth: 18, minHeight: 18) - .background(color) - .cornerRadius(10) } struct UserPicker_Previews: PreviewProvider { static var previews: some View { + @State var activeSheet: UserPickerSheet? + let m = ChatModel() m.users = [UserInfo.sampleData, UserInfo.sampleData] return UserPicker( - showSettings: Binding.constant(false), - showConnectDesktop: Binding.constant(false), - userPickerVisible: Binding.constant(true) + activeSheet: $activeSheet ) .environmentObject(m) } diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 88e4bffe9f..7b80dd1544 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -8,15 +8,22 @@ import SwiftUI -func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { +func getTopViewController() -> UIViewController? { let keyWindowScene = UIApplication.shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene if let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first, - let rootViewController = keyWindow.rootViewController { + let rootViewController = keyWindow.rootViewController { // Find the top-most presented view controller var topController = rootViewController while let presentedViewController = topController.presentedViewController { topController = presentedViewController } + return topController + } + return nil +} + +func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { + if let topController = getTopViewController() { let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil) if let completed = completed { activityViewController.completionWithItemsHandler = { _, _, _, _ in @@ -26,3 +33,22 @@ func showShareSheet(items: [Any], completed: (() -> Void)? = nil) { topController.present(activityViewController, animated: true) } } + +func showAlert( + title: String, + message: String? = nil, + buttonTitle: String, + buttonAction: @escaping () -> Void, + cancelButton: Bool +) -> Void { + if let topController = getTopViewController() { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: buttonTitle, style: .default) { _ in + buttonAction() + }) + if cancelButton { + alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel)) + } + topController.present(alert, animated: true) + } +} diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 9cc229ba80..4b9e001906 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -56,8 +56,6 @@ private enum MigrateFromDeviceViewAlert: Identifiable { struct MigrateFromDevice: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Environment(\.dismiss) var dismiss: DismissAction - @Binding var showSettings: Bool @Binding var showProgressOnSettings: Bool @State private var migrationState: MigrationFromState = .chatStopInProgress @State private var useKeychain = storeDBPassphraseGroupDefault.get() @@ -106,9 +104,6 @@ struct MigrateFromDevice: View { finishedView(chatDeletion) } } - .modifier(BackButton(label: "Back", disabled: $backDisabled) { - dismiss() - }) .onChange(of: migrationState) { state in backDisabled = switch migrationState { case .chatStopInProgress, .archiving, .linkShown, .finished: true @@ -590,7 +585,7 @@ struct MigrateFromDevice: View { } catch let error { fatalError("Error starting chat \(responseError(error))") } - showSettings = false + dismissAllSheets(animated: true) } } catch let error { alert = .error(title: "Error deleting database", error: responseError(error)) @@ -613,9 +608,7 @@ struct MigrateFromDevice: View { } // Hide settings anyway if chatDbStatus is not ok, probably passphrase needs to be entered if dismiss || m.chatDbStatus != .ok { - await MainActor.run { - showSettings = false - } + dismissAllSheets(animated: true) } } @@ -767,6 +760,6 @@ private class MigrationChatReceiver { struct MigrateFromDevice_Previews: PreviewProvider { static var previews: some View { - MigrateFromDevice(showSettings: Binding.constant(true), showProgressOnSettings: Binding.constant(false)) + MigrateFromDevice(showProgressOnSettings: Binding.constant(false)) } } diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift index be063334d3..b1f68c09f4 100644 --- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift +++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift @@ -59,13 +59,6 @@ struct ConnectDesktopView: View { var body: some View { if viaSettings { viewBody - .modifier(BackButton(label: "Back", disabled: Binding.constant(false)) { - if m.activeRemoteCtrl { - alert = .disconnectDesktop(action: .back) - } else { - dismiss() - } - }) } else { NavigationView { viewBody diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index 0c10da2103..bd8171623a 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -32,6 +32,17 @@ struct PreferencesView: View { .disabled(currentPreferences == preferences) } } + .onDisappear { + if currentPreferences != preferences { + showAlert( + title: NSLocalizedString("Your chat preferences", comment: "alert title"), + message: NSLocalizedString("Chat preferences were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: savePreferences, + cancelButton: true + ) + } + } } private func featureSection(_ feature: ChatFeature, _ allowFeature: Binding) -> some View { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index d9c83803dd..463ac4ae07 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -262,7 +262,9 @@ struct SettingsView: View { var body: some View { ZStack { - settingsView() + NavigationView { + settingsView() + } if showProgress { progressView() } @@ -274,63 +276,7 @@ struct SettingsView: View { @ViewBuilder func settingsView() -> some View { let user = chatModel.currentUser - NavigationView { List { - Section(header: Text("You").foregroundColor(theme.colors.secondary)) { - if let user = user { - NavigationLink { - UserProfile() - .navigationTitle("Your current profile") - .modifier(ThemedBackground()) - } label: { - ProfilePreview(profileOf: user) - .padding(.leading, -8) - } - } - - NavigationLink { - UserProfilesView(showSettings: $showSettings) - } label: { - settingsRow("person.crop.rectangle.stack", color: theme.colors.secondary) { Text("Your chat profiles") } - } - - - if let user = user { - NavigationLink { - UserAddressView(shareViaProfile: user.addressShared) - .navigationTitle("SimpleX address") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("qrcode", color: theme.colors.secondary) { Text("Your SimpleX address") } - } - - NavigationLink { - PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences) - .navigationTitle("Your preferences") - .modifier(ThemedBackground(grouped: true)) - } label: { - settingsRow("switch.2", color: theme.colors.secondary) { Text("Chat preferences") } - } - } - - NavigationLink { - ConnectDesktopView(viaSettings: true) - } label: { - settingsRow("desktopcomputer", color: theme.colors.secondary) { Text("Use from desktop") } - } - - NavigationLink { - MigrateFromDevice(showSettings: $showSettings, showProgressOnSettings: $showProgress) - .navigationTitle("Migrate device") - .modifier(ThemedBackground(grouped: true)) - .navigationBarTitleDisplayMode(.large) - } label: { - settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } - } - } - .disabled(chatModel.chatRunning != true) - Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) { NavigationLink { NotificationsView() @@ -381,10 +327,20 @@ struct SettingsView: View { } .disabled(chatModel.chatRunning != true) } - - chatDatabaseRow() } + Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) { + chatDatabaseRow() + NavigationLink { + MigrateFromDevice(showProgressOnSettings: $showProgress) + .navigationTitle("Migrate device") + .modifier(ThemedBackground(grouped: true)) + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") } + } + } + Section(header: Text("Help").foregroundColor(theme.colors.secondary)) { if let user = user { NavigationLink { @@ -462,11 +418,10 @@ struct SettingsView: View { } .navigationTitle("Your settings") .modifier(ThemedBackground(grouped: true)) - } - .onDisappear { - chatModel.showingTerminal = false - chatModel.terminalItems = [] - } + .onDisappear { + chatModel.showingTerminal = false + chatModel.terminalItems = [] + } } private func chatDatabaseRow() -> some View { @@ -549,17 +504,18 @@ struct ProfilePreview: View { HStack { ProfileImage(imageStr: profileOf.image, size: 44, color: color) .padding(.trailing, 6) - .padding(.vertical, 6) - VStack(alignment: .leading) { - Text(profileOf.displayName) - .fontWeight(.bold) - .font(.title2) - if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { - Text(profileOf.fullName) - } - } + profileName().lineLimit(1) } } + + private func profileName() -> Text { + var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) + if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { + t = t + Text(" (" + profileOf.fullName + ")") +// .font(.callout) + } + return t + } } struct SettingsView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index fa95c51d36..7efc8a46f5 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -14,7 +14,6 @@ struct UserAddressView: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject private var chatModel: ChatModel @EnvironmentObject var theme: AppTheme - @State var viaCreateLinkView = false @State var shareViaProfile = false @State private var aas = AutoAcceptState() @State private var savedAAS = AutoAcceptState() @@ -22,7 +21,6 @@ struct UserAddressView: View { @State private var showMailView = false @State private var mailViewResult: Result? = nil @State private var alert: UserAddressAlert? - @State private var showSaveDialogue = false @State private var progressIndicator = false @FocusState private var keyboardVisible: Bool @@ -44,26 +42,19 @@ struct UserAddressView: View { var body: some View { ZStack { - if viaCreateLinkView { - userAddressScrollView() - } else { - userAddressScrollView() - .modifier(BackButton(disabled: Binding.constant(false)) { - if savedAAS == aas { - dismiss() - } else { - keyboardVisible = false - showSaveDialogue = true - } - }) - .confirmationDialog("Save settings?", isPresented: $showSaveDialogue) { - Button("Save auto-accept settings") { - saveAAS() - dismiss() - } - Button("Exit without saving") { dismiss() } + userAddressScrollView() + .onDisappear { + if savedAAS != aas { + showAlert( + title: NSLocalizedString("Auto-accept settings", comment: "alert title"), + message: NSLocalizedString("Settings were changed.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save", comment: "alert button"), + buttonAction: saveAAS, + cancelButton: true + ) } - } + } + if progressIndicator { ZStack { if chatModel.userAddress != nil { @@ -238,7 +229,7 @@ struct UserAddressView: View { } } } label: { - Label("Create SimpleX address", systemImage: "qrcode") + Label("Create public address", systemImage: "qrcode") } } @@ -342,7 +333,7 @@ struct UserAddressView: View { } } } - + private struct AutoAcceptState: Equatable { var enable = false var incognito = false @@ -447,6 +438,8 @@ struct UserAddressView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() chatModel.userAddress = UserContactLink(connReqContact: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") + + return Group { UserAddressView() .environmentObject(chatModel) diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 06342db529..330ce56e0b 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -9,7 +9,6 @@ import SimpleXChat struct UserProfilesView: View { @EnvironmentObject private var m: ChatModel @EnvironmentObject private var theme: AppTheme - @Binding var showSettings: Bool @Environment(\.editMode) private var editMode @AppStorage(DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE) private var showHiddenProfilesNotice = true @AppStorage(DEFAULT_SHOW_MUTE_PROFILE_ALERT) private var showMuteProfileAlert = true @@ -96,8 +95,7 @@ struct UserProfilesView: View { } label: { Label("Add profile", systemImage: "plus") } - .frame(height: 44) - .padding(.vertical, 4) + .frame(height: 38) } } footer: { Text("Tap to activate profile.") @@ -285,7 +283,7 @@ struct UserProfilesView: View { await MainActor.run { onboardingStageDefault.set(.step1_SimpleXInfo) m.onboardingStage = .step1_SimpleXInfo - showSettings = false + dismissAllSheets() } } } else { @@ -308,14 +306,14 @@ struct UserProfilesView: View { Task { do { try await changeActiveUserAsync_(user.userId, viewPwd: userViewPassword(user)) + dismissAllSheets() } catch { await MainActor.run { alert = .activateUserError(error: responseError(error)) } } } } label: { HStack { - ProfileImage(imageStr: user.image, size: 44) - .padding(.vertical, 4) + ProfileImage(imageStr: user.image, size: 38) .padding(.trailing, 12) Text(user.chatViewName) Spacer() @@ -415,6 +413,6 @@ public func correctPassword(_ user: User, _ pwd: String) -> Bool { struct UserProfilesView_Previews: PreviewProvider { static var previews: some View { - UserProfilesView(showSettings: Binding.constant(true)) + UserProfilesView() } } From 87c55dbbf7196cb932ea533a5447f78ef7dd60be Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:08:16 +0400 Subject: [PATCH 039/704] multiplatform: fix delete messages alert (#4862) --- .../kotlin/chat/simplex/common/views/chat/ChatView.kt | 8 +++++++- .../chat/simplex/common/views/chat/item/ChatItemView.kt | 4 ++-- .../common/src/commonMain/resources/MR/base/strings.xml | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) 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 92c023c518..690ba89ef9 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 @@ -105,6 +105,7 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null + val fullDeleteAllowed = remember(chatInfo) { chatInfo.featureEnabled(ChatFeature.FullDelete) } SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) { ChatLayout( remoteHostId = remoteHostId, @@ -142,10 +143,15 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - chatInfo = chatInfo, deleteItems = { canDeleteForAll -> val itemIds = selectedChatItems.value + val questionText = + if (!canDeleteForAll || fullDeleteAllowed || chatInfo is ChatInfo.Local) + generalGetString(MR.strings.delete_messages_cannot_be_undone_warning) + else + generalGetString(MR.strings.delete_messages_mark_deleted_warning) if (itemIds != null) { deleteMessagesAlertDialog( itemIds.sorted(), - generalGetString(if (itemIds.size == 1) MR.strings.delete_message_mark_deleted_warning else MR.strings.delete_messages_mark_deleted_warning), + questionText = questionText, forAll = canDeleteForAll, deleteMessages = { ids, forAll -> deleteMessages(chatRh, chatInfo, ids, forAll, moderate = false) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 29717e3ecf..516e47e7ed 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -135,7 +135,7 @@ fun ChatItemView( } fun deleteMessageQuestionText(): String { - return if (!sent || fullDeleteAllowed) { + return if (!sent || fullDeleteAllowed || cInfo is ChatInfo.Local) { generalGetString(MR.strings.delete_message_cannot_be_undone_warning) } else { generalGetString(MR.strings.delete_message_mark_deleted_warning) @@ -637,7 +637,7 @@ fun DeleteItemAction( } deleteMessagesAlertDialog( itemIds, - generalGetString(if (itemIds.size == 1) MR.strings.delete_message_mark_deleted_warning else MR.strings.delete_messages_mark_deleted_warning), + generalGetString(MR.strings.delete_messages_cannot_be_undone_warning), forAll = false, deleteMessages = { ids, _ -> deleteMessages(ids) } ) 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 b8bbeb5c53..57a94bdf44 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -318,6 +318,7 @@ Delete message? Delete %d messages? Message will be deleted - this cannot be undone! + Messages will be deleted - this cannot be undone! Message will be marked for deletion. The recipient(s) will be able to reveal this message. Messages will be marked for deletion. The recipient(s) will be able to reveal these messages. Delete member message? From acf2f1fbbe2522111122e566d9ca533a72b73726 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 11 Sep 2024 15:51:28 +0100 Subject: [PATCH 040/704] android, desktop: new user picker (#4796) * user picker as modal * android dirty layout * color mode switcher * close picker on desktop opening modals * cleanup * remote hosts working * icon buttons * profile picker modal for external shares * remove stroke * color changes * add unread badge to users row * chat database settings section * safe remove of you settings section * picker should now open for single user * remove create profile from settings * paddings * handle big names * fonts and align * simple animations and shadow * address messaging * active is grey * padding * hide non active devices from pills * picker positioning * pills order * change view all profiles icon * bigger space between profiles * hosts ordering and fixes * device pill in app bar * simplex address -> public * better switch of opacity bg * create public address * font match * add icon for dark mode * padding * profile name as header * h2 is too big * icon colors * icons * settings as modal * center settings * fix use from desktop * remove logs * bar colors * remove drawer unused code * animate shading * fade in fade out * add system mode toast * shading colors * stop pushing shade up * same button as ios for opening all profiles * simplify nav bar color set * broken transition change * color mix * gradient and horizontal scroll * separate title * align avatars to top * picker should always remain open * use chevron icon to see all profiles * improvements on status and nav color set * best case bars switching working * change bar and shading on theme change * remove unused var * reset navbar colors on navigate * updated icon color * protect android calls * desktop menu matching size of right side modals * remove shading from desktop * close user picker on settings click in desktop * bigger profile image smaller gap to name * fix spacer for row scroll on android * smaller profile name * remove unused code * small refactor * unused * move desktop/mobile connection * close drawer on swipe down 30% * progress dump on new android design * paddings in scroller * gradient * android paddings * split inactive user picker between platforms * move your chat profiles inside android specific * always show your chat profiles in desktop * fix profile creation in desktop * remove unused var * update android space between badges * initial desktop design * center android icons with avatar * centered avatars * unread badge * extra space in the end of user list for android * aligned paddings on desktop * desktop paddings * paddings * remove you * unread badge same style as chatlist * use bedtime moon for dark mode * chevron same size as sun/moon * chevron and gradient * paddings * split android and desktop scaffold for picker * move bars logic to android * remove android check * more android checks * initial version of swipable modal * muted as grey * unused * close drawer on 3/4 * better close control * make all animations match * move shadow with offset * always close pciker on selection * animated float doing nothing * sync animation * animation using single float * fixed warnings * better state update * fix scrim color * better handling of picker closure on desktop * landscape mode * intentation * rename UserPickerScaffold * hide shadow when picker not open * reset inactive user scroll position on pick * unused class * left panel after new menu can be without padding * small changes * make ActiveProfilePicker reusable to reduce code duplication * make picker scrollable * refactor * refactor and fix instant reload of profiles * refactor * icon sizes * returned back ability to scroll to the picker on Android * setting system theme on desktop's right click * box * refactor * picker pill * fix desktop shadow * small change * hiding keyboard when opening picker * state specifying --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../main/java/chat/simplex/app/SimplexApp.kt | 27 +- .../views/chatlist/UserPicker.android.kt | 222 +++++++ .../kotlin/chat/simplex/common/App.kt | 97 ++- .../chat/simplex/common/platform/Platform.kt | 4 +- .../chat/simplex/common/views/TerminalView.kt | 1 - .../simplex/common/views/chat/ChatView.kt | 1 - .../common/views/chatlist/ChatListView.kt | 57 +- .../common/views/chatlist/ShareListView.kt | 51 +- .../common/views/chatlist/UserPicker.kt | 614 +++++++++++------- .../common/views/helpers/AnimationUtils.kt | 2 + .../common/views/helpers/CloseSheetBar.kt | 11 +- .../simplex/common/views/helpers/Utils.kt | 8 +- .../common/views/newchat/NewChatView.kt | 145 +++-- .../common/views/usersettings/Appearance.kt | 34 + .../usersettings/SetDeliveryReceiptsView.kt | 5 +- .../common/views/usersettings/SettingsView.kt | 57 +- .../views/usersettings/UserAddressView.kt | 4 +- .../views/usersettings/UserProfileView.kt | 1 + .../views/usersettings/UserProfilesView.kt | 9 +- .../commonMain/resources/MR/base/strings.xml | 8 + .../resources/MR/images/ic_add_group.svg | 1 + .../resources/MR/images/ic_bedtime_moon.svg | 1 + .../views/chatlist/UserPicker.desktop.kt | 86 +++ 23 files changed, 945 insertions(+), 501 deletions(-) create mode 100644 apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index e0bd2b0861..49edde55bb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -7,6 +7,7 @@ import chat.simplex.common.platform.Log import android.content.Intent import android.content.pm.ActivityInfo import android.os.* +import androidx.compose.animation.core.* import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.graphics.Color @@ -259,7 +260,6 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider override fun androidSetNightModeIfSupported() { if (Build.VERSION.SDK_INT < 31) return - val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM_THEME_NAME) { null } else { @@ -274,6 +274,31 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider uiModeManager.setApplicationNightMode(mode) } + override fun androidSetDrawerStatusAndNavBarColor( + isLight: Boolean, + drawerShadingColor: Color, + toolbarOnTop: Boolean, + navBarColor: Color, + ) { + val window = mainActivity.get()?.window ?: return + + @Suppress("DEPRECATION") + val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView) + // Blend status bar color to the animated color + val colors = CurrentColors.value.colors + val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background + window.statusBarColor = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() + val navBar = navBarColor.toArgb() + + if (window.navigationBarColor != navBar) { + window.navigationBarColor = navBar + } + + if (windowInsetController?.isAppearanceLightNavigationBars != isLight) { + windowInsetController?.isAppearanceLightNavigationBars = isLight + } + } + override fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) { val window = mainActivity.get()?.window ?: return @Suppress("DEPRECATION") diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt new file mode 100644 index 0000000000..6c16a75874 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -0,0 +1,222 @@ +package chat.simplex.common.views.chatlist + +import SectionItemView +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.DrawerDefaults.ScrimOpacity +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.* +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.User +import chat.simplex.common.model.UserInfo +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +actual fun UserPickerInactiveUsersSection( + users: List, + stopped: Boolean, + onShowAllProfilesClicked: () -> Unit, + onUserClicked: (user: User) -> Unit, +) { + val scrollState = rememberScrollState() + + if (users.isNotEmpty()) { + SectionItemView( + padding = PaddingValues( + start = 16.dp, + top = if (windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL else DEFAULT_PADDING_HALF, + bottom = DEFAULT_PADDING_HALF), + disabled = stopped + ) { + Box { + Row( + modifier = Modifier.padding(end = DEFAULT_PADDING + 30.dp).horizontalScroll(scrollState) + ) { + users.forEach { u -> + UserPickerInactiveUserBadge(u, stopped) { + onUserClicked(it) + withBGApi { + delay(500) + scrollState.scrollTo(0) + } + } + Spacer(Modifier.width(20.dp)) + } + Spacer(Modifier.width(60.dp)) + } + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier + .fillMaxWidth() + .padding(end = DEFAULT_PADDING + 30.dp) + .height(60.dp) + ) { + Canvas(modifier = Modifier.size(60.dp)) { + drawRect( + brush = Brush.horizontalGradient( + colors = listOf( + Color.Transparent, + CurrentColors.value.colors.surface, + ) + ), + ) + } + } + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(60.dp) + .fillMaxWidth() + .padding(end = DEFAULT_PADDING) + ) { + IconButton( + onClick = onShowAllProfilesClicked, + enabled = !stopped + ) { + Icon( + painterResource(MR.images.ic_chevron_right), + stringResource(MR.strings.your_chat_profiles), + tint = MaterialTheme.colors.secondary, + modifier = Modifier.size(34.dp) + ) + } + } + } + } + } else { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + onShowAllProfilesClicked + ) + } +} + +private fun calculateFraction(pos: Float) = + (pos / 1f).coerceIn(0f, 1f) + +@Composable +actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow, content: @Composable () -> Unit) { + val pickerIsVisible = pickerState.collectAsState().value.isVisible() + val dismissState = rememberDismissState(initialValue = if (pickerIsVisible) DismissValue.Default else DismissValue.DismissedToEnd) { + if (it == DismissValue.DismissedToEnd && pickerState.value.isVisible()) { + pickerState.value = AnimatedViewState.HIDING + } + true + } + val height = remember { mutableIntStateOf(0) } + val heightValue = height.intValue + val clickableModifier = if (pickerIsVisible) { + Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { pickerState.value = AnimatedViewState.HIDING }) + } else { + Modifier + } + Box( + Modifier + .fillMaxSize() + .then(clickableModifier) + .drawBehind { + val pos = when { + dismissState.progress.from == DismissValue.Default && dismissState.progress.to == DismissValue.Default -> 1f + dismissState.progress.from == DismissValue.DismissedToEnd && dismissState.progress.to == DismissValue.DismissedToEnd -> 0f + dismissState.progress.to == DismissValue.Default -> dismissState.progress.fraction + else -> 1 - dismissState.progress.fraction + } + val colors = CurrentColors.value.colors + val resultingColor = if (colors.isLight) colors.onSurface.copy(alpha = ScrimOpacity) else Color.Black.copy(0.64f) + val adjustedAlpha = resultingColor.alpha * calculateFraction(pos = pos) + val shadingColor = resultingColor.copy(alpha = adjustedAlpha) + + if (pickerState.value.isVisible()) { + platform.androidSetDrawerStatusAndNavBarColor( + isLight = colors.isLight, + drawerShadingColor = shadingColor, + toolbarOnTop = !appPrefs.oneHandUI.get(), + navBarColor = colors.surface + ) + } else if (ModalManager.start.modalCount.value == 0) { + platform.androidSetDrawerStatusAndNavBarColor( + isLight = colors.isLight, + drawerShadingColor = shadingColor, + toolbarOnTop = !appPrefs.oneHandUI.get(), + navBarColor = (if (appPrefs.oneHandUI.get() && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { + colors.background.mixWith(CurrentColors.value.colors.onBackground, 0.97f) + } else { + colors.background + }) + ) + } + drawRect( + if (pos != 0f) resultingColor else Color.Transparent, + alpha = calculateFraction(pos = pos) + ) + } + .graphicsLayer { + if (heightValue == 0) { + alpha = 0f + } + translationY = dismissState.offset.value + }, + contentAlignment = Alignment.BottomCenter + ) { + Box( + Modifier.onSizeChanged { height.intValue = it.height } + ) { + KeyChangeEffect(pickerIsVisible) { + if (pickerState.value.isVisible()) { + try { + dismissState.animateTo(DismissValue.Default, userPickerAnimSpec()) + } catch (e: CancellationException) { + Log.e(TAG, "Cancelled animateTo: ${e.stackTraceToString()}") + pickerState.value = AnimatedViewState.GONE + } + } else { + try { + dismissState.animateTo(DismissValue.DismissedToEnd, userPickerAnimSpec()) + } catch (e: CancellationException) { + Log.e(TAG, "Cancelled animateTo2: ${e.stackTraceToString()}") + pickerState.value = AnimatedViewState.VISIBLE + } + } + } + val draggableModifier = if (height.intValue != 0) + Modifier.draggableBottomDrawerModifier( + state = dismissState, + swipeDistance = height.intValue.toFloat(), + ) + else Modifier + Box(draggableModifier.then(modifier)) { + content() + } + } + } +} + +private fun Modifier.draggableBottomDrawerModifier( + state: DismissState, + swipeDistance: Float, +): Modifier = this.swipeable( + state = state, + anchors = mapOf(0f to DismissValue.Default, swipeDistance to DismissValue.DismissedToEnd), + thresholds = { _, _ -> FractionalThreshold(0.3f) }, + orientation = Orientation.Vertical, + resistance = null +) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 3cba89922d..b95aed45d2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -6,14 +6,11 @@ import androidx.compose.animation.core.Animatable import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -42,12 +39,6 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlin.math.sqrt - -data class SettingsViewState( - val userPickerState: MutableStateFlow, - val scaffoldState: ScaffoldState -) @Composable fun AppScreen() { @@ -145,13 +136,11 @@ fun MainScreen() { userPickerState.value = AnimatedViewState.VISIBLE } } - val scaffoldState = rememberScaffoldState() - val settingsState = remember { SettingsViewState(userPickerState, scaffoldState) } SetupClipboardListener() if (appPlatform.isAndroid) { - AndroidScreen(settingsState) + AndroidScreen(userPickerState) } else { - DesktopScreen(settingsState) + DesktopScreen(userPickerState) } } } @@ -249,7 +238,7 @@ fun MainScreen() { val ANDROID_CALL_TOP_PADDING = 40.dp @Composable -fun AndroidScreen(settingsState: SettingsViewState) { +fun AndroidScreen(userPickerState: MutableStateFlow) { BoxWithConstraints { val call = remember { chatModel.activeCall} .value val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted @@ -262,7 +251,7 @@ fun AndroidScreen(settingsState: SettingsViewState) { } .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) ) { - StartPartOfScreen(settingsState) + StartPartOfScreen(userPickerState) } val scope = rememberCoroutineScope() val onComposed: suspend (chatId: String?) -> Unit = { chatId -> @@ -318,15 +307,15 @@ fun AndroidScreen(settingsState: SettingsViewState) { } @Composable -fun StartPartOfScreen(settingsState: SettingsViewState) { +fun StartPartOfScreen(userPickerState: MutableStateFlow) { if (chatModel.setDeliveryReceipts.value) { SetDeliveryReceiptsView(chatModel) } else { val stopped = chatModel.chatRunning.value == false if (chatModel.sharedContent.value == null) - ChatListView(chatModel, settingsState, AppLock::setPerformLA, stopped) + ChatListView(chatModel, userPickerState, AppLock::setPerformLA, stopped) else - ShareListView(chatModel, settingsState, stopped) + ShareListView(chatModel, stopped) } } @@ -367,49 +356,41 @@ fun EndPartOfScreen() { } @Composable -fun DesktopScreen(settingsState: SettingsViewState) { - Box { - // 56.dp is a size of unused space of settings drawer - Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier + 56.dp)) { - StartPartOfScreen(settingsState) - } - Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { - ModalManager.start.showInView() - SwitchingUsersView() - } - Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { - Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { - CenterPartOfScreen() - } - if (ModalManager.end.hasModalsOpen()) { - VerticalDivider() - } - Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { - EndPartOfScreen() - } - } - val (userPickerState, scaffoldState ) = settingsState - val scope = rememberCoroutineScope() - if (scaffoldState.drawerState.isOpen || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { - Box( - Modifier - .fillMaxSize() - .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { - ModalManager.start.closeModals() - scope.launch { settingsState.scaffoldState.drawerState.close() } - }) - ) - } - VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) +fun DesktopScreen(userPickerState: MutableStateFlow) { + Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { + StartPartOfScreen(userPickerState) tryOrShowError("UserPicker", error = {}) { - UserPicker(chatModel, userPickerState) { - scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } - userPickerState.value = AnimatedViewState.GONE - } + UserPicker(chatModel, userPickerState, setPerformLA = AppLock::setPerformLA) } - ModalManager.fullscreen.showInView() } + Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { + ModalManager.start.showInView() + SwitchingUsersView() + } + Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { + Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { + CenterPartOfScreen() + } + if (ModalManager.end.hasModalsOpen()) { + VerticalDivider() + } + Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { + EndPartOfScreen() + } + } + if (userPickerState.collectAsState().value.isVisible() || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { + Box( + Modifier + .fillMaxSize() + .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { + ModalManager.start.closeModals() + userPickerState.value = AnimatedViewState.HIDING + }) + ) + } + VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) + ModalManager.fullscreen.showInView() } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index 44fcddb54c..5dfa5aa200 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -1,7 +1,6 @@ package chat.simplex.common.platform -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.* import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.* @@ -22,6 +21,7 @@ interface PlatformInterface { fun androidIsBackgroundCallAllowed(): Boolean = true fun androidSetNightModeIfSupported() {} fun androidSetStatusAndNavBarColors(isLight: Boolean, backgroundColor: Color, hasTop: Boolean, hasBottom: Boolean) {} + fun androidSetDrawerStatusAndNavBarColor(isLight: Boolean, drawerShadingColor: Color, toolbarOnTop: Boolean, navBarColor: Color) {} fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {} fun androidPictureInPictureAllowed(): Boolean = true fun androidCallEnded() {} 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 5cb97d7d80..e44a174b53 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 @@ -109,7 +109,6 @@ fun TerminalLayout( } }, contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, modifier = Modifier.navigationBarsWithImePadding() ) { contentPadding -> Surface( 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 690ba89ef9..511230cc83 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 @@ -660,7 +660,6 @@ fun ChatLayout( modifier = Modifier.navigationBarsWithImePadding(), floatingActionButton = { floatingButton.value() }, contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, backgroundColor = Color.Unspecified ) { contentPadding -> val wallpaperImage = MaterialTheme.wallpaper.type.image diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 11b1006f41..54c67674ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -23,7 +23,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.* -import chat.simplex.common.SettingsViewState +import chat.simplex.common.AppLock import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts @@ -135,7 +135,7 @@ fun ToggleChatListCard() { } @Composable -fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { +fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { val oneHandUI = remember { appPrefs.oneHandUI.state } LaunchedEffect(Unit) { if (shouldShowWhatsNew(chatModel)) { @@ -153,18 +153,15 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf VideoPlayerHolder.stopAll() } } - val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } - val scope = rememberCoroutineScope() - val (userPickerState, scaffoldState ) = settingsState Scaffold( topBar = { if (!oneHandUI.value) { - Column(Modifier.padding(end = endPadding)) { + Column { ChatListToolbar( - scaffoldState.drawerState, userPickerState, stopped, + setPerformLA, ) Divider() } @@ -172,33 +169,17 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf }, bottomBar = { if (oneHandUI.value) { - Column(Modifier.padding(end = endPadding)) { + Column { Divider() ChatListToolbar( - scaffoldState.drawerState, userPickerState, stopped, - ) - } - } - }, - scaffoldState = scaffoldState, - drawerContent = { - tryOrShowError("Settings", error = { ErrorSettingsView() }) { - val handler = remember { AppBarHandler() } - CompositionLocalProvider( - LocalAppBarHandler provides handler - ) { - ModalView(showClose = appPlatform.isDesktop, close = { scope.launch { scaffoldState.drawerState.close() } }) { - SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) - } + setPerformLA, + ) } } }, contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, - drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f), - drawerGesturesEnabled = appPlatform.isAndroid, floatingActionButton = { if (!oneHandUI.value && searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) { FloatingActionButton( @@ -208,7 +189,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } }, Modifier - .padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp) + .padding(end = DEFAULT_PADDING - 16.dp, bottom = DEFAULT_PADDING - 16.dp) .size(AppBarHeight * fontSizeSqrtMultiplier), elevation = FloatingActionButtonDefaults.elevation( defaultElevation = 0.dp, @@ -224,7 +205,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } } ) { - Box(Modifier.padding(it).padding(end = endPadding)) { + Box(Modifier.padding(it)) { Box( modifier = Modifier .fillMaxSize() @@ -252,11 +233,8 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf UserPicker( chatModel = chatModel, userPickerState = userPickerState, - contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart - ) { - scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } - userPickerState.value = AnimatedViewState.GONE - } + setPerformLA = AppLock::setPerformLA + ) } } } @@ -278,7 +256,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { } @Composable -private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean) { +private fun ChatListToolbar(userPickerState: MutableStateFlow, stopped: Boolean, setPerformLA: (Boolean) -> Unit) { val serversSummary: MutableState = remember { mutableStateOf(null) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val updatingProgress = remember { chatModel.updatingProgress }.value @@ -344,23 +322,22 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt } } } - val scope = rememberCoroutineScope() val clipboard = LocalClipboardManager.current DefaultTopAppBar( navigationButton = { if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) { - NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } } + NavigationButtonMenu { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + } } else { val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } } val allRead = users .filter { u -> !u.user.activeUser && !u.user.hidden } .all { u -> u.unreadCount == 0 } UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) { - if (users.size == 1 && chatModel.remoteHosts.isEmpty()) { - scope.launch { drawerState.open() } - } else { userPickerState.value = AnimatedViewState.VISIBLE - } } } }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index 886b82de7d..b4d0b05584 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -11,31 +11,24 @@ import androidx.compose.ui.graphics.Color import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import chat.simplex.common.SettingsViewState import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* +import chat.simplex.common.views.newchat.ActiveProfilePicker import chat.simplex.res.MR -import kotlinx.coroutines.flow.MutableStateFlow @Composable -fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stopped: Boolean) { +fun ShareListView(chatModel: ChatModel, stopped: Boolean) { var searchInList by rememberSaveable { mutableStateOf("") } - val (userPickerState, scaffoldState) = settingsState - val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp val oneHandUI = remember { appPrefs.oneHandUI.state } Scaffold( - Modifier.padding(end = endPadding), contentColor = LocalContentColor.current, - drawerContentColor = LocalContentColor.current, - scaffoldState = scaffoldState, topBar = { if (!oneHandUI.value) { Column { - ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } Divider() } } @@ -44,7 +37,7 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe if (oneHandUI.value) { Column { Divider() - ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } + ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } } } } @@ -92,21 +85,6 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe } } } - if (appPlatform.isAndroid) { - tryOrShowError("UserPicker", error = {}) { - UserPicker( - chatModel, - userPickerState, - showSettings = false, - showCancel = true, - contentAlignment = if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart, - cancelClicked = { - chatModel.sharedContent.value = null - userPickerState.value = AnimatedViewState.GONE - } - ) - } - } } private fun hasSimplexLink(msg: String): Boolean { @@ -122,7 +100,7 @@ private fun EmptyList() { } @Composable -private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableStateFlow, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { +private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchValueChanged: (String) -> Unit) { var showSearch by rememberSaveable { mutableStateOf(false) } val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false } if (showSearch) { @@ -138,7 +116,24 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState .filter { u -> !u.user.activeUser && !u.user.hidden } .all { u -> u.unreadCount == 0 } UserProfileButton(chatModel.currentUser.value?.profile?.image, allRead) { - userPickerState.value = AnimatedViewState.VISIBLE + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { + ActiveProfilePicker( + search = search, + rhId = chatModel.remoteHostId, + close = close, + contactConnection = null, + showIncognito = false + ) + } + ) + } } } else -> NavigationButtonBack(onButtonClicked = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index e15bc3863e..338690c8e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -1,53 +1,51 @@ package chat.simplex.common.views.chatlist import SectionItemView -import androidx.compose.animation.core.* +import SectionView +import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.* -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.TextStyle import dev.icerock.moko.resources.compose.painterResource import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* import chat.simplex.common.model.* -import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.CreateProfile +import chat.simplex.common.views.localauth.VerticalDivider +import chat.simplex.common.views.newchat.* import chat.simplex.common.views.remote.* -import chat.simplex.common.views.usersettings.doWithAuth +import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.AppearanceScope.ColorModeSwitcher import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlin.math.roundToInt @Composable fun UserPicker( chatModel: ChatModel, userPickerState: MutableStateFlow, - showSettings: Boolean = true, - contentAlignment: Alignment = Alignment.TopStart, - showCancel: Boolean = false, - cancelClicked: () -> Unit = {}, - useFromDesktopClicked: () -> Unit = {}, - settingsClicked: () -> Unit = {}, + setPerformLA: (Boolean) -> Unit, ) { - val scope = rememberCoroutineScope() var newChat by remember { mutableStateOf(userPickerState.value) } if (newChat.isVisible()) { BackHandler { @@ -67,18 +65,32 @@ fun UserPicker( .sortedBy { it.hostDeviceName } } } - val animatedFloat = remember { Animatable(if (newChat.isVisible()) 0f else 1f) } + + val view = LocalMultiplatformView() LaunchedEffect(Unit) { launch { userPickerState.collect { newChat = it + if (it.isVisible()) { + hideKeyboard(view) + } launch { - animatedFloat.animateTo(if (newChat.isVisible()) 1f else 0f, newChatSheetAnimSpec()) if (newChat.isHiding()) userPickerState.value = AnimatedViewState.GONE } } } } + + LaunchedEffect(Unit) { + launch { + snapshotFlow { ModalManager.start.modalCount.value } + .filter { it > 0 } + .collect { + closePicker(userPickerState) + } + } + } + LaunchedEffect(Unit) { snapshotFlow { newChat.isVisible() } .distinctUntilChanged() @@ -124,110 +136,143 @@ fun UserPicker( } } } - val UsersView: @Composable ColumnScope.() -> Unit = { - users.forEach { u -> - UserProfilePickerItem(u.user, u.unreadCount, openSettings = settingsClicked) { - userPickerState.value = AnimatedViewState.HIDING - if (!u.user.activeUser) { - withBGApi { - controller.showProgressIfNeeded { - ModalManager.closeAllModalsEverywhere() - chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null) - } - } - } - } - Divider(Modifier.requiredHeight(1.dp)) - if (u.user.activeUser) Divider(Modifier.requiredHeight(0.5.dp)) - } - } - val xOffset = with(LocalDensity.current) { 10.dp.roundToPx() } - val maxWidth = with(LocalDensity.current) { windowWidth() * density } - Box(Modifier - .fillMaxSize() - .offset { IntOffset(if (newChat.isGone()) -maxWidth.value.roundToInt() else xOffset, 0) } - .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { userPickerState.value = AnimatedViewState.HIDING }) - .padding(bottom = 10.dp, top = 10.dp) - .graphicsLayer { - alpha = animatedFloat.value - translationY = (if (appPrefs.oneHandUI.state.value) -1 else 1) * (animatedFloat.value - 1) * xOffset - }, - contentAlignment = contentAlignment + + PlatformUserPicker( + modifier = Modifier + .height(IntrinsicSize.Min) + .fillMaxWidth() + .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier) + .background(MaterialTheme.colors.surface) + .padding(vertical = DEFAULT_PADDING), + pickerState = userPickerState ) { - Column( - Modifier - .widthIn(min = 260.dp) - .width(IntrinsicSize.Min) - .height(IntrinsicSize.Min) - .shadow(8.dp, RoundedCornerShape(corner = CornerSize(25.dp)), clip = true) - .background(MaterialTheme.colors.surface, RoundedCornerShape(corner = CornerSize(25.dp))) - .clip(RoundedCornerShape(corner = CornerSize(25.dp))) - ) { - val currentRemoteHost = remember { chatModel.currentRemoteHost }.value - Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) { - if (remoteHosts.isNotEmpty()) { - if (currentRemoteHost == null && chatModel.localUserCreated.value == true) { - LocalDevicePickerItem(true) { - userPickerState.value = AnimatedViewState.HIDING - switchToLocalDevice() - } - Divider(Modifier.requiredHeight(1.dp)) - } else if (currentRemoteHost != null) { - val connecting = rememberSaveable { mutableStateOf(false) } - RemoteHostPickerItem(currentRemoteHost, - actionButtonClick = { - userPickerState.value = AnimatedViewState.HIDING - stopRemoteHostAndReloadHosts(currentRemoteHost, true) - }) { - userPickerState.value = AnimatedViewState.HIDING - switchToRemoteHost(currentRemoteHost, connecting) - } - Divider(Modifier.requiredHeight(1.dp)) - } - } + @Composable + fun FirstSection() { + if (remoteHosts.isNotEmpty()) { + val currentRemoteHost = remember { chatModel.currentRemoteHost }.value + val localDeviceActive = currentRemoteHost == null && chatModel.localUserCreated.value == true - UsersView() - - if (remoteHosts.isNotEmpty() && currentRemoteHost != null && chatModel.localUserCreated.value == true) { - LocalDevicePickerItem(false) { + DevicePickerRow( + localDeviceActive = localDeviceActive, + remoteHosts = remoteHosts, + onRemoteHostClick = { h, connecting -> + userPickerState.value = AnimatedViewState.HIDING + switchToRemoteHost(h, connecting) + }, + onLocalDeviceClick = { userPickerState.value = AnimatedViewState.HIDING switchToLocalDevice() + }, + onRemoteHostActionButtonClick = { h -> + userPickerState.value = AnimatedViewState.HIDING + stopRemoteHostAndReloadHosts(h, true) + } + ) + } + ActiveUserSection( + chatModel = chatModel, + userPickerState = userPickerState, + ) + } + + @Composable + fun SecondSection() { + GlobalSettingsSection( + chatModel = chatModel, + userPickerState = userPickerState, + setPerformLA = setPerformLA, + onUserClicked = { user -> + userPickerState.value = AnimatedViewState.HIDING + if (!user.activeUser) { + withBGApi { + controller.showProgressIfNeeded { + ModalManager.closeAllModalsEverywhere() + chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) + } + } + } + }, + onShowAllProfilesClicked = { + doWithAuth( + generalGetString(MR.strings.auth_open_chat_profiles), + generalGetString(MR.strings.auth_log_in_using_credential) + ) { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + val profileHidden = rememberSaveable { mutableStateOf(false) } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { UserProfilesView(chatModel, search, profileHidden) }) + } } - Divider(Modifier.requiredHeight(1.dp)) } - remoteHosts.filter { !it.activeHost }.forEach { h -> - val connecting = rememberSaveable { mutableStateOf(false) } - RemoteHostPickerItem(h, - actionButtonClick = { - userPickerState.value = AnimatedViewState.HIDING - stopRemoteHostAndReloadHosts(h, false) - }) { - userPickerState.value = AnimatedViewState.HIDING - switchToRemoteHost(h, connecting) - } - Divider(Modifier.requiredHeight(1.dp)) + ) + } + + if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { + Column { + FirstSection() + Divider(Modifier.padding(DEFAULT_PADDING)) + SecondSection() + } + } else { + Row { + Box(Modifier.weight(1f)) { + FirstSection() + } + VerticalDivider() + Box(Modifier.weight(1f)) { + SecondSection() } } - if (appPlatform.isAndroid) { - UseFromDesktopPickerItem { - ModalManager.start.showCustomModal { close -> - ConnectDesktopView(close) - } - userPickerState.value = AnimatedViewState.GONE - } - Divider(Modifier.requiredHeight(1.dp)) - } else { - if (remoteHosts.isEmpty()) { - LinkAMobilePickerItem { - ModalManager.start.showModal { - ConnectMobileView() - } - userPickerState.value = AnimatedViewState.GONE - } - Divider(Modifier.requiredHeight(1.dp)) - } - if (chatModel.desktopNoUserNoRemote) { - CreateInitialProfile { + } + } +} + +@Composable +private fun ActiveUserSection( + chatModel: ChatModel, + userPickerState: MutableStateFlow, +) { + val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> + { + ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } + } + } + val currentUser = remember { chatModel.currentUser }.value + val stopped = chatModel.chatRunning.value == false + + if (currentUser != null) { + SectionView { + SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(currentUser.profile, stopped = stopped) + } + UserPickerOptionRow( + painterResource(MR.images.ic_qr_code), + if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_public_contact_address) else generalGetString(MR.strings.create_public_contact_address), + showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped + ) + UserPickerOptionRow( + painterResource(MR.images.ic_toggle_on), + stringResource(MR.strings.chat_preferences), + click = if (stopped) null else ({ + showCustomModal { m, close -> + PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) + }() + }), + disabled = stopped + ) + } + } else { + SectionView { + if (chatModel.desktopNoUserNoRemote) { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + generalGetString(MR.strings.create_chat_profile), + { doWithAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.center.showModalCloseable { close -> LaunchedEffect(Unit) { @@ -237,15 +282,76 @@ fun UserPicker( } } } - Divider(Modifier.requiredHeight(1.dp)) + ) + } + } + } +} + +@Composable +private fun GlobalSettingsSection( + chatModel: ChatModel, + userPickerState: MutableStateFlow, + setPerformLA: (Boolean) -> Unit, + onUserClicked: (user: User) -> Unit, + onShowAllProfilesClicked: () -> Unit +) { + val stopped = chatModel.chatRunning.value == false + val users by remember { + derivedStateOf { + chatModel.users + .filter { u -> !u.user.hidden && !u.user.activeUser } + } + } + + SectionView(headerBottomPadding = if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_PADDING else 0.dp) { + UserPickerInactiveUsersSection( + users = users, + onShowAllProfilesClicked = onShowAllProfilesClicked, + onUserClicked = onUserClicked, + stopped = stopped + ) + + if (appPlatform.isAndroid) { + val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) + + UserPickerOptionRow( + painterResource(MR.images.ic_desktop), + text, + click = { + ModalManager.start.showCustomModal { close -> + ConnectDesktopView(close) + } } - } - if (showSettings) { - SettingsPickerItem(settingsClicked) - } - if (showCancel) { - CancelPickerItem(cancelClicked) - } + ) + } else { + UserPickerOptionRow( + icon = painterResource(MR.images.ic_smartphone_300), + text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), + click = { + userPickerState.value = AnimatedViewState.HIDING + ModalManager.start.showModal { + ConnectMobileView() + } + }, + disabled = stopped + ) + } + + SectionItemView( + click = { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + }, + padding = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) + ) { + val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) + Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text, color = Color.Unspecified) + Spacer(Modifier.weight(1f)) + ColorModeSwitcher() } } } @@ -296,7 +402,7 @@ fun UserProfilePickerItem( } } else if (!u.showNtfs) { Icon(painterResource(MR.images.ic_notifications_off), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary) - } else { + } else { Box(Modifier.size(20.dp)) } } @@ -325,136 +431,157 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr } @Composable -fun RemoteHostPickerItem(h: RemoteHostInfo, onLongClick: () -> Unit = {}, actionButtonClick: () -> Unit = {}, onClick: () -> Unit) { - Row( - Modifier - .fillMaxWidth() - .background(color = if (h.activeHost) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) - .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick - ) - .onRightClick { onLongClick() } - .padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - RemoteHostRow(h) - if (h.sessionState is RemoteHostSessionState.Connected) { - HostDisconnectButton(actionButtonClick) - } else { - Box(Modifier.size(20.dp)) +fun UserPickerOptionRow(icon: Painter, text: String, click: (() -> Unit)? = null, disabled: Boolean = false) { + SectionItemView(click, disabled = disabled, extraPadding = true) { + Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text = text, color = if (disabled) MaterialTheme.colors.secondary else Color.Unspecified) + } +} + +@Composable +fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = 60.dp, onClick: (user: User) -> Unit) { + Box { + IconButton( + onClick = { onClick(userInfo.user) }, + enabled = !stopped + ) { + Box { + ProfileImage(size = size, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (userInfo.unreadCount > 0) { + unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs) + } + } } } } @Composable -fun RemoteHostRow(h: RemoteHostInfo) { - Row( - Modifier - .widthIn(max = windowWidth() * 0.7f) - .padding(start = 17.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(painterResource(MR.images.ic_smartphone_300), h.hostDeviceName, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Text( - h.hostDeviceName, - modifier = Modifier.padding(start = 26.dp, end = 8.dp), - color = if (h.activeHost) MaterialTheme.colors.onBackground else MenuTextColor, - fontSize = 14.sp, - ) - } -} - -@Composable -fun LocalDevicePickerItem(active: Boolean, onLongClick: () -> Unit = {}, onClick: () -> Unit) { +private fun DevicePickerRow( + localDeviceActive: Boolean, + remoteHosts: List, + onLocalDeviceClick: () -> Unit, + onRemoteHostClick: (rh: RemoteHostInfo, connecting: MutableState) -> Unit, + onRemoteHostActionButtonClick: (rh: RemoteHostInfo) -> Unit, +) { Row( Modifier .fillMaxWidth() - .background(color = if (active) MaterialTheme.colors.surface.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified) .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .combinedClickable( - onClick = if (active) {{}} else onClick, - onLongClick = onLongClick, - interactionSource = remember { MutableInteractionSource() }, - indication = if (!active) LocalIndication.current else null - ) - .onRightClick { onLongClick() } - .padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), - horizontalArrangement = Arrangement.SpaceBetween, + .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), + horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { - LocalDeviceRow(active) - Box(Modifier.size(20.dp)) + val activeHost = remoteHosts.firstOrNull { h -> h.activeHost } + + if (activeHost != null) { + val connecting = rememberSaveable { mutableStateOf(false) } + + DevicePill( + active = true, + icon = painterResource(MR.images.ic_smartphone_300), + text = activeHost.hostDeviceName, + actionButtonVisible = activeHost.sessionState is RemoteHostSessionState.Connected, + onActionButtonClick = { onRemoteHostActionButtonClick(activeHost) } + ) { + onRemoteHostClick(activeHost, connecting) + } + } + + DevicePill( + active = localDeviceActive, + icon = painterResource(MR.images.ic_desktop), + text = stringResource(MR.strings.this_device), + actionButtonVisible = false + ) { + onLocalDeviceClick() + } + + remoteHosts.filter { h -> h.sessionState is RemoteHostSessionState.Connected && !h.activeHost }.forEach { h -> + val connecting = rememberSaveable { mutableStateOf(false) } + + DevicePill( + active = h.activeHost, + icon = painterResource(MR.images.ic_smartphone_300), + text = h.hostDeviceName, + actionButtonVisible = h.sessionState is RemoteHostSessionState.Connected, + onActionButtonClick = { onRemoteHostActionButtonClick(h) } + ) { + onRemoteHostClick(h, connecting) + } + } } } @Composable -fun LocalDeviceRow(active: Boolean) { +expect fun UserPickerInactiveUsersSection( + users: List, + stopped: Boolean, + onShowAllProfilesClicked: () -> Unit, + onUserClicked: (user: User) -> Unit, +) + +@Composable +expect fun PlatformUserPicker( + modifier: Modifier, + pickerState: MutableStateFlow, + content: @Composable () -> Unit +) + +@Composable +fun DevicePill( + active: Boolean, + icon: Painter, + text: String, + actionButtonVisible: Boolean, + onActionButtonClick: (() -> Unit)? = null, + onClick: () -> Unit) { Row( Modifier - .widthIn(max = windowWidth() * 0.7f) - .padding(start = 17.dp, end = DEFAULT_PADDING), + .clip(RoundedCornerShape(8.dp)) + .border( + BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant), + shape = RoundedCornerShape(8.dp) + ) + .background(if (active) MaterialTheme.colors.secondaryVariant else Color.Transparent) + .clickable( + enabled = !active, + onClick = onClick, + interactionSource = remember { MutableInteractionSource() }, + indication = LocalIndication.current + ), verticalAlignment = Alignment.CenterVertically ) { - Icon(painterResource(MR.images.ic_desktop), stringResource(MR.strings.this_device), Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Text( - stringResource(MR.strings.this_device), - modifier = Modifier.padding(start = 26.dp, end = 8.dp), - color = if (active) MaterialTheme.colors.onBackground else MenuTextColor, - fontSize = 14.sp, - ) - } -} - -@Composable -private fun UseFromDesktopPickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun LinkAMobilePickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.link_a_mobile) - Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun CreateInitialProfile(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.create_chat_profile) - Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun SettingsPickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) - } -} - -@Composable -private fun CancelPickerItem(onClick: () -> Unit) { - SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { - val text = generalGetString(MR.strings.cancel_verb) - Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) - Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) - Text(text, color = MenuTextColor) + Row( + Modifier.padding(horizontal = 6.dp, vertical = 4.dp) + ) { + Icon( + icon, + text, + Modifier.size(16.dp * fontSizeSqrtMultiplier), + tint = MaterialTheme.colors.onSurface + ) + Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON * fontSizeSqrtMultiplier)) + Text( + text, + color = MaterialTheme.colors.onSurface, + fontSize = 12.sp, + ) + if (onActionButtonClick != null && actionButtonVisible) { + val interactionSource = remember { MutableInteractionSource() } + val hovered = interactionSource.collectIsHoveredAsState().value + Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON * fontSizeSqrtMultiplier)) + IconButton(onActionButtonClick, Modifier.requiredSize(16.dp * fontSizeSqrtMultiplier)) { + Icon( + painterResource(if (hovered) MR.images.ic_wifi_off else MR.images.ic_wifi), + null, + Modifier.size(16.dp * fontSizeSqrtMultiplier).hoverable(interactionSource), + tint = if (hovered) WarningOrange else MaterialTheme.colors.onBackground + ) + } + } + } } } @@ -472,6 +599,29 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) { } } +@Composable +private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { + Text( + if (unreadCount > 0) unreadCountStr(unreadCount) else "", + color = Color.White, + fontSize = 10.sp, + style = TextStyle(textAlign = TextAlign.Center), + modifier = Modifier + .offset(y = 3.sp.toDp()) + .background(if (userMuted) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.secondary, shape = CircleShape) + .badgeLayout() + .padding(horizontal = 2.sp.toDp()) + .padding(vertical = 2.sp.toDp()) + .align(Alignment.TopEnd) + ) +} + + +private suspend fun closePicker(userPickerState: MutableStateFlow) { + delay(500) + userPickerState.value = AnimatedViewState.HIDING +} + private fun switchToLocalDevice() { withBGApi { chatController.switchUIRemoteHost(null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt index 6a400295ed..4fdbd97d23 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AnimationUtils.kt @@ -7,3 +7,5 @@ fun chatListAnimationSpec() = tween(durationMillis = 250, easing = FastOu fun newChatSheetAnimSpec() = tween(256, 0, LinearEasing) fun audioProgressBarAnimationSpec() = tween(durationMillis = 30, easing = LinearEasing) + +fun userPickerAnimSpec() = tween(256, 0, FastOutSlowInEasing) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 90f8299404..104c05309c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.* import chat.simplex.common.platform.appPlatform import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chatlist.DevicePill import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import kotlin.math.absoluteValue @@ -157,9 +158,13 @@ private fun bottomTitleAlpha(connection: CollapsingAppBarNestedScrollConnection? @Composable private fun HostDeviceTitle(hostDevice: Pair, extraPadding: Boolean = false) { Row(Modifier.fillMaxWidth().padding(top = 5.dp, bottom = if (extraPadding) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { - Icon(painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), null, Modifier.size(15.dp), tint = MaterialTheme.colors.secondary) - Spacer(Modifier.width(10.dp)) - Text(hostDevice.second, color = MaterialTheme.colors.secondary) + DevicePill( + active = true, + onClick = {}, + actionButtonVisible = false, + icon = painterResource(if (hostDevice.first == null) MR.images.ic_desktop else MR.images.ic_smartphone_300), + text = hostDevice.second + ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 7512cf872e..7b504116cc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -588,9 +588,11 @@ fun KeyChangeEffect( var anyChange by remember { mutableStateOf(false) } LaunchedEffect(key1) { if (anyChange || key1 != prevKey) { - block(prevKey) + val prev = prevKey prevKey = key1 anyChange = true + // Call it as the last statement because the coroutine can be cancelled earlier + block(prev) } } } @@ -610,8 +612,8 @@ fun KeyChangeEffect( var anyChange by remember { mutableStateOf(false) } LaunchedEffect(key1, key2) { if (anyChange || key1 != initialKey || key2 != initialKey2) { - block() anyChange = true + block() } } } @@ -633,8 +635,8 @@ fun KeyChangeEffect( var anyChange by remember { mutableStateOf(false) } LaunchedEffect(key1, key2, key3) { if (anyChange || key1 != initialKey || key2 != initialKey2 || key3 != initialKey3) { - block() anyChange = true + block() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 544f5f72bb..a05de0e8b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -2,7 +2,6 @@ package chat.simplex.common.views.newchat import SectionBottomSpacer import SectionItemView -import SectionSpacer import SectionTextFooter import SectionView import TextIconSpaced @@ -267,24 +266,13 @@ private fun ProfilePickerOption( ) } -private fun filteredProfiles(users: List, searchTextOrPassword: String): List { - val s = searchTextOrPassword.trim() - val lower = s.lowercase() - return users.filter { u -> - if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) { - true - } else { - correctPassword(u, s) - } - } -} - @Composable -private fun ActiveProfilePicker( +fun ActiveProfilePicker( search: MutableState, contactConnection: PendingContactConnection?, close: () -> Unit, - rhId: Long? + rhId: Long?, + showIncognito: Boolean = true ) { val switchingProfile = remember { mutableStateOf(false) } val incognito = remember { @@ -292,11 +280,9 @@ private fun ActiveProfilePicker( } val selectedProfile by remember { chatModel.currentUser } val searchTextOrPassword = rememberSaveable { search } - val profiles = remember { - chatModel.users.map { it.user }.sortedBy { !it.activeUser } - } - val filteredProfiles by remember { - derivedStateOf { filteredProfiles(profiles, searchTextOrPassword.value) } + // Intentionally don't use derivedStateOf in order to NOT change an order after user was selected + val filteredProfiles = remember(searchTextOrPassword.value) { + filteredProfiles(chatModel.users.map { it.user }.sortedBy { !it.activeUser }, searchTextOrPassword.value) } var progressByTimeout by rememberSaveable { mutableStateOf(false) } @@ -322,32 +308,38 @@ private fun ActiveProfilePicker( switchingProfile.value = true withApi { try { + var updatedConn: PendingContactConnection? = null; + if (contactConnection != null) { - val conn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) - if (conn != null) { + updatedConn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId) + if (updatedConn != null) { withChats { - updateContactConnection(rhId, conn) - updateShownConnection(conn) + updateContactConnection(rhId, updatedConn) + updateShownConnection(updatedConn) } - controller.changeActiveUser_( - rhId = user.remoteHostId, - toUserId = user.userId, - viewPwd = if (user.hidden) searchTextOrPassword.value else null - ) - - if (chatModel.currentUser.value?.userId != user.userId) { - AlertManager.shared.showAlertMsg(generalGetString( - MR.strings.switching_profile_error_title), - String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) - ) - } - - withChats { - updateContactConnection(user.remoteHostId, conn) - } - close.invoke() } } + + controller.changeActiveUser_( + rhId = user.remoteHostId, + toUserId = user.userId, + viewPwd = if (user.hidden) searchTextOrPassword.value else null + ) + + if (chatModel.currentUser.value?.userId != user.userId) { + AlertManager.shared.showAlertMsg(generalGetString( + MR.strings.switching_profile_error_title), + String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName) + ) + } + + if (updatedConn != null) { + withChats { + updateContactConnection(user.remoteHostId, updatedConn) + } + } + + close() } finally { switchingProfile.value = false } @@ -364,24 +356,21 @@ private fun ActiveProfilePicker( title = stringResource(MR.strings.incognito), selected = incognito, onSelected = { - if (!incognito) { - switchingProfile.value = true - withApi { - try { - if (contactConnection != null) { - val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) + if (incognito || switchingProfile.value || contactConnection == null) return@ProfilePickerOption - if (conn != null) { - withChats { - updateContactConnection(rhId, conn) - updateShownConnection(conn) - } - close.invoke() - } + switchingProfile.value = true + withApi { + try { + val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) + if (conn != null) { + withChats { + updateContactConnection(rhId, conn) + updateShownConnection(conn) } - } finally { - switchingProfile.value = false + close() } + } finally { + switchingProfile.value = false } } }, @@ -413,20 +402,18 @@ private fun ActiveProfilePicker( if (activeProfile != null) { val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId } - - if (incognito) { - item { - IncognitoUserOption() - } - item { - ProfilePickerUserOption(activeProfile) - } - } else { - item { - ProfilePickerUserOption(activeProfile) - } - item { - IncognitoUserOption() + item { + when { + !showIncognito -> + ProfilePickerUserOption(activeProfile) + incognito -> { + IncognitoUserOption() + ProfilePickerUserOption(activeProfile) + } + else -> { + ProfilePickerUserOption(activeProfile) + IncognitoUserOption() + } } } @@ -434,8 +421,10 @@ private fun ActiveProfilePicker( ProfilePickerUserOption(p) } } else { - item { - IncognitoUserOption() + if (showIncognito) { + item { + IncognitoUserOption() + } } itemsIndexed(filteredProfiles) { _, p -> ProfilePickerUserOption(p) @@ -641,6 +630,18 @@ fun LinkTextView(link: String, share: Boolean) { } } +private fun filteredProfiles(users: List, searchTextOrPassword: String): List { + val s = searchTextOrPassword.trim() + val lower = s.lowercase() + return users.filter { u -> + if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) { + true + } else { + correctPassword(u, s) + } + } +} + private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boolean { if (text != null && strIsSimplexLink(text)) { connect(rhId, text, close) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 3747ae047a..bef837ba94 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -9,6 +9,7 @@ import SectionView import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors @@ -606,6 +607,39 @@ object AppearanceScope { } } + @Composable + fun ColorModeSwitcher() { + val currentTheme by CurrentColors.collectAsState() + val themeMode = if (remember { appPrefs.currentTheme.state }.value == DefaultTheme.SYSTEM_THEME_NAME) { + if (systemInDarkThemeCurrently) DefaultThemeMode.DARK else DefaultThemeMode.LIGHT + } else { + currentTheme.base.mode + } + + val onLongClick = { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) + showToast(generalGetString(MR.strings.system_mode_toast)) + + saveThemeToDatabase(null) + } + Box( + modifier = Modifier + .clip(CircleShape) + .combinedClickable( + onClick = { + ThemeManager.applyTheme(if (themeMode == DefaultThemeMode.LIGHT) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.themeName) + saveThemeToDatabase(null) + }, + onLongClick = onLongClick + ) + .onRightClick(onLongClick) + .size(44.dp), + contentAlignment = Alignment.Center + ) { + Icon(painterResource(if (themeMode == DefaultThemeMode.LIGHT) MR.images.ic_light_mode else MR.images.ic_bedtime_moon), stringResource(MR.strings.color_mode_light), tint = MaterialTheme.colors.secondary) + } + } + private var updateBackendJob: Job = Job() private fun saveThemeToDatabase(themeUserDestination: Pair?) { val remoteHostId = chatModel.remoteHostId() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index 2f6c0395ec..0229e7da2a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -73,10 +73,7 @@ private fun SetDeliveryReceiptsLayout( skip: () -> Unit, userCount: Int, ) { - // This view located in the left panel which means it has to have a padding from right side in order - // to see scroll bar. And this padding should be applied to upper element, not scrollable column modifier - val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp - Box(Modifier.padding(top = DEFAULT_PADDING, end = endPadding)) { + Box(Modifier.padding(top = DEFAULT_PADDING)) { ColumnWithScrollBar( Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, 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 3e1522b288..bb4a0b61b0 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 @@ -31,13 +31,11 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.migration.MigrateFromDeviceView import chat.simplex.common.views.onboarding.SimpleXInfo import chat.simplex.common.views.onboarding.WhatsNewView -import chat.simplex.common.views.remote.ConnectDesktopView -import chat.simplex.common.views.remote.ConnectMobileView import chat.simplex.res.MR import kotlinx.coroutines.* @Composable -fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerState: DrawerState) { +fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: () -> Unit) { val user = chatModel.currentUser.value val stopped = chatModel.chatRunning.value == false SettingsLayout( @@ -71,10 +69,9 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerSt } }, withAuth = ::doWithAuth, - drawerState = drawerState, ) KeyChangeEffect(chatModel.updatingProgress.value != null) { - drawerState.close() + close() } } @@ -96,18 +93,11 @@ fun SettingsLayout( showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit, - drawerState: DrawerState, ) { val scope = rememberCoroutineScope() - val closeSettings: () -> Unit = { scope.launch { drawerState.close() } } val view = LocalMultiplatformView() - if (drawerState.isOpen) { - BackHandler { - closeSettings() - } - LaunchedEffect(Unit) { - hideKeyboard(view) - } + LaunchedEffect(Unit) { + hideKeyboard(view) } val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current @@ -118,46 +108,22 @@ fun SettingsLayout( ) { AppBarTitle(stringResource(MR.strings.your_settings)) - SectionView(stringResource(MR.strings.settings_section_title_you)) { - val profileHidden = rememberSaveable { mutableStateOf(false) } - if (profile != null) { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(profile, stopped = stopped) - } - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden, drawerState) } } }, disabled = stopped) - SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped) - ChatPreferencesItem(showCustomModal, stopped = stopped) - } else if (chatModel.localUserCreated.value == false) { - SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { - withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { - ModalManager.center.showModalCloseable { close -> - LaunchedEffect(Unit) { - closeSettings() - } - CreateProfile(chatModel, close) - } - } - }, disabled = stopped) - } - if (appPlatform.isDesktop) { - SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped) - } else { - SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal { it, close -> ConnectDesktopView(close) }, disabled = stopped) - } - SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped) - } - SectionDividerSpaced() - SectionView(stringResource(MR.strings.settings_section_title_settings)) { SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }) - DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } SectionDividerSpaced() + SectionView(stringResource(MR.strings.settings_section_title_chat_database)) { + DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) + SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped) + } + + SectionDividerSpaced() + SectionView(stringResource(MR.strings.settings_section_title_help)) { SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped) SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped) @@ -535,7 +501,6 @@ fun PreviewSettingsLayout() { showCustomModal = { {} }, showVersion = {}, withAuth = { _, _, _ -> }, - drawerState = DrawerState(DrawerValue.Closed), ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index b357272e16..1f546fb863 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -174,7 +174,7 @@ private fun UserAddressLayout( saveAas: (AutoAcceptState, MutableState) -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId)) + AppBarTitle(stringResource(MR.strings.public_address), hostDevice(user?.remoteHostId)) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally, @@ -230,7 +230,7 @@ private fun UserAddressLayout( private fun CreateAddressButton(onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_qr_code), - stringResource(MR.strings.create_simplex_address), + stringResource(MR.strings.create_public_address), onClick, iconColor = MaterialTheme.colors.primary, textColor = MaterialTheme.colors.primary, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index e3636ec9c5..10acaffe1a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -34,6 +34,7 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { KeyChangeEffect(u.value?.remoteHostId, u.value?.userId) { close() } + if (user != null) { var profile by remember { mutableStateOf(user.profile.toProfile()) } UserProfileLayout( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index d4334dfed2..dcf8351166 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -36,11 +36,10 @@ import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* @Composable -fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: MutableState, drawerState: DrawerState) { +fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: MutableState) { val searchTextOrPassword = rememberSaveable { search } val users by remember { derivedStateOf { m.users.map { it.user } } } val filteredUsers by remember { derivedStateOf { filteredUsers(m, searchTextOrPassword.value) } } - val scope = rememberCoroutineScope() UserProfilesLayout( users = users, filteredUsers = filteredUsers, @@ -51,12 +50,6 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: addUser = { ModalManager.center.showModalCloseable { close -> CreateProfile(m, close) - if (appPlatform.isDesktop) { - // Hide settings to allow clicks to pass through to CreateProfile view - DisposableEffectOnGone(always = { scope.launch { drawerState.close() } }) { - // Show settings again to allow intercept clicks to close modals after profile creation finishes - scope.launch(NonCancellable) { drawerState.open() } } - } } }, activateUser = { user -> 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 cfdb58b7e4..704c6533ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -701,6 +701,10 @@ %s is verified %s is not verified + + Create public address + Your public address + Your settings Your SimpleX address @@ -849,6 +853,8 @@ Notifications will stop working until you re-launch the app + Public address + Create public address Create address Delete address? Your contacts will remain connected. @@ -1150,6 +1156,7 @@ YOU SETTINGS + CHAT DATABASE HELP SUPPORT SIMPLEX CHAT APP @@ -1714,6 +1721,7 @@ Remove image Font size Zoom + System mode Good afternoon! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg new file mode 100644 index 0000000000..158f4cfab0 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_add_group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg new file mode 100644 index 0000000000..ed5bc12d4a --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_bedtime_moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt new file mode 100644 index 0000000000..583d5437c3 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -0,0 +1,86 @@ +package chat.simplex.common.views.chatlist + +import androidx.compose.animation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.User +import chat.simplex.common.model.UserInfo +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +actual fun UserPickerInactiveUsersSection( + users: List, + stopped: Boolean, + onShowAllProfilesClicked: () -> Unit, + onUserClicked: (user: User) -> Unit, +) { + if (users.isNotEmpty()) { + val userRows = users.chunked(5) + val rowsToDisplay = if (userRows.size > 2) 2 else userRows.size + val horizontalPadding = DEFAULT_PADDING_HALF + 8.dp + + Column(Modifier + .padding(horizontal = horizontalPadding, vertical = DEFAULT_PADDING_HALF) + .height(55.dp * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) + ) { + ColumnWithScrollBar( + verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) + ) { + val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (55.dp * 5)) / 5 + + userRows.forEach { row -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(spaceBetween), + ) { + row.forEach { u -> + UserPickerInactiveUserBadge(u, stopped, size = 55.dp) { + onUserClicked(u.user) + } + } + } + } + } + } + } + + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + onShowAllProfilesClicked + ) +} + +@Composable +actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow, content: @Composable () -> Unit) { + AnimatedVisibility( + visible = pickerState.value.isVisible(), + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + Modifier + .fillMaxSize() + .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { pickerState.value = AnimatedViewState.HIDING }), + contentAlignment = Alignment.TopStart + ) { + ColumnWithScrollBar(modifier) { + content() + } + } + } +} \ No newline at end of file From 46d774a8220d332b283ba3d8c1305d3dbb0d4a0c Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 11 Sep 2024 21:30:09 +0100 Subject: [PATCH 041/704] core: bulk forward missing files error handling (#4860) * add types * wip dump * collect errors * Update src/Simplex/Chat/View.hs Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * test with not received files * remove ciFileLoaded * undo refactoring * test for skipping missing file with text * add test for empty message * remove fdescribes * copy or cleanup files after collecting errors and forward reqs * don't forward w/t content * translate CIFSRcvAborted into FFENotAccepted * refactor * refactor --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- src/Simplex/Chat.hs | 211 ++++++++++++++++++--------------- src/Simplex/Chat/Controller.hs | 6 +- src/Simplex/Chat/Messages.hs | 39 +++--- src/Simplex/Chat/View.hs | 9 +- tests/ChatTests/Forward.hs | 45 +++++-- 5 files changed, 190 insertions(+), 120 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c140025648..8f9d985efe 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -980,47 +980,70 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of + APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL ignoreMissingFiles -> withUser $ \user -> case toCType of CTDirect -> do - cmrs <- prepareForward user - case L.nonEmpty cmrs of - Just cmrs' -> - withContactLock "forwardChatItem, to contact" toChatId $ - sendContactContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + cmrs <- prepareForwardOrFail user + withContactLock "forwardChatItem, to contact" toChatId $ + sendContactContentMessages user toChatId False itemTTL cmrs CTGroup -> do - cmrs <- prepareForward user - case L.nonEmpty cmrs of - Just cmrs' -> - withGroupLock "forwardChatItem, to group" toChatId $ - sendGroupContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + cmrs <- prepareForwardOrFail user + withGroupLock "forwardChatItem, to group" toChatId $ + sendGroupContentMessages user toChatId False itemTTL cmrs CTLocal -> do - cmrs <- prepareForward user - case L.nonEmpty cmrs of - Just cmrs' -> - createNoteFolderContentItems user toChatId cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + cmrs <- prepareForwardOrFail user + createNoteFolderContentItems user toChatId cmrs CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - prepareForward :: User -> CM [ComposeMessageReq] + prepareForwardOrFail :: User -> CM (NonEmpty ComposeMessageReq) + prepareForwardOrFail user = do + (errs, cmrs) <- partitionEithers . L.toList <$> prepareForward user + case sortOn fst errs of + [] -> case L.nonEmpty (catMaybes cmrs) of + Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Just cmrs' -> do + -- copy forwarded files, in case originals are deleted + withFilesFolder $ \filesFolder -> do + let toFolder cf@CryptoFile {filePath} = cf {filePath = filesFolder filePath} :: CryptoFile + forM_ cmrs' $ \case + (_, Just (fromCF, toCF)) -> + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ + copyCryptoFile (toFolder fromCF) (toFolder toCF) + _ -> pure () + pure $ L.map fst cmrs' + errs'@((err, _) : _) -> do + -- cleanup files + withFilesFolder $ \filesFolder -> + forM_ cmrs $ \case + Just (_, Just (_, CryptoFile {filePath = toFPath})) -> do + let fsToPath = filesFolder toFPath + removeFile fsToPath `catchChatError` \e -> + logError ("prepareForwardOrFail: failed to clean up " <> tshow fsToPath <> ": " <> tshow e) + _ -> pure () + throwChatError $ case err of + FFENotAccepted _ -> CEForwardFilesNotAccepted files msgCount + FFEInProgress -> CEForwardFilesInProgress filesCount msgCount + FFEMissing -> CEForwardFilesMissing filesCount msgCount + FFEFailed -> CEForwardFilesFailed filesCount msgCount + where + msgCount = foldl' (\cnt (_, hasContent) -> if hasContent then cnt + 1 else cnt) 0 errs' + filesCount = foldl' (\cnt (e, _) -> if err == e then cnt + 1 else cnt) 0 errs' + files = foldl' (\ftIds -> \case (FFENotAccepted ftId, _) -> ftId : ftIds; _ -> ftIds) [] errs' + prepareForward :: User -> CM (NonEmpty (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile))))) prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do ct <- withFastStore $ \db -> getContact db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + items <- withFastStore $ \db -> mapM (getDirectChatItem db user fromChatId) itemIds mapM (ciComposeMsgReq ct) items where - getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) - getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId - ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) ciComposeMsgReq ct (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + fc <- forwardContent ci mc + forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) + pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) where forwardName :: Contact -> ContactName forwardName Contact {profile = LocalProfile {displayName, localAlias}} @@ -1028,35 +1051,31 @@ processChatCommand' vr = \case | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + items <- withFastStore $ \db -> mapM (getGroupChatItem db user fromChatId) itemIds mapM (ciComposeMsgReq gInfo) items where - getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId - ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) ciComposeMsgReq gInfo (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + fc <- forwardContent ci mc + forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) + pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) where forwardName :: GroupInfo -> ContactName forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + items <- withFastStore $ \db -> mapM (getLocalChatItem db user fromChatId) itemIds mapM ciComposeMsgReq items where - getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) - getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId - ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq + ciComposeMsgReq :: CChatItem 'CTLocal -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) ciComposeMsgReq (CChatItem _ ci) = do (mc, _) <- forwardMC ci - file <- forwardCryptoFile ci - let ciff = forwardCIFF ci Nothing - pure (ComposedMessage file Nothing mc, ciff) + fc <- forwardContent ci mc + forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do + let ciff = forwardCIFF ci Nothing + pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where @@ -1070,48 +1089,53 @@ processChatCommand' vr = \case Nothing -> ciff Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF - forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) - forwardCryptoFile ChatItem {file = Nothing} = pure Nothing - forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of - CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) - Just filesFolder -> do - let fsFromPath = filesFolder filePath - ifM - (doesFileExist fsFromPath) - ( do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - -- to keep forwarded file in case original is deleted - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) - ) - (pure Nothing) - _ -> pure Nothing - copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () - copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do - fromSizeFull <- getFileSize fsFromPath - let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs - CF.withFile fromCF ReadMode $ \fromH -> - CF.withFile toCF WriteMode $ \toH -> do - copyChunks fromH toH fromSize - forM_ fromArgs $ \_ -> CF.hGetTag fromH - forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH + forwardContent :: ChatItem c d -> MsgContent -> CM (Either (ForwardFileError, Bool) (Maybe (MsgContent, Maybe (CryptoFile, CryptoFile)))) + forwardContent ChatItem {file = Nothing} mc = pure $ Right $ Just (mc, Nothing) + forwardContent ChatItem {file = Just ciFile} mc = case ciFile of + CIFile {fileId, fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} -> case ciFileForwardError fileId fileStatus of + Just e -> pure $ ignoreOrError e + Nothing -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Right $ Just (mc, Just (fromCF, fromCF))) (pure $ ignoreOrError FFEMissing) + Just filesFolder -> + ifM (doesFileExist $ filesFolder filePath) forwardedFile (pure $ ignoreOrError FFEMissing) + where + forwardedFile = do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + pure $ Right $ Just (mc, Just (fromCF, toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) + _ -> pure $ ignoreOrError FFEMissing where - copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () - copyChunks r w size = do - let chSize = min size U.chunkSize - chSize' = fromIntegral chSize - size' = size - chSize - ch <- liftIO $ CF.hGet r chSize' - when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" - liftIO . CF.hPut w $ LB.fromStrict ch - when (size' > 0) $ copyChunks r w size' + ignoreOrError err = if ignoreMissingFiles then Right (newContent mc) else Left (err, hasContent mc) + where + newContent mc' = case mc' of + MCImage {} -> Just (mc', Nothing) + _ | msgContentText mc' /= "" -> Just (MCText $ msgContentText mc', Nothing) + _ -> Nothing + hasContent mc' = isJust $ newContent mc' + copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () + copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do + fromSizeFull <- getFileSize fsFromPath + let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs + CF.withFile fromCF ReadMode $ \fromH -> + CF.withFile toCF WriteMode $ \toH -> do + copyChunks fromH toH fromSize + forM_ fromArgs $ \_ -> CF.hGetTag fromH + forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH + where + copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () + copyChunks r w size = do + let chSize = min size U.chunkSize + chSize' = fromIntegral chSize + size' = size - chSize + ch <- liftIO $ CF.hGet r chSize' + when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" + liftIO . CF.hPut w $ LB.fromStrict ch + when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of @@ -1831,17 +1855,17 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing True ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing True ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing True SendMessage (ChatName cType name) msg -> withUser $ \user -> do let mc = MCText msg case cType of @@ -3332,9 +3356,10 @@ deleteFilesLocally files = delete fPath = removeFile fPath `catchAll` \_ -> removePathForcibly fPath `catchAll_` pure () - -- perform an action only if filesFolder is set (i.e. on mobile devices) - withFilesFolder :: (FilePath -> CM ()) -> CM () - withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action + +-- perform an action only if filesFolder is set (i.e. on mobile devices) +withFilesFolder :: (FilePath -> CM ()) -> CM () +withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action updateCallItemStatus :: User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> CM () updateCallItemStatus user ct@Contact {contactId} Call {chatItemId} receivedStatus msgId_ = do @@ -7888,7 +7913,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), + "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP <*> (" ignore_files=" *> onOffP <|> pure False)), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7268d0734a..7c49e9e7e4 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -298,7 +298,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} - | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} + | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int, ignoreMissingFiles :: Bool} | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) @@ -1178,6 +1178,10 @@ data ChatErrorType | CEFallbackToSMPProhibited {fileId :: FileTransferId} | CEInlineFileProhibited {fileId :: FileTransferId} | CEInvalidQuote + | CEForwardFilesNotAccepted {files :: [FileTransferId], msgCount :: Int} -- contentCount is the count of messages if files are ignored + | CEForwardFilesInProgress {filesCount :: Int, msgCount :: Int} + | CEForwardFilesMissing {filesCount :: Int, msgCount :: Int} + | CEForwardFilesFailed {filesCount :: Int, msgCount :: Int} | CEInvalidForward | CEInvalidChatItemUpdate | CEInvalidChatItemDelete diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index f6c59cbcb5..0e4edbd685 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime) +import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) import Data.Type.Equality import Data.Typeable (Typeable) import Database.SQLite.Simple.FromField (FromField (..)) @@ -577,23 +577,26 @@ ciFileEnded = \case CIFSRcvWarning {} -> False CIFSInvalid {} -> True -ciFileLoaded :: CIFileStatus d -> Bool -ciFileLoaded = \case - CIFSSndStored -> True - CIFSSndTransfer {} -> True - CIFSSndComplete -> True - CIFSSndCancelled -> True - CIFSSndError {} -> True - CIFSSndWarning {} -> True - CIFSRcvInvitation -> False - CIFSRcvAccepted -> False - CIFSRcvTransfer {} -> False - CIFSRcvAborted -> False - CIFSRcvCancelled -> False - CIFSRcvComplete -> True - CIFSRcvError {} -> False - CIFSRcvWarning {} -> False - CIFSInvalid {} -> False +data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing + deriving (Eq, Ord) + +ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError +ciFileForwardError fId = \case + CIFSSndStored -> Nothing + CIFSSndTransfer {} -> Nothing + CIFSSndComplete -> Nothing + CIFSSndCancelled -> Nothing + CIFSSndError {} -> Nothing + CIFSSndWarning {} -> Nothing + CIFSRcvInvitation -> Just $ FFENotAccepted fId + CIFSRcvAccepted -> Just FFEInProgress + CIFSRcvTransfer {} -> Just FFEInProgress + CIFSRcvAborted -> Just $ FFENotAccepted fId + CIFSRcvCancelled -> Just FFEFailed -- ? + CIFSRcvComplete -> Nothing + CIFSRcvError {} -> Just FFEFailed + CIFSRcvWarning {} -> Just FFEFailed + CIFSInvalid {} -> Just FFEFailed -- ? data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2158599c4b..e8814fb32d 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2034,7 +2034,11 @@ viewChatError isCmd logLevel testView = \case CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] - CEInvalidForward -> ["cannot forward this message"] + CEInvalidForward -> ["cannot forward message(s)"] + CEForwardFilesNotAccepted files msgCount -> [plain $ "Some files are not accepted: " <> intercalate ", " (map show files), showForwardMsgCount msgCount] + CEForwardFilesInProgress cnt msgCount -> [plain $ "Still receiving " <> show cnt <> " file(s)", showForwardMsgCount msgCount] + CEForwardFilesMissing cnt msgCount -> [plain $ show cnt <> " file(s) are missing", showForwardMsgCount msgCount] + CEForwardFilesFailed cnt msgCount -> [plain $ show cnt <> " file(s) failed", showForwardMsgCount msgCount] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] @@ -2053,6 +2057,9 @@ viewChatError isCmd logLevel testView = \case CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"] CEInternalError e -> ["internal chat error: " <> plain e] CEException e -> ["exception: " <> plain e] + where + showForwardMsgCount 0 = "No other messages to forward" + showForwardMsgCount msgCount = plain $ "Use ignore_files to forward " <> show msgCount <> " message(s)" -- e -> ["chat error: " <> sShow e] ChatErrorStore err -> case err of SEDuplicateName -> ["this display name is already used by user, contact or group"] diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index b339053abf..1897cddef5 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -671,6 +671,8 @@ testMultiForwardFiles = \alice bob cath -> withXFTPServer $ do setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_3.jpg" + copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_4.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" @@ -688,7 +690,10 @@ testMultiForwardFiles = let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + cm4 = "{\"filePath\": \"test_4.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 3\"}}" + cm5 = "{\"filePath\": \"test_3.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}" + + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "," <> cm5 <> "]") alice <# "@bob message without file" @@ -700,6 +705,13 @@ testMultiForwardFiles = alice <# "/f @bob test.pdf" alice <## "use /fc 2 to cancel sending" + alice <# "@bob sending file 3" + alice <# "/f @bob test_4.jpg" + alice <## "use /fc 3 to cancel sending" + + alice <# "/f @bob test_3.jpg" + alice <## "use /fc 4 to cancel sending" + bob <# "alice> message without file" bob <# "alice> sending file 1" @@ -710,8 +722,17 @@ testMultiForwardFiles = bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 2 [/ | ] to receive it" + bob <# "alice> sending file 3" + bob <# "alice> sends file test_4.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 3 [/ | ] to receive it" + + bob <# "alice> sends file test_3.jpg (136.5 KiB / 139737 bytes)" + bob <## "use /fr 4 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" + alice <## "completed uploading file 3 (test_4.jpg) for bob" + alice <## "completed uploading file 4 (test_3.jpg) for bob" bob ##> "/fr 1" bob @@ -720,6 +741,11 @@ testMultiForwardFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" + -- try to forward file without receiving 2nd file + let msgId1 = (read msgIdZero :: Int) + 1 + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5)) + bob <### ["3 file(s) are missing", "Use ignore_files to forward 2 message(s)"] + bob ##> "/fr 2" bob <### [ "saving file 2 from alice to test.pdf", @@ -736,8 +762,7 @@ testMultiForwardFiles = dest2 `shouldBe` src2 -- forward file - let msgId1 = (read msgIdZero :: Int) + 1 - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5) <> " ignore_files=on") -- messages printed for bob bob <# "@cath <- you @alice" @@ -749,12 +774,15 @@ testMultiForwardFiles = bob <# "@cath <- @alice" bob <## " sending file 1" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 3 to cancel sending" + bob <## "use /fc 5 to cancel sending" bob <# "@cath <- @alice" bob <## " sending file 2" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 4 to cancel sending" + bob <## "use /fc 6 to cancel sending" + + bob <# "@cath <- @alice" + bob <## " sending file 3" -- messages printed for cath cath <# "bob> -> forwarded" @@ -773,9 +801,12 @@ testMultiForwardFiles = cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 2 [/ | ] to receive it" + cath <# "bob> -> forwarded" + cath <## " sending file 3" -- No file sent here + -- file transfer - bob <## "completed uploading file 3 (test_1.jpg) for cath" - bob <## "completed uploading file 4 (test_1.pdf) for cath" + bob <## "completed uploading file 5 (test_1.jpg) for cath" + bob <## "completed uploading file 6 (test_1.pdf) for cath" cath ##> "/fr 1" cath From 2ab5f141196c0c7cbd240ba703994aeb899f01d5 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:31:05 +0000 Subject: [PATCH 042/704] docs: remove outdated latest version number from downloads (#4854) --- docs/DOWNLOADS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md index 06850e76d2..df22ff64a2 100644 --- a/docs/DOWNLOADS.md +++ b/docs/DOWNLOADS.md @@ -1,14 +1,12 @@ --- title: Download SimpleX apps permalink: /downloads/index.html -revision: 03.07.2024 +revision: 09.09.2024 --- -| Updated 03.07.2024 | Languages: EN | +| Updated 09.09.2024 | Languages: EN | # Download SimpleX apps -The latest stable version is v5.8. - You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases). - [desktop](#desktop-app) From dfdb4af64618e4d123db3bad40e7ba12b7cbdc7e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 12 Sep 2024 08:52:09 +0100 Subject: [PATCH 043/704] Revert "core: bulk forward missing files error handling (#4860)" This reverts commit 46d774a8220d332b283ba3d8c1305d3dbb0d4a0c. --- src/Simplex/Chat.hs | 211 +++++++++++++++------------------ src/Simplex/Chat/Controller.hs | 6 +- src/Simplex/Chat/Messages.hs | 39 +++--- src/Simplex/Chat/View.hs | 9 +- tests/ChatTests/Forward.hs | 45 ++----- 5 files changed, 120 insertions(+), 190 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 8f9d985efe..c140025648 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -980,70 +980,47 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") - APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL ignoreMissingFiles -> withUser $ \user -> case toCType of + APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of CTDirect -> do - cmrs <- prepareForwardOrFail user - withContactLock "forwardChatItem, to contact" toChatId $ - sendContactContentMessages user toChatId False itemTTL cmrs + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withContactLock "forwardChatItem, to contact" toChatId $ + sendContactContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTGroup -> do - cmrs <- prepareForwardOrFail user - withGroupLock "forwardChatItem, to group" toChatId $ - sendGroupContentMessages user toChatId False itemTTL cmrs + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + withGroupLock "forwardChatItem, to group" toChatId $ + sendGroupContentMessages user toChatId False itemTTL cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTLocal -> do - cmrs <- prepareForwardOrFail user - createNoteFolderContentItems user toChatId cmrs + cmrs <- prepareForward user + case L.nonEmpty cmrs of + Just cmrs' -> + createNoteFolderContentItems user toChatId cmrs' + Nothing -> throwChatError $ CEInternalError "no chat items to forward" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where - prepareForwardOrFail :: User -> CM (NonEmpty ComposeMessageReq) - prepareForwardOrFail user = do - (errs, cmrs) <- partitionEithers . L.toList <$> prepareForward user - case sortOn fst errs of - [] -> case L.nonEmpty (catMaybes cmrs) of - Nothing -> throwChatError $ CEInternalError "no chat items to forward" - Just cmrs' -> do - -- copy forwarded files, in case originals are deleted - withFilesFolder $ \filesFolder -> do - let toFolder cf@CryptoFile {filePath} = cf {filePath = filesFolder filePath} :: CryptoFile - forM_ cmrs' $ \case - (_, Just (fromCF, toCF)) -> - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ - copyCryptoFile (toFolder fromCF) (toFolder toCF) - _ -> pure () - pure $ L.map fst cmrs' - errs'@((err, _) : _) -> do - -- cleanup files - withFilesFolder $ \filesFolder -> - forM_ cmrs $ \case - Just (_, Just (_, CryptoFile {filePath = toFPath})) -> do - let fsToPath = filesFolder toFPath - removeFile fsToPath `catchChatError` \e -> - logError ("prepareForwardOrFail: failed to clean up " <> tshow fsToPath <> ": " <> tshow e) - _ -> pure () - throwChatError $ case err of - FFENotAccepted _ -> CEForwardFilesNotAccepted files msgCount - FFEInProgress -> CEForwardFilesInProgress filesCount msgCount - FFEMissing -> CEForwardFilesMissing filesCount msgCount - FFEFailed -> CEForwardFilesFailed filesCount msgCount - where - msgCount = foldl' (\cnt (_, hasContent) -> if hasContent then cnt + 1 else cnt) 0 errs' - filesCount = foldl' (\cnt (e, _) -> if err == e then cnt + 1 else cnt) 0 errs' - files = foldl' (\ftIds -> \case (FFENotAccepted ftId, _) -> ftId : ftIds; _ -> ftIds) [] errs' - prepareForward :: User -> CM (NonEmpty (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile))))) + prepareForward :: User -> CM [ComposeMessageReq] prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do ct <- withFastStore $ \db -> getContact db vr user fromChatId - items <- withFastStore $ \db -> mapM (getDirectChatItem db user fromChatId) itemIds + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs mapM (ciComposeMsgReq ct) items where - ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) + getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) + getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq ciComposeMsgReq ct (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - fc <- forwardContent ci mc - forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) where forwardName :: Contact -> ContactName forwardName Contact {profile = LocalProfile {displayName, localAlias}} @@ -1051,31 +1028,35 @@ processChatCommand' vr = \case | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId - items <- withFastStore $ \db -> mapM (getGroupChatItem db user fromChatId) itemIds + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs mapM (ciComposeMsgReq gInfo) items where - ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) + getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) + getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq ciComposeMsgReq gInfo (CChatItem _ ci) = do (mc, mDir) <- forwardMC ci - fc <- forwardContent ci mc - forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do - let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) + file <- forwardCryptoFile ci + let itemId = chatItemId' ci + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) + pure (ComposedMessage file Nothing mc, ciff) where forwardName :: GroupInfo -> ContactName forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - items <- withFastStore $ \db -> mapM (getLocalChatItem db user fromChatId) itemIds + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs mapM ciComposeMsgReq items where - ciComposeMsgReq :: CChatItem 'CTLocal -> CM (Either (ForwardFileError, Bool) (Maybe (ComposeMessageReq, Maybe (CryptoFile, CryptoFile)))) + getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) + getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId + ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq ciComposeMsgReq (CChatItem _ ci) = do (mc, _) <- forwardMC ci - fc <- forwardContent ci mc - forM fc $ \mcFile -> forM mcFile $ \(mc'', file_) -> do - let ciff = forwardCIFF ci Nothing - pure ((ComposedMessage (snd <$> file_) Nothing mc'', ciff), file_) + file <- forwardCryptoFile ci + let ciff = forwardCIFF ci Nothing + pure (ComposedMessage file Nothing mc, ciff) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where @@ -1089,53 +1070,48 @@ processChatCommand' vr = \case Nothing -> ciff Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF - forwardContent :: ChatItem c d -> MsgContent -> CM (Either (ForwardFileError, Bool) (Maybe (MsgContent, Maybe (CryptoFile, CryptoFile)))) - forwardContent ChatItem {file = Nothing} mc = pure $ Right $ Just (mc, Nothing) - forwardContent ChatItem {file = Just ciFile} mc = case ciFile of - CIFile {fileId, fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} -> case ciFileForwardError fileId fileStatus of - Just e -> pure $ ignoreOrError e - Nothing -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Right $ Just (mc, Just (fromCF, fromCF))) (pure $ ignoreOrError FFEMissing) - Just filesFolder -> - ifM (doesFileExist $ filesFolder filePath) forwardedFile (pure $ ignoreOrError FFEMissing) - where - forwardedFile = do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - pure $ Right $ Just (mc, Just (fromCF, toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) - _ -> pure $ ignoreOrError FFEMissing + forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) + forwardCryptoFile ChatItem {file = Nothing} = pure Nothing + forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of + CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) + Just filesFolder -> do + let fsFromPath = filesFolder filePath + ifM + (doesFileExist fsFromPath) + ( do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + -- to keep forwarded file in case original is deleted + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF + pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) + ) + (pure Nothing) + _ -> pure Nothing + copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () + copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do + fromSizeFull <- getFileSize fsFromPath + let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs + CF.withFile fromCF ReadMode $ \fromH -> + CF.withFile toCF WriteMode $ \toH -> do + copyChunks fromH toH fromSize + forM_ fromArgs $ \_ -> CF.hGetTag fromH + forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH where - ignoreOrError err = if ignoreMissingFiles then Right (newContent mc) else Left (err, hasContent mc) - where - newContent mc' = case mc' of - MCImage {} -> Just (mc', Nothing) - _ | msgContentText mc' /= "" -> Just (MCText $ msgContentText mc', Nothing) - _ -> Nothing - hasContent mc' = isJust $ newContent mc' - copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () - copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do - fromSizeFull <- getFileSize fsFromPath - let fromSize = fromSizeFull - maybe 0 (const $ toInteger C.authTagSize) fromArgs - CF.withFile fromCF ReadMode $ \fromH -> - CF.withFile toCF WriteMode $ \toH -> do - copyChunks fromH toH fromSize - forM_ fromArgs $ \_ -> CF.hGetTag fromH - forM_ toArgs $ \_ -> liftIO $ CF.hPutTag toH - where - copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () - copyChunks r w size = do - let chSize = min size U.chunkSize - chSize' = fromIntegral chSize - size' = size - chSize - ch <- liftIO $ CF.hGet r chSize' - when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" - liftIO . CF.hPut w $ LB.fromStrict ch - when (size' > 0) $ copyChunks r w size' + copyChunks :: CF.CryptoFileHandle -> CF.CryptoFileHandle -> Integer -> ExceptT CF.FTCryptoError IO () + copyChunks r w size = do + let chSize = min size U.chunkSize + chSize' = fromIntegral chSize + size' = size - chSize + ch <- liftIO $ CF.hGet r chSize' + when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF" + liftIO . CF.hPut w $ LB.fromStrict ch + when (size' > 0) $ copyChunks r w size' APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId APIChatRead chatRef@(ChatRef cType chatId) fromToIds -> withUser $ \_ -> case cType of @@ -1855,17 +1831,17 @@ processChatCommand' vr = \case contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing True + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing True + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg toChatRef <- getChatRef user toChatName - processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing True + processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing SendMessage (ChatName cType name) msg -> withUser $ \user -> do let mc = MCText msg case cType of @@ -3356,10 +3332,9 @@ deleteFilesLocally files = delete fPath = removeFile fPath `catchAll` \_ -> removePathForcibly fPath `catchAll_` pure () - --- perform an action only if filesFolder is set (i.e. on mobile devices) -withFilesFolder :: (FilePath -> CM ()) -> CM () -withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action + -- perform an action only if filesFolder is set (i.e. on mobile devices) + withFilesFolder :: (FilePath -> CM ()) -> CM () + withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action updateCallItemStatus :: User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> CM () updateCallItemStatus user ct@Contact {contactId} Call {chatItemId} receivedStatus msgId_ = do @@ -7913,7 +7888,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), - "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP <*> (" ignore_files=" *> onOffP <|> pure False)), + "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, "/_read chat " *> (APIChatRead <$> chatRefP <*> optional (A.space *> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7c49e9e7e4..7268d0734a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -298,7 +298,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} - | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int, ignoreMissingFiles :: Bool} + | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) @@ -1178,10 +1178,6 @@ data ChatErrorType | CEFallbackToSMPProhibited {fileId :: FileTransferId} | CEInlineFileProhibited {fileId :: FileTransferId} | CEInvalidQuote - | CEForwardFilesNotAccepted {files :: [FileTransferId], msgCount :: Int} -- contentCount is the count of messages if files are ignored - | CEForwardFilesInProgress {filesCount :: Int, msgCount :: Int} - | CEForwardFilesMissing {filesCount :: Int, msgCount :: Int} - | CEForwardFilesFailed {filesCount :: Int, msgCount :: Int} | CEInvalidForward | CEInvalidChatItemUpdate | CEInvalidChatItemDelete diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 0e4edbd685..f6c59cbcb5 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) +import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime) import Data.Type.Equality import Data.Typeable (Typeable) import Database.SQLite.Simple.FromField (FromField (..)) @@ -577,26 +577,23 @@ ciFileEnded = \case CIFSRcvWarning {} -> False CIFSInvalid {} -> True -data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing - deriving (Eq, Ord) - -ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError -ciFileForwardError fId = \case - CIFSSndStored -> Nothing - CIFSSndTransfer {} -> Nothing - CIFSSndComplete -> Nothing - CIFSSndCancelled -> Nothing - CIFSSndError {} -> Nothing - CIFSSndWarning {} -> Nothing - CIFSRcvInvitation -> Just $ FFENotAccepted fId - CIFSRcvAccepted -> Just FFEInProgress - CIFSRcvTransfer {} -> Just FFEInProgress - CIFSRcvAborted -> Just $ FFENotAccepted fId - CIFSRcvCancelled -> Just FFEFailed -- ? - CIFSRcvComplete -> Nothing - CIFSRcvError {} -> Just FFEFailed - CIFSRcvWarning {} -> Just FFEFailed - CIFSInvalid {} -> Just FFEFailed -- ? +ciFileLoaded :: CIFileStatus d -> Bool +ciFileLoaded = \case + CIFSSndStored -> True + CIFSSndTransfer {} -> True + CIFSSndComplete -> True + CIFSSndCancelled -> True + CIFSSndError {} -> True + CIFSSndWarning {} -> True + CIFSRcvInvitation -> False + CIFSRcvAccepted -> False + CIFSRcvTransfer {} -> False + CIFSRcvAborted -> False + CIFSRcvCancelled -> False + CIFSRcvComplete -> True + CIFSRcvError {} -> False + CIFSRcvWarning {} -> False + CIFSInvalid {} -> False data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index e8814fb32d..2158599c4b 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2034,11 +2034,7 @@ viewChatError isCmd logLevel testView = \case CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] - CEInvalidForward -> ["cannot forward message(s)"] - CEForwardFilesNotAccepted files msgCount -> [plain $ "Some files are not accepted: " <> intercalate ", " (map show files), showForwardMsgCount msgCount] - CEForwardFilesInProgress cnt msgCount -> [plain $ "Still receiving " <> show cnt <> " file(s)", showForwardMsgCount msgCount] - CEForwardFilesMissing cnt msgCount -> [plain $ show cnt <> " file(s) are missing", showForwardMsgCount msgCount] - CEForwardFilesFailed cnt msgCount -> [plain $ show cnt <> " file(s) failed", showForwardMsgCount msgCount] + CEInvalidForward -> ["cannot forward this message"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] @@ -2057,9 +2053,6 @@ viewChatError isCmd logLevel testView = \case CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"] CEInternalError e -> ["internal chat error: " <> plain e] CEException e -> ["exception: " <> plain e] - where - showForwardMsgCount 0 = "No other messages to forward" - showForwardMsgCount msgCount = plain $ "Use ignore_files to forward " <> show msgCount <> " message(s)" -- e -> ["chat error: " <> sShow e] ChatErrorStore err -> case err of SEDuplicateName -> ["this display name is already used by user, contact or group"] diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index 1897cddef5..b339053abf 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -671,8 +671,6 @@ testMultiForwardFiles = \alice bob cath -> withXFTPServer $ do setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" - copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_3.jpg" - copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test_4.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" @@ -690,10 +688,7 @@ testMultiForwardFiles = let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" - cm4 = "{\"filePath\": \"test_4.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 3\"}}" - cm5 = "{\"filePath\": \"test_3.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}" - - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "," <> cm5 <> "]") + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") alice <# "@bob message without file" @@ -705,13 +700,6 @@ testMultiForwardFiles = alice <# "/f @bob test.pdf" alice <## "use /fc 2 to cancel sending" - alice <# "@bob sending file 3" - alice <# "/f @bob test_4.jpg" - alice <## "use /fc 3 to cancel sending" - - alice <# "/f @bob test_3.jpg" - alice <## "use /fc 4 to cancel sending" - bob <# "alice> message without file" bob <# "alice> sending file 1" @@ -722,17 +710,8 @@ testMultiForwardFiles = bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 2 [/ | ] to receive it" - bob <# "alice> sending file 3" - bob <# "alice> sends file test_4.jpg (136.5 KiB / 139737 bytes)" - bob <## "use /fr 3 [/ | ] to receive it" - - bob <# "alice> sends file test_3.jpg (136.5 KiB / 139737 bytes)" - bob <## "use /fr 4 [/ | ] to receive it" - alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" - alice <## "completed uploading file 3 (test_4.jpg) for bob" - alice <## "completed uploading file 4 (test_3.jpg) for bob" bob ##> "/fr 1" bob @@ -741,11 +720,6 @@ testMultiForwardFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" - -- try to forward file without receiving 2nd file - let msgId1 = (read msgIdZero :: Int) + 1 - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5)) - bob <### ["3 file(s) are missing", "Use ignore_files to forward 2 message(s)"] - bob ##> "/fr 2" bob <### [ "saving file 2 from alice to test.pdf", @@ -762,7 +736,8 @@ testMultiForwardFiles = dest2 `shouldBe` src2 -- forward file - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3) <> "," <> show (msgId1 + 4) <> "," <> show (msgId1 + 5) <> " ignore_files=on") + let msgId1 = (read msgIdZero :: Int) + 1 + bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) -- messages printed for bob bob <# "@cath <- you @alice" @@ -774,15 +749,12 @@ testMultiForwardFiles = bob <# "@cath <- @alice" bob <## " sending file 1" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 5 to cancel sending" + bob <## "use /fc 3 to cancel sending" bob <# "@cath <- @alice" bob <## " sending file 2" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 6 to cancel sending" - - bob <# "@cath <- @alice" - bob <## " sending file 3" + bob <## "use /fc 4 to cancel sending" -- messages printed for cath cath <# "bob> -> forwarded" @@ -801,12 +773,9 @@ testMultiForwardFiles = cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 2 [/ | ] to receive it" - cath <# "bob> -> forwarded" - cath <## " sending file 3" -- No file sent here - -- file transfer - bob <## "completed uploading file 5 (test_1.jpg) for cath" - bob <## "completed uploading file 6 (test_1.pdf) for cath" + bob <## "completed uploading file 3 (test_1.jpg) for cath" + bob <## "completed uploading file 4 (test_1.pdf) for cath" cath ##> "/fr 1" cath From 5f0b5c5a9fe83d4f42c6b0f5bf6041349fc4e55c Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:51:57 +0000 Subject: [PATCH 044/704] desktop: fix vlc dependency (2) (#4869) --- apps/multiplatform/common/build.gradle.kts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 1670672753..1aaa061daa 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -99,10 +99,15 @@ kotlin { val desktopMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0") - implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") + implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") { + exclude("net.java.dev.jna") + } + // For jSystemThemeDetector only + implementation("net.java.dev.jna:jna-platform:5.14.0") implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT") implementation("org.slf4j:slf4j-simple:2.0.12") implementation("uk.co.caprica:vlcj:4.8.3") + implementation("net.java.dev.jna:jna:5.14.0") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") From f6f204467511ba716d3c4ee6832cfd14ffb49abc Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 12 Sep 2024 15:21:29 +0100 Subject: [PATCH 045/704] core: plan forwarding chat items, api types (#4865) * core: plan forwarding chat items, api types * remove empty content, refactor get items * another refactor * plan * test * more tests * text --- src/Simplex/Chat.hs | 192 ++++++++++++++++++++------------- src/Simplex/Chat/Controller.hs | 11 ++ src/Simplex/Chat/Messages.hs | 21 ++++ src/Simplex/Chat/View.hs | 17 ++- tests/ChatTests/Forward.hs | 91 ++++++++++++---- 5 files changed, 241 insertions(+), 91 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c140025648..fb5c4b4962 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -843,9 +843,7 @@ processChatCommand' vr = \case CTContactConnection -> pure $ chatCmdError (Just user) "not supported" APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of CTDirect -> withContactLock "deleteChatItem" chatId $ do - ct <- withStore $ \db -> getContact db vr user chatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (ct, items) <- getCommandDirectChatItems user chatId itemIds case mode of CIDMInternal -> deleteDirectCIs user ct items True False CIDMBroadcast -> do @@ -858,13 +856,9 @@ processChatCommand' vr = \case if featureAllowed SCFFullDelete forUser ct then deleteDirectCIs user ct items True False else markDirectCIsDeleted user ct items True =<< liftIO getCurrentTime - where - getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) - getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user chatId itemId CTGroup -> withGroupLock "deleteChatItem" chatId $ do - Group gInfo ms <- withStore $ \db -> getGroup db vr user chatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (gInfo, items) <- getCommandGroupChatItems user chatId itemIds + ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo case mode of CIDMInternal -> deleteGroupCIs user gInfo items True False Nothing =<< liftIO getCurrentTime CIDMBroadcast -> do @@ -874,17 +868,9 @@ processChatCommand' vr = \case events = L.nonEmpty $ map (`XMsgDel` Nothing) msgIds mapM_ (sendGroupMessages user gInfo ms) events delGroupChatItems user gInfo items Nothing - where - getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user chatId itemId CTLocal -> do - nf <- withStore $ \db -> getNoteFolder db user chatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (nf, items) <- getCommandLocalChatItems user chatId itemIds deleteLocalCIs user nf items True False - where - getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) - getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user chatId itemId CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where @@ -902,9 +888,8 @@ processChatCommand' vr = \case itemsMsgIds :: [CChatItem c] -> [SharedMsgId] itemsMsgIds = mapMaybe (\(CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId}}) -> itemSharedMsgId) APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do - Group gInfo@GroupInfo {membership} ms <- withStore $ \db -> getGroup db vr user gId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db user) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs + (gInfo@GroupInfo {membership}, items) <- getCommandGroupChatItems user gId itemIds + ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo assertDeletable gInfo items assertUserGroupRole gInfo GRAdmin let msgMemIds = itemsMsgMemIds gInfo items @@ -912,8 +897,6 @@ processChatCommand' vr = \case mapM_ (sendGroupMessages user gInfo ms) events delGroupChatItems user gInfo items (Just membership) where - getGroupCI :: DB.Connection -> User -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db user itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user gId itemId assertDeletable :: GroupInfo -> [CChatItem 'CTGroup] -> CM () assertDeletable GroupInfo {membership = GroupMember {memberRole = membershipMemRole}} items = unless (all itemDeletable items) $ throwChatError CEInvalidChatItemDelete @@ -980,6 +963,51 @@ processChatCommand' vr = \case throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed") when (add && length rs >= maxMsgReactions) $ throwChatError (CECommandError "too many reactions") + APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of + CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds + CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds + CTLocal -> planForward user . snd =<< getCommandLocalChatItems user fromChatId itemIds + CTContactRequest -> pure $ chatCmdError (Just user) "not supported" + CTContactConnection -> pure $ chatCmdError (Just user) "not supported" + where + planForward :: User -> [CChatItem c] -> CM ChatResponse + planForward user items = do + (itemIds', forwardErrors) <- unzip <$> mapM planItemForward items + let forwardConfirmation = case catMaybes forwardErrors of + [] -> Nothing + errs -> Just $ case mainErr of + FFENotAccepted _ -> FCFilesNotAccepted fileIds + FFEInProgress -> FCFilesInProgress filesCount + FFEMissing -> FCFilesMissing filesCount + FFEFailed -> FCFilesFailed filesCount + where + mainErr = minimum errs + fileIds = catMaybes $ map (\case FFENotAccepted ftId -> Just ftId; _ -> Nothing) errs + filesCount = length $ filter (mainErr ==) errs + pure CRForwardPlan {user, itemsCount = length itemIds, chatItemIds = catMaybes itemIds', forwardConfirmation} + where + planItemForward :: CChatItem c -> CM (Maybe ChatItemId, Maybe ForwardFileError) + planItemForward (CChatItem _ ci) = forwardMsgContent ci >>= maybe (pure (Nothing, Nothing)) (forwardContentPlan ci) + forwardContentPlan :: ChatItem c d -> MsgContent -> CM (Maybe ChatItemId, Maybe ForwardFileError) + forwardContentPlan ChatItem {file, meta = CIMeta {itemId}} mc = case file of + Nothing -> pure (Just itemId, Nothing) + Just CIFile {fileId, fileStatus, fileSource} -> case ciFileForwardError fileId fileStatus of + Just err -> pure $ itemIdWithoutFile err + Nothing -> case fileSource of + Just CryptoFile {filePath} -> do + exists <- doesFileExist . maybe filePath ( filePath) =<< chatReadVar filesFolder + pure $ if exists then (Just itemId, Nothing) else itemIdWithoutFile FFEMissing + Nothing -> pure $ itemIdWithoutFile FFEMissing + where + itemIdWithoutFile err = (if hasContent then Just itemId else Nothing, Just err) + hasContent = case mc of + MCText _ -> True + MCLink {} -> True + MCImage {} -> True + MCVideo {text} -> text /= "" + MCVoice {text} -> text /= "" + MCFile t -> t /= "" + MCUnknown {} -> True APIForwardChatItems (ChatRef toCType toChatId) (ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of CTDirect -> do cmrs <- prepareForward user @@ -987,96 +1015,76 @@ processChatCommand' vr = \case Just cmrs' -> withContactLock "forwardChatItem, to contact" toChatId $ sendContactContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Nothing -> pure $ CRNewChatItems user [] CTGroup -> do cmrs <- prepareForward user case L.nonEmpty cmrs of Just cmrs' -> withGroupLock "forwardChatItem, to group" toChatId $ sendGroupContentMessages user toChatId False itemTTL cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Nothing -> pure $ CRNewChatItems user [] CTLocal -> do cmrs <- prepareForward user case L.nonEmpty cmrs of Just cmrs' -> createNoteFolderContentItems user toChatId cmrs' - Nothing -> throwChatError $ CEInternalError "no chat items to forward" + Nothing -> pure $ CRNewChatItems user [] CTContactRequest -> pure $ chatCmdError (Just user) "not supported" CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where prepareForward :: User -> CM [ComposeMessageReq] prepareForward user = case fromCType of CTDirect -> withContactLock "forwardChatItem, from contact" fromChatId $ do - ct <- withFastStore $ \db -> getContact db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM (ciComposeMsgReq ct) items + (ct, items) <- getCommandDirectChatItems user fromChatId itemIds + catMaybes <$> mapM (\ci -> ciComposeMsgReq ct ci <$$> prepareMsgReq ci) items where - getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) - getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user fromChatId itemId - ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> CM ComposeMessageReq - ciComposeMsgReq ct (CChatItem _ ci) = do - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci + ciComposeMsgReq :: Contact -> CChatItem 'CTDirect -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq + ciComposeMsgReq ct (CChatItem md ci) (mc', file) = let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + ciff = forwardCIFF ci $ Just (CIFFContact (forwardName ct) (toMsgDirection md) (Just fromChatId) (Just itemId)) + in (ComposedMessage file Nothing mc', ciff) where forwardName :: Contact -> ContactName forwardName Contact {profile = LocalProfile {displayName, localAlias}} | localAlias /= "" = localAlias | otherwise = displayName CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do - gInfo <- withFastStore $ \db -> getGroupInfo db vr user fromChatId - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM (ciComposeMsgReq gInfo) items + (gInfo, items) <- getCommandGroupChatItems user fromChatId itemIds + catMaybes <$> mapM (\ci -> ciComposeMsgReq gInfo ci <$$> prepareMsgReq ci) items where - getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) - getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user fromChatId itemId - ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> CM ComposeMessageReq - ciComposeMsgReq gInfo (CChatItem _ ci) = do - (mc, mDir) <- forwardMC ci - file <- forwardCryptoFile ci + ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq + ciComposeMsgReq gInfo (CChatItem md ci) (mc', file) = do let itemId = chatItemId' ci - ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) mDir (Just fromChatId) (Just itemId)) - pure (ComposedMessage file Nothing mc, ciff) + ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) (toMsgDirection md) (Just fromChatId) (Just itemId)) + in (ComposedMessage file Nothing mc', ciff) where forwardName :: GroupInfo -> ContactName forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName CTLocal -> do - (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) - unless (null errs) $ toView $ CRChatErrors (Just user) errs - mapM ciComposeMsgReq items + (_, items) <- getCommandLocalChatItems user fromChatId itemIds + catMaybes <$> mapM (\ci -> ciComposeMsgReq ci <$$> prepareMsgReq ci) items where - getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) - getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user fromChatId itemId - ciComposeMsgReq :: CChatItem 'CTLocal -> CM ComposeMessageReq - ciComposeMsgReq (CChatItem _ ci) = do - (mc, _) <- forwardMC ci - file <- forwardCryptoFile ci + ciComposeMsgReq :: CChatItem 'CTLocal -> (MsgContent, Maybe CryptoFile) -> ComposeMessageReq + ciComposeMsgReq (CChatItem _ ci) (mc', file) = let ciff = forwardCIFF ci Nothing - pure (ComposedMessage file Nothing mc, ciff) + in (ComposedMessage file Nothing mc', ciff) CTContactRequest -> throwChatError $ CECommandError "not supported" CTContactConnection -> throwChatError $ CECommandError "not supported" where - forwardMC :: ChatItem c d -> CM (MsgContent, MsgDirection) - forwardMC ChatItem {meta = CIMeta {itemDeleted = Just _}} = throwChatError CEInvalidForward - forwardMC ChatItem {content = CISndMsgContent fmc} = pure (fmc, MDSnd) - forwardMC ChatItem {content = CIRcvMsgContent fmc} = pure (fmc, MDRcv) - forwardMC _ = throwChatError CEInvalidForward + prepareMsgReq :: CChatItem c -> CM (Maybe (MsgContent, Maybe CryptoFile)) + prepareMsgReq (CChatItem _ ci) = forwardMsgContent ci $>>= forwardContent ci forwardCIFF :: ChatItem c d -> Maybe CIForwardedFrom -> Maybe CIForwardedFrom forwardCIFF ChatItem {meta = CIMeta {itemForwarded}} ciff = case itemForwarded of Nothing -> ciff Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF - forwardCryptoFile :: ChatItem c d -> CM (Maybe CryptoFile) - forwardCryptoFile ChatItem {file = Nothing} = pure Nothing - forwardCryptoFile ChatItem {file = Just ciFile} = case ciFile of + forwardContent :: ChatItem c d -> MsgContent -> CM (Maybe (MsgContent, Maybe CryptoFile)) + forwardContent ChatItem {file = Nothing} mc = pure $ Just (mc, Nothing) + forwardContent ChatItem {file = Just ciFile} mc = case ciFile of CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> chatReadVar filesFolder >>= \case Nothing -> - ifM (doesFileExist filePath) (pure $ Just fromCF) (pure Nothing) + ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile) Just filesFolder -> do let fsFromPath = filesFolder filePath ifM @@ -1089,10 +1097,17 @@ processChatCommand' vr = \case let toCF = CryptoFile fsNewPath cfArgs -- to keep forwarded file in case original is deleted liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile) + pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) ) - (pure Nothing) - _ -> pure Nothing + (pure contentWithoutFile) + _ -> pure contentWithoutFile + where + contentWithoutFile = case mc of + MCImage {} -> Just (mc, Nothing) + MCLink {} -> Just (mc, Nothing) + _ | contentText /= "" -> Just (MCText contentText, Nothing) + _ -> Nothing + contentText = msgContentText mc copyCryptoFile :: CryptoFile -> CryptoFile -> ExceptT CF.FTCryptoError IO () copyCryptoFile fromCF@CryptoFile {filePath = fsFromPath, cryptoArgs = fromArgs} toCF@CryptoFile {cryptoArgs = toArgs} = do fromSizeFull <- getFileSize fsFromPath @@ -3087,6 +3102,38 @@ processChatCommand' vr = \case | (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <- zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_) ] + getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect]) + getCommandDirectChatItems user ctId itemIds = do + ct <- withFastStore $ \db -> getContact db vr user ctId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure (ct, items) + where + getDirectCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTDirect)) + getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user ctId itemId + getCommandGroupChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (GroupInfo, [CChatItem 'CTGroup]) + getCommandGroupChatItems user gId itemIds = do + gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure (gInfo, items) + where + getGroupCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTGroup)) + getGroupCI db itemId = runExceptT . withExceptT ChatErrorStore $ getGroupChatItem db user gId itemId + getCommandLocalChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (NoteFolder, [CChatItem 'CTLocal]) + getCommandLocalChatItems user nfId itemIds = do + nf <- withStore $ \db -> getNoteFolder db user nfId + (errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getLocalCI db) (L.toList itemIds)) + unless (null errs) $ toView $ CRChatErrors (Just user) errs + pure (nf, items) + where + getLocalCI :: DB.Connection -> ChatItemId -> IO (Either ChatError (CChatItem 'CTLocal)) + getLocalCI db itemId = runExceptT . withExceptT ChatErrorStore $ getLocalChatItem db user nfId itemId + forwardMsgContent :: ChatItem c d -> CM (Maybe MsgContent) + forwardMsgContent ChatItem {meta = CIMeta {itemDeleted = Just _}} = pure Nothing -- this can be deleted after selection + forwardMsgContent ChatItem {content = CISndMsgContent fmc} = pure $ Just fmc + forwardMsgContent ChatItem {content = CIRcvMsgContent fmc} = pure $ Just fmc + forwardMsgContent _ = throwChatError CEInvalidForward createNoteFolderContentItems :: User -> NoteFolderId -> NonEmpty ComposeMessageReq -> CM ChatResponse createNoteFolderContentItems user folderId cmrs = do assertNoQuotes @@ -7888,6 +7935,7 @@ chatCommandP = "/_delete item " *> (APIDeleteChatItem <$> chatRefP <*> _strP <* A.space <*> ciDeleteMode), "/_delete member item #" *> (APIDeleteMemberChatItem <$> A.decimal <*> _strP), "/_reaction " *> (APIChatItemReaction <$> chatRefP <* A.space <*> A.decimal <* A.space <*> onOffP <* A.space <*> jsonP), + "/_forward plan " *> (APIPlanForwardChatItems <$> chatRefP <*> _strP), "/_forward " *> (APIForwardChatItems <$> chatRefP <* A.space <*> chatRefP <*> _strP <*> sendMessageTTLP), "/_read user " *> (APIUserRead <$> A.decimal), "/read user" $> UserRead, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 7268d0734a..99739cc6bf 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -298,6 +298,7 @@ data ChatCommand | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} + | APIPlanForwardChatItems {fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId} | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int} | APIUserRead UserId | UserRead @@ -649,6 +650,7 @@ data ChatResponse | CRContactRequestAlreadyAccepted {user :: User, contact :: Contact} | CRLeftMemberUser {user :: User, groupInfo :: GroupInfo} | CRGroupDeletedUser {user :: User, groupInfo :: GroupInfo} + | CRForwardPlan {user :: User, itemsCount :: Int, chatItemIds :: [ChatItemId], forwardConfirmation :: Maybe ForwardConfirmation} | CRRcvFileDescrReady {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer, rcvFileDescr :: RcvFileDescr} | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} @@ -905,6 +907,13 @@ connectionPlanProceed = \case GLPConnectingConfirmReconnect -> True _ -> False +data ForwardConfirmation + = FCFilesNotAccepted {fileIds :: [FileTransferId]} + | FCFilesInProgress {filesCount :: Int} + | FCFilesMissing {filesCount :: Int} + | FCFilesFailed {filesCount :: Int} + deriving (Show) + newtype UserPwd = UserPwd {unUserPwd :: Text} deriving (Eq, Show) @@ -1463,6 +1472,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan) +$(JQ.deriveJSON defaultJSON ''ForwardConfirmation) + $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHE") ''RemoteHostError) diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index f6c59cbcb5..2b21857408 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -595,6 +595,27 @@ ciFileLoaded = \case CIFSRcvWarning {} -> False CIFSInvalid {} -> False +data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing + deriving (Eq, Ord) + +ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError +ciFileForwardError fId = \case + CIFSSndStored -> Nothing + CIFSSndTransfer {} -> Nothing + CIFSSndComplete -> Nothing + CIFSSndCancelled -> Nothing + CIFSSndError {} -> Nothing + CIFSSndWarning {} -> Nothing + CIFSRcvInvitation -> Just $ FFENotAccepted fId + CIFSRcvAccepted -> Just FFEInProgress + CIFSRcvTransfer {} -> Just FFEInProgress + CIFSRcvAborted -> Just $ FFENotAccepted fId + CIFSRcvCancelled -> Just FFEFailed + CIFSRcvComplete -> Nothing + CIFSRcvError {} -> Just FFEFailed + CIFSRcvWarning {} -> Just FFEFailed + CIFSInvalid {} -> Just FFEFailed + data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) deriving instance Show ACIFileStatus diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2158599c4b..cb686ef2b0 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -203,6 +203,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRUnknownMemberBlocked u g byM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember byM <> " blocked an unknown member, creating unknown member record " <> ttyMember um] CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m] CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"] + CRForwardPlan u count itemIds fc -> ttyUser u $ viewForwardPlan count itemIds fc CRRcvFileDescrReady _ _ _ _ -> [] CRRcvFileProgressXFTP {} -> [] CRRcvFileAccepted u ci -> ttyUser u $ savingFile' ci @@ -930,6 +931,20 @@ viewUserContactLinkDeleted = "To create a new chat address use " <> highlight' "/ad" ] +viewForwardPlan :: Int -> [ChatItemId] -> Maybe ForwardConfirmation -> [StyledString] +viewForwardPlan count itemIds = maybe [forwardCount] $ \fc -> [confirmation fc, forwardCount] + where + confirmation = \case + FCFilesNotAccepted fileIds -> plain $ "Files can be received: " <> intercalate ", " (map show fileIds) + FCFilesInProgress cnt -> plain $ "Still receiving " <> show cnt <> " file(s)" + FCFilesMissing cnt -> plain $ show cnt <> " file(s) are missing" + FCFilesFailed cnt -> plain $ "Receiving " <> show cnt <> " file(s) failed" + forwardCount + | count == len = "all messages can be forwarded" + | len == 0 = "nothing to forward" + | otherwise = plain $ show len <> " message(s) out of " <> show count <> " can be forwarded" + len = length itemIds + connReqContact_ :: StyledString -> ConnReqContact -> [StyledString] connReqContact_ intro cReq = [ intro, @@ -2034,7 +2049,7 @@ viewChatError isCmd logLevel testView = \case CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInvalidQuote -> ["cannot reply to this message"] - CEInvalidForward -> ["cannot forward this message"] + CEInvalidForward -> ["cannot forward message(s)"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] CEHasCurrentCall -> ["call already in progress"] diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index b339053abf..8f1595fd6c 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -7,7 +7,11 @@ import ChatClient import ChatTests.Utils import Control.Concurrent (threadDelay) import qualified Data.ByteString.Char8 as B -import System.Directory (copyFile, doesFileExist) +import Data.List (intercalate) +import qualified Data.Text as T +import System.Directory (copyFile, doesFileExist, removeFile) +import Simplex.Chat (fixedImagePreview) +import Simplex.Chat.Types (ImageData (..)) import Test.Hspec hiding (it) chatForwardTests :: SpecWith FilePath @@ -613,6 +617,8 @@ testForwardContactToContactMulti = alice <# "bob> hey" msgId2 <- lastItemId alice + alice ##> ("/_forward plan @2 " <> msgId1 <> "," <> msgId2) + alice <## "all messages can be forwarded" alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2) alice <# "@cath <- you @bob" alice <## " hi" @@ -642,6 +648,8 @@ testForwardGroupToGroupMulti = alice <# "#team bob> hey" msgId2 <- lastItemId alice + alice ##> ("/_forward plan #1 " <> msgId1 <> "," <> msgId2) + alice <## "all messages can be forwarded" alice ##> ("/_forward #2 #1 " <> msgId1 <> "," <> msgId2) alice <# "#club <- you #team" alice <## " hi" @@ -672,6 +680,7 @@ testMultiForwardFiles = setRelativePaths alice "./tests/tmp/alice_app_files" "./tests/tmp/alice_xftp" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" + copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" connectUsers alice bob @@ -686,32 +695,46 @@ testMultiForwardFiles = -- send original files let cm1 = "{\"msgContent\": {\"type\": \"text\", \"text\": \"message without file\"}}" - cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 1\"}}" - cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"sending file 2\"}}" - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "]") + ImageData img = fixedImagePreview + cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}" + cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}" + cm4 = "{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"message with large file\"}}" + alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "]") alice <# "@bob message without file" - alice <# "@bob sending file 1" alice <# "/f @bob test.jpg" alice <## "use /fc 1 to cancel sending" - alice <# "@bob sending file 2" alice <# "/f @bob test.pdf" alice <## "use /fc 2 to cancel sending" + alice <# "@bob message with large file" + alice <# "/f @bob test_1MB.pdf" + alice <## "use /fc 3 to cancel sending" + bob <# "alice> message without file" - bob <# "alice> sending file 1" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <## "use /fr 1 [/ | ] to receive it" - bob <# "alice> sending file 2" bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <## "use /fr 2 [/ | ] to receive it" + bob <# "alice> message with large file" + bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" + bob <## "use /fr 3 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" + alice <## "completed uploading file 3 (test_1MB.pdf) for bob" + + -- IDs to forward + let msgId1 = (read msgIdZero :: Int) + 1 + msgIds = intercalate "," $ map show [msgId1, msgId1 + 1, msgId1 + 2, msgId1 + 3, msgId1 + 4] + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 1, 2, 3" + bob <## "4 message(s) out of 5 can be forwarded" bob ##> "/fr 1" bob @@ -720,6 +743,10 @@ testMultiForwardFiles = ] bob <## "completed receiving file 1 (test.jpg) from alice" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 2, 3" + bob <## "4 message(s) out of 5 can be forwarded" + bob ##> "/fr 2" bob <### [ "saving file 2 from alice to test.pdf", @@ -736,8 +763,10 @@ testMultiForwardFiles = dest2 `shouldBe` src2 -- forward file - let msgId1 = (read msgIdZero :: Int) + 1 - bob ##> ("/_forward @3 @2 " <> show msgId1 <> "," <> show (msgId1 + 1) <> "," <> show (msgId1 + 2) <> "," <> show (msgId1 + 3)) + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 3" + bob <## "all messages can be forwarded" + bob ##> ("/_forward @3 @2 " <> msgIds) -- messages printed for bob bob <# "@cath <- you @alice" @@ -747,14 +776,17 @@ testMultiForwardFiles = bob <## " message without file" bob <# "@cath <- @alice" - bob <## " sending file 1" + bob <## " test_1.jpg" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 3 to cancel sending" + bob <## "use /fc 4 to cancel sending" bob <# "@cath <- @alice" - bob <## " sending file 2" + bob <## " test_1.pdf" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 4 to cancel sending" + bob <## "use /fc 5 to cancel sending" + + bob <# "@cath <- @alice" + bob <## " message with large file" -- messages printed for cath cath <# "bob> -> forwarded" @@ -764,18 +796,21 @@ testMultiForwardFiles = cath <## " message without file" cath <# "bob> -> forwarded" - cath <## " sending file 1" + cath <## " test_1.jpg" cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)" cath <## "use /fr 1 [/ | ] to receive it" cath <# "bob> -> forwarded" - cath <## " sending file 2" + cath <## " test_1.pdf" cath <# "bob> sends file test_1.pdf (266.0 KiB / 272376 bytes)" cath <## "use /fr 2 [/ | ] to receive it" + cath <# "bob> -> forwarded" + cath <## " message with large file" + -- file transfer - bob <## "completed uploading file 3 (test_1.jpg) for cath" - bob <## "completed uploading file 4 (test_1.pdf) for cath" + bob <## "completed uploading file 4 (test_1.jpg) for cath" + bob <## "completed uploading file 5 (test_1.pdf) for cath" cath ##> "/fr 1" cath @@ -801,6 +836,26 @@ testMultiForwardFiles = dest2C <- B.readFile "./tests/tmp/cath_app_files/test_1.pdf" dest2C `shouldBe` src2B + bob ##> "/fr 3" + bob + <### [ "saving file 3 from alice to test_1MB.pdf", + "started receiving file 3 (test_1MB.pdf) from alice" + ] + bob <## "completed receiving file 3 (test_1MB.pdf) from alice" + + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "all messages can be forwarded" + + removeFile "./tests/tmp/bob_app_files/test_1MB.pdf" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "1 file(s) are missing" + bob <## "all messages can be forwarded" + + removeFile "./tests/tmp/bob_app_files/test.pdf" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "2 file(s) are missing" + bob <## "4 message(s) out of 5 can be forwarded" + -- deleting original file doesn't delete forwarded file checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do bob ##> "/clear alice" From 7aec147ceca6292f8ce918ba4c5a4d6f48614b5e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 12 Sep 2024 22:19:17 +0100 Subject: [PATCH 046/704] core: update simplexmq 6.0.4.0 --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index fc958929af..51b35e0db7 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: 946e16339e16e026f51185ebfb48c3a0c5a5b2e1 + tag: f5e666ae4f41351d5d5ac416cd6fb1d5fadc8ab7 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index fa3c6d4839..ec230ee09e 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."946e16339e16e026f51185ebfb48c3a0c5a5b2e1" = "1jkx2f14h5krmy467zyifsc58dys89pkpn08cyf1q9v78in7nwfd"; + "https://github.com/simplex-chat/simplexmq.git"."f5e666ae4f41351d5d5ac416cd6fb1d5fadc8ab7" = "1cq9apm9vp40v4ck0wcbis4463q3cjd9fbx5511hhh6lah6llifc"; "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 4cfda911247b997afb767da2f7e2b322117414b3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 13 Sep 2024 09:35:11 +0100 Subject: [PATCH 047/704] core: fix ForwardConfirmation encoding (#4872) --- src/Simplex/Chat/Controller.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 99739cc6bf..7dccbb663d 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -1472,7 +1472,7 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan) -$(JQ.deriveJSON defaultJSON ''ForwardConfirmation) +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType) From 4447b66b4ef09f3c2d8b0507076deb561bf71a3b Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:02:32 +0000 Subject: [PATCH 048/704] android: fix showing logs from core (#4880) --- .../main/java/chat/simplex/app/SimplexApp.kt | 2 +- .../common/platform/AppCommon.android.kt | 27 +++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 49edde55bb..b6773ceb42 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -66,7 +66,7 @@ class SimplexApp: Application(), LifecycleEventObserver, Configuration.Provider } } context = this - initHaskell() + initHaskell(packageName) initMultiplatform() runMigrations() tmpDir.deleteRecursively() diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 8cd51e8298..cd1672f3e9 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -31,22 +31,19 @@ lateinit var androidAppContext: Context var mainActivity: WeakReference = WeakReference(null) var callActivity: WeakReference = WeakReference(null) -fun initHaskell() { - val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000) +fun initHaskell(packageName: String) { val s = Semaphore(0) thread(name="stdout/stderr pipe") { Log.d(TAG, "starting server") - var server: LocalServerSocket? = null - for (i in 0..100) { - try { - server = LocalServerSocket(socketName + i) - break - } catch (e: IOException) { - Log.e(TAG, e.stackTraceToString()) - } - } - if (server == null) { - throw Error("Unable to setup local server socket. Contact developers") + val server: LocalServerSocket + try { + server = LocalServerSocket(packageName) + } catch (e: IOException) { + Log.e(TAG, e.stackTraceToString()) + Log.e(TAG, "Unable to setup local server socket. Contact developers") + s.release() + // Will not have logs from backend + return@thread } Log.d(TAG, "started server") s.release() @@ -60,7 +57,7 @@ fun initHaskell() { Log.d(TAG, "starting receiver loop") while (true) { val line = input.readLine() ?: break - Log.w("$TAG (stdout/stderr)", line) + Log.w(TAG, "(stdout/stderr) $line") logbuffer.add(line) } Log.w(TAG, "exited receiver loop") @@ -70,7 +67,7 @@ fun initHaskell() { System.loadLibrary("app-lib") s.acquire() - pipeStdOutToSocket(socketName) + pipeStdOutToSocket(packageName) initHS() } From c6ab8ec6b35c83030527ad62e54f59ca748c5cc1 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:16:23 +0400 Subject: [PATCH 049/704] core: cleanup empty file on error; check file status on forward (#4878) --- src/Simplex/Chat.hs | 63 +++++++++++++++++++++--------------- src/Simplex/Chat/Messages.hs | 4 +-- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index fb5c4b4962..fa80a2e7ec 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -995,7 +995,7 @@ processChatCommand' vr = \case Just err -> pure $ itemIdWithoutFile err Nothing -> case fileSource of Just CryptoFile {filePath} -> do - exists <- doesFileExist . maybe filePath ( filePath) =<< chatReadVar filesFolder + exists <- doesFileExist =<< lift (toFSFilePath filePath) pure $ if exists then (Just itemId, Nothing) else itemIdWithoutFile FFEMissing Nothing -> pure $ itemIdWithoutFile FFEMissing where @@ -1079,27 +1079,28 @@ processChatCommand' vr = \case Just CIFFUnknown -> ciff Just prevCIFF -> Just prevCIFF forwardContent :: ChatItem c d -> MsgContent -> CM (Maybe (MsgContent, Maybe CryptoFile)) - forwardContent ChatItem {file = Nothing} mc = pure $ Just (mc, Nothing) - forwardContent ChatItem {file = Just ciFile} mc = case ciFile of - CIFile {fileName, fileSource = Just fromCF@CryptoFile {filePath}} -> - chatReadVar filesFolder >>= \case - Nothing -> - ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile) - Just filesFolder -> do - let fsFromPath = filesFolder filePath - ifM - (doesFileExist fsFromPath) - ( do - fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName - liftIO $ B.writeFile fsNewPath "" -- create empty file - encrypt <- chatReadVar encryptLocalFiles - cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing - let toCF = CryptoFile fsNewPath cfArgs - -- to keep forwarded file in case original is deleted - liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF - pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) - ) - (pure contentWithoutFile) + forwardContent ChatItem {file} mc = case file of + Nothing -> pure $ Just (mc, Nothing) + Just CIFile {fileName, fileStatus, fileSource = Just fromCF@CryptoFile {filePath}} + | ciFileLoaded fileStatus -> + chatReadVar filesFolder >>= \case + Nothing -> + ifM (doesFileExist filePath) (pure $ Just (mc, Just fromCF)) (pure contentWithoutFile) + Just filesFolder -> do + let fsFromPath = filesFolder filePath + ifM + (doesFileExist fsFromPath) + ( do + fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName + liftIO $ B.writeFile fsNewPath "" -- create empty file + encrypt <- chatReadVar encryptLocalFiles + cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing + let toCF = CryptoFile fsNewPath cfArgs + -- to keep forwarded file in case original is deleted + liftIOEither $ runExceptT $ withExceptT (ChatError . CEInternalError . show) $ copyCryptoFile (fromCF {filePath = fsFromPath} :: CryptoFile) toCF + pure $ Just (mc, Just (toCF {filePath = takeFileName fsNewPath} :: CryptoFile)) + ) + (pure contentWithoutFile) _ -> pure contentWithoutFile where contentWithoutFile = case mc of @@ -3444,7 +3445,7 @@ callStatusItemContent user Contact {contactId} chatItemId receivedStatus = do -- used during file transfer for actual operations with file system toFSFilePath :: FilePath -> CM' FilePath toFSFilePath f = - maybe f ( f) <$> (readTVarIO =<< asks filesFolder) + maybe f ( f) <$> (chatReadVar' filesFolder) setFileToEncrypt :: RcvFileTransfer -> CM RcvFileTransfer setFileToEncrypt ft@RcvFileTransfer {fileId} = do @@ -3566,7 +3567,9 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} relaysNotApproved :: [XFTPServer] -> CM () relaysNotApproved unknownSrvs = do aci_ <- resetRcvCIFileStatus user fileId CIFSRcvInvitation - forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci + forM_ aci_ $ \aci -> do + cleanupACIFile aci + toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs getNetworkConfig :: CM' NetworkConfig @@ -4290,14 +4293,22 @@ processAgentMsgRcvFile _corrId aFileId msg = do RFERR e | e == FILE NOT_APPROVED -> do aci_ <- resetRcvCIFileStatus user fileId CIFSRcvAborted + forM_ aci_ cleanupACIFile agentXFTPDeleteRcvFile aFileId fileId forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci | otherwise -> do - ci <- withStore $ \db -> do + aci_ <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId (CIFSRcvError $ agentFileError e) lookupChatItemByFileId db vr user fileId + forM_ aci_ cleanupACIFile agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileError user ci e ft + toView $ CRRcvFileError user aci_ e ft + +cleanupACIFile :: AChatItem -> CM () +cleanupACIFile (AChatItem _ _ _ ChatItem {file = Just CIFile {fileSource = Just CryptoFile {filePath}}}) = do + fsFilePath <- lift $ toFSFilePath filePath + removeFile fsFilePath `catchChatError` \_ -> pure () +cleanupACIFile _ = pure () processAgentMessageConn :: VersionRangeChat -> User -> ACorrId -> ConnId -> AEvent 'AEConn -> CM () processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 2b21857408..50e68e5bf4 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeLatin1, encodeUtf8) -import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime) +import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, nominalDay) import Data.Type.Equality import Data.Typeable (Typeable) import Database.SQLite.Simple.FromField (FromField (..)) @@ -596,7 +596,7 @@ ciFileLoaded = \case CIFSInvalid {} -> False data ForwardFileError = FFENotAccepted FileTransferId | FFEInProgress | FFEFailed | FFEMissing - deriving (Eq, Ord) + deriving (Eq, Ord) ciFileForwardError :: FileTransferId -> CIFileStatus d -> Maybe ForwardFileError ciFileForwardError fId = \case From b5f91f2d31733dac3507510146ec308115e5102c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 14 Sep 2024 10:57:55 +0100 Subject: [PATCH 050/704] core: test forwarding image preview without attached file (#4877) --- tests/ChatTests/Forward.hs | 50 +++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/tests/ChatTests/Forward.hs b/tests/ChatTests/Forward.hs index 8f1595fd6c..3b861a8417 100644 --- a/tests/ChatTests/Forward.hs +++ b/tests/ChatTests/Forward.hs @@ -681,6 +681,7 @@ testMultiForwardFiles = copyFile "./tests/fixtures/test.jpg" "./tests/tmp/alice_app_files/test.jpg" copyFile "./tests/fixtures/test.pdf" "./tests/tmp/alice_app_files/test.pdf" copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" + copyFile "./tests/fixtures/logo.jpg" "./tests/tmp/alice_app_files/logo.jpg" setRelativePaths bob "./tests/tmp/bob_app_files" "./tests/tmp/bob_xftp" setRelativePaths cath "./tests/tmp/cath_app_files" "./tests/tmp/cath_xftp" connectUsers alice bob @@ -699,7 +700,8 @@ testMultiForwardFiles = cm2 = "{\"filePath\": \"test.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}" cm3 = "{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}" cm4 = "{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"message with large file\"}}" - alice ##> ("/_send @2 json [" <> cm1 <> "," <> cm2 <> "," <> cm3 <> "," <> cm4 <> "]") + cm5 = "{\"filePath\": \"logo.jpg\", \"msgContent\": {\"type\": \"image\", \"image\":\"" <> T.unpack img <> "\", \"text\": \"\"}}" + alice ##> ("/_send @2 json [" <> intercalate "," [cm1, cm2, cm3, cm4, cm5] <> "]") alice <# "@bob message without file" @@ -713,6 +715,9 @@ testMultiForwardFiles = alice <# "/f @bob test_1MB.pdf" alice <## "use /fc 3 to cancel sending" + alice <# "/f @bob logo.jpg" + alice <## "use /fc 4 to cancel sending" + bob <# "alice> message without file" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" @@ -725,16 +730,20 @@ testMultiForwardFiles = bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" bob <## "use /fr 3 [/ | ] to receive it" + bob <# "alice> sends file logo.jpg (31.3 KiB / 32080 bytes)" + bob <## "use /fr 4 [/ | ] to receive it" + alice <## "completed uploading file 1 (test.jpg) for bob" alice <## "completed uploading file 2 (test.pdf) for bob" alice <## "completed uploading file 3 (test_1MB.pdf) for bob" + alice <## "completed uploading file 4 (logo.jpg) for bob" -- IDs to forward let msgId1 = (read msgIdZero :: Int) + 1 - msgIds = intercalate "," $ map show [msgId1, msgId1 + 1, msgId1 + 2, msgId1 + 3, msgId1 + 4] + msgIds = intercalate "," $ map (show . (msgId1 +)) [0..5] bob ##> ("/_forward plan @2 " <> msgIds) - bob <## "Files can be received: 1, 2, 3" - bob <## "4 message(s) out of 5 can be forwarded" + bob <## "Files can be received: 1, 2, 3, 4" + bob <## "5 message(s) out of 6 can be forwarded" bob ##> "/fr 1" bob @@ -744,8 +753,8 @@ testMultiForwardFiles = bob <## "completed receiving file 1 (test.jpg) from alice" bob ##> ("/_forward plan @2 " <> msgIds) - bob <## "Files can be received: 2, 3" - bob <## "4 message(s) out of 5 can be forwarded" + bob <## "Files can be received: 2, 3, 4" + bob <## "5 message(s) out of 6 can be forwarded" bob ##> "/fr 2" bob @@ -764,7 +773,7 @@ testMultiForwardFiles = -- forward file bob ##> ("/_forward plan @2 " <> msgIds) - bob <## "Files can be received: 3" + bob <## "Files can be received: 3, 4" bob <## "all messages can be forwarded" bob ##> ("/_forward @3 @2 " <> msgIds) @@ -778,16 +787,19 @@ testMultiForwardFiles = bob <# "@cath <- @alice" bob <## " test_1.jpg" bob <# "/f @cath test_1.jpg" - bob <## "use /fc 4 to cancel sending" + bob <## "use /fc 5 to cancel sending" bob <# "@cath <- @alice" bob <## " test_1.pdf" bob <# "/f @cath test_1.pdf" - bob <## "use /fc 5 to cancel sending" + bob <## "use /fc 6 to cancel sending" bob <# "@cath <- @alice" bob <## " message with large file" + bob <# "@cath <- @alice" + bob <## "" + -- messages printed for cath cath <# "bob> -> forwarded" cath <## " hi" @@ -808,9 +820,12 @@ testMultiForwardFiles = cath <# "bob> -> forwarded" cath <## " message with large file" + cath <# "bob> -> forwarded" + cath <## "" + -- file transfer - bob <## "completed uploading file 4 (test_1.jpg) for cath" - bob <## "completed uploading file 5 (test_1.pdf) for cath" + bob <## "completed uploading file 5 (test_1.jpg) for cath" + bob <## "completed uploading file 6 (test_1.pdf) for cath" cath ##> "/fr 1" cath @@ -843,6 +858,17 @@ testMultiForwardFiles = ] bob <## "completed receiving file 3 (test_1MB.pdf) from alice" + bob ##> ("/_forward plan @2 " <> msgIds) + bob <## "Files can be received: 4" + bob <## "all messages can be forwarded" + + bob ##> "/fr 4" + bob + <### [ "saving file 4 from alice to logo.jpg", + "started receiving file 4 (logo.jpg) from alice" + ] + bob <## "completed receiving file 4 (logo.jpg) from alice" + bob ##> ("/_forward plan @2 " <> msgIds) bob <## "all messages can be forwarded" @@ -854,7 +880,7 @@ testMultiForwardFiles = removeFile "./tests/tmp/bob_app_files/test.pdf" bob ##> ("/_forward plan @2 " <> msgIds) bob <## "2 file(s) are missing" - bob <## "4 message(s) out of 5 can be forwarded" + bob <## "5 message(s) out of 6 can be forwarded" -- deleting original file doesn't delete forwarded file checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do From de7882c9044e1dc5b0aa9d28f526725df70e2a24 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Mon, 16 Sep 2024 15:28:45 +0300 Subject: [PATCH 052/704] ios: update user profile sheet design (#4871) * ios: update user profile sheet design * revert views * improve validation * minor * align with create profile * alert on dismiss * revert x appearance * update size * move the fullname * focus on appear * profile image * localizations --------- Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/ChatList/ChatListView.swift | 4 +- .../Shared/Views/ChatList/UserPicker.swift | 2 +- .../Shared/Views/Database/DatabaseView.swift | 4 +- .../Views/Migration/MigrateFromDevice.swift | 2 +- .../Views/Migration/MigrateToDevice.swift | 2 +- .../Views/UserSettings/UserAddressView.swift | 2 +- .../Views/UserSettings/UserProfile.swift | 226 ++++++++---------- .../bg.xcloc/Localized Contents/bg.xliff | 80 ++++--- .../cs.xcloc/Localized Contents/cs.xliff | 78 +++--- .../de.xcloc/Localized Contents/de.xliff | 80 ++++--- .../en.xcloc/Localized Contents/en.xliff | 90 ++++--- .../es.xcloc/Localized Contents/es.xliff | 80 ++++--- .../fi.xcloc/Localized Contents/fi.xliff | 78 +++--- .../fr.xcloc/Localized Contents/fr.xliff | 80 ++++--- .../hu.xcloc/Localized Contents/hu.xliff | 80 ++++--- .../it.xcloc/Localized Contents/it.xliff | 80 ++++--- .../ja.xcloc/Localized Contents/ja.xliff | 78 +++--- .../nl.xcloc/Localized Contents/nl.xliff | 80 ++++--- .../pl.xcloc/Localized Contents/pl.xliff | 80 ++++--- .../ru.xcloc/Localized Contents/ru.xliff | 80 ++++--- .../th.xcloc/Localized Contents/th.xliff | 78 +++--- .../tr.xcloc/Localized Contents/tr.xliff | 80 ++++--- .../uk.xcloc/Localized Contents/uk.xliff | 80 ++++--- .../Localized Contents/zh-Hans.xliff | 79 +++--- .../common/views/chatlist/UserPicker.kt | 2 +- .../views/usersettings/UserAddressView.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 6 - 27 files changed, 925 insertions(+), 690 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 156b8694c4..4d1c182554 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -66,7 +66,7 @@ struct ChatListView: View { case .address: NavigationView { UserAddressView(shareViaProfile: currentUser.addressShared) - .navigationTitle("Public address") + .navigationTitle("SimpleX address") .navigationBarTitleDisplayMode(.large) .modifier(ThemedBackground(grouped: true)) } @@ -78,7 +78,7 @@ struct ChatListView: View { NavigationView { UserProfile() .navigationTitle("Your current profile") - .modifier(ThemedBackground()) + .modifier(ThemedBackground(grouped: true)) } case .chatPreferences: NavigationView { diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index 9d7f6bbd9c..efe54cb036 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -49,7 +49,7 @@ struct UserPicker: View { activeSheet = .currentProfile } - openSheetOnTap(title: m.userAddress == nil ? "Create public address" : "Your public address", icon: "qrcode") { + openSheetOnTap(title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", icon: "qrcode") { activeSheet = .address } diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index f5b5287971..d0de0e1bd3 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -270,12 +270,12 @@ struct DatabaseView: View { case let .archiveImportedWithErrors(errs): return Alert( title: Text("Chat database imported"), - message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs) + message: Text("Restart the app to use imported chat database") + Text(verbatim: "\n") + Text("Some non-fatal errors occurred during import:") + archiveErrorsText(errs) ) case let .archiveExportedWithErrors(archivePath, errs): return Alert( title: Text("Chat database exported"), - message: Text("You may save the exported archive.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), + message: Text("You may save the exported archive.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), dismissButton: .default(Text("Continue")) { showShareSheet(items: [archivePath]) } diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 5f7b8a4534..73e5b97057 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -177,7 +177,7 @@ struct MigrateFromDevice: View { case let .archiveExportedWithErrors(archivePath, errs): return Alert( title: Text("Chat database exported"), - message: Text("You may migrate the exported database.") + Text(verbatim: "\n\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), + message: Text("You may migrate the exported database.") + Text(verbatim: "\n") + Text("Some file(s) were not exported:") + archiveErrorsText(errs), dismissButton: .default(Text("Continue")) { Task { await uploadArchive(path: archivePath) } } diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index e2df68a0e4..fe0eec609b 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -571,7 +571,7 @@ struct MigrateToDevice: View { AlertManager.shared.showAlert( Alert( title: Text("Error migrating settings"), - message: Text ("Not all settings were migrated. Repeat migration if you need them.") + Text("\n\n") + Text(responseError(error))) + message: Text ("Some app settings were not migrated.") + Text("\n") + Text(responseError(error))) ) } hideView() diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 7efc8a46f5..2469dc59db 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -229,7 +229,7 @@ struct UserAddressView: View { } } } label: { - Label("Create public address", systemImage: "qrcode") + Label("Create SimpleX address", systemImage: "qrcode") } } diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 198fd495bd..6ca661ae48 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -11,8 +11,11 @@ import SimpleXChat struct UserProfile: View { @EnvironmentObject var chatModel: ChatModel + @EnvironmentObject var theme: AppTheme + @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner @State private var profile = Profile(displayName: "", fullName: "") - @State private var editProfile = false + @State private var currentProfileHash: Int? + // Modals @State private var showChooseSource = false @State private var showImagePicker = false @State private var showTakePhoto = false @@ -21,85 +24,83 @@ struct UserProfile: View { @FocusState private var focusDisplayName var body: some View { - let user: User = chatModel.currentUser! - - return VStack(alignment: .leading) { - Text("Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile.") - .padding(.bottom) - - if editProfile { - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { + List { + Group { + if profile.image != nil { + ZStack(alignment: .bottomTrailing) { + ZStack(alignment: .topTrailing) { + profileImageView(profile.image) + .onTapGesture { showChooseSource = true } + overlayButton("multiply", edge: .top) { profile.image = nil } + } + overlayButton("camera", edge: .bottom) { showChooseSource = true } + } + } else { + ZStack(alignment: .center) { profileImageView(profile.image) - if user.image != nil { - Button { - profile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - } + editImageButton { showChooseSource = true } + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.top) + .contentShape(Rectangle()) + + Section { + HStack { + TextField("Enter your name…", text: $profile.displayName) + .focused($focusDisplayName) + if !validDisplayName(profile.displayName) { + Button { + alert = .invalidNameError(validName: mkValidName(profile.displayName)) + } label: { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) } } - - editImageButton { showChooseSource = true } } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - ZStack(alignment: .leading) { - if !validNewProfileName(user) { - Button { - alert = .invalidNameError(validName: mkValidName(profile.displayName)) - } label: { - Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } - } else { - Image(systemName: "exclamationmark.circle").foregroundColor(.clear) - } - profileNameTextEdit("Profile name", $profile.displayName) - .focused($focusDisplayName) - } - .padding(.bottom) - if showFullName(user) { - profileNameTextEdit("Full name (optional)", $profile.fullName) - .padding(.bottom) - } - HStack(spacing: 20) { - Button("Cancel") { editProfile = false } - Button("Save (and notify contacts)") { saveProfile() } - .disabled(!canSaveProfile(user)) - } + if let user = chatModel.currentUser, showFullName(user) { + TextField("Full name (optional)", text: $profile.fullName) } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) - } else { - ZStack(alignment: .center) { - profileImageView(user.image) - .onTapGesture { startEditingImage(user) } + } footer: { + Text("Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.") + } - if user.image == nil { - editImageButton { startEditingImage(user) } - } + Section { + Button(action: getCurrentProfile) { + Text("Reset") } - .frame(maxWidth: .infinity, alignment: .center) - - VStack(alignment: .leading) { - profileNameView("Profile name:", user.profile.displayName) - if showFullName(user) { - profileNameView("Full name:", user.profile.fullName) - } - Button("Edit") { - profile = fromLocalProfile(user.profile) - editProfile = true - focusDisplayName = true - } + .disabled(currentProfileHash == profile.hashValue) + Button(action: saveProfile) { + Text("Save (and notify contacts)") } - .frame(maxWidth: .infinity, minHeight: 120, alignment: .leading) + .disabled(!canSaveProfile) } } - .padding() - .frame(maxHeight: .infinity, alignment: .top) + // Lifecycle + .onAppear { + getCurrentProfile() + } + .onDisappear { + if canSaveProfile { + showAlert( + title: NSLocalizedString("Save your profile?", comment: "alert title"), + message: NSLocalizedString("Your profile was changed. If you save it, the updated profile will be sent to all your contacts.", comment: "alert message"), + buttonTitle: NSLocalizedString("Save (and notify contacts)", comment: "alert button"), + buttonAction: saveProfile, + cancelButton: true + ) + } + } + .onChange(of: chosenImage) { image in + if let image { + profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + profile.image = nil + } + } + // Modals .confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) { Button("Take picture") { showTakePhoto = true @@ -126,57 +127,49 @@ struct UserProfile: View { } } } - .onChange(of: chosenImage) { image in - if let image = image { - profile.image = resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) - } else { - profile.image = nil - } - } .alert(item: $alert) { a in userProfileAlert(a, $profile.displayName) } } - func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding) -> some View { - TextField(label, text: name) - .padding(.leading, 32) - } - - func profileNameView(_ label: LocalizedStringKey, _ name: String) -> some View { - HStack { - Text(label) - Text(name).fontWeight(.bold) - } - .padding(.bottom) - } - - func startEditingImage(_ user: User) { - profile = fromLocalProfile(user.profile) - editProfile = true - showChooseSource = true - } - - private func validNewProfileName(_ user: User) -> Bool { - profile.displayName == user.profile.displayName || validDisplayName(profile.displayName.trimmingCharacters(in: .whitespaces)) + @ViewBuilder + private func overlayButton( + _ systemName: String, + edge: Edge.Set, + action: @escaping () -> Void + ) -> some View { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 12) + .foregroundColor(theme.colors.primary) + .padding(6) + .frame(width: 36, height: 36, alignment: .center) + .background(radius >= 20 ? Color.clear : theme.colors.background.opacity(0.5)) + .clipShape(Circle()) + .contentShape(Circle()) + .padding([.trailing, edge], -12) + .onTapGesture(perform: action) } private func showFullName(_ user: User) -> Bool { user.profile.fullName != "" && user.profile.fullName != user.profile.displayName } - - private func canSaveProfile(_ user: User) -> Bool { - profile.displayName.trimmingCharacters(in: .whitespaces) != "" && validNewProfileName(user) + + private var canSaveProfile: Bool { + currentProfileHash != profile.hashValue && + profile.displayName.trimmingCharacters(in: .whitespaces) != "" && + validDisplayName(profile.displayName) } - func saveProfile() { + private func saveProfile() { + focusDisplayName = false Task { do { profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) if let (newProfile, _) = try await apiUpdateProfile(profile: profile) { - DispatchQueue.main.async { + await MainActor.run { chatModel.updateCurrentUser(newProfile) - profile = newProfile + getCurrentProfile() } - editProfile = false } else { alert = .duplicateUserError } @@ -185,6 +178,13 @@ struct UserProfile: View { } } } + + private func getCurrentProfile() { + if let user = chatModel.currentUser { + profile = fromLocalProfile(user.profile) + currentProfileHash = profile.hashValue + } + } } func profileImageView(_ imageStr: String?) -> some View { @@ -201,19 +201,3 @@ func editImageButton(action: @escaping () -> Void) -> some View { .frame(width: 48) } } - -struct UserProfile_Previews: PreviewProvider { - static var previews: some View { - let chatModel1 = ChatModel() - chatModel1.currentUser = User.sampleData - let chatModel2 = ChatModel() - chatModel2.currentUser = User.sampleData - chatModel2.currentUser?.profile.image = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAAqACAAQAAAABAAAAgKADAAQAAAABAAAAgAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/+ICNElDQ19QUk9GSUxFAAEBAAACJGFwcGwEAAAAbW50clJHQiBYWVogB+EABwAHAA0AFgAgYWNzcEFQUEwAAAAAQVBQTAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsyhqVgiV/EE04mRPV0eoVggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKZGVzYwAAAPwAAABlY3BydAAAAWQAAAAjd3RwdAAAAYgAAAAUclhZWgAAAZwAAAAUZ1hZWgAAAbAAAAAUYlhZWgAAAcQAAAAUclRSQwAAAdgAAAAgY2hhZAAAAfgAAAAsYlRSQwAAAdgAAAAgZ1RSQwAAAdgAAAAgZGVzYwAAAAAAAAALRGlzcGxheSBQMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDE3AABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAACD3wAAPb////+7WFlaIAAAAAAAAEq/AACxNwAACrlYWVogAAAAAAAAKDgAABELAADIuXBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeTAAD9kP//+6L///2jAAAD3AAAwG7/wAARCACAAIADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwABAQEBAQECAQECAwICAgMEAwMDAwQGBAQEBAQGBwYGBgYGBgcHBwcHBwcHCAgICAgICQkJCQkLCwsLCwsLCwsL/9sAQwECAgIDAwMFAwMFCwgGCAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL/90ABAAI/9oADAMBAAIRAxEAPwD4N1TV59SxpunRtBb/APPP/lo+eMsf4R+uKyxNa6Y32a3UTzjoi8Ip9/8AOfYV0tx4d1a8VlsojaWo6uThj+Pb6Cs2CCGyP2LQ4xPIMBpGIVVz7ngV+Ap31P2C1iSDQbnWXRtVYyMT8kSDkZ9B29zXXReD7ZVOkX0QlLgg2ycjBH8ZHXPoOK9O8L6LpljZidWMjyqMzAdc/wB3PJ+p4qjrPiuxs1a38LwLJIn35ScoP94jlm9hxW8ZKJm1fY/Gv4yeA/E37L3xf07xz4GuH0260+7i1bRLpDkwzQOHVfQ+WwAI7r1zmv7fv2Nv2nfCv7YH7PHh346+FwkD6nEYtRs1OTZ6jBhbiA98K/zJnrGynvX8u3x3+G6fFvwXcadcOZNTQebZyN1EgH3QB91W6H657VD/AMEYP2qdQ/Zb/aRuPgN8RpjZeFviJcJabJztWy1tPkgkOeFE3+ok9zGTwtfY5Nj1Vjyt6nzuZ4XlfMj+zamH5TupVYnhhgjsaRyMYNe8eEMC7jxxU+1SMYFQFyaevPWgRqaeuSVFb0SDgAZI/SsLS9w4kxux1HTNdTEAMDvQJst20UitvA4rotMh8ycbuAv6k1Rs3UgcHjrXc6Xb2iTKVIJPQEcZ96qKMW7nWabpNmzRyEE9wOlegtplzFCLiMbEcfKw5/XP51l6ZPK6b2SJsdd64A/Kr0t5fyRsqsPLU5baNo49P0q2I//Q8iuPD17eeTpVy32u2ufls5lAC5P8MmOA2O/Q/XIrHl+GWn+CGN7qyC9ugxkSID92nvz1+pwK/TKb9j34t3Pw/PjXXrpdR165L3F7pkiDz5RISzHzFIUzliXKBQCTgMGwD8P6zompRzR2V2xuLWV9sE7ggo4yPLlBxhgRgE8k8cHivyPPMl9g3iMMrw6r+X/gH6PlmZ+1tRrP3uj7/wDBPnjXdR1rXWDao5jtm4S3h43gf3jwSPyH1rW0Xw9f6uyw2MYSNAAT/Ag/qa9ii+GTWEv2nV8nfztH3m/+t/nirMsVtMPscGIYYuCqjj8fWvmo+9qz227aI5O38NeH/DeJIGE079ZW9fQf/W/Ovyx/ba+C1x/aR+K/h6FoLa5dUvDH8rRzj7kgI+7ux253DPev1yuINKtF3XriOMDlm+83+6O1eNePZoPH2h3ngWC032N7E0UhI7HuPcdQfWvQweJdKakjkxFFTjZn6+f8Eu/2yE/a+/Zss9R8TXCyeMvCpTSfECZ+eSZF/dXWPS5jG4n/AJ6Bx2r9JGbd0r+GX9jD476z/wAE5v20IL3xPM7eGdUZdK8QBeUewmYGO6A7tbviT127171/cfaXdve28d1aSJNFKqukiHcjqwyGUjggggg9xX6Dhq6q01JM+NxVF05tdCyRQCOvakY4GRTFYd66DmN2xk2sK6eE5+YVxlo5EwB4rrLZiTyePWgmSOmsAThCcZPFdxZ5KruJyprgrWQ5G3tXS21+FABzVrYyZ6ZZTTSqCR8vQ4rUudWgW1e3QMrBScj1/D+tcpp1+UXaOn09fWtKP7OAzNjK+tNiP//R/oYjkSW9NgqsWVA7HHyrk4AJ9Tzx6CvjL9qz4M+FrbRrn4q2s0Fjcs6R3ttKdsd+ZCFBUf8APx0xj/WAYOCA1fVF58Y/hbb/AAwPxlXWIH8OCHzhdKc57bAv3vM3fLsxu3cYzX58eGdH8f8A7b/xIHi/xOs2k+DNGkK28AOCgPVQejXMg++/IiU7RyefmI+Z79+qPl++0JpR/wATG7Z9M4WOQfeVv7srdT/snp+NeWa9bfZXez8KxCZQcGVhiJT/AOzH6fnX7K/Fn9mfwzf6N9r+GmnwWV3DF5UlmBiC8iAxtbPAkx0c/e6N/eH5s+IvDcuj2jWcUTJYwsYXDrtktHXgxuvBxngE9Oh9/is6yVUr4nDL3Oq7enl+R9Plmac9qNZ+90ff/gnybLoheT7XrM3nMo5JH8h2HtXJa9/aGoMYbAC0gTqwH7x1H8hXsHiWGDRUboqr/Eeck+nrXj9/d3twWmlzbQHnn77e/tXzaqXXuntuNtz4z/ay+Eul+NPAf9u+H4TLq2kqzEAfNLAeXU/T7w/Ed6/XL/giD+2n/wALr+Ck37Nnjq78zxV8PYkW0Z2+a60VjthbJ5LWzfuW/wBjyz3NfCGuJLLm30tSsT8OT/U1+b1v4w8VfsE/tXeHf2kfhqjz2Vvcl5rdDiO4tZflu7Q+zoSUz0baeq19RkWMUZexk/Q8LNMLzx51uf3yIxPXvTQuTkVw3wz+IfhH4seBNG+JngS7W+0XX7OG/sp1P34ZlDLn0Izhh2YEGu+LAHFfXo+XJ4P9cp6YNdbCWHFcerFSCK6OGcMBk0wOmtZMVswurDNcnHKB7VqxXbDGKaZEoncRXpt4iy8fWlN44XdM5+bGPauWbUAI9p5NeH/E39oTwF8OAdO1W6+06kfuWVuQ0vtvOcIPdiPalOrGC5pOyHToym7RV2f/0nXmiaPrF/ceJvC1hrUnhC11EyFGZsIN2Mtg+QLjy+A5GQcZI6V/QP8ABrWvhd4i+GmnXXwZeI6DAnkxRxgq0LL95JFb5hJnO7dyTz3qt4f8EeCPC3g5Pht4csYItKt4fKNngMpjfOd4PJLckk8k18FeKvBXj79kHxu/xW+ECte+F711XUtNdiVC54VvQj/lnL2+63FfNNqWh7rVtT9JdItdaitpV8QSxyy+a5VowVURE/KDnuB1PQ9a/OD4yfEbwv8AEP4rx6F8JNIfXb4QyQXMlqAwvmQgEBThSkQBUysQpyFBOBjE+NH7WWu/HtrH4QfACxvYpNZHl3bSr5M7kjLQqc/JGo5ml/u8DrX2X+z38A9C+B3hzyQUvNbvVX7dehcA7ekUQ/hiT+Fe/U81m1bVj1Px/wDiX4FXQ4b7WNItJXitXZLq3nU+fpzjqpQ87PQ88eowa+JdanuvP+03JzG3Kk87voP8a/pi+NPwStfiAo8V+GDHaeI7aPYsjj91dxj/AJYzjuOyv1X6V+Mfxk+By6eL7xPodhLE9kzDUNJYfvbSXqWUd4z147cjivjc3ybkviMMtOq7eaPo8tzXmtRrvXo/8z4aaC/1a3drrbDbr6nCgepPc+36V4T8Z/A/h7xz4KvPB8uGmcb4LhhxHKv3WUeh6HPY17TrMuo3dysUA3p0VUGEArCudFt7aH7bqjguOQP6V89SquLUk9T26lNNWZ7L/wAEJv2vNQ8L6xq/7BPxZma3ureafUPDHnHvy93Zg/X9/EO+XA7Cv6fFwRnNfwWftIWHi/wL4u0T9pX4Vu2ma74buobpJY+GEkDBo5CO4B+Vx3U4PFf2VfshftPeFf2tv2e/Dvx18LbYhq0G29tQcm0vovluID/uPkr6oVPev0TLsWq9FT69T43MMN7KpdbM+q1kA+WtuF8qCa5H7SD0qvrnjbw34L0KTxD4qvobCyhBLzTuFUY7DPU+wya7nNJXZwxu3ZHoqyqq5JxXnPxL+Nvw3+EemjUPHmqxWIbPlxcvNIR2WNcsfrjFflz8cf8AgpDJMZ/DvwKgwOVOq3S/rFGf0LV8MaZp/jf4j603ibxTdT3U053PdXRLu+eflB7fkK8PFZ5TheNHV/h/wT2cLlFSfvVNF+J+hnxI/ba8cfEa5fQfhnG+h6e5KCY/NeTD6jIjH0yfcV514W8HX2plrjUiWLEtIWbcSSOS7dST/k1x2g2PhrwdZhpyFbHzEnLk+5/oK6eDxRq2soYdPH2S0xjjh2H9K+erY+pVlzTdz3aWEhSjaCsf/9P+gafwFajxovjGKeVJSqrJEPuOVUoD7ZBGR32ivgn9pz9pHUfGOvP+zb8BIDrGr6kZLO/nhwUXH34UY/LwP9bJ91BxndxXyp41/ab/AGivht4c1D9mf+0La7vrOY6f/asUpe4WP7vlRzEhRnIHmMNyAkcEcfpB+zB+zBo37O/hQ3moBL3xLfxA312gyFA5EEOeRGp79Xb5j2x8wfQHyHZ/CP41fsg6lZ/GHT3tvEVvDC0WqxwIU8uGUqXXnnaCoIlHQj5vlOR+lPwv+Lngv4v+Gk8UeC7oTRBvLnib5ZYJcZKSL1B9D0YcgkU/QfEkXitbuzuLR7S5tGCTwS4bAfO3kcEEA5B/lg1+Yn7Qdtbfsd/E/TPiT8IdShs21jzDc6HIf3TRIQWyB0hYnCE8xt9044Ckr7k7H7AiUEf4V438U/hZa+O0TXNGkWy120XbDcEfJKn/ADxmA+8h7Hqp5HpWN8Efjv4N+OvhFfFHhOTy5otqXlnIR51tKRnaw7g9VccMOnOQPXZ71Yo2mdgiqMsWOAAOufasXoyrXPw++NX7P9zHdX174Q0wWOqW/wC81DSjjMe7J86HHDxtgnC5zzjkEV+Z3iOS20u7PlZupiT+9YYQH/ZWv6hvjRp3grXPAJ8c3t6lldabGZLC/j5be3KxY/jSUgAp+IwRkfzs/tYan4Vi+LM8nhzyo5bq2gnu4Iukd04PmDI6ZGGIHc18hnmW06K+s09LvVefkfRZTjZ1H7Cetlo/8z5d1bQk1m1ng1OMTRXCGOVX+7tbg5+tQf8ABPL9o/xV/wAE9vi/r3gDxhYahrPw18WSrMJbGMzvZXcYwkyxjn5k/dyr1OFI6VqBpJ8LdPiM9gOv0FWFTzJBFbJtzgADliT0H515uAzKphpNxV0z0sVhIVo8sj9rviP/AMFJPhxpuhJ/wqm2n1rUbhcqbmJreKLP95T8zEeg/GvzP8Y/Eb4vftA+Ije+Kb2XUWU/JCDstoAewH3Rj8TXmOi+HrJYTd63MII1OPLB+d8diev4DtXtWjeIrPTNNENtD9mjx8kY+V2H0/hH60YzNK2IdpPTsthYXL6VHWK17s2/C3gHQvDCLqPiKRZ7hei/wKfYdz7mu9/4TGa5lEGjREA8Z7/5+lec2Ntf65KLm+IjhXkZ4UCunt9X0zTONN56gu39K4k2dtlueh6Xpdxcz/a9UfMi84J4X+grv7fxNaaehi0oCWUDDSH7o+leNW99f30fls3l2+eT0z61oDVFgiEOngtgY3Y/kP61pEln/9T74+Ff/BPn4e6R8MnsPieWvfFF+haS+gkbbZM3RIQeHA/jLjMhznAwBufCz42+Mf2bPEsHwM/aNlMmiONmj6+cmIRg4Cuxz+7GQMn5oicNlcGvWf2ffiB418d/Dfwn4tvR9st9StTb3IVVUxSw8NK7E5O4qRgeo46msH9tXx78JfAfwS1CL4oQx30l8ki6XZ5Ama7VTtkQ9UWPIMjdNvynO4A/NHvnqP7Rn7Q/gX9nLwY3iXVGiudR1BS2n2aOA102PvkjpEowWfpjgcmviz9nH9njxT8afFEn7SX7TkJvJL8+bp+mXSfIUP3JJIyPljUf6qI9vmPOK+DfgboFl4V+LfhHxt+1DpWoW/he7iL6bJfRt9mLpgwOwbOYIyd23sSrFdvX+iZ7n7bY+fpkqHzU3RSj50IYZVuDhh34PIqG7bBufnr8Zv2fvF3wa8Vf8L8/ZgQ20sAJ1DR4lLRPF1fbGPvRHGWjHKn5kxjFe8fDD9qX4Q/FL4cXni/V7uHS2sIv+JpYXLgyQE/3RwZEc8Rso+bpwcive/E/irQPBOgXfizxTeJYafp8ZmnnkOFRR+pJPAA5J4GTX8uP7Uf7R3hHWPilqfjDwNpo02HVZ8wWqL84jAAaVlHAeUguVHAY/Unnq1oU6bnVdkuv6GtOlKclCmtWfQn7X37bl7qEqaB4HRbaCyXytOssgiBTgedL281hzg9Onrn8xl1eNpJNQ1C4M00zGSSV23M7HqST1Oa5K7Np44uf7Psmkubp3M0hCjcG9ZGzjn1r3fwR8LrDRokvNaIlmABw3IU/l1/yBXwWZY+eJnzS0itl/XU+tweEjh4WW73ZmaHpev8AiNhJCjW9vjh2+8w9hXqVnpukeGoFe4cqVIJdjyT2/X86W+8U2ljG1rpCiRxxu6jNeO+IrbX9amEzuwERy3rz9eB/M15jdztSPQhr7ahrEt/b/Ky8bXHIz0bn1HPP4CvW/CsEUKNqOqybQ3zZb77n2z/OvnvS2khv4r5wZLiLAUADbx6jvjtmvWNGinvbn7TqjlyRnGcjNNR0DmPTZtYuNSxb2KlY+w7fX3rd063toHDTAzSj+H/H0+lYulwz3Moislx2yOD+n9KzvF3xX8C/DCIwXbi+1NvuWsJzhj/fPRRxVRRV7ntNlp91eRm61F1hgUZOTtVawtT+JGiaQDYeF4hf3J+Uyn/VqT6dya+GNb+M3j74i339n3rx2ttG2PItwwT2yxALH6ce9e3eGLXyLFcofN24wf6nsPYU9gP/1fof9kb9uf4LeBf2QYLjxVctDrujNcIdJAImuJHkYoIiRjaejFsbMHI6Zf8As+/BTxt+1l4/X9qT9pSPdpW4NoukOCIpI0OYyUPS3Q8qDzK3zNkdfkv/AIJ4/s0ah+0xZWv7Q3xmjik8PCZvstqgwuoSQnYC3cwJtwSeZmBz8uc/vtp3iPQrm+k0LT50M9oMNCo27QuFIXgAheAdudp4ODXzeyPfbIviJ4C8I/FLwnceCPHFmLvTrkdOjxOPuyRt/A69iPocgkV+dehfEbxr+wf4ot/hz8W5ZtZ+Hd+7DS9VRCz2h67CvoM/PFnK/eTK5FfpHrviHR/DejXXiDxBdRWNhYxNPcXEzBI4o0GWZieAAK/mw/bP/bF1n9pvxTH4a8DxvD4X0mZjYRSAo88pBQ3Uw6jKkiOP+FSc/MxxhUqQpwc6jtFFU6cqk1GCu2W/26f269Y+Nutnwv4KElv4cs5M2ds/ytcOOPtE2O/9xP4R7kmvz00L4e614kvTqniKR087qf429h/dH616Zofg/S/D+dW16Xz7k/MXbr9AO3+ea2W1q8v/AN1pqeTE3AYj5iPb/P4V8DmWZzxU9NILZfq/M+uwWCjh495dWa2jWPh7wZaC10+FFfsqD5ifUnrn3/WpbibUtVI+0Psj/uA449z/AErPjtrTTI/tepybc8kE5Ymse78UXV0fL0hPIjHG89fw9K8u3c7W7Grd38WjOEt0Blx95v4c+i/41iW5ur+VmvHIG7IHTmqscK2ymaY5dhnLck/Qf41sWlqyqZp3EWevrRZCu2bdgoUiCIYOeT3zXp2hrp+nRfb9VmWCFerP1PsB3NeNz+K9O0eApYr58q/xN0B9f/1VzZ1q/wBQv/td07Mw6lvT2HRR+pockhpHp3jv4q6pdwnR/CObKBxgyf8ALZx7dxXz5p+i6tPqryW8WXYHLSgso7/Oe59s16Np9rNdXTG0Uh24Z++Pr2H5n6V6LZ22k+HoFudVcBs/LHjv7L1J9z+lRzGyiM8IeCI7fZfXKguFUGRjkcDnaD/WvQrrxNYaQo0rSYzLMR25wfUn/P0rift2ueJG2RB7S3PRV/1jD3PRRj/9ddh4b0C1iJKAY/MZPv8AxH9KhS1Lt3P/1v0M/YPkRP2ZNBhiARY3uVCqMAAStwAOwr6budO8L6Fe3PjW/dbUQRySzTSSlII12jzJGBIRTtQbnwOBya+Lf+CevizRdf8A2VNH1vS7lJbQT3hMmcBQshJ3Z+7t75xivy7/AG6/27G+OWpy/CP4WXTL4OgfE9wmQ2qyIeG7H7MrfcU48w4Y8bRXy9ScYRc5uyW59BGEpT5YrUs/tq/tm6r+0x4gPw3+G9xJa+CdPmDM/KNqMiHiVxwfKB5ijPX77c4C/GVlc2eip9h0SLz5z94noD/tH/J9hXJaTZXUkGxT5MA5YZxnPdm9/QV1j3WmeHoFkuPk4+Vf4mHsP4R7n8q+DzTMpYufLHSC2/zZ9XgcFHDxu/iZaj0i6uZDqGtThtvJzwoqrdeJY7RzbaYuSRw7Dt7f5xXE6h4kvNamG/5YgcqmcLj1Pc/X8qtLAwQGPDyPzk9B/n0ryuXsdzkW5LyS4k8+/kLsx4X/AB/wFdFYxXVwyxW6gMe55Ix6Cm6Z4et7JTqevzCJj1Zu/wBBUepeNba3t2svDcflL/FPJyT9BSsuormlcPYaJGHuGM0zcjJrk7vUbvUZwJD8vO1Rwo/Dv+Ncvda3AP3s7FpHOSzHLE+w7Utm+q6uTFZDyo8/Mx6/WomWkb+baDDTPlj0ReSPqRnFdBpukXeptv2iK3Xl3Y4RQPU1mWkFhpOQF+0XAwCO+TnAJ6L9OvtViJNV8RShdTcC2j5ESfLEvufU/Xn0rNstRPQI9QtwgsfCyiYr/wAvLjEQP+yv8X1P610mj+H0WcXWpO1xeMOWbl8fyQU3RbbMSiyG1EH+sbjgf3R2+tdbamytrc3KnbErANM3OWPOAP4iR0qGzdGotg2xbNBktjKJk/p1P48fSuziOn6DBtuj5twekYP3Sf7xH8q8/ttbvriUw6eGgSTv/wAtZB65/hH0P49qll1PS9FJF0RLP2jU5xn1qLiP/9k=" - return Group { - UserProfile() - .environmentObject(chatModel1) - UserProfile() - .environmentObject(chatModel2) - } - } -} 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 89e628e2b6..f55c87b1b8 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1007,6 +1007,10 @@ Автоматично приемане на ŠøŠ·Š¾Š±Ń€Š°Š¶ŠµŠ½ŠøŃ No comment provided by engineer. + + Auto-accept settings + alert title + Back ŠŠ°Š·Š°Š“ @@ -1171,7 +1175,7 @@ Cancel ŠžŃ‚ŠŗŠ°Š· - No comment provided by engineer. + alert button Cancel migration @@ -1314,6 +1318,10 @@ Чат настройки No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2789,6 +2797,10 @@ This is your own one-time link! Š“Ń€ŠµŃˆŠŗŠ° при зарежГане на %@ ŃŃŠŃ€Š²ŃŠŃ€Šø No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Š“Ń€ŠµŃˆŠŗŠ° при Š¾Ń‚Š²Š°Ń€ŃŠ½Šµ на чата @@ -3210,11 +3222,6 @@ Error: %2$@ Пълно име (Š½ŠµŠ·Š°Š“ŃŠŠ»Š¶ŠøŃ‚ŠµŠ»Š½Š¾) No comment provided by engineer. - - Full name: - Пълно име: - No comment provided by engineer. - Fully decentralized – visible only to members. ŠŠ°ŠæŃŠŠ»Š½Š¾ Гецентрализирана – виГима е само за членовете. @@ -4907,16 +4914,6 @@ Error: %@ ŠŸŃ€Š¾Ń„ŠøŠ»Š½Šø ŠøŠ·Š¾Š±Ń€Š°Š¶ŠµŠ½ŠøŃ No comment provided by engineer. - - Profile name - Име на профила - No comment provided by engineer. - - - Profile name: - Име на профила: - No comment provided by engineer. - Profile password ŠŸŃ€Š¾Ń„ŠøŠ»Š½Š° парола @@ -5224,6 +5221,10 @@ Enable in *Network & servers* settings. ŠŸŃ€ŠµŠ¼Š°Ń…Š²Š°Š½Šµ No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5409,12 +5410,13 @@ Enable in *Network & servers* settings. Save Запази - chat item action + alert button + chat item action Save (and notify contacts) Запази (Šø увеГоми контактите) - No comment provided by engineer. + alert button Save and notify contact @@ -5440,11 +5442,6 @@ Enable in *Network & servers* settings. Запази архив No comment provided by engineer. - - Save auto-accept settings - Запази настройките за автоматично приемане - No comment provided by engineer. - Save group profile Запази профила на Š³Ń€ŃƒŠæŠ°Ń‚а @@ -5480,16 +5477,15 @@ Enable in *Network & servers* settings. Запази ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ? No comment provided by engineer. - - Save settings? - Запази настройките? - No comment provided by engineer. - Save welcome message? Запази ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŠµŃ‚Š¾ при посрещане? No comment provided by engineer. + + Save your profile? + alert title + Saved Запазено @@ -5905,6 +5901,10 @@ Enable in *Network & servers* settings. ŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠø No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images ŠŸŃ€Š¾Š¼ŠµŠ½ŠµŃ‚Šµ формата на профилните ŠøŠ·Š¾Š±Ń€Š°Š¶ŠµŠ½ŠøŃ @@ -6101,6 +6101,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6459,6 +6463,10 @@ It can happen because of some bug or when the connection is compromised.Š¢ŠµŠŗŃŃ‚ŃŠŃ‚, който поставихте, не е SimpleX линк за Š²Ń€ŃŠŠ·ŠŗŠ°. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7511,6 +7519,10 @@ Repeat connection request? Š’Š°ŃˆŠ°Ń‚Š° чат база Ганни не е криптирана - заГайте парола, за Га я криптирате. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Š’Š°ŃˆŠøŃ‚Šµ чат профили @@ -7565,13 +7577,15 @@ Repeat connection request? Š’Š°ŃˆŠøŃŃ‚ профил **%@** ще бъГе споГелен. 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 ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ не могат Га Š²ŠøŠ“ŃŃ‚ Š²Š°ŃˆŠøŃ профил. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Š’Š°ŃˆŠøŃŃ‚ профил се ŃŃŠŃ…Ń€Š°Š½ŃŠ²Š° на Š²Š°ŃˆŠµŃ‚о ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Š¾ Šø се ŃŠæŠ¾Š“ŠµŠ»Ń само с Š²Š°ŃˆŠøŃ‚Šµ контакти. SimpleX ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ не могат Га Š²ŠøŠ“ŃŃ‚ Š²Š°ŃˆŠøŃ профил. No comment provided by engineer. + + 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. Š’Š°ŃˆŠøŃŃ‚ профил, контакти Šø Гоставени ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŃ се ŃŃŠŃ…Ń€Š°Š½ŃŠ²Š°Ń‚ на Š²Š°ŃˆŠµŃ‚о ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Š¾. 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 65bc83d096..3b29a1e51f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -977,6 +977,10 @@ Automaticky přijĆ­mat obrĆ”zky No comment provided by engineer. + + Auto-accept settings + alert title + Back Zpět @@ -1131,7 +1135,7 @@ Cancel ZruÅ”it - No comment provided by engineer. + alert button Cancel migration @@ -1270,6 +1274,10 @@ Předvolby chatu No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2694,6 +2702,10 @@ This is your own one-time link! Chyba načƭtĆ”nĆ­ %@ serverÅÆ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3099,11 +3111,6 @@ Error: %2$@ CelĆ© jmĆ©no (volitelně) No comment provided by engineer. - - Full name: - CelĆ© jmĆ©no: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4729,14 +4736,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password Heslo profilu @@ -5038,6 +5037,10 @@ Enable in *Network & servers* settings. Odstranit No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5216,12 +5219,13 @@ Enable in *Network & servers* settings. Save Uložit - chat item action + alert button + chat item action Save (and notify contacts) Uložit (a informovat kontakty) - No comment provided by engineer. + alert button Save and notify contact @@ -5247,11 +5251,6 @@ Enable in *Network & servers* settings. Uložit archiv No comment provided by engineer. - - Save auto-accept settings - Uložit nastavenĆ­ automatickĆ©ho přijĆ­mĆ”nĆ­ - No comment provided by engineer. - Save group profile UloženĆ­ profilu skupiny @@ -5287,16 +5286,15 @@ Enable in *Network & servers* settings. Uložit servery? No comment provided by engineer. - - Save settings? - Uložit nastavenĆ­? - No comment provided by engineer. - Save welcome message? Uložit uvĆ­tacĆ­ zprĆ”vu? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5703,6 +5701,10 @@ Enable in *Network & servers* settings. NastavenĆ­ No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5894,6 +5896,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6243,6 +6249,10 @@ Může se to stĆ”t kvÅÆli nějakĆ© chybě, nebo pokud je spojenĆ­ kompromitovĆ”n The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7240,6 +7250,10 @@ Repeat connection request? VaÅ”e chat databĆ”ze nenĆ­ Å”ifrovĆ”na – nastavte přístupovou frĆ”zi pro jejĆ­ Å”ifrovĆ”nĆ­. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles VaÅ”e chat profily @@ -7293,13 +7307,15 @@ Repeat connection request? VÔŔ profil **%@** bude sdĆ­len. 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. + + 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. No comment provided by engineer. + + 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Ć­. 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 357b1c3c47..cf2f85f2c0 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1024,6 +1024,10 @@ Bilder automatisch akzeptieren No comment provided by engineer. + + Auto-accept settings + alert title + Back Zurück @@ -1197,7 +1201,7 @@ Cancel Abbrechen - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Chat-PrƤferenzen No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Chat-Design @@ -2870,6 +2878,10 @@ Das ist Ihr eigener Einmal-Link! Fehler beim Laden von %@ Servern No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Fehler beim Ɩffnen des Chats @@ -3309,11 +3321,6 @@ Fehler: %2$@ VollstƤndiger Name (optional) No comment provided by engineer. - - Full name: - VollstƤndiger Name: - No comment provided by engineer. - Fully decentralized – visible only to members. VollstƤndig dezentralisiert – nur für Mitglieder sichtbar. @@ -5045,16 +5052,6 @@ Fehler: %@ Profil-Bilder No comment provided by engineer. - - Profile name - Profilname - No comment provided by engineer. - - - Profile name: - Profilname: - No comment provided by engineer. - Profile password Passwort für Profil @@ -5378,6 +5375,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Entfernen No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Bild entfernen @@ -5571,12 +5572,13 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save Speichern - chat item action + alert button + chat item action Save (and notify contacts) Speichern (und Kontakte benachrichtigen) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Archiv speichern No comment provided by engineer. - - Save auto-accept settings - Einstellungen von "Automatisch akzeptieren" speichern - No comment provided by engineer. - Save group profile Gruppenprofil speichern @@ -5643,16 +5640,15 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Alle Server speichern? No comment provided by engineer. - - Save settings? - Einstellungen speichern? - No comment provided by engineer. - Save welcome message? Begrüßungsmeldung speichern? No comment provided by engineer. + + Save your profile? + alert title + Saved Abgespeichert @@ -6092,6 +6088,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Einstellungen No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Form der Profil-Bilder @@ -6296,6 +6296,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Weich blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Einzelne Datei(en) wurde(n) nicht exportiert: @@ -6667,6 +6671,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der von Ihnen eingefügte Text ist kein SimpleX-Link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Design @@ -7750,6 +7758,10 @@ Verbindungsanfrage wiederholen? Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Ihre Chat-Profile @@ -7804,13 +7816,15 @@ Verbindungsanfrage wiederholen? Ihr Profil **%@** wird 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. + + 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. No comment provided by engineer. + + 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. Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem GerƤt gespeichert. 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 006996c8d9..6eb935222f 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1025,6 +1025,11 @@ Auto-accept images No comment provided by engineer. + + Auto-accept settings + Auto-accept settings + alert title + Back Back @@ -1198,7 +1203,7 @@ Cancel Cancel - No comment provided by engineer. + alert button Cancel migration @@ -1346,6 +1351,11 @@ Chat preferences No comment provided by engineer. + + Chat preferences were changed. + Chat preferences were changed. + alert message + Chat theme Chat theme @@ -2874,6 +2884,11 @@ This is your own one-time link! Error loading %@ servers No comment provided by engineer. + + Error migrating settings + Error migrating settings + No comment provided by engineer. + Error opening chat Error opening chat @@ -3314,11 +3329,6 @@ Error: %2$@ Full name (optional) No comment provided by engineer. - - Full name: - Full name: - No comment provided by engineer. - Fully decentralized – visible only to members. Fully decentralized – visible only to members. @@ -5051,16 +5061,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - Profile name - No comment provided by engineer. - - - Profile name: - Profile name: - No comment provided by engineer. - Profile password Profile password @@ -5384,6 +5384,11 @@ Enable in *Network & servers* settings. Remove No comment provided by engineer. + + Remove archive? + Remove archive? + No comment provided by engineer. + Remove image Remove image @@ -5577,12 +5582,13 @@ Enable in *Network & servers* settings. Save Save - chat item action + alert button + chat item action Save (and notify contacts) Save (and notify contacts) - No comment provided by engineer. + alert button Save and notify contact @@ -5609,11 +5615,6 @@ Enable in *Network & servers* settings. Save archive No comment provided by engineer. - - Save auto-accept settings - Save auto-accept settings - No comment provided by engineer. - Save group profile Save group profile @@ -5649,16 +5650,16 @@ Enable in *Network & servers* settings. Save servers? No comment provided by engineer. - - Save settings? - Save settings? - No comment provided by engineer. - Save welcome message? Save welcome message? No comment provided by engineer. + + Save your profile? + Save your profile? + alert title + Saved Saved @@ -6099,6 +6100,11 @@ Enable in *Network & servers* settings. Settings No comment provided by engineer. + + Settings were changed. + Settings were changed. + alert message + Shape profile images Shape profile images @@ -6304,6 +6310,11 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Some file(s) were not exported: @@ -6676,6 +6687,11 @@ It can happen because of some bug or when the connection is compromised.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Themes @@ -7759,6 +7775,11 @@ Repeat connection request? Your chat database is not encrypted - set passphrase to encrypt it. No comment provided by engineer. + + Your chat preferences + Your chat preferences + alert title + Your chat profiles Your chat profiles @@ -7814,13 +7835,16 @@ Repeat connection request? Your profile **%@** will be shared. 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. + + 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. No comment provided by engineer. + + Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + 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. 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 529b185e25..a10a1594de 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1024,6 +1024,10 @@ Aceptar imĆ”genes automĆ”ticamente No comment provided by engineer. + + Auto-accept settings + alert title + Back Volver @@ -1197,7 +1201,7 @@ Cancel Cancelar - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Preferencias de Chat No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Tema de chat @@ -2870,6 +2878,10 @@ This is your own one-time link! Error al cargar servidores %@ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Error al abrir chat @@ -3309,11 +3321,6 @@ Error: %2$@ Nombre completo (opcional) No comment provided by engineer. - - Full name: - Nombre completo: - No comment provided by engineer. - Fully decentralized – visible only to members. Completamente descentralizado y sólo visible para los miembros. @@ -5045,16 +5052,6 @@ Error: %@ Forma de los perfiles No comment provided by engineer. - - Profile name - Nombre del perfil - No comment provided by engineer. - - - Profile name: - Nombre del perfil: - No comment provided by engineer. - Profile password ContraseƱa del perfil @@ -5378,6 +5375,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Eliminar No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Eliminar imagen @@ -5571,12 +5572,13 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Save Guardar - chat item action + alert button + chat item action Save (and notify contacts) Guardar (y notificar contactos) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Guardar archivo No comment provided by engineer. - - Save auto-accept settings - Guardar configuración de auto aceptar - No comment provided by engineer. - Save group profile Guardar perfil de grupo @@ -5643,16 +5640,15 @@ ActĆ­valo en ajustes de *Servidores y Redes*. ĀæGuardar servidores? No comment provided by engineer. - - Save settings? - ĀæGuardar configuración? - No comment provided by engineer. - Save welcome message? ĀæGuardar mensaje de bienvenida? No comment provided by engineer. + + Save your profile? + alert title + Saved Guardado @@ -6092,6 +6088,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Configuración No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Dar forma a las imĆ”genes de perfil @@ -6296,6 +6296,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Suave blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Algunos archivos no han sido exportados: @@ -6667,6 +6671,10 @@ Puede ocurrir por algĆŗn bug o cuando la conexión estĆ” comprometida. El texto pegado no es un enlace SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Temas @@ -7750,6 +7758,10 @@ Repeat connection request? La base de datos no estĆ” cifrada - establece una contraseƱa para cifrarla. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Mis perfiles @@ -7804,13 +7816,15 @@ Repeat connection request? El perfil **%@** serĆ” compartido. 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. + + 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. No comment provided by engineer. + + 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. Tu perfil, contactos y mensajes se almacenan en tu dispositivo. 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 fc31e4f2df..cf2ea3c36d 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -971,6 +971,10 @@ HyvƤksy kuvat automaattisesti No comment provided by engineer. + + Auto-accept settings + alert title + Back Takaisin @@ -1124,7 +1128,7 @@ Cancel Peruuta - No comment provided by engineer. + alert button Cancel migration @@ -1263,6 +1267,10 @@ Chat-asetukset No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2685,6 +2693,10 @@ This is your own one-time link! Virhe %@-palvelimien lataamisessa No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3089,11 +3101,6 @@ Error: %2$@ Koko nimi (valinnainen) No comment provided by engineer. - - Full name: - Koko nimi: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4717,14 +4724,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password Profiilin salasana @@ -5026,6 +5025,10 @@ Enable in *Network & servers* settings. Poista No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5204,12 +5207,13 @@ Enable in *Network & servers* settings. Save Tallenna - chat item action + alert button + chat item action Save (and notify contacts) Tallenna (ja ilmoita kontakteille) - No comment provided by engineer. + alert button Save and notify contact @@ -5235,11 +5239,6 @@ Enable in *Network & servers* settings. Tallenna arkisto No comment provided by engineer. - - Save auto-accept settings - Tallenna automaattisen hyvƤksynnƤn asetukset - No comment provided by engineer. - Save group profile Tallenna ryhmƤprofiili @@ -5275,16 +5274,15 @@ Enable in *Network & servers* settings. Tallenna palvelimet? No comment provided by engineer. - - Save settings? - Tallenna asetukset? - No comment provided by engineer. - Save welcome message? Tallenna tervetuloviesti? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5690,6 +5688,10 @@ Enable in *Network & servers* settings. Asetukset No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5880,6 +5882,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6229,6 +6235,10 @@ TƤmƤ voi johtua jostain virheestƤ tai siitƤ, ettƤ yhteys on vaarantunut.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7225,6 +7235,10 @@ Repeat connection request? Keskustelut-tietokantasi ei ole salattu - aseta tunnuslause sen salaamiseksi. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Keskusteluprofiilisi @@ -7278,13 +7292,15 @@ Repeat connection request? Profiilisi **%@** jaetaan. 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. + + 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. No comment provided by engineer. + + 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. 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 6970039315..547d0f3674 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1024,6 +1024,10 @@ Images auto-acceptĆ©es No comment provided by engineer. + + Auto-accept settings + alert title + Back Retour @@ -1197,7 +1201,7 @@ Cancel Annuler - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ PrĆ©fĆ©rences de chat No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme ThĆØme de chat @@ -2870,6 +2878,10 @@ Il s'agit de votre propre lien unique ! Erreur lors du chargement des serveurs %@ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Erreur lors de l'ouverture du chat @@ -3309,11 +3321,6 @@ Erreur : %2$@ Nom complet (optionnel) No comment provided by engineer. - - Full name: - Nom complet : - No comment provided by engineer. - Fully decentralized – visible only to members. EntiĆØrement dĆ©centralisĆ© – visible que par ses membres. @@ -5045,16 +5052,6 @@ Erreur : %@ Images de profil No comment provided by engineer. - - Profile name - Nom du profil - No comment provided by engineer. - - - Profile name: - Nom du profil : - No comment provided by engineer. - Profile password Mot de passe de profil @@ -5378,6 +5375,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Supprimer No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Enlever l'image @@ -5571,12 +5572,13 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Save Enregistrer - chat item action + alert button + chat item action Save (and notify contacts) Enregistrer (et en informer les contacts) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Enregistrer l'archive No comment provided by engineer. - - Save auto-accept settings - Enregistrer les paramĆØtres de validation automatique - No comment provided by engineer. - Save group profile Enregistrer le profil du groupe @@ -5643,16 +5640,15 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Enregistrer les serveurs ? No comment provided by engineer. - - Save settings? - Enregistrer les paramĆØtres ? - No comment provided by engineer. - Save welcome message? Enregistrer le message d'accueil ? No comment provided by engineer. + + Save your profile? + alert title + Saved EnregistrĆ© @@ -6092,6 +6088,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. ParamĆØtres No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Images de profil modelable @@ -6296,6 +6296,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. LĆ©ger blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Certains fichiers n'ont pas Ć©tĆ© exportĆ©s : @@ -6667,6 +6671,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le texte collĆ© n'est pas un lien SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes ThĆØmes @@ -7750,6 +7758,10 @@ RĆ©pĆ©ter la demande de connexion ? Votre base de donnĆ©es de chat n'est pas chiffrĆ©e - dĆ©finisez une phrase secrĆØte. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Vos profils de chat @@ -7804,13 +7816,15 @@ RĆ©pĆ©ter la demande de connexion ? Votre profil **%@** sera partagĆ©. 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. + + 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. No comment provided by engineer. + + 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. Votre profil, vos contacts et les messages reƧus sont stockĆ©s sur votre appareil. 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 de95de3421..89bce685a0 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1024,6 +1024,10 @@ KĆ©pek automatikus elfogadĆ”sa No comment provided by engineer. + + Auto-accept settings + alert title + Back Vissza @@ -1197,7 +1201,7 @@ Cancel MĆ©gse - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ CsevegĆ©si beĆ”llĆ­tĆ”sok No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme CsevegĆ©s tĆ©mĆ”ja @@ -2870,6 +2878,10 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Hiba a %@ kiszolgĆ”lók betƶltĆ©sekor No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Hiba a csevegĆ©s megnyitĆ”sakor @@ -3309,11 +3321,6 @@ Hiba: %2$@ Teljes nĆ©v (opcionĆ”lis) No comment provided by engineer. - - Full name: - Teljes nĆ©v: - No comment provided by engineer. - Fully decentralized – visible only to members. Teljesen decentralizĆ”lt - kizĆ”rólag tagok szĆ”mĆ”ra lĆ”tható. @@ -5045,16 +5052,6 @@ Hiba: %@ ProfilkĆ©pek No comment provided by engineer. - - Profile name - ProfilnĆ©v - No comment provided by engineer. - - - Profile name: - Profil neve: - No comment provided by engineer. - Profile password Profiljelszó @@ -5378,6 +5375,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. EltĆ”volĆ­tĆ”s No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image KĆ©p eltĆ”volĆ­tĆ”sa @@ -5571,12 +5572,13 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Save MentĆ©s - chat item action + alert button + chat item action Save (and notify contacts) MentĆ©s (Ć©s az ismerősƶk Ć©rtesĆ­tĆ©se) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. ArchĆ­vum mentĆ©se No comment provided by engineer. - - Save auto-accept settings - Automatikus elfogadĆ”si beĆ”llĆ­tĆ”sok mentĆ©se - No comment provided by engineer. - Save group profile Csoportprofil elmentĆ©se @@ -5643,16 +5640,15 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. KiszolgĆ”lók mentĆ©se? No comment provided by engineer. - - Save settings? - BeĆ”llĆ­tĆ”sok mentĆ©se? - No comment provided by engineer. - Save welcome message? Üdvƶzlőszƶveg mentĆ©se? No comment provided by engineer. + + Save your profile? + alert title + Saved Mentett @@ -6092,6 +6088,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. BeĆ”llĆ­tĆ”sok No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images ProfilkĆ©p alakzat @@ -6296,6 +6296,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Enyhe blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: NĆ©hĆ”ny fĆ”jl nem került exportĆ”lĆ”sra: @@ -6667,6 +6671,10 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. A beillesztett szƶveg nem egy SimpleX hivatkozĆ”s. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes TĆ©mĆ”k @@ -7750,6 +7758,10 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? A csevegĆ©si adatbĆ”zis nincs titkosĆ­tva – adjon meg egy jelmondatot a titkosĆ­tĆ”shoz. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles CsevegĆ©si profilok @@ -7804,13 +7816,15 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? A(z) **%@** nevű profilja megosztĆ”sra fog kerülni. No comment provided by engineer. - - Your profile is stored on your device and shared only with your contacts. -SimpleX servers cannot see your profile. - Profilja az eszkƶzƶn van tĆ”rolva, Ć©s csak az ismerősƶkkel kerül megosztĆ”sra. -A SimpleX kiszolgĆ”lók nem lĆ”tjhatjĆ”k profiljĆ”t. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Profilja az eszkƶzƶn van tĆ”rolva, Ć©s csak az ismerősƶkkel kerül megosztĆ”sra. A SimpleX kiszolgĆ”lók nem lĆ”tjhatjĆ”k profiljĆ”t. No comment provided by engineer. + + 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. Profilja, ismerősei Ć©s az elküldƶtt üzenetei az eszkƶzƶn kerülnek tĆ”rolĆ”sra. 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 700d181aab..a5e013fec2 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1024,6 +1024,10 @@ Auto-accetta immagini No comment provided by engineer. + + Auto-accept settings + alert title + Back Indietro @@ -1197,7 +1201,7 @@ Cancel Annulla - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Preferenze della chat No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Tema della chat @@ -2870,6 +2878,10 @@ Questo ĆØ il tuo link una tantum! Errore nel caricamento dei server %@ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Errore di apertura della chat @@ -3309,11 +3321,6 @@ Errore: %2$@ Nome completo (facoltativo) No comment provided by engineer. - - Full name: - Nome completo: - No comment provided by engineer. - Fully decentralized – visible only to members. Completamente decentralizzato: visibile solo ai membri. @@ -5045,16 +5052,6 @@ Errore: %@ Immagini del profilo No comment provided by engineer. - - Profile name - Nome del profilo - No comment provided by engineer. - - - Profile name: - Nome del profilo: - No comment provided by engineer. - Profile password Password del profilo @@ -5378,6 +5375,10 @@ Attivalo nelle impostazioni *Rete e server*. Rimuovi No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Rimuovi immagine @@ -5571,12 +5572,13 @@ Attivalo nelle impostazioni *Rete e server*. Save Salva - chat item action + alert button + chat item action Save (and notify contacts) Salva (e avvisa i contatti) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Attivalo nelle impostazioni *Rete e server*. Salva archivio No comment provided by engineer. - - Save auto-accept settings - Salva le impostazioni di accettazione automatica - No comment provided by engineer. - Save group profile Salva il profilo del gruppo @@ -5643,16 +5640,15 @@ Attivalo nelle impostazioni *Rete e server*. Salvare i server? No comment provided by engineer. - - Save settings? - Salvare le impostazioni? - No comment provided by engineer. - Save welcome message? Salvare il messaggio di benvenuto? No comment provided by engineer. + + Save your profile? + alert title + Saved Salvato @@ -6092,6 +6088,10 @@ Attivalo nelle impostazioni *Rete e server*. Impostazioni No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Forma delle immagini del profilo @@ -6296,6 +6296,10 @@ Attivalo nelle impostazioni *Rete e server*. Leggera blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Alcuni file non sono stati esportati: @@ -6667,6 +6671,10 @@ Può accadere a causa di qualche bug o quando la connessione ĆØ compromessa.Il testo che hai incollato non ĆØ un link SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Temi @@ -7750,6 +7758,10 @@ Ripetere la richiesta di connessione? Il tuo database della chat non ĆØ crittografato: imposta la password per crittografarlo. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles I tuoi profili di chat @@ -7804,13 +7816,15 @@ Ripetere la richiesta di connessione? VerrĆ  condiviso il tuo profilo **%@**. 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. + + 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. No comment provided by engineer. + + 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. Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo. 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 0b9b431c8b..3edfb59c57 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -994,6 +994,10 @@ ē”»åƒć‚’č‡Ŗå‹•ēš„ć«å—äæ” No comment provided by engineer. + + Auto-accept settings + alert title + Back ęˆ»ć‚‹ @@ -1148,7 +1152,7 @@ Cancel äø­ę­¢ - No comment provided by engineer. + alert button Cancel migration @@ -1287,6 +1291,10 @@ チャット設定 No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2710,6 +2718,10 @@ This is your own one-time link! %@ ć‚µćƒ¼ćƒćƒ¼ć®ćƒ­ćƒ¼ćƒ‰äø­ć«ć‚Øćƒ©ćƒ¼ćŒē™ŗē”Ÿ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3114,11 +3126,6 @@ Error: %2$@ ćƒ•ćƒ«ćƒćƒ¼ćƒ  (ä»»ę„): No comment provided by engineer. - - Full name: - ćƒ•ćƒ«ćƒćƒ¼ćƒ ļ¼š - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4743,14 +4750,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ć®ćƒ‘ć‚¹ćƒÆćƒ¼ćƒ‰ @@ -5051,6 +5050,10 @@ Enable in *Network & servers* settings. 削除 No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5229,12 +5232,13 @@ Enable in *Network & servers* settings. Save äæå­˜ - chat item action + alert button + chat item action Save (and notify contacts) äæå­˜ļ¼ˆé€£ēµ”å…ˆć«é€šēŸ„ļ¼‰ - No comment provided by engineer. + alert button Save and notify contact @@ -5260,11 +5264,6 @@ Enable in *Network & servers* settings. ć‚¢ćƒ¼ć‚«ć‚¤ćƒ–ć‚’äæå­˜ No comment provided by engineer. - - Save auto-accept settings - č‡Ŗå‹•å—ć‘å…„ć‚ŒčØ­å®šć‚’äæå­˜ć™ć‚‹ - No comment provided by engineer. - Save group profile ć‚°ćƒ«ćƒ¼ćƒ—ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ć®äæå­˜ @@ -5300,16 +5299,15 @@ Enable in *Network & servers* settings. ć‚µćƒ¼ćƒć‚’äæå­˜ć—ć¾ć™ć‹ļ¼Ÿ No comment provided by engineer. - - Save settings? - čØ­å®šć‚’äæå­˜ć—ć¾ć™ć‹ļ¼Ÿ - No comment provided by engineer. - Save welcome message? ć‚¦ć‚§ćƒ«ć‚«ćƒ ćƒ”ćƒƒć‚»ćƒ¼ć‚øć‚’äæå­˜ć—ć¾ć™ć‹ļ¼Ÿ No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5708,6 +5706,10 @@ Enable in *Network & servers* settings. 設定 No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5899,6 +5901,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6248,6 +6254,10 @@ It can happen because of some bug or when the connection is compromised.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7243,6 +7253,10 @@ Repeat connection request? チャット ćƒ‡ćƒ¼ć‚æćƒ™ćƒ¼ć‚¹ćÆęš—å·åŒ–ć•ć‚Œć¦ć„ć¾ć›ć‚“ - ęš—å·åŒ–ć™ć‚‹ć«ćÆćƒ‘ć‚¹ćƒ•ćƒ¬ćƒ¼ć‚ŗć‚’čØ­å®šć—ć¦ćć ć•ć„ć€‚ No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles ć‚ćŖćŸć®ćƒćƒ£ćƒƒćƒˆćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ« @@ -7296,13 +7310,15 @@ Repeat connection request? ć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚”ć‚¤ćƒ« **%@** ćŒå…±ęœ‰ć•ć‚Œć¾ć™ć€‚ 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 ć‚µćƒ¼ćƒćƒ¼ćÆć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚”ć‚¤ćƒ«ć‚’å‚ē…§ć§ćć¾ć›ć‚“ć€‚ + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ćÆćƒ‡ćƒć‚¤ć‚¹ć«äæå­˜ć•ć‚Œć€é€£ēµ”å…ˆćØć®ćæå…±ęœ‰ć•ć‚Œć¾ć™ć€‚ SimpleX ć‚µćƒ¼ćƒćƒ¼ćÆć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚”ć‚¤ćƒ«ć‚’å‚ē…§ć§ćć¾ć›ć‚“ć€‚ No comment provided by engineer. + + 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. ć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ć€é€£ēµ”å…ˆć€é€äæ”ć—ćŸćƒ”ćƒƒć‚»ćƒ¼ć‚øćŒć”č‡Ŗåˆ†ć®ē«Æęœ«ć«äæå­˜ć•ć‚Œć¾ć™ć€‚ 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 cd52825882..61fdcc1258 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1024,6 +1024,10 @@ Afbeeldingen automatisch accepteren No comment provided by engineer. + + Auto-accept settings + alert title + Back Terug @@ -1197,7 +1201,7 @@ Cancel Annuleren - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Gesprek voorkeuren No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Chat thema @@ -2870,6 +2878,10 @@ Dit is uw eigen eenmalige link! Fout bij het laden van %@ servers No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Fout bij het openen van de chat @@ -3309,11 +3321,6 @@ Fout: %2$@ Volledige naam (optioneel) No comment provided by engineer. - - Full name: - Volledige naam: - No comment provided by engineer. - Fully decentralized – visible only to members. Volledig gedecentraliseerd – alleen zichtbaar voor leden. @@ -5045,16 +5052,6 @@ Fout: %@ Profiel afbeeldingen No comment provided by engineer. - - Profile name - Profielnaam - No comment provided by engineer. - - - Profile name: - Profielnaam: - No comment provided by engineer. - Profile password Profiel wachtwoord @@ -5378,6 +5375,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Verwijderen No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Verwijder afbeelding @@ -5571,12 +5572,13 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save Opslaan - chat item action + alert button + chat item action Save (and notify contacts) Bewaar (en informeer contacten) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Bewaar archief No comment provided by engineer. - - Save auto-accept settings - Sla instellingen voor automatisch accepteren op - No comment provided by engineer. - Save group profile Groep profiel opslaan @@ -5643,16 +5640,15 @@ Schakel dit in in *Netwerk en servers*-instellingen. Servers opslaan? No comment provided by engineer. - - Save settings? - Instellingen opslaan? - No comment provided by engineer. - Save welcome message? Welkom bericht opslaan? No comment provided by engineer. + + Save your profile? + alert title + Saved Opgeslagen @@ -6092,6 +6088,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Instellingen No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Vorm profiel afbeeldingen @@ -6296,6 +6296,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Sommige bestanden zijn niet geĆ«xporteerd: @@ -6667,6 +6671,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De tekst die u hebt geplakt is geen SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Thema's @@ -7750,6 +7758,10 @@ Verbindingsverzoek herhalen? Uw chat database is niet versleuteld, stel een wachtwoord in om deze te versleutelen. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Uw chat profielen @@ -7804,13 +7816,15 @@ Verbindingsverzoek herhalen? Uw profiel **%@** wordt gedeeld. 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. + + 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. No comment provided by engineer. + + 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. Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen. 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 a474f30768..87f19d69a3 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1024,6 +1024,10 @@ Automatyczne akceptowanie obrazów No comment provided by engineer. + + Auto-accept settings + alert title + Back Wstecz @@ -1197,7 +1201,7 @@ Cancel Anuluj - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ Preferencje czatu No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Motyw czatu @@ -2870,6 +2878,10 @@ To jest twój jednorazowy link! Błąd ładowania %@ serwerów No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Błąd otwierania czatu @@ -3309,11 +3321,6 @@ Błąd: %2$@ Pełna nazwa (opcjonalna) No comment provided by engineer. - - Full name: - Pełna nazwa: - No comment provided by engineer. - Fully decentralized – visible only to members. W pełni zdecentralizowana – widoczna tylko dla członków. @@ -5045,16 +5052,6 @@ Błąd: %@ Zdjęcia profilowe No comment provided by engineer. - - Profile name - Nazwa profilu - No comment provided by engineer. - - - Profile name: - Nazwa profilu: - No comment provided by engineer. - Profile password Hasło profilu @@ -5378,6 +5375,10 @@ Włącz w ustawianiach *Sieć i serwery* . Usuń No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Usuń obraz @@ -5571,12 +5572,13 @@ Włącz w ustawianiach *Sieć i serwery* . Save Zapisz - chat item action + alert button + chat item action Save (and notify contacts) Zapisz (i powiadom kontakty) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Włącz w ustawianiach *Sieć i serwery* . Zapisz archiwum No comment provided by engineer. - - Save auto-accept settings - Zapisz ustawienia automatycznej akceptacji - No comment provided by engineer. - Save group profile Zapisz profil grupy @@ -5643,16 +5640,15 @@ Włącz w ustawianiach *Sieć i serwery* . Zapisać serwery? No comment provided by engineer. - - Save settings? - Zapisać ustawienia? - No comment provided by engineer. - Save welcome message? Zapisać wiadomość powitalną? No comment provided by engineer. + + Save your profile? + alert title + Saved Zapisane @@ -6092,6 +6088,10 @@ Włącz w ustawianiach *Sieć i serwery* . Ustawienia No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Kształtuj obrazy profilowe @@ -6296,6 +6296,10 @@ Włącz w ustawianiach *Sieć i serwery* . Łagodny blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Niektóre plik(i) nie zostały wyeksportowane: @@ -6667,6 +6671,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Tekst, który wkleiłeś nie jest linkiem SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Motywy @@ -7750,6 +7758,10 @@ Powtórzyć prośbę połączenia? Baza danych czatu nie jest szyfrowana - ustaw hasło, aby ją zaszyfrować. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Twoje profile czatu @@ -7804,13 +7816,15 @@ 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 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. + + 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. No comment provided by engineer. + + 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. Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu. 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 957e599e85..f2e278c9a4 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1024,6 +1024,10 @@ Автоприем изображений No comment provided by engineer. + + Auto-accept settings + alert title + Back ŠŠ°Š·Š°Š“ @@ -1197,7 +1201,7 @@ Cancel ŠžŃ‚Š¼ŠµŠ½ŠøŃ‚ŃŒ - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ ŠŸŃ€ŠµŠ“ŠæŠ¾Ń‡Ń‚ŠµŠ½ŠøŃ No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Тема чата @@ -2870,6 +2878,10 @@ This is your own one-time link! ŠžŃˆŠøŠ±ŠŗŠ° Š·Š°Š³Ń€ŃƒŠ·ŠŗŠø %@ серверов No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat ŠžŃˆŠøŠ±ŠŗŠ° Š“Š¾ŃŃ‚ŃƒŠæŠ° Šŗ Ń‡Š°Ń‚Ńƒ @@ -3309,11 +3321,6 @@ Error: %2$@ Полное ŠøŠ¼Ń (не Š¾Š±ŃŠ·Š°Ń‚ŠµŠ»ŃŒŠ½Š¾) No comment provided by engineer. - - Full name: - Полное ŠøŠ¼Ń: - No comment provided by engineer. - Fully decentralized – visible only to members. Š“Ń€ŃƒŠæŠæŠ° ŠæŠ¾Š»Š½Š¾ŃŃ‚ŃŒŃŽ Гецентрализована – она виГна Ń‚Š¾Š»ŃŒŠŗŠ¾ членам. @@ -5045,16 +5052,6 @@ Error: %@ ŠšŠ°Ń€Ń‚ŠøŠ½ŠŗŠø профилей No comment provided by engineer. - - Profile name - Š˜Š¼Ń ŠæŃ€Š¾Ń„ŠøŠ»Ń - No comment provided by engineer. - - - Profile name: - Š˜Š¼Ń ŠæŃ€Š¾Ń„ŠøŠ»Ń: - No comment provided by engineer. - Profile password ŠŸŠ°Ń€Š¾Š»ŃŒ ŠæŃ€Š¾Ń„ŠøŠ»Ń @@ -5378,6 +5375,10 @@ Enable in *Network & servers* settings. Š£Š“Š°Š»ŠøŃ‚ŃŒ No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image Š£Š“Š°Š»ŠøŃ‚ŃŒ изображение @@ -5571,12 +5572,13 @@ Enable in *Network & servers* settings. Save Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ - chat item action + alert button + chat item action Save (and notify contacts) Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ (Šø ŃƒŠ²ŠµŠ“Š¾Š¼ŠøŃ‚ŃŒ контакты) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Enable in *Network & servers* settings. Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ архив No comment provided by engineer. - - Save auto-accept settings - Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ настройки автоприема - No comment provided by engineer. - Save group profile Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ Š³Ń€ŃƒŠæŠæŃ‹ @@ -5643,16 +5640,15 @@ Enable in *Network & servers* settings. Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ серверы? No comment provided by engineer. - - Save settings? - Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ настройки? - No comment provided by engineer. - Save welcome message? Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ приветственное сообщение? No comment provided by engineer. + + Save your profile? + alert title + Saved Дохранено @@ -6092,6 +6088,10 @@ Enable in *Network & servers* settings. ŠŠ°ŃŃ‚Ń€Š¾Š¹ŠŗŠø No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Форма картинок профилей @@ -6296,6 +6296,10 @@ Enable in *Network & servers* settings. Длабое blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: ŠŠµŠŗŠ¾Ń‚Š¾Ń€Ń‹Šµ файл(ы) не были ŃŠŗŃŠæŠ¾Ń€Ń‚ŠøŃ€Š¾Š²Š°Š½Ń‹: @@ -6667,6 +6671,10 @@ It can happen because of some bug or when the connection is compromised.Вставленный текст не ŃŠ²Š»ŃŠµŃ‚ŃŃ SimpleX-ссылкой. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Темы @@ -7750,6 +7758,10 @@ Repeat connection request? База Ганных ŠŠ• Š·Š°ŃˆŠøŃ„Ń€Š¾Š²Š°Š½Š°. Установите ŠæŠ°Ń€Š¾Š»ŃŒ, чтобы Š·Š°Ń‰ŠøŃ‚ŠøŃ‚ŃŒ Š’Š°ŃˆŠø Ганные. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Š’Š°ŃˆŠø профили чата @@ -7804,13 +7816,15 @@ Repeat connection request? Š‘ŃƒŠ“ŠµŃ‚ отправлен Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ **%@**. 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 серверы не Š¼Š¾Š³ŃƒŃ‚ ŠæŠ¾Š»ŃƒŃ‡ŠøŃ‚ŃŒ Š“Š¾ŃŃ‚ŃƒŠæ Šŗ Š’Š°ŃˆŠµŠ¼Ńƒ ŠæŃ€Š¾Ń„ŠøŠ»ŃŽ. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ Ń…Ń€Š°Š½ŠøŃ‚ŃŃ на Š’Š°ŃˆŠµŠ¼ ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Šµ Šø Š¾Ń‚ŠæŃ€Š°Š²Š»ŃŠµŃ‚ŃŃ Ń‚Š¾Š»ŃŒŠŗŠ¾ Š’Š°ŃˆŠøŠ¼ контактам. SimpleX серверы не Š¼Š¾Š³ŃƒŃ‚ ŠæŠ¾Š»ŃƒŃ‡ŠøŃ‚ŃŒ Š“Š¾ŃŃ‚ŃƒŠæ Šŗ Š’Š°ŃˆŠµŠ¼Ńƒ ŠæŃ€Š¾Ń„ŠøŠ»ŃŽ. No comment provided by engineer. + + 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. Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ, контакты Šø Гоставленные ŃŠ¾Š¾Š±Ń‰ŠµŠ½ŠøŃ Ń…Ń€Š°Š½ŃŃ‚ŃŃ на Š’Š°ŃˆŠµŠ¼ ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Šµ. 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 366f67d0cd..870c01af8f 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -963,6 +963,10 @@ ąø¢ąø­ąø”ąø£ąø±ąøšąø ąø²ąøžąø­ąø±ąø•ą¹‚ąø™ąø”ąø±ąø•ąø“ No comment provided by engineer. + + Auto-accept settings + alert title + Back กคับ @@ -1116,7 +1120,7 @@ Cancel ยกเคณก - No comment provided by engineer. + alert button Cancel migration @@ -1255,6 +1259,10 @@ ąø„ą¹ˆąø²ąøą¹ąø²ąø«ąø™ąø”ą¹ƒąø™ąøąø²ąø£ą¹ąøŠąø— No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2670,6 +2678,10 @@ This is your own one-time link! ą¹‚ąø«ąø„ąø”ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ %@ ąøœąø“ąø”ąøžąø„ąø²ąø” No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3074,11 +3086,6 @@ Error: %2$@ ąøŠąø·ą¹ˆąø­ą¹€ąø•ą¹‡ąø” (ą¹„ąø”ą¹ˆąøšąø±ąø‡ąø„ąø±ąøš) No comment provided by engineer. - - Full name: - ąøŠąø·ą¹ˆąø­ą¹€ąø•ą¹‡ąø”: - No comment provided by engineer. - Fully decentralized – visible only to members. No comment provided by engineer. @@ -4696,14 +4703,6 @@ Error: %@ Profile images No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - No comment provided by engineer. - Profile password ąø£ąø«ąø±ąøŖąøœą¹ˆąø²ąø™ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œ @@ -5003,6 +5002,10 @@ Enable in *Network & servers* settings. คบ No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5181,12 +5184,13 @@ Enable in *Network & servers* settings. Save ąøšąø±ąø™ąø—ąø¶ąø - chat item action + alert button + chat item action Save (and notify contacts) ąøšąø±ąø™ąø—ąø¶ąø (ą¹ąø„ąø°ą¹ąøˆą¹‰ąø‡ąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­) - No comment provided by engineer. + alert button Save and notify contact @@ -5212,11 +5216,6 @@ Enable in *Network & servers* settings. ąøšąø±ąø™ąø—ąø¶ąøą¹„ąøŸąø„ą¹Œą¹€ąøą¹‡ąøšąø–ąø²ąø§ąø£ No comment provided by engineer. - - Save auto-accept settings - ąøšąø±ąø™ąø—ąø¶ąøąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø²ąøąø²ąø£ąø¢ąø­ąø”ąø£ąø±ąøšąø­ąø±ąø•ą¹‚ąø™ąø”ąø±ąø•ąø“ - No comment provided by engineer. - Save group profile ąøšąø±ąø™ąø—ąø¶ąøą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąøąø„ąøøą¹ˆąø” @@ -5252,16 +5251,15 @@ Enable in *Network & servers* settings. ąøšąø±ąø™ąø—ąø¶ąøą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ? No comment provided by engineer. - - Save settings? - ąøšąø±ąø™ąø—ąø¶ąøąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø²? - No comment provided by engineer. - Save welcome message? ąøšąø±ąø™ąø—ąø¶ąøąø‚ą¹‰ąø­ąø„ąø§ąø²ąø”ąø•ą¹‰ąø­ąø™ąø£ąø±ąøš? No comment provided by engineer. + + Save your profile? + alert title + Saved No comment provided by engineer. @@ -5665,6 +5663,10 @@ Enable in *Network & servers* settings. ąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø² No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images No comment provided by engineer. @@ -5853,6 +5855,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6203,6 +6209,10 @@ It can happen because of some bug or when the connection is compromised.The text you pasted is not a SimpleX link. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7194,6 +7204,10 @@ Repeat connection request? ąøąø²ąø™ąø‚ą¹‰ąø­ąø”ąø¹ąø„ąøąø²ąø£ą¹ąøŠąø—ąø‚ąø­ąø‡ąø„ąøøąø“ą¹„ąø”ą¹ˆą¹„ąø”ą¹‰ąø–ąø¹ąø encrypt - ąø•ąø±ą¹‰ąø‡ąø£ąø«ąø±ąøŖąøœą¹ˆąø²ąø™ą¹€ąøžąø·ą¹ˆąø­ encrypt No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œą¹ąøŠąø—ąø‚ąø­ąø‡ąø„ąøøąø“ @@ -7246,13 +7260,15 @@ Repeat connection request? Your profile **%@** will be shared. 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 ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąø”ąø¹ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹„ąø”ą¹‰ + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø–ąø¹ąøąøˆąø±ąø”ą¹€ąøą¹‡ąøšą¹„ąø§ą¹‰ą¹ƒąø™ąø­ąøøąø›ąøąø£ąø“ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹ąø„ąø°ą¹ąøŠąø£ą¹Œąøąø±ąøšąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ąø‚ąø­ąø‡ąø„ąøøąø“ą¹€ąø—ą¹ˆąø²ąø™ąø±ą¹‰ąø™ ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ SimpleX ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąø”ąø¹ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹„ąø”ą¹‰ No comment provided by engineer. + + 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. ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œ ąø£ąø²ąø¢ąøŠąø·ą¹ˆąø­ąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ ą¹ąø„ąø°ąø‚ą¹‰ąø­ąø„ąø§ąø²ąø”ąø—ąøµą¹ˆąøŖą¹ˆąø‡ąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø–ąø¹ąøąøˆąø±ąø”ą¹€ąøą¹‡ąøšą¹„ąø§ą¹‰ą¹ƒąø™ąø­ąøøąø›ąøąø£ąø“ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ 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 13fa3c8a84..3e1199666f 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1009,6 +1009,10 @@ Fotoğrafları otomatik kabul et No comment provided by engineer. + + Auto-accept settings + alert title + Back Geri @@ -1173,7 +1177,7 @@ Cancel İptal et - No comment provided by engineer. + alert button Cancel migration @@ -1317,6 +1321,10 @@ Sohbet tercihleri No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2797,6 +2805,10 @@ Bu senin kendi tek kullanımlık bağlantın! %@ sunucuları yüklenirken hata oluştu No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Sohbeti aƧarken sorun oluştu @@ -3223,11 +3235,6 @@ Hata: %2$@ Bütün isim (opsiyonel) No comment provided by engineer. - - Full name: - Bütün isim: - No comment provided by engineer. - Fully decentralized – visible only to members. Tamamiyle merkezi olmayan - sadece kişilere gƶrünür. @@ -4926,16 +4933,6 @@ Hata: %@ Profil resimleri No comment provided by engineer. - - Profile name - Profil ismi - No comment provided by engineer. - - - Profile name: - Profil ismi: - No comment provided by engineer. - Profile password Profil parolası @@ -5246,6 +5243,10 @@ Enable in *Network & servers* settings. Sil No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5432,12 +5433,13 @@ Enable in *Network & servers* settings. Save Kaydet - chat item action + alert button + chat item action Save (and notify contacts) Kaydet (ve kişilere bildir) - No comment provided by engineer. + alert button Save and notify contact @@ -5463,11 +5465,6 @@ Enable in *Network & servers* settings. Arşivi kaydet No comment provided by engineer. - - Save auto-accept settings - Otomatik kabul et ayarlarını kaydet - No comment provided by engineer. - Save group profile Grup profilini kaydet @@ -5503,16 +5500,15 @@ Enable in *Network & servers* settings. Sunucular kaydedilsin mi? No comment provided by engineer. - - Save settings? - Ayarlar kaydedilsin mi? - No comment provided by engineer. - Save welcome message? Hoşgeldin mesajı kaydedilsin mi? No comment provided by engineer. + + Save your profile? + alert title + Saved Kaydedildi @@ -5932,6 +5928,10 @@ Enable in *Network & servers* settings. Ayarlar No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Profil resimlerini şekillendir @@ -6130,6 +6130,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6489,6 +6493,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Yapıştırdığın metin bir SimpleX bağlantısı değildir. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7548,6 +7556,10 @@ Bağlantı isteği tekrarlansın mı? Sohbet veritabanınız şifrelenmemiş - şifrelemek iƧin parola ayarlayın. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Sohbet profillerin @@ -7602,13 +7614,15 @@ Bağlantı isteği tekrarlansın mı? Profiliniz **%@** paylaşılacaktır. 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. + + 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. No comment provided by engineer. + + 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. Profiliniz, kişileriniz ve gƶnderilmiş mesajlar cihazınızda saklanır. 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 69685620ba..d371b29109 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1024,6 +1024,10 @@ Автоматичне ŠæŃ€ŠøŠ¹Š½ŃŃ‚Ń‚Ń Š·Š¾Š±Ń€Š°Š¶ŠµŠ½ŃŒ No comment provided by engineer. + + Auto-accept settings + alert title + Back ŠŠ°Š·Š°Š“ @@ -1197,7 +1201,7 @@ Cancel Š”ŠŗŠ°ŃŃƒŠ²Š°Ń‚Šø - No comment provided by engineer. + alert button Cancel migration @@ -1345,6 +1349,10 @@ ŠŠ°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń Ń‡Š°Ń‚Ńƒ No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme Тема Ń‡Š°Ń‚Ńƒ @@ -2870,6 +2878,10 @@ This is your own one-time link! Помилка Š·Š°Š²Š°Š½Ń‚Š°Š¶ŠµŠ½Š½Ń %@ серверів No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat Помилка Š²Ń–Š“ŠŗŃ€ŠøŃ‚Ń‚Ń Ń‡Š°Ń‚Ńƒ @@ -3309,11 +3321,6 @@ Error: %2$@ Повне ім'я (необов'ŃŠ·ŠŗŠ¾Š²Š¾) No comment provided by engineer. - - Full name: - Повне ім'я: - No comment provided by engineer. - Fully decentralized – visible only to members. ŠŸŠ¾Š²Š½Ń–ŃŃ‚ŃŽ Гецентралізована - виГима лише Š“Š»Ń ŃƒŃ‡Š°ŃŠ½ŠøŠŗŃ–Š². @@ -5045,16 +5052,6 @@ Error: %@ Š—Š¾Š±Ń€Š°Š¶ŠµŠ½Š½Ń ŠæŃ€Š¾Ń„Ń–Š»ŃŽ No comment provided by engineer. - - Profile name - ŠŠ°Š·Š²Š° ŠæŃ€Š¾Ń„Ń–Š»ŃŽ - No comment provided by engineer. - - - Profile name: - Ім'я ŠæŃ€Š¾Ń„Ń–Š»ŃŽ: - No comment provided by engineer. - Profile password ŠŸŠ°Ń€Š¾Š»ŃŒ Го ŠæŃ€Š¾Ń„Ń–Š»ŃŽ @@ -5378,6 +5375,10 @@ Enable in *Network & servers* settings. ВиГалити No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image ВиГалити Š·Š¾Š±Ń€Š°Š¶ŠµŠ½Š½Ń @@ -5571,12 +5572,13 @@ Enable in *Network & servers* settings. Save Зберегти - chat item action + alert button + chat item action Save (and notify contacts) Зберегти (і повіГомити контактам) - No comment provided by engineer. + alert button Save and notify contact @@ -5603,11 +5605,6 @@ Enable in *Network & servers* settings. Зберегти архів No comment provided by engineer. - - Save auto-accept settings - Зберегти Š½Š°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń Š°Š²Ń‚Š¾ŠæŃ€ŠøŠ¹Š¾Š¼Ńƒ - No comment provided by engineer. - Save group profile Зберегти ŠæŃ€Š¾Ń„Ń–Š»ŃŒ Š³Ń€ŃƒŠæŠø @@ -5643,16 +5640,15 @@ Enable in *Network & servers* settings. Зберегти сервери? No comment provided by engineer. - - Save settings? - Зберегти Š½Š°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń? - No comment provided by engineer. - Save welcome message? Зберегти Š²Ń–Ń‚Š°Š»ŃŒŠ½Šµ ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń? No comment provided by engineer. + + Save your profile? + alert title + Saved Збережено @@ -6092,6 +6088,10 @@ Enable in *Network & servers* settings. ŠŠ°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images Š”Ń„Š¾Ń€Š¼ŃƒŠ¹Ń‚Šµ Š·Š¾Š±Ń€Š°Š¶ŠµŠ½Š½Ń ŠæŃ€Š¾Ń„Ń–Š»ŃŽ @@ -6296,6 +6296,10 @@ Enable in *Network & servers* settings. М'ŃŠŗŠøŠ¹ blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: Š”ŠµŃŠŗŃ– файли не було експортовано: @@ -6667,6 +6671,10 @@ It can happen because of some bug or when the connection is compromised.Текст, ŃŠŗŠøŠ¹ ви вставили, не є ŠæŠ¾ŃŠøŠ»Š°Š½Š½ŃŠ¼ SimpleX. No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes Теми @@ -7750,6 +7758,10 @@ Repeat connection request? Š’Š°ŃˆŠ° база Ганих Ń‡Š°Ń‚Ńƒ не Š·Š°ŃˆŠøŃ„рована - Š²ŃŃ‚Š°Š½Š¾Š²Ń–Ń‚ŃŒ ŠŗŠ»ŃŽŃ‡Š¾Š²Ńƒ Ń„Ń€Š°Š·Ńƒ, щоб Š·Š°ŃˆŠøŃ„Ń€ŃƒŠ²Š°Ń‚Šø її. No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles Š’Š°ŃˆŃ– профілі Ń‡Š°Ń‚Ńƒ @@ -7804,13 +7816,15 @@ Repeat connection request? Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ **%@** буГе Š¾ŠæŃƒŠ±Š»Ń–кований. 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 не Š±Š°Ń‡Š°Ń‚ŃŒ ваш ŠæŃ€Š¾Ń„Ń–Š»ŃŒ. + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ Š·Š±ŠµŃ€Ń–Š³Š°Ń”Ń‚ŃŒŃŃ на вашому пристрої і Š“Š¾ŃŃ‚ŃƒŠæŠ½ŠøŠ¹ лише вашим контактам. Дервери SimpleX не Š±Š°Ń‡Š°Ń‚ŃŒ ваш ŠæŃ€Š¾Ń„Ń–Š»ŃŒ. No comment provided by engineer. + + 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. Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ, контакти та Гоставлені ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń Š·Š±ŠµŃ€Ń–Š³Š°ŃŽŃ‚ŃŒŃŃ на вашому пристрої. 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 b524846ffd..d7d6161be8 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 @@ -994,6 +994,10 @@ č‡ŖåŠØęŽ„å—å›¾ē‰‡ No comment provided by engineer. + + Auto-accept settings + alert title + Back čæ”å›ž @@ -1158,7 +1162,7 @@ Cancel å–ę¶ˆ - No comment provided by engineer. + alert button Cancel migration @@ -1301,6 +1305,10 @@ čŠå¤©åå„½č®¾ē½® No comment provided by engineer. + + Chat preferences were changed. + alert message + Chat theme No comment provided by engineer. @@ -2762,6 +2770,10 @@ This is your own one-time link! 加载 %@ ęœåŠ”å™Øé”™čÆÆ No comment provided by engineer. + + Error migrating settings + No comment provided by engineer. + Error opening chat No comment provided by engineer. @@ -3181,11 +3193,6 @@ Error: %2$@ å…Øåļ¼ˆåÆé€‰ļ¼‰ No comment provided by engineer. - - Full name: - å…Øåļ¼š - No comment provided by engineer. - Fully decentralized – visible only to members. å®Œå…ØåŽ»äø­åæƒåŒ– - ä»…åÆ¹ęˆå‘˜åÆč§ć€‚ @@ -4861,15 +4868,6 @@ Error: %@ 个人资料图 No comment provided by engineer. - - Profile name - No comment provided by engineer. - - - Profile name: - ę˜¾ē¤ŗåļ¼š - No comment provided by engineer. - Profile password 个人资料密码 @@ -5174,6 +5172,10 @@ Enable in *Network & servers* settings. 移除 No comment provided by engineer. + + Remove archive? + No comment provided by engineer. + Remove image No comment provided by engineer. @@ -5359,12 +5361,13 @@ Enable in *Network & servers* settings. Save äæå­˜ - chat item action + alert button + chat item action Save (and notify contacts) äæå­˜ļ¼ˆå¹¶é€šēŸ„č”ē³»äŗŗļ¼‰ - No comment provided by engineer. + alert button Save and notify contact @@ -5390,11 +5393,6 @@ Enable in *Network & servers* settings. äæå­˜å­˜ę”£ No comment provided by engineer. - - Save auto-accept settings - äæå­˜č‡ŖåŠØęŽ„å—č®¾ē½® - No comment provided by engineer. - Save group profile äæå­˜ē¾¤ē»„čµ„ę–™ @@ -5430,16 +5428,15 @@ Enable in *Network & servers* settings. äæå­˜ęœåŠ”å™Øļ¼Ÿ No comment provided by engineer. - - Save settings? - äæå­˜č®¾ē½®ļ¼Ÿ - No comment provided by engineer. - Save welcome message? äæå­˜ę¬¢čæŽäæ”ęÆļ¼Ÿ No comment provided by engineer. + + Save your profile? + alert title + Saved å·²äæå­˜ @@ -5855,6 +5852,10 @@ Enable in *Network & servers* settings. 设置 No comment provided by engineer. + + Settings were changed. + alert message + Shape profile images ę”¹å˜äøŖäŗŗčµ„ę–™å›¾å½¢ēŠ¶ @@ -6051,6 +6052,10 @@ Enable in *Network & servers* settings. Soft blur media + + Some app settings were not migrated. + No comment provided by engineer. + Some file(s) were not exported: No comment provided by engineer. @@ -6409,6 +6414,10 @@ It can happen because of some bug or when the connection is compromised.ę‚Øē²˜č““ēš„ę–‡ęœ¬äøę˜Æ SimpleX é“¾ęŽ„ć€‚ No comment provided by engineer. + + The uploaded database archive will be permanently removed from the servers. + No comment provided by engineer. + Themes No comment provided by engineer. @@ -7448,6 +7457,10 @@ Repeat connection request? ę‚Øēš„čŠå¤©ę•°ę®åŗ“ęœŖåŠ åÆ†ā€”ā€”č®¾ē½®åÆ†ē ę„åŠ åÆ†ć€‚ No comment provided by engineer. + + Your chat preferences + alert title + Your chat profiles ę‚Øēš„čŠå¤©čµ„ę–™ @@ -7501,13 +7514,15 @@ Repeat connection request? ę‚Øēš„äøŖäŗŗčµ„ę–™ **%@** 将被共享。 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 ęœåŠ”å™Øę— ę³•ēœ‹åˆ°ę‚Øēš„čµ„ę–™ć€‚ + + Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. + ę‚Øēš„čµ„ę–™å­˜å‚ØåœØę‚Øēš„č®¾å¤‡äøŠå¹¶ä»…äøŽę‚Øēš„č”ē³»äŗŗå…±äŗ«ć€‚ SimpleX ęœåŠ”å™Øę— ę³•ēœ‹åˆ°ę‚Øēš„čµ„ę–™ć€‚ No comment provided by engineer. + + 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. ę‚Øēš„čµ„ę–™ć€č”ē³»äŗŗå’Œå‘é€ēš„ę¶ˆęÆå­˜å‚ØåœØę‚Øēš„č®¾å¤‡äøŠć€‚ diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 338690c8e8..41fe127093 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -252,7 +252,7 @@ private fun ActiveUserSection( } UserPickerOptionRow( painterResource(MR.images.ic_qr_code), - if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_public_contact_address) else generalGetString(MR.strings.create_public_contact_address), + if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped ) UserPickerOptionRow( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 1f546fb863..b357272e16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -174,7 +174,7 @@ private fun UserAddressLayout( saveAas: (AutoAcceptState, MutableState) -> Unit, ) { ColumnWithScrollBar { - AppBarTitle(stringResource(MR.strings.public_address), hostDevice(user?.remoteHostId)) + AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId)) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally, @@ -230,7 +230,7 @@ private fun UserAddressLayout( private fun CreateAddressButton(onClick: () -> Unit) { SettingsActionItem( painterResource(MR.images.ic_qr_code), - stringResource(MR.strings.create_public_address), + stringResource(MR.strings.create_simplex_address), onClick, iconColor = MaterialTheme.colors.primary, textColor = MaterialTheme.colors.primary, 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 704c6533ae..f3e491245b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -701,10 +701,6 @@ %s is verified %s is not verified - - Create public address - Your public address - Your settings Your SimpleX address @@ -853,8 +849,6 @@ Notifications will stop working until you re-launch the app - Public address - Create public address Create address Delete address? Your contacts will remain connected. From 4f99075b148a9052d81272a7b344de384ecf63ff Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 16 Sep 2024 16:50:37 +0100 Subject: [PATCH 053/704] ui: translations (#4889) * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 36.4% (741 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 5.8% (118 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 36.4% (741 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 5.8% (118 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Indonesian) Currently translated at 6.3% (130 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (German) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/zh_Hans/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 83.5% (1697 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Indonesian) Currently translated at 12.9% (263 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2032 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1785 of 1785 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 37.1% (755 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 37.8% (769 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Czech) Currently translated at 88.7% (1804 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Vietnamese) Currently translated at 38.4% (782 of 2032 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * process localizations * fix links --------- Co-authored-by: summoner001 Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: rbasliana Co-authored-by: Ghost of Sparta Co-authored-by: mlanp Co-authored-by: re me Co-authored-by: Mateus Pereira Co-authored-by: rbasliana <91536894+rbasliana@users.noreply.github.com> Co-authored-by: zenobit --- .../de.xcloc/Localized Contents/de.xliff | 26 +- .../hu.xcloc/Localized Contents/hu.xliff | 148 +-- .../Localized Contents/zh-Hans.xliff | 399 ++++++- .../zh-Hans.lproj/InfoPlist.strings | 14 +- .../zh-Hans.lproj/Localizable.strings | 116 +- apps/ios/bg.lproj/Localizable.strings | 24 +- apps/ios/cs.lproj/Localizable.strings | 18 +- apps/ios/de.lproj/Localizable.strings | 50 +- apps/ios/es.lproj/Localizable.strings | 24 +- apps/ios/fi.lproj/Localizable.strings | 18 +- apps/ios/fr.lproj/Localizable.strings | 24 +- apps/ios/hu.lproj/Localizable.strings | 168 ++- apps/ios/it.lproj/Localizable.strings | 24 +- apps/ios/ja.lproj/Localizable.strings | 18 +- apps/ios/nl.lproj/Localizable.strings | 24 +- apps/ios/pl.lproj/Localizable.strings | 24 +- apps/ios/ru.lproj/Localizable.strings | 24 +- apps/ios/th.lproj/Localizable.strings | 18 +- apps/ios/tr.lproj/Localizable.strings | 24 +- apps/ios/uk.lproj/Localizable.strings | 24 +- apps/ios/zh-Hans.lproj/Localizable.strings | 997 +++++++++++++++++- .../SimpleX--iOS--InfoPlist.strings | 3 + .../commonMain/resources/MR/cs/strings.xml | 7 +- .../commonMain/resources/MR/de/strings.xml | 36 +- .../commonMain/resources/MR/hu/strings.xml | 272 ++--- .../commonMain/resources/MR/in/strings.xml | 168 ++- .../resources/MR/pt-rBR/strings.xml | 461 +++++++- .../commonMain/resources/MR/vi/strings.xml | 57 + 28 files changed, 2529 insertions(+), 681 deletions(-) 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 cf2f85f2c0..884c322dae 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1405,22 +1405,22 @@ Clear - Lƶschen + Entfernen swipe action Clear conversation - Chatinhalte lƶschen + Chat-Inhalte entfernen No comment provided by engineer. Clear conversation? - Unterhaltung lƶschen? + Chat-Inhalte entfernen? No comment provided by engineer. Clear private notes? - Private Notizen lƶschen? + Private Notizen entfernen? No comment provided by engineer. @@ -1734,7 +1734,7 @@ Das ist Ihr eigener Einmal-Link! Conversation deleted! - Unterhaltung gelƶscht! + Chat-Inhalte entfernt! No comment provided by engineer. @@ -3478,7 +3478,7 @@ Fehler: %2$@ Group will be deleted for you - this cannot be undone! - Die Gruppe wird für Sie gelƶscht. Dies kann nicht rückgƤngig gemacht werden! + Die Gruppe wird nur bei Ihnen gelƶscht. Dies kann nicht rückgƤngig gemacht werden! No comment provided by engineer. @@ -3933,7 +3933,7 @@ Das ist Ihr Link für die Gruppe %@! Keep conversation - Unterhaltung behalten + Chat-Inhalte beibehalten No comment provided by engineer. @@ -4635,7 +4635,7 @@ Dies erfordert die Aktivierung eines VPNs. Only delete conversation - Nur die Unterhaltung lƶschen + Nur die Chat-Inhalte lƶschen No comment provided by engineer. @@ -5176,7 +5176,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reachable chat toolbar - Erreichbare Chat-Symbolleiste + Chat-Symbolleiste unten No comment provided by engineer. @@ -5716,7 +5716,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Search or paste SimpleX link - Suchen oder fügen Sie den SimpleX-Link ein + Suchen oder SimpleX-Link einfügen No comment provided by engineer. @@ -6628,7 +6628,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The messages will be deleted for all members. - Die Nachrichten werden für alle Mitglieder gelƶscht werden. + Die Nachrichten werden für alle Gruppenmitglieder gelƶscht. No comment provided by engineer. @@ -7131,7 +7131,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use the app with one hand. - Die App mit einer Hand nutzen. + Die App mit einer Hand bedienen. No comment provided by engineer. @@ -7563,7 +7563,7 @@ Verbindungsanfrage wiederholen? You can still view conversation with %@ in the list of chats. - Sie kƶnnen in der Chatliste weiterhin die Unterhaltung mit %@ einsehen. + Sie kƶnnen in der Chat-Liste weiterhin die Unterhaltung mit %@ einsehen. 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 89bce685a0..063dd3d14a 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -427,8 +427,8 @@ - voice messages up to 5 minutes. - custom time to disappear. - editing history. - - hangüzenetek legfeljebb 5 perces időtartamig. -- egyedi eltűnĆ©si időhatĆ”r megadĆ”sa. + - 5 perc hosszĆŗsĆ”gĆŗ hangüzenetek. +- egyedi üzenet-eltűnĆ©si időkorlĆ”t. - előzmĆ©nyek szerkesztĆ©se. No comment provided by engineer. @@ -496,7 +496,7 @@ <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> <p>Üdvƶzlƶm!</p> -<p><a href=ā€ž%@ā€>Csatlakozzon hozzĆ”m a SimpleX Chaten</a></p> +<p><a href=ā€ž%@ā€>Csatlakozzon hozzĆ”m a SimpleX Chaten keresztül</a></p> email text @@ -570,7 +570,7 @@ Accept connection request? - KapcsolódĆ”si kĆ©relem elfogadĆ”sa? + IsmerőskĆ©relem elfogadĆ”sa? No comment provided by engineer. @@ -1050,7 +1050,7 @@ Bad message hash - HibĆ”s az üzenet ellenőrzőösszege + HibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke No comment provided by engineer. @@ -1530,7 +1530,7 @@ Connect to desktop - KapcsolódĆ”s szĆ”mĆ­tógĆ©phez + TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel No comment provided by engineer. @@ -1554,7 +1554,7 @@ Ez az ƶn SimpleX cĆ­me! Connect to yourself? This is your own one-time link! KapcsolódĆ”s sajĆ”t magĆ”hoz? -Ez az egyszer hasznĆ”latos hivatkozĆ”sa! +Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! No comment provided by engineer. @@ -1584,7 +1584,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Connected desktop - Csatlakoztatott szĆ”mĆ­tógĆ©p + TĆ”rsĆ­tott szĆ”mĆ­tógĆ©p No comment provided by engineer. @@ -1679,7 +1679,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Contact already exists - LĆ©tező ismerős + Az ismerős mĆ”r lĆ©tezik No comment provided by engineer. @@ -1793,7 +1793,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Create group link - Csoportos hivatkozĆ”s lĆ©trehozĆ”sa + CsoporthivatkozĆ”s lĆ©trehozĆ”sa No comment provided by engineer. @@ -1848,7 +1848,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Creating archive link - ArchĆ­v hivatkozĆ”s lĆ©trehozĆ”sa + ArchĆ­vum hivatkozĆ”s lĆ©trehozĆ”sa No comment provided by engineer. @@ -2177,7 +2177,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Delete pending connection? - Függő kapcsolatfelvĆ©teli kĆ©rĆ©sek tƶrlĆ©se? + Függőben lĆ©vő ismerőskĆ©relem tƶrlĆ©se? No comment provided by engineer. @@ -2487,7 +2487,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Duplicate display name! - DuplikĆ”lt megjelenĆ­tĆ©si nĆ©v! + DuplikĆ”lt megjelenĆ­tett nĆ©v! No comment provided by engineer. @@ -2732,7 +2732,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Error adding member(s) - Hiba a tag(-ok) hozzĆ”adĆ”sakor + Hiba a tag(ok) hozzĆ”adĆ”sakor No comment provided by engineer. @@ -2775,7 +2775,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Error creating group link - Hiba a csoport hivatkozĆ”sĆ”nak lĆ©trehozĆ”sakor + Hiba a csoporthivatkozĆ”s lĆ©trehozĆ”sakor No comment provided by engineer. @@ -2998,7 +2998,7 @@ Ez az egyszer hasznĆ”latos hivatkozĆ”sa! Error updating group link - Hiba a csoport hivatkozĆ”s frissĆ­tĆ©sekor + Hiba a csoporthivatkozĆ”s frissĆ­tĆ©sekor No comment provided by engineer. @@ -3398,12 +3398,12 @@ Hiba: %2$@ Group link - Csoport hivatkozĆ”s + CsoporthivatkozĆ”s No comment provided by engineer. Group links - Csoport hivatkozĆ”sok + CsoporthivatkozĆ”sok No comment provided by engineer. @@ -3473,7 +3473,7 @@ Hiba: %2$@ Group will be deleted for all members - this cannot be undone! - Csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza! + A csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza! No comment provided by engineer. @@ -3770,12 +3770,12 @@ Hiba: %2$@ Invalid connection link - ƉrvĆ©nytelen kapcsolati hivatkozĆ”s + ƉrvĆ©nytelen kapcsolattartĆ”si hivatkozĆ”s No comment provided by engineer. Invalid display name! - ƉrvĆ©nytelen megjelenĆ­tendő felhaszĆ”lónĆ©v! + ƉrvĆ©nytelen megjelenĆ­tendő nĆ©v! No comment provided by engineer. @@ -3993,7 +3993,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Let's talk in SimpleX Chat - BeszĆ©lgessünk a SimpleX Chat-ben + BeszĆ©lgessünk a SimpleX Chatben email subject @@ -4008,17 +4008,17 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Link mobile and desktop apps! šŸ”— - TĆ”rsĆ­tsa ƶssze a mobil Ć©s az asztali alkalmazĆ”sokat! šŸ”— + TĆ”rsĆ­tsa ƶssze a mobil Ć©s asztali alkalmazĆ”sokat! šŸ”— No comment provided by engineer. Linked desktop options - Ɩsszekapcsolt szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok + TĆ”rsĆ­tott szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok No comment provided by engineer. Linked desktops - Ɩsszekapcsolt szĆ”mĆ­tógĆ©pek + TĆ”rsĆ­tott szĆ”mĆ­tógĆ©pek No comment provided by engineer. @@ -4083,7 +4083,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* - Sokan kĆ©rdeztĆ©k: *ha a SimpleX-nek nincsenek felhasznĆ”lói azonosĆ­tói, akkor hogyan tud üzeneteket kĆ©zbesĆ­teni?* + Sokan kĆ©rdeztĆ©k: *ha a SimpleX Chatnek nincsenek felhasznĆ”lói azonosĆ­tói, akkor hogyan tud üzeneteket kĆ©zbesĆ­teni?* No comment provided by engineer. @@ -4342,12 +4342,12 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Moderated at - ModerĆ”lva lett ekkor: + ModerĆ”lva ekkor: No comment provided by engineer. Moderated at: %@ - ModerĆ”lva lett ekkor: %@ + ModerĆ”lva ekkor: %@ copied message info @@ -4432,7 +4432,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! New contact request - Új kapcsolattartĆ”si kĆ©relem + Új ismerőskĆ©relem notification @@ -4492,7 +4492,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! No contacts selected - Nem kerültek ismerősƶk kivĆ”lasztĆ”sra + Nincs kivĆ”lasztva ismerős No comment provided by engineer. @@ -4557,7 +4557,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Nothing selected - Semmi sincs kivĆ”lasztva + Nincs kivĆ”lasztva semmi No comment provided by engineer. @@ -4601,7 +4601,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Old database archive - RĆ©gi adatbĆ”zis archĆ­vum + RĆ©gi adatbĆ”zis-archĆ­vum No comment provided by engineer. @@ -4630,7 +4630,7 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. - Csak a klienseszkƶzƶk tĆ”roljĆ”k a felhasznĆ”lói profilokat, nĆ©vjegyeket, csoportokat Ć©s a **2 rĆ©tegű vĆ©gponttól-vĆ©gpontig titkosĆ­tĆ”ssal** küldƶtt üzeneteket. + Csak a klienseszkƶzƶk tĆ”roljĆ”k a felhasznĆ”lói profilokat, nĆ©vjegyeket, csoportokat Ć©s a **2 rĆ©tegű vĆ©gpontok kƶzƶtti titkosĆ­tĆ”ssal** küldƶtt üzeneteket. No comment provided by engineer. @@ -4765,7 +4765,7 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. Or securely share this file link - Vagy a fĆ”jl hivĆ­tkozĆ”sĆ”nak biztonsĆ”gos megosztĆ”sa + Vagy ossza meg biztonsĆ”gosan ezt a fĆ”jlhivatkozĆ”st No comment provided by engineer. @@ -4825,7 +4825,7 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. Past member %@ - MĆ”r nem tag - %@ + %@ (mĆ”r nem tag) past/unknown group member @@ -4845,12 +4845,12 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. Paste the link you received - Fogadott hivatkozĆ”s beillesztĆ©se + Kapott hivatkozĆ”s beillesztĆ©se No comment provided by engineer. Pending - Függő + Függőben No comment provided by engineer. @@ -4897,7 +4897,7 @@ Minden tovĆ”bbi problĆ©mĆ”t osszon meg a fejlesztőkkel. Please check that you used the correct link or ask your contact to send you another one. - Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg ismerősĆ©t, hogy küldjƶn egy mĆ”sikat. + Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg az ismerősĆ©t, hogy küldjƶn egy mĆ”sikat. No comment provided by engineer. @@ -5451,7 +5451,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reset - Alaphelyzetbe Ć”llĆ­tĆ”s + VisszaĆ”llĆ­tĆ”s No comment provided by engineer. @@ -5471,7 +5471,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reset colors - SzĆ­nek alaphelyzetbe Ć”llĆ­tĆ”sa + SzĆ­nek visszaĆ”llĆ­tĆ”sa No comment provided by engineer. @@ -5481,7 +5481,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reset to defaults - Alaphelyzetbe Ć”llĆ­tĆ”s + VisszaĆ”llĆ­tĆ”s alaphelyzetbe No comment provided by engineer. @@ -6020,12 +6020,12 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Servers info - informĆ”ciók a kiszolgĆ”lókról + InformĆ”ciók a kiszolgĆ”lókról No comment provided by engineer. Servers statistics will be reset - this cannot be undone! - A kiszolgĆ”lók statisztikĆ”i visszaĆ”llnak - ez nem vonható vissza! + A kiszolgĆ”lók statisztikĆ”i visszaĆ”llnak - ez a művelet nem vonható vissza! No comment provided by engineer. @@ -6243,7 +6243,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. SimpleX group link - SimpleX csoport hivatkozĆ”s + SimpleX csoporthivatkozĆ”s simplex link type @@ -6437,7 +6437,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Support SimpleX Chat - TĆ”mogassa a SimpleX Chatet + SimpleX Chat tĆ”mogatĆ”sa No comment provided by engineer. @@ -6556,7 +6556,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Thanks to the users – contribute via Weblate! - Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten! + Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! No comment provided by engineer. @@ -6588,12 +6588,12 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. The code you scanned is not a SimpleX link QR code. - A beolvasott kód nem egy SimpleX hivatkozĆ”s QR-kód. + A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozĆ”s. No comment provided by engineer. The connection you accepted will be cancelled! - Az ƶn Ć”ltal elfogadott kapcsolat vissza lesz vonva! + Az ƶn Ć”ltal elfogadott kĆ©relem vissza lesz vonva! No comment provided by engineer. @@ -6613,7 +6613,7 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. The hash of the previous message is different. - Az előző üzenet ellenőrzőösszege külƶnbƶzik. + Az előző üzenet hasĆ­tó Ć©rtĆ©ke külƶnbƶzik. No comment provided by engineer. @@ -6722,7 +6722,7 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. This display name is invalid. Please choose another name. - Ez a megjelenĆ­tett felhasznĆ”lónĆ©v Ć©rvĆ©nytelen. VĆ”lasszon egy mĆ”sik nevet. + Ez a megjelenĆ­tett nĆ©v Ć©rvĆ©nytelen. VĆ”lasszon egy mĆ”sik nevet. No comment provided by engineer. @@ -6742,12 +6742,12 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. This is your own one-time link! - Ez az egyszer hasznĆ”latos hivatkozĆ”sa! + Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! No comment provided by engineer. This link was used with another mobile device, please create a new link on the desktop. - Ezt a hivatkozĆ”st egy mĆ”sik mobilleszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n. + Ezt a hivatkozĆ”st egy mĆ”sik mobileszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n. No comment provided by engineer. @@ -6854,12 +6854,12 @@ A funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”ll Trying to connect to the server used to receive messages from this contact (error: %@). - KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l (hiba: %@). + KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l (hiba: %@). No comment provided by engineer. Trying to connect to the server used to receive messages from this contact. - KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. + KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. No comment provided by engineer. @@ -6965,8 +6965,8 @@ A funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”ll Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. To connect, please ask your contact to create another connection link and check that you have a stable network connection. - Hacsak az ismerőse nem tƶrƶlte a kapcsolatot, vagy ez a hivatkozĆ”s mĆ”r hasznĆ”latban volt, lehet hogy ez egy hiba – jelentse a problĆ©mĆ”t. -A kapcsolódĆ”shoz kĆ©rje meg ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsolati hivatkozĆ”st, Ć©s ellenőrizze, hogy a hĆ”lózati kapcsolat stabil-e. + Hacsak az ismerőse nem tƶrƶlte a kapcsolatot, vagy ez a hivatkozĆ”s mĆ”r hasznĆ”latban volt egyszer, lehet hogy ez egy hiba – jelentse a problĆ©mĆ”t. +A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsolattartĆ”si hivatkozĆ”st, Ć©s ellenőrizze, hogy a hĆ”lózati kapcsolat stabil-e. No comment provided by engineer. @@ -7091,7 +7091,7 @@ A kapcsolódĆ”shoz kĆ©rje meg ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsol Use from desktop - HasznĆ”lat szĆ”mĆ­tógĆ©pről + TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel No comment provided by engineer. @@ -7431,7 +7431,7 @@ A kapcsolódĆ”shoz kĆ©rje meg ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsol You are already connected to %@. - MĆ”r kapcsolódva van hozzĆ”: %@. + Ɩn mĆ”r kapcsolódva van ehhez: %@. No comment provided by engineer. @@ -7473,7 +7473,7 @@ CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You are connected to the server used to receive messages from this contact. - MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. + MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. No comment provided by engineer. @@ -7523,7 +7523,7 @@ CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You can make it visible to your SimpleX contacts via Settings. - LĆ”thatóvĆ” teheti SimpleX ismerősƶk szĆ”mĆ”ra a BeĆ”llĆ­tĆ”sokban. + LĆ”thatóvĆ” teheti a SimpleXbeli ismerősei szĆ”mĆ”ra a ā€žBeĆ”llĆ­tĆ”sokbanā€. No comment provided by engineer. @@ -7670,7 +7670,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You will be connected when group link host's device is online, please wait or check later! - Akkor lesz kapcsolódva, amikor a csoportos hivatkozĆ”s tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! + Akkor lesz kapcsolódva, amikor a csoporthivatkozĆ”s tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! No comment provided by engineer. @@ -7735,7 +7735,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? Your SimpleX address - Az ƶn SimpleX cĆ­me + Profil SimpleX cĆ­me No comment provided by engineer. @@ -7862,7 +7862,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat) + [CsillagozĆ”s a GitHubon](https://github.com/simplex-chat/simplex-chat) No comment provided by engineer. @@ -7942,7 +7942,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? bad message hash - hibĆ”s az üzenet ellenőrzőösszege + hibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke integrity error chat item @@ -7972,7 +7972,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? call error - hiba a hĆ­vĆ”sban + hĆ­vĆ”shiba call status @@ -8067,7 +8067,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? connecting call… - hĆ­vĆ”s kapcsolódik… + kapcsolódĆ”si hĆ­vĆ”s… call status @@ -8307,12 +8307,12 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? incognito via group link - inkognitó a csoportos hivatkozĆ”son keresztül + inkognitó a csoporthivatkozĆ”son keresztül chat list item description incognito via one-time link - inkognitó az egyszer hasznĆ”latos hivatkozĆ”son keresztül + inkognitó egy egyszer hasznĆ”latos hivatkozĆ”son keresztül chat list item description @@ -8362,7 +8362,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? invited via your group link - meghĆ­va az ƶn csoport hivatkozĆ”sĆ”n keresztül + meghĆ­va az ƶn csoporthivatkozĆ”sĆ”n keresztül rcv group event chat item @@ -8382,7 +8382,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? marked deleted - tƶrƶltnek jelƶlve + tƶrlĆ©sre jelƶlve marked deleted chat item preview text @@ -8524,7 +8524,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? received answer… - fogadott vĆ”lasz… + vĆ”lasz fogadĆ”sa… No comment provided by engineer. @@ -8698,12 +8698,12 @@ utoljĆ”ra fogadott üzenet: %2$@ via group link - csoport hivatkozĆ”son keresztül + a csoporthivatkozĆ”son keresztül chat list item description via one-time link - egyszer hasznĆ”latos hivatkozĆ”son keresztül + egy egyszer hasznĆ”latos hivatkozĆ”son keresztül chat list item description @@ -8768,7 +8768,7 @@ utoljĆ”ra fogadott üzenet: %2$@ you blocked %@ - ƶn letiltotta %@-t + ƶn letiltotta őt: %@ snd group event chat item 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 d7d6161be8..b7abefd465 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 @@ -109,6 +109,7 @@ %@ downloaded + %@ 已下载 No comment provided by engineer. @@ -128,6 +129,7 @@ %@ uploaded + %@ 已上传 No comment provided by engineer. @@ -226,14 +228,17 @@ %lld messages blocked by admin + %lld č¢«ē®”ē†å‘˜é˜»ę­¢ēš„ę¶ˆęÆ No comment provided by engineer. %lld messages marked deleted + %lld ę ‡č®°äøŗå·²åˆ é™¤ēš„ę¶ˆęÆ No comment provided by engineer. %lld messages moderated by %@ + %lld å®”ę øēš„ē•™čØ€ by %@ No comment provided by engineer. @@ -308,10 +313,12 @@ (new) + (ꖰ) No comment provided by engineer. (this device v%@) + (此设备 v%@) No comment provided by engineer. @@ -321,6 +328,7 @@ **Add contact**: to create a new invitation link, or connect via a link you received. + **ę·»åŠ č”ē³»äŗŗ**: åˆ›å»ŗę–°ēš„é‚€čÆ·é“¾ęŽ„ļ¼Œęˆ–é€ščæ‡ę‚Øę”¶åˆ°ēš„é“¾ęŽ„čæ›č”ŒčæžęŽ„. No comment provided by engineer. @@ -330,6 +338,7 @@ **Create group**: to create a new group. + **åˆ›å»ŗē¾¤ē»„**: åˆ›å»ŗäø€äøŖę–°ē¾¤ē»„. No comment provided by engineer. @@ -344,6 +353,7 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **čÆ·ę³Øę„**: åœØäø¤å°č®¾å¤‡äøŠä½æē”Øē›øåŒēš„ę•°ę®åŗ“å°†ē “åę„č‡Ŗę‚Øēš„čæžęŽ„ēš„ę¶ˆęÆč§£åÆ†ļ¼Œä½œäøŗäø€ē§å®‰å…ØäæęŠ¤. No comment provided by engineer. @@ -363,6 +373,7 @@ **Warning**: the archive will be removed. + **č­¦å‘Š**: å­˜ę”£å°†č¢«åˆ é™¤. No comment provided by engineer. @@ -428,6 +439,7 @@ 0 sec + 0 ē§’ time to disappear @@ -546,6 +558,7 @@ Accent + 强调 No comment provided by engineer. @@ -573,14 +586,17 @@ Acknowledged + 甮认 No comment provided by engineer. Acknowledgement errors + 甮认错误 No comment provided by engineer. Active connections + ę“»åŠØčæžęŽ„ No comment provided by engineer. @@ -625,14 +641,17 @@ Additional accent + 附加重音 No comment provided by engineer. Additional accent 2 + 附加重音 2 No comment provided by engineer. Additional secondary + é™„åŠ äŗŒēŗ§ No comment provided by engineer. @@ -662,6 +681,7 @@ Advanced settings + 高级设置 No comment provided by engineer. @@ -681,6 +701,7 @@ All data is private to your device. + ę‰€ęœ‰ę•°ę®éƒ½ę˜Æę‚Øč®¾å¤‡ēš„ē§ęœ‰ę•°ę®. No comment provided by engineer. @@ -700,10 +721,12 @@ All new messages from %@ will be hidden! + ę„č‡Ŗ %@ ēš„ę‰€ęœ‰ę–°ę¶ˆęÆéƒ½å°†č¢«éšč—! No comment provided by engineer. All profiles + ę‰€ęœ‰é…ē½®ę–‡ä»¶ No comment provided by engineer. @@ -733,6 +756,7 @@ Allow calls? + å…č®øé€ščÆļ¼Ÿ No comment provided by engineer. @@ -742,11 +766,12 @@ Allow downgrade + å…č®øé™ēŗ§ No comment provided by engineer. Allow irreversible message deletion only if your contact allows it to you. (24 hours) - ä»…ęœ‰ę‚Øēš„č”ē³»äŗŗč®øåÆåŽę‰å…č®øäøåÆę’¤å›žę¶ˆęÆē§»é™¤ć€‚ + ä»…ęœ‰ę‚Øēš„č”ē³»äŗŗč®øåÆåŽę‰å…č®øäøåÆę’¤å›žę¶ˆęÆē§»é™¤ No comment provided by engineer. @@ -771,11 +796,12 @@ Allow sharing + 允许共享 No comment provided by engineer. Allow to irreversibly delete sent messages. (24 hours) - å…č®øäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆć€‚ + å…č®øäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆ No comment provided by engineer. @@ -815,7 +841,7 @@ Allow your contacts to irreversibly delete sent messages. (24 hours) - å…č®øę‚Øēš„č”ē³»äŗŗäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆć€‚ + å…č®øę‚Øēš„č”ē³»äŗŗäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆ No comment provided by engineer. @@ -845,6 +871,7 @@ Always use private routing. + å§‹ē»ˆä½æē”Øē§ęœ‰č·Æē”±ć€‚ No comment provided by engineer. @@ -914,6 +941,7 @@ Apply to + åŗ”ē”ØäŗŽ No comment provided by engineer. @@ -923,10 +951,12 @@ Archive contacts to chat later. + å­˜ę”£č”ē³»äŗŗä»„ä¾æēØåŽčŠå¤©. No comment provided by engineer. Archived contacts + å·²å­˜ę”£ēš„č”ē³»äŗŗ No comment provided by engineer. @@ -1005,6 +1035,7 @@ Background + čƒŒę™Æ No comment provided by engineer. @@ -1034,10 +1065,12 @@ Better networking + ę›“å„½ēš„ē½‘ē»œ No comment provided by engineer. Black + 黑色 No comment provided by engineer. @@ -1077,10 +1110,12 @@ Blur for better privacy. + ęØ”ē³Šå¤„ē†ļ¼Œęé«˜ē§åÆ†ę€§. No comment provided by engineer. Blur media + ęØ”ē³ŠåŖ’ä½“ No comment provided by engineer. @@ -1090,7 +1125,7 @@ Both you and your contact can irreversibly delete sent messages. (24 hours) - ę‚Øå’Œę‚Øēš„č”ē³»äŗŗéƒ½åÆä»„äøåÆé€†č½¬åœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆć€‚ + ę‚Øå’Œę‚Øēš„č”ē³»äŗŗéƒ½åÆä»„äøåÆé€†č½¬åœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆ No comment provided by engineer. @@ -1130,6 +1165,7 @@ Calls prohibited! + ē¦ę­¢ę„ē”µļ¼ No comment provided by engineer. @@ -1139,10 +1175,12 @@ Can't call contact + ę— ę³•å‘¼å«č”ē³»äŗŗ No comment provided by engineer. Can't call member + ę— ę³•å‘¼å«ęˆå‘˜ No comment provided by engineer. @@ -1157,6 +1195,7 @@ Can't message member + ę— ę³•å‘ęˆå‘˜å‘é€ę¶ˆęÆ No comment provided by engineer. @@ -1176,6 +1215,7 @@ Cannot forward message + ę— ę³•č½¬å‘ę¶ˆęÆ No comment provided by engineer. @@ -1185,6 +1225,7 @@ Capacity exceeded - recipient did not receive previously sent messages. + č¶…å‡ŗå®¹é‡-ę”¶ä»¶äŗŗęœŖę”¶åˆ°ä»„å‰å‘é€ēš„é‚®ä»¶ć€‚ snd error text @@ -1250,6 +1291,7 @@ Chat colors + čŠå¤©é¢œč‰² No comment provided by engineer. @@ -1269,6 +1311,7 @@ Chat database exported + åÆ¼å‡ŗēš„čŠå¤©ę•°ę®åŗ“ No comment provided by engineer. @@ -1293,6 +1336,7 @@ Chat list + čŠå¤©åˆ—č”Ø No comment provided by engineer. @@ -1311,6 +1355,7 @@ Chat theme + 聊天主题 No comment provided by engineer. @@ -1330,6 +1375,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. + åœØę–°č®¾å¤‡äøŠé€‰ę‹©ā€œä»Žå¦äø€äøŖč®¾å¤‡čæē§»ā€å¹¶ę‰«ęäŗŒē»“ē ć€‚ No comment provided by engineer. @@ -1344,14 +1390,17 @@ Chunks deleted + å·²åˆ é™¤ēš„å— No comment provided by engineer. Chunks downloaded + äø‹č½½ēš„å— No comment provided by engineer. Chunks uploaded + å·²äø‹č½½ēš„åŒŗå— No comment provided by engineer. @@ -1381,10 +1430,12 @@ Color chats with the new themes. + ä½æē”Øę–°äø»é¢˜äøŗčŠå¤©ē€č‰²ć€‚ No comment provided by engineer. Color mode + é¢œč‰²ęØ”å¼ No comment provided by engineer. @@ -1399,6 +1450,7 @@ Completed + 已完成 No comment provided by engineer. @@ -1408,6 +1460,7 @@ Configured %@ servers + å·²é…ē½® %@ ęœåŠ”å™Ø No comment provided by engineer. @@ -1422,6 +1475,7 @@ Confirm contact deletion? + ē”®č®¤åˆ é™¤č”ē³»äŗŗļ¼Ÿ No comment provided by engineer. @@ -1431,6 +1485,7 @@ Confirm files from unknown servers. + ē”®č®¤ę„č‡ŖęœŖēŸ„ęœåŠ”å™Øēš„ę–‡ä»¶ć€‚ No comment provided by engineer. @@ -1480,6 +1535,7 @@ Connect to your friends faster. + ę›“åæ«åœ°äøŽę‚Øēš„ęœ‹å‹č”ē³»ć€‚ No comment provided by engineer. @@ -1490,15 +1546,20 @@ Connect to yourself? This is your own SimpleX address! + äøŽč‡Ŗå·±å»ŗē«‹č”ē³»ļ¼Ÿ +čæ™ę˜Æę‚Øč‡Ŗå·±ēš„ SimpleX åœ°å€ļ¼ No comment provided by engineer. Connect to yourself? This is your own one-time link! + äøŽč‡Ŗå·±å»ŗē«‹č”ē³»ļ¼Ÿ +čæ™ę˜Æę‚Øč‡Ŗå·±ēš„äø€ę¬”ę€§é“¾ęŽ„ļ¼ No comment provided by engineer. Connect via contact address + é€ščæ‡č”ē³»åœ°å€čæžęŽ„ No comment provided by engineer. @@ -1513,10 +1574,12 @@ This is your own one-time link! Connect with %@ + äøŽ %@čæžęŽ„ No comment provided by engineer. Connected + å·²čæžęŽ„ No comment provided by engineer. @@ -1526,6 +1589,7 @@ This is your own one-time link! Connected servers + å·²čæžęŽ„ēš„ęœåŠ”å™Ø No comment provided by engineer. @@ -1535,6 +1599,7 @@ This is your own one-time link! Connecting + ę­£åœØčæžęŽ„ No comment provided by engineer. @@ -1549,6 +1614,7 @@ This is your own one-time link! Connecting to contact, please wait or check later! + ę­£åœØčæžęŽ„åˆ°č”ē³»äŗŗļ¼ŒčÆ·ēØå€™ęˆ–ēØåŽę£€ęŸ„ļ¼ No comment provided by engineer. @@ -1563,6 +1629,7 @@ This is your own one-time link! Connection and servers status. + čæžęŽ„å’ŒęœåŠ”å™ØēŠ¶ę€ć€‚ No comment provided by engineer. @@ -1577,6 +1644,7 @@ This is your own one-time link! Connection notifications + čæžęŽ„é€šēŸ„ No comment provided by engineer. @@ -1596,10 +1664,12 @@ This is your own one-time link! Connection with desktop stopped + äøŽę”Œé¢ēš„čæžęŽ„å·²åœę­¢ No comment provided by engineer. Connections + čæžęŽ„ No comment provided by engineer. @@ -1614,6 +1684,7 @@ This is your own one-time link! Contact deleted! + č”ē³»äŗŗå·²åˆ é™¤ļ¼ No comment provided by engineer. @@ -1628,6 +1699,7 @@ This is your own one-time link! Contact is deleted. + č”ē³»äŗŗč¢«åˆ é™¤ć€‚ No comment provided by engineer. @@ -1642,6 +1714,7 @@ This is your own one-time link! Contact will be deleted - this cannot be undone! + č”ē³»äŗŗå°†č¢«åˆ é™¤-čæ™ę˜Æę— ę³•ę’¤ę¶ˆēš„ļ¼ No comment provided by engineer. @@ -1661,6 +1734,7 @@ This is your own one-time link! Conversation deleted! + åÆ¹čÆå·²åˆ é™¤ļ¼ No comment provided by engineer. @@ -1670,6 +1744,7 @@ This is your own one-time link! Copy error + å¤åˆ¶é”™čÆÆ No comment provided by engineer. @@ -1683,6 +1758,7 @@ This is your own one-time link! Correct name to %@? + å°†åē§°ę›“ę­£äøŗ %@? No comment provided by engineer. @@ -1697,7 +1773,7 @@ This is your own one-time link! Create a group using a random profile. - ä½æē”Øéšęœŗčŗ«ä»½åˆ›å»ŗē¾¤ē»„ + ä½æē”Øéšęœŗčŗ«ä»½åˆ›å»ŗē¾¤ē»„. No comment provided by engineer. @@ -1752,6 +1828,7 @@ This is your own one-time link! Created + å·²åˆ›å»ŗ No comment provided by engineer. @@ -1761,6 +1838,7 @@ This is your own one-time link! Created at: %@ + åˆ›å»ŗäŗŽļ¼š%@ copied message info @@ -1790,6 +1868,7 @@ This is your own one-time link! Current profile + å½“å‰é…ē½®ę–‡ä»¶ No comment provided by engineer. @@ -1804,6 +1883,7 @@ This is your own one-time link! Customize theme + č‡Ŗå®šä¹‰äø»é¢˜ No comment provided by engineer. @@ -1813,6 +1893,7 @@ This is your own one-time link! Dark mode colors + ę·±č‰²ęØ”å¼é¢œč‰² No comment provided by engineer. @@ -1915,6 +1996,7 @@ This is your own one-time link! Debug delivery + č°ƒčÆ•äŗ¤ä»˜ No comment provided by engineer. @@ -1935,10 +2017,12 @@ This is your own one-time link! Delete %lld messages of members? + åˆ é™¤ęˆå‘˜ēš„ %lld 消息? No comment provided by engineer. Delete %lld messages? + 删除 %lld 消息? No comment provided by engineer. @@ -1998,6 +2082,7 @@ This is your own one-time link! Delete contact? + åˆ é™¤č”ē³»äŗŗļ¼Ÿ No comment provided by engineer. @@ -2107,6 +2192,7 @@ This is your own one-time link! Delete up to 20 messages at once. + äø€ę¬”ęœ€å¤šåˆ é™¤ 20 ę”äæ”ęÆć€‚ No comment provided by engineer. @@ -2116,10 +2202,12 @@ This is your own one-time link! Delete without notification + åˆ é™¤č€Œäøé€šēŸ„ No comment provided by engineer. Deleted + 已删除 No comment provided by engineer. @@ -2134,6 +2222,7 @@ This is your own one-time link! Deletion errors + åˆ é™¤é”™čÆÆ No comment provided by engineer. @@ -2163,6 +2252,7 @@ This is your own one-time link! Desktop app version %@ is not compatible with this app. + ę”Œé¢åŗ”ē”ØēØ‹åŗē‰ˆęœ¬ %@ äøŽę­¤åŗ”ē”ØēØ‹åŗäøå…¼å®¹ć€‚ No comment provided by engineer. @@ -2172,22 +2262,27 @@ This is your own one-time link! Destination server address of %@ is incompatible with forwarding server %@ settings. + ē›®ę ‡ęœåŠ”å™Øåœ°å€ %@ äøŽč½¬å‘ęœåŠ”å™Ø %@ č®¾ē½®äøå…¼å®¹ć€‚ No comment provided by engineer. Destination server error: %@ + ē›®ę ‡ęœåŠ”å™Øé”™čÆÆļ¼š%@ snd error text Destination server version of %@ is incompatible with forwarding server %@. + ē›®ę ‡ęœåŠ”å™Øē‰ˆęœ¬ %@ äøŽč½¬å‘ęœåŠ”å™Ø %@ äøå…¼å®¹ć€‚ No comment provided by engineer. Detailed statistics + čÆ¦ē»†ēš„ē»Ÿč®”ę•°ę® No comment provided by engineer. Details + 详细俔息 No comment provided by engineer. @@ -2197,6 +2292,7 @@ This is your own one-time link! Developer options + å¼€å‘č€…é€‰é”¹ No comment provided by engineer. @@ -2251,6 +2347,7 @@ This is your own one-time link! Disabled + 禁用 No comment provided by engineer. @@ -2305,6 +2402,7 @@ This is your own one-time link! Do NOT send messages directly, even if your or destination server does not support private routing. + čÆ·å‹æē›“ęŽ„å‘é€ę¶ˆęÆļ¼Œå³ä½æę‚Øēš„ęœåŠ”å™Øęˆ–ē›®ę ‡ęœåŠ”å™Øäøę”ÆęŒē§ęœ‰č·Æē”±ć€‚ No comment provided by engineer. @@ -2314,6 +2412,7 @@ This is your own one-time link! Do NOT use private routing. + äøč¦ä½æē”Øē§ęœ‰č·Æē”±ć€‚ No comment provided by engineer. @@ -2353,6 +2452,7 @@ This is your own one-time link! Download errors + 下载错误 No comment provided by engineer. @@ -2367,10 +2467,12 @@ This is your own one-time link! Downloaded + 已下载 No comment provided by engineer. Downloaded files + äø‹č½½ēš„ę–‡ä»¶ No comment provided by engineer. @@ -2475,6 +2577,7 @@ This is your own one-time link! Enabled + 已启用 No comment provided by engineer. @@ -2514,6 +2617,7 @@ This is your own one-time link! Encrypted message: app is stopped + åŠ åÆ†ę¶ˆęÆļ¼šåŗ”ē”ØēØ‹åŗå·²åœę­¢ notification @@ -2563,6 +2667,7 @@ This is your own one-time link! Enter group name… + č¾“å…„ē»„åē§°ā€¦ No comment provided by engineer. @@ -2602,6 +2707,7 @@ This is your own one-time link! Enter your name… + čÆ·č¾“å…„ę‚Øēš„å§“åā€¦ No comment provided by engineer. @@ -2654,6 +2760,7 @@ This is your own one-time link! Error connecting to forwarding server %@. Please try later. + čæžęŽ„åˆ°č½¬å‘ęœåŠ”å™Ø %@ ę—¶å‡ŗé”™ć€‚čÆ·ēØåŽå°čÆ•ć€‚ No comment provided by engineer. @@ -2753,6 +2860,7 @@ This is your own one-time link! Error exporting theme: %@ + åÆ¼å‡ŗäø»é¢˜ę—¶å‡ŗé”™ļ¼š %@ No comment provided by engineer. @@ -2776,6 +2884,7 @@ This is your own one-time link! Error opening chat + ę‰“å¼€čŠå¤©ę—¶å‡ŗé”™ No comment provided by engineer. @@ -2785,10 +2894,12 @@ This is your own one-time link! Error reconnecting server + é‡ę–°čæžęŽ„ęœåŠ”å™Øę—¶å‡ŗé”™ No comment provided by engineer. Error reconnecting servers + é‡ę–°čæžęŽ„ęœåŠ”å™Øę—¶å‡ŗé”™ No comment provided by engineer. @@ -2798,6 +2909,7 @@ This is your own one-time link! Error resetting statistics + é‡ē½®ē»Ÿč®”äæ”ęÆę—¶å‡ŗé”™ No comment provided by engineer. @@ -2837,6 +2949,7 @@ This is your own one-time link! Error scanning code: %@ + ę‰«ęä»£ē ę—¶å‡ŗé”™ļ¼š%@ No comment provided by engineer. @@ -2936,6 +3049,7 @@ This is your own one-time link! Errors + 错误 No comment provided by engineer. @@ -2965,6 +3079,7 @@ This is your own one-time link! Export theme + åÆ¼å‡ŗäø»é¢˜ No comment provided by engineer. @@ -3004,22 +3119,27 @@ This is your own one-time link! File error + 文件错误 No comment provided by engineer. File not found - most likely file was deleted or cancelled. + ę‰¾äøåˆ°ę–‡ä»¶ - å¾ˆåÆčƒ½ę–‡ä»¶å·²č¢«åˆ é™¤ęˆ–å–ę¶ˆć€‚ file error text File server error: %@ + ę–‡ä»¶ęœåŠ”å™Øé”™čÆÆļ¼š%@ file error text File status + ę–‡ä»¶ēŠ¶ę€ No comment provided by engineer. File status: %@ + ę–‡ä»¶ēŠ¶ę€ļ¼š%@ copied message info @@ -3044,6 +3164,7 @@ This is your own one-time link! Files + ꖇ件 No comment provided by engineer. @@ -3083,7 +3204,7 @@ This is your own one-time link! Finalize migration on another device. - åœØå¦äø€éƒØč®¾å¤‡äøŠå®Œęˆčæē§» + åœØå¦äø€éƒØč®¾å¤‡äøŠå®Œęˆčæē§». No comment provided by engineer. @@ -3153,24 +3274,31 @@ This is your own one-time link! Forwarding server %@ failed to connect to destination server %@. Please try later. + č½¬å‘ęœåŠ”å™Ø %@ ę— ę³•čæžęŽ„åˆ°ē›®ę ‡ęœåŠ”å™Ø %@ć€‚čÆ·ēØåŽå°čÆ•ć€‚ No comment provided by engineer. Forwarding server address is incompatible with network settings: %@. + č½¬å‘ęœåŠ”å™Øåœ°å€äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ļ¼š%@怂 No comment provided by engineer. Forwarding server version is incompatible with network settings: %@. + č½¬å‘ęœåŠ”å™Øē‰ˆęœ¬äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ļ¼š%@怂 No comment provided by engineer. Forwarding server: %1$@ Destination server error: %2$@ + č½¬å‘ęœåŠ”å™Øļ¼š %1$@ +ē›®ę ‡ęœåŠ”å™Øé”™čÆÆļ¼š %2$@ snd error text Forwarding server: %1$@ Error: %2$@ + č½¬å‘ęœåŠ”å™Øļ¼š %1$@ +é”™čÆÆļ¼š %2$@ snd error text @@ -3215,10 +3343,12 @@ Error: %2$@ Good afternoon! + äø‹åˆå„½ļ¼ message preview Good morning! + ę—©äøŠå„½ļ¼ message preview @@ -3228,6 +3358,7 @@ Error: %2$@ Group already exists + ē¾¤ē»„å·²å­˜åœØ No comment provided by engineer. @@ -3282,7 +3413,7 @@ Error: %2$@ Group members can irreversibly delete sent messages. (24 hours) - ē¾¤ē»„ęˆå‘˜åÆä»„äøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆć€‚ + ē¾¤ē»„ęˆå‘˜åÆä»„äøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆ No comment provided by engineer. @@ -3427,6 +3558,7 @@ Error: %2$@ Hungarian interface + åŒˆē‰™åˆ©čÆ­ē•Œé¢ No comment provided by engineer. @@ -3501,6 +3633,7 @@ Error: %2$@ Import theme + åÆ¼å…„äø»é¢˜ No comment provided by engineer. @@ -3627,6 +3760,7 @@ Error: %2$@ Interface colors + ē•Œé¢é¢œč‰² No comment provided by engineer. @@ -3661,6 +3795,7 @@ Error: %2$@ Invalid response + ę— ę•ˆēš„å“åŗ” No comment provided by engineer. @@ -3731,6 +3866,7 @@ Error: %2$@ It protects your IP address and connections. + å®ƒåÆä»„äæęŠ¤ę‚Øēš„ IP åœ°å€å’ŒčæžęŽ„ć€‚ No comment provided by engineer. @@ -3775,11 +3911,14 @@ Error: %2$@ Join with current profile + ä½æē”Øå½“å‰ę”£ę”ˆåŠ å…„ No comment provided by engineer. Join your group? This is your link for group %@! + åŠ å…„ę‚Øēš„ē¾¤ē»„ļ¼Ÿ +čæ™ę˜Æę‚Øē»„ %@ ēš„é“¾ęŽ„ļ¼ No comment provided by engineer. @@ -3794,10 +3933,12 @@ This is your link for group %@! Keep conversation + äæęŒåÆ¹čÆ No comment provided by engineer. Keep the app open to use it from desktop + äæęŒåŗ”ē”ØēØ‹åŗę‰“å¼€ēŠ¶ę€ä»„ä»Žę”Œé¢ä½æē”Øå®ƒ No comment provided by engineer. @@ -3972,10 +4113,12 @@ This is your link for group %@! Media & file servers + Media & file servers No comment provided by engineer. Medium + äø­ē­‰ blur media @@ -3985,6 +4128,7 @@ This is your link for group %@! Member inactive + ęˆå‘˜äøę“»č·ƒ item status text @@ -4004,6 +4148,7 @@ This is your link for group %@! Menus + čœå• No comment provided by engineer. @@ -4018,6 +4163,7 @@ This is your link for group %@! Message delivery warning + ę¶ˆęÆä¼ é€’č­¦å‘Š item status text @@ -4027,14 +4173,17 @@ This is your link for group %@! Message forwarded + ę¶ˆęÆå·²č½¬å‘ item status text Message may be delivered later if member becomes active. + å¦‚ęžœ member å˜äøŗę“»åŠØēŠ¶ę€ļ¼Œåˆ™ēØåŽåÆčƒ½ä¼šå‘é€ę¶ˆęÆć€‚ item status description Message queue info + ę¶ˆęÆé˜Ÿåˆ—äæ”ęÆ No comment provided by engineer. @@ -4054,10 +4203,12 @@ This is your link for group %@! Message reception + ę¶ˆęÆęŽ„ę”¶ No comment provided by engineer. Message servers + ę¶ˆęÆęœåŠ”å™Ø No comment provided by engineer. @@ -4071,10 +4222,12 @@ This is your link for group %@! Message status + ę¶ˆęÆēŠ¶ę€ No comment provided by engineer. Message status: %@ + ę¶ˆęÆēŠ¶ę€ļ¼š%@ copied message info @@ -4099,22 +4252,27 @@ This is your link for group %@! Messages from %@ will be shown! + å°†ę˜¾ē¤ŗę„č‡Ŗ %@ ēš„ę¶ˆęÆļ¼ No comment provided by engineer. Messages received + ę”¶åˆ°ēš„ę¶ˆęÆ No comment provided by engineer. Messages sent + å·²å‘é€ēš„ę¶ˆęÆ No comment provided by engineer. Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + ę¶ˆęÆć€ę–‡ä»¶å’Œé€ščÆå—åˆ° **ē«Æåˆ°ē«ÆåŠ åÆ†** ēš„äæęŠ¤ļ¼Œå…·ęœ‰å®Œå…Øę­£å‘äæåÆ†ć€å¦č®¤å’Œé—Æå…„ę¢å¤ć€‚ No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + ę¶ˆęÆć€ę–‡ä»¶å’Œé€ščÆå—åˆ° **ęŠ—é‡å­ e2e åŠ åÆ†** ēš„äæęŠ¤ļ¼Œå…·ęœ‰å®Œå…Øę­£å‘äæåÆ†ć€å¦č®¤å’Œé—Æå…„ę¢å¤ć€‚ No comment provided by engineer. @@ -4239,6 +4397,7 @@ This is your link for group %@! Network issues - message expired after many attempts to send it. + ē½‘ē»œé—®é¢˜ - ę¶ˆęÆåœØå¤šę¬”å°čÆ•å‘é€åŽčæ‡ęœŸć€‚ snd error text @@ -4268,6 +4427,7 @@ This is your link for group %@! New chat experience šŸŽ‰ + ę–°ēš„čŠå¤©ä½“éŖŒ šŸŽ‰ No comment provided by engineer. @@ -4302,6 +4462,7 @@ This is your link for group %@! New media options + 新媒体选锹 No comment provided by engineer. @@ -4351,6 +4512,7 @@ This is your link for group %@! No direct connection yet, message is forwarded by admin. + čæ˜ę²”ęœ‰ē›“ęŽ„čæžęŽ„ļ¼Œę¶ˆęÆē”±ē®”ē†å‘˜č½¬å‘ć€‚ item status description @@ -4370,6 +4532,7 @@ This is your link for group %@! No info, try to reload + ę— äæ”ęÆļ¼Œå°čÆ•é‡ę–°åŠ č½½ No comment provided by engineer. @@ -4394,6 +4557,7 @@ This is your link for group %@! Nothing selected + ęœŖé€‰äø­ä»»ä½•å†…å®¹ No comment provided by engineer. @@ -4448,13 +4612,15 @@ This is your link for group %@! Onion hosts will be **required** for connection. Requires compatible VPN. - Onion äø»ęœŗå°†ē”ØäŗŽčæžęŽ„ć€‚éœ€č¦åÆē”Ø VPN怂 + Onion äø»ęœŗå°†ę˜ÆčæžęŽ„ę‰€åæ…éœ€ēš„ć€‚ +éœ€č¦å…¼å®¹ēš„ VPN怂 No comment provided by engineer. Onion hosts will be used when available. Requires compatible VPN. - å½“åÆē”Øę—¶ļ¼Œå°†ä½æē”Ø Onion äø»ęœŗć€‚éœ€č¦åÆē”Ø VPN怂 + å¦‚ęžœåÆē”Øļ¼Œå°†ä½æē”Øę“‹č‘±äø»ęœŗć€‚ +éœ€č¦å…¼å®¹ēš„ VPN怂 No comment provided by engineer. @@ -4469,6 +4635,7 @@ Requires compatible VPN. Only delete conversation + ä»…åˆ é™¤åÆ¹čÆ No comment provided by engineer. @@ -4493,7 +4660,7 @@ Requires compatible VPN. Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) - åŖęœ‰ę‚ØåÆä»„äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚Øēš„č”ē³»äŗŗåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰ć€‚ + åŖęœ‰ę‚ØåÆä»„äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚Øēš„č”ē³»äŗŗåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰ No comment provided by engineer. @@ -4518,7 +4685,7 @@ Requires compatible VPN. Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) - åŖęœ‰ę‚Øēš„č”ē³»äŗŗę‰čƒ½äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚ØåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰ć€‚ + åŖęœ‰ę‚Øēš„č”ē³»äŗŗę‰čƒ½äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚ØåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰ No comment provided by engineer. @@ -4563,10 +4730,12 @@ Requires compatible VPN. Open migration to another device + ę‰“å¼€čæē§»åˆ°å¦äø€å°č®¾å¤‡ authentication reason Open server settings + ę‰“å¼€ęœåŠ”å™Øč®¾ē½® No comment provided by engineer. @@ -4581,6 +4750,7 @@ Requires compatible VPN. Opening app… + ę­£åœØę‰“å¼€åŗ”ē”ØēØ‹åŗā€¦ No comment provided by engineer. @@ -4610,6 +4780,7 @@ Requires compatible VPN. Other %@ servers + 其他 %@ ęœåŠ”å™Ø No comment provided by engineer. @@ -4654,6 +4825,7 @@ Requires compatible VPN. Past member %@ + å‰ä»»ęˆå‘˜ %@ past/unknown group member @@ -4678,6 +4850,7 @@ Requires compatible VPN. Pending + 待定 No comment provided by engineer. @@ -4702,10 +4875,12 @@ Requires compatible VPN. Play from the chat list. + ä»ŽčŠå¤©åˆ—č”Øę’­ę”¾ć€‚ No comment provided by engineer. Please ask your contact to enable calls. + čÆ·č¦ę±‚ę‚Øēš„č”ē³»äŗŗå¼€é€šé€ščÆåŠŸčƒ½ć€‚ No comment provided by engineer. @@ -4716,6 +4891,8 @@ Requires compatible VPN. Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + čÆ·ę£€ęŸ„ē§»åŠØč®¾å¤‡å’Œę”Œé¢ę˜Æå¦čæžęŽ„åˆ°åŒäø€ęœ¬åœ°ē½‘ē»œļ¼Œä»„åŠę”Œé¢é˜²ē«å¢™ę˜Æå¦å…č®øčæžęŽ„ć€‚ +čÆ·äøŽå¼€å‘äŗŗå‘˜åˆ†äŗ«ä»»ä½•å…¶ä»–é—®é¢˜ć€‚ No comment provided by engineer. @@ -4741,6 +4918,8 @@ Please share any other issues with the developers. Please contact developers. Error: %@ + čÆ·č”ē³»å¼€å‘äŗŗå‘˜ć€‚ +é”™čÆÆļ¼š%@ No comment provided by engineer. @@ -4815,6 +4994,7 @@ Error: %@ Previously connected servers + ä»„å‰čæžęŽ„ēš„ęœåŠ”å™Ø No comment provided by engineer. @@ -4834,10 +5014,12 @@ Error: %@ Private message routing + ē§ęœ‰ę¶ˆęÆč·Æē”± No comment provided by engineer. Private message routing šŸš€ + ē§ęœ‰ę¶ˆęÆč·Æē”± šŸš€ No comment provided by engineer. @@ -4847,10 +5029,12 @@ Error: %@ Private routing + 专用路由 No comment provided by engineer. Private routing error + 专用路由错误 No comment provided by engineer. @@ -4875,6 +5059,7 @@ Error: %@ Profile theme + äøŖäŗŗčµ„ę–™äø»é¢˜ No comment provided by engineer. @@ -4904,6 +5089,7 @@ Error: %@ Prohibit sending SimpleX links. + ē¦ę­¢å‘é€ SimpleX é“¾ęŽ„ć€‚ No comment provided by engineer. @@ -4928,6 +5114,7 @@ Error: %@ Protect IP address + äæęŠ¤ IP 地址 No comment provided by engineer. @@ -4938,6 +5125,8 @@ Error: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. + äæęŠ¤ę‚Øēš„ IP åœ°å€å…å—č”ē³»äŗŗé€‰ę‹©ēš„ę¶ˆęÆäø­ē»§ēš„ę”»å‡»ć€‚ +在*ē½‘ē»œå’ŒęœåŠ”å™Ø*设置中启用。 No comment provided by engineer. @@ -4957,10 +5146,12 @@ Enable in *Network & servers* settings. Proxied + 代理 No comment provided by engineer. Proxied servers + ä»£ē†ęœåŠ”å™Ø No comment provided by engineer. @@ -4970,6 +5161,7 @@ Enable in *Network & servers* settings. Push server + ęŽØé€ęœåŠ”å™Ø No comment provided by engineer. @@ -4984,6 +5176,7 @@ Enable in *Network & servers* settings. Reachable chat toolbar + åÆč®æé—®ēš„čŠå¤©å·„å…·ę  No comment provided by engineer. @@ -5033,6 +5226,7 @@ Enable in *Network & servers* settings. Receive errors + ęŽ„ę”¶é”™čÆÆ No comment provided by engineer. @@ -5057,14 +5251,17 @@ Enable in *Network & servers* settings. Received messages + ę”¶åˆ°ēš„ę¶ˆęÆ No comment provided by engineer. Received reply + å·²ę”¶åˆ°å›žå¤ No comment provided by engineer. Received total + ꎄꔶꀻꕰ No comment provided by engineer. @@ -5084,6 +5281,7 @@ Enable in *Network & servers* settings. Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + ęœ€čæ‘ēš„åŽ†å²č®°å½•å’Œę”¹čæ›ēš„ [ē›®å½•ęœŗå™Øäŗŗ](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). No comment provided by engineer. @@ -5098,6 +5296,7 @@ Enable in *Network & servers* settings. Reconnect + é‡ę–°čæžęŽ„ No comment provided by engineer. @@ -5107,18 +5306,22 @@ Enable in *Network & servers* settings. Reconnect all servers + é‡ę–°čæžęŽ„ę‰€ęœ‰ęœåŠ”å™Ø No comment provided by engineer. Reconnect all servers? + é‡ę–°čæžęŽ„ę‰€ęœ‰ęœåŠ”å™Øļ¼Ÿ No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + é‡ę–°čæžęŽ„ęœåŠ”å™Øä»„å¼ŗåˆ¶å‘é€äæ”ęÆć€‚å®ƒä½æē”Øé¢å¤–ēš„ęµé‡ć€‚ No comment provided by engineer. Reconnect server? + é‡ę–°čæžęŽ„ęœåŠ”å™Øļ¼Ÿ No comment provided by engineer. @@ -5178,6 +5381,7 @@ Enable in *Network & servers* settings. Remove image + 移除图片 No comment provided by engineer. @@ -5252,14 +5456,17 @@ Enable in *Network & servers* settings. Reset all hints + é‡ē½®ę‰€ęœ‰ęē¤ŗ No comment provided by engineer. Reset all statistics + é‡ē½®ę‰€ęœ‰ē»Ÿč®”äæ”ęÆ No comment provided by engineer. Reset all statistics? + é‡ē½®ę‰€ęœ‰ē»Ÿč®”äæ”ęÆļ¼Ÿ No comment provided by engineer. @@ -5269,6 +5476,7 @@ Enable in *Network & servers* settings. Reset to app theme + é‡ē½®äøŗåŗ”ē”ØēØ‹åŗäø»é¢˜ No comment provided by engineer. @@ -5278,6 +5486,7 @@ Enable in *Network & servers* settings. Reset to user theme + é‡ē½®äøŗē”Øęˆ·äø»é¢˜ No comment provided by engineer. @@ -5347,10 +5556,12 @@ Enable in *Network & servers* settings. SMP server + SMP ęœåŠ”å™Ø No comment provided by engineer. Safely receive files + å®‰å…ØęŽ„ę”¶ę–‡ä»¶ No comment provided by engineer. @@ -5381,6 +5592,7 @@ Enable in *Network & servers* settings. Save and reconnect + äæå­˜å¹¶é‡ę–°čæžęŽ„ No comment provided by engineer. @@ -5459,10 +5671,12 @@ Enable in *Network & servers* settings. Scale + 规樔 No comment provided by engineer. Scan / Paste link + ę‰«ę / ē²˜č““é“¾ęŽ„ No comment provided by engineer. @@ -5507,6 +5721,7 @@ Enable in *Network & servers* settings. Secondary + 二级 No comment provided by engineer. @@ -5516,6 +5731,7 @@ Enable in *Network & servers* settings. Secured + ę‹…äæ No comment provided by engineer. @@ -5539,10 +5755,12 @@ Enable in *Network & servers* settings. Selected %lld + é€‰å®šēš„ %lld No comment provided by engineer. Selected chat preferences prohibit this message. + é€‰å®šēš„čŠå¤©é¦–é€‰é”¹ē¦ę­¢ę­¤ę¶ˆęÆć€‚ No comment provided by engineer. @@ -5592,6 +5810,7 @@ Enable in *Network & servers* settings. Send errors + å‘é€é”™čÆÆ No comment provided by engineer. @@ -5606,14 +5825,17 @@ Enable in *Network & servers* settings. Send message to enable calls. + å‘é€ę¶ˆęÆä»„åÆē”Øå‘¼å«ć€‚ No comment provided by engineer. Send messages directly when IP address is protected and your or destination server does not support private routing. + 当 IP åœ°å€å—åˆ°äæęŠ¤å¹¶äø”ę‚Øęˆ–ē›®ę ‡ęœåŠ”å™Øäøę”ÆęŒē§ęœ‰č·Æē”±ę—¶ļ¼Œē›“ęŽ„å‘é€ę¶ˆęÆć€‚ No comment provided by engineer. Send messages directly when your or destination server does not support private routing. + å½“ę‚Øęˆ–ē›®ę ‡ęœåŠ”å™Øäøę”ÆęŒē§ęœ‰č·Æē”±ę—¶ļ¼Œē›“ęŽ„å‘é€ę¶ˆęÆć€‚ No comment provided by engineer. @@ -5708,6 +5930,7 @@ Enable in *Network & servers* settings. Sent directly + ē›“ęŽ„å‘é€ No comment provided by engineer. @@ -5722,6 +5945,7 @@ Enable in *Network & servers* settings. Sent messages + å·²å‘é€ēš„ę¶ˆęÆ No comment provided by engineer. @@ -5731,26 +5955,32 @@ Enable in *Network & servers* settings. Sent reply + 已发送回复 No comment provided by engineer. Sent total + å‘é€ę€»ę•° No comment provided by engineer. Sent via proxy + é€ščæ‡ä»£ē†å‘é€ No comment provided by engineer. Server address + ęœåŠ”å™Øåœ°å€ No comment provided by engineer. Server address is incompatible with network settings. + ęœåŠ”å™Øåœ°å€äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ć€‚ srv error text. Server address is incompatible with network settings: %@. + ęœåŠ”å™Øåœ°å€äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ļ¼š%@怂 No comment provided by engineer. @@ -5770,14 +6000,17 @@ Enable in *Network & servers* settings. Server type + ęœåŠ”å™Øē±»åž‹ No comment provided by engineer. Server version is incompatible with network settings. + ęœåŠ”å™Øē‰ˆęœ¬äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ć€‚ srv error text Server version is incompatible with your app: %@. + ęœåŠ”å™Øē‰ˆęœ¬äøŽä½ ēš„åŗ”ē”ØēØ‹åŗäøå…¼å®¹ļ¼š%@怂 No comment provided by engineer. @@ -5787,10 +6020,12 @@ Enable in *Network & servers* settings. Servers info + ęœåŠ”å™Øäæ”ęÆ No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + ęœåŠ”å™Øē»Ÿč®”äæ”ęÆå°†č¢«é‡ē½® - ę­¤ę“ä½œę— ę³•ę’¤ę¶ˆļ¼ No comment provided by engineer. @@ -5810,6 +6045,7 @@ Enable in *Network & servers* settings. Set default theme + 设置默认主题 No comment provided by engineer. @@ -5883,6 +6119,7 @@ Enable in *Network & servers* settings. Share from other apps. + ä»Žå…¶ä»–åŗ”ē”ØēØ‹åŗå…±äŗ«ć€‚ No comment provided by engineer. @@ -5901,6 +6138,7 @@ Enable in *Network & servers* settings. Share to SimpleX + åˆ†äŗ«åˆ° SimpleX No comment provided by engineer. @@ -5930,10 +6168,12 @@ Enable in *Network & servers* settings. Show message status + ę˜¾ē¤ŗę¶ˆęÆēŠ¶ę€ No comment provided by engineer. Show percentage + ę˜¾ē¤ŗē™¾åˆ†ęÆ” No comment provided by engineer. @@ -5943,6 +6183,7 @@ Enable in *Network & servers* settings. Show → on messages sent via private routing. + 显示 → é€ščæ‡äø“ē”Øč·Æē”±å‘é€ēš„äæ”ęÆ. No comment provided by engineer. @@ -5952,6 +6193,7 @@ Enable in *Network & servers* settings. SimpleX + SimpleX No comment provided by engineer. @@ -6031,6 +6273,7 @@ Enable in *Network & servers* settings. Size + 大小 No comment provided by engineer. @@ -6050,6 +6293,7 @@ Enable in *Network & servers* settings. Soft + 软 blur media @@ -6058,6 +6302,7 @@ Enable in *Network & servers* settings. Some file(s) were not exported: + ęŸäŗ›ę–‡ä»¶ęœŖåÆ¼å‡ŗļ¼š No comment provided by engineer. @@ -6067,6 +6312,7 @@ Enable in *Network & servers* settings. Some non-fatal errors occurred during import: + åÆ¼å…„čæ‡ēØ‹äø­å‡ŗēŽ°äø€äŗ›éžč‡“å‘½é”™čÆÆļ¼š No comment provided by engineer. @@ -6076,7 +6322,7 @@ Enable in *Network & servers* settings. Square, circle, or anything in between. - ę–¹å½¢ć€åœ†å½¢ć€ęˆ–äø¤č€…ä¹‹é—“ēš„ä»»ę„å½¢ēŠ¶ + ę–¹å½¢ć€åœ†å½¢ć€ęˆ–äø¤č€…ä¹‹é—“ēš„ä»»ę„å½¢ēŠ¶. No comment provided by engineer. @@ -6096,10 +6342,12 @@ Enable in *Network & servers* settings. Starting from %@. + 从 %@ 开始。 No comment provided by engineer. Statistics + 统讔 No comment provided by engineer. @@ -6164,6 +6412,7 @@ Enable in *Network & servers* settings. Strong + åŠ ē²— blur media @@ -6173,14 +6422,17 @@ Enable in *Network & servers* settings. Subscribed + å·²č®¢é˜… No comment provided by engineer. Subscription errors + č®¢é˜…é”™čÆÆ No comment provided by engineer. Subscriptions ignored + åæ½ē•„č®¢é˜… No comment provided by engineer. @@ -6200,6 +6452,7 @@ Enable in *Network & servers* settings. TCP connection + TCP čæžęŽ„ No comment provided by engineer. @@ -6268,6 +6521,7 @@ Enable in *Network & servers* settings. Temporary file error + 专时文件错误 No comment provided by engineer. @@ -6324,6 +6578,7 @@ It can happen because of some bug or when the connection is compromised. The app will ask to confirm downloads from unknown file servers (except .onion). + čÆ„åŗ”ē”ØēØ‹åŗå°†č¦ę±‚ē”®č®¤ä»ŽęœŖēŸ„ę–‡ä»¶ęœåŠ”å™Øļ¼ˆ.onion 除外)下载。 No comment provided by engineer. @@ -6373,10 +6628,12 @@ It can happen because of some bug or when the connection is compromised. The messages will be deleted for all members. + å°†åˆ é™¤ę‰€ęœ‰ęˆå‘˜ēš„ę¶ˆęÆć€‚ No comment provided by engineer. The messages will be marked as moderated for all members. + åÆ¹äŗŽę‰€ęœ‰ęˆå‘˜ļ¼Œčæ™äŗ›ę¶ˆęÆå°†č¢«ę ‡č®°äøŗå·²å®”ę øć€‚ No comment provided by engineer. @@ -6420,6 +6677,7 @@ It can happen because of some bug or when the connection is compromised. Themes + 主题 No comment provided by engineer. @@ -6489,6 +6747,7 @@ It can happen because of some bug or when the connection is compromised. This link was used with another mobile device, please create a new link on the desktop. + ę­¤é“¾ęŽ„å·²åœØå…¶ä»–ē§»åŠØč®¾å¤‡äøŠä½æē”Øļ¼ŒčÆ·åœØę”Œé¢äøŠåˆ›å»ŗę–°é“¾ęŽ„ć€‚ No comment provided by engineer. @@ -6498,6 +6757,7 @@ It can happen because of some bug or when the connection is compromised. Title + ę ‡é¢˜ No comment provided by engineer. @@ -6532,6 +6792,7 @@ It can happen because of some bug or when the connection is compromised. To protect your IP address, private routing uses your SMP servers to deliver messages. + äøŗäŗ†äæęŠ¤ę‚Øēš„ IP åœ°å€ļ¼Œē§ęœ‰č·Æē”±ä½æē”Øę‚Øēš„ SMP ęœåŠ”å™Øę„ä¼ é€’é‚®ä»¶ć€‚ No comment provided by engineer. @@ -6563,6 +6824,7 @@ You will be prompted to complete authentication before this feature is enabled.< Toggle chat list: + åˆ‡ę¢čŠå¤©åˆ—č”Øļ¼š No comment provided by engineer. @@ -6572,10 +6834,12 @@ You will be prompted to complete authentication before this feature is enabled.< Toolbar opacity + å·„å…·ę äøé€ę˜Žåŗ¦ No comment provided by engineer. Total + 共讔 No comment provided by engineer. @@ -6585,6 +6849,7 @@ You will be prompted to complete authentication before this feature is enabled.< Transport sessions + ä¼ č¾“ä¼ščÆ No comment provided by engineer. @@ -6599,6 +6864,7 @@ You will be prompted to complete authentication before this feature is enabled.< Turkish interface + åœŸč€³å…¶čÆ­ē•Œé¢ No comment provided by engineer. @@ -6688,6 +6954,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! + ęœŖēŸ„ęœåŠ”å™Øļ¼ No comment provided by engineer. @@ -6754,6 +7021,7 @@ To connect, please ask your contact to create another connection link and check Update settings? + ę›“ę–°č®¾ē½®ļ¼Ÿ No comment provided by engineer. @@ -6768,6 +7036,7 @@ To connect, please ask your contact to create another connection link and check Upload errors + äøŠä¼ é”™čÆÆ No comment provided by engineer. @@ -6782,10 +7051,12 @@ To connect, please ask your contact to create another connection link and check Uploaded + 已上传 No comment provided by engineer. Uploaded files + å·²äøŠä¼ ēš„ę–‡ä»¶ No comment provided by engineer. @@ -6835,14 +7106,17 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? + ä»…ä½æē”Øęœ¬åœ°é€šēŸ„ļ¼Ÿ No comment provided by engineer. Use private routing with unknown servers when IP address is not protected. + 当 IP åœ°å€äøå—äæęŠ¤ę—¶ļ¼ŒåÆ¹ęœŖēŸ„ęœåŠ”å™Øä½æē”Øē§ęœ‰č·Æē”±ć€‚ No comment provided by engineer. Use private routing with unknown servers. + åÆ¹ęœŖēŸ„ęœåŠ”å™Øä½æē”Øē§ęœ‰č·Æē”±ć€‚ No comment provided by engineer. @@ -6852,11 +7126,12 @@ To connect, please ask your contact to create another connection link and check Use the app while in the call. - é€ščÆę—¶ä½æē”Øęœ¬åŗ”ē”Ø + é€ščÆę—¶ä½æē”Øęœ¬åŗ”ē”Ø. No comment provided by engineer. Use the app with one hand. + ē”Øäø€åŖę‰‹ä½æē”Øåŗ”ē”ØēØ‹åŗć€‚ No comment provided by engineer. @@ -6866,6 +7141,7 @@ To connect, please ask your contact to create another connection link and check User selection + ē”Øęˆ·é€‰ę‹© No comment provided by engineer. @@ -6980,6 +7256,7 @@ To connect, please ask your contact to create another connection link and check Waiting for desktop... + ę­£åœØē­‰å¾…ę”Œé¢... No comment provided by engineer. @@ -6999,15 +7276,17 @@ To connect, please ask your contact to create another connection link and check Wallpaper accent + 壁纸装鄰 No comment provided by engineer. Wallpaper background + å£ēŗøčƒŒę™Æ No comment provided by engineer. Warning: starting chat on multiple devices is not supported and will cause message delivery failures - č­¦å‘Šļ¼šäøę”ÆęŒåœØå¤šéƒØč®¾å¤‡äøŠåÆåŠØčŠå¤©ļ¼Œčæ™ä¹ˆåšä¼šåÆ¼č‡“ę¶ˆęÆä¼ é€å¤±č“„ć€‚ + č­¦å‘Šļ¼šäøę”ÆęŒåœØå¤šéƒØč®¾å¤‡äøŠåÆåŠØčŠå¤©ļ¼Œčæ™ä¹ˆåšä¼šåÆ¼č‡“ę¶ˆęÆä¼ é€å¤±č“„ No comment provided by engineer. @@ -7092,10 +7371,12 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to file servers. + å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ę–‡ä»¶ęœåŠ”å™ØåÆč§ć€‚ No comment provided by engineer. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ä»„äø‹ XFTP äø­ē»§åÆč§ļ¼š%@怂 No comment provided by engineer. @@ -7105,10 +7386,12 @@ To connect, please ask your contact to create another connection link and check Wrong key or unknown connection - most likely this connection is deleted. + åÆ†é’„é”™čÆÆęˆ–čæžęŽ„ęœŖēŸ„ - å¾ˆåÆčƒ½ę­¤čæžęŽ„å·²č¢«åˆ é™¤ć€‚ snd error text Wrong key or unknown file chunk address - most likely file is deleted. + åÆ†é’„é”™čÆÆęˆ–ę–‡ä»¶å—åœ°å€ęœŖēŸ„ - å¾ˆåÆčƒ½ę–‡ä»¶å·²åˆ é™¤ć€‚ file error text @@ -7118,6 +7401,7 @@ To connect, please ask your contact to create another connection link and check XFTP server + XFTP ęœåŠ”å™Ø No comment provided by engineer. @@ -7127,6 +7411,7 @@ To connect, please ask your contact to create another connection link and check You **must not** use the same database on two devices. + 您 **äøå¾—** åœØäø¤å°č®¾å¤‡äøŠä½æē”Øē›øåŒēš„ę•°ę®åŗ“ć€‚ No comment provided by engineer. @@ -7151,6 +7436,7 @@ To connect, please ask your contact to create another connection link and check You are already connecting to %@. + ę‚Øå·²čæžęŽ„åˆ° %@怂 No comment provided by engineer. @@ -7160,14 +7446,17 @@ To connect, please ask your contact to create another connection link and check You are already in group %@. + ę‚Øå·²åœØē»„ %@ 中。 No comment provided by engineer. You are already joining the group %@. + ę‚Øå·²åŠ å…„ē»„ %@怂 No comment provided by engineer. You are already joining the group via this link! + ę‚Øå·²ē»é€ščæ‡ę­¤é“¾ęŽ„åŠ å…„ē¾¤ē»„ļ¼ No comment provided by engineer. @@ -7178,6 +7467,8 @@ To connect, please ask your contact to create another connection link and check You are already joining the group! Repeat join request? + ę‚Øå·²ē»åŠ å…„äŗ†čæ™äøŖē¾¤ē»„ļ¼ +é‡å¤åŠ å…„čÆ·ę±‚ļ¼Ÿ No comment provided by engineer. @@ -7192,6 +7483,7 @@ Repeat join request? You are not connected to these servers. Private routing is used to deliver messages to them. + ę‚ØęœŖčæžęŽ„åˆ°čæ™äŗ›ęœåŠ”å™Øć€‚ē§ęœ‰č·Æē”±ē”ØäŗŽå‘ä»–ä»¬å‘é€ę¶ˆęÆć€‚ No comment provided by engineer. @@ -7201,6 +7493,7 @@ Repeat join request? You can change it in Appearance settings. + ę‚ØåÆä»„åœØå¤–č§‚č®¾ē½®äø­ę›“ę”¹å®ƒć€‚ No comment provided by engineer. @@ -7240,6 +7533,7 @@ Repeat join request? You can send messages to %@ from Archived contacts. + ę‚ØåÆä»„ä»Žå­˜ę”£ēš„č”ē³»äŗŗå‘%@å‘é€ę¶ˆęÆć€‚ No comment provided by engineer. @@ -7269,6 +7563,7 @@ Repeat join request? You can still view conversation with %@ in the list of chats. + ę‚Øä»ē„¶åÆä»„åœØčŠå¤©åˆ—č”Øäø­ęŸ„ēœ‹äøŽ %@ēš„åÆ¹čÆć€‚ No comment provided by engineer. @@ -7309,6 +7604,8 @@ Repeat join request? You have already requested connection! Repeat connection request? + ę‚Øå·²ē»čÆ·ę±‚čæžęŽ„äŗ†ļ¼ +é‡å¤čæžęŽ„čÆ·ę±‚ļ¼Ÿ No comment provided by engineer. @@ -7333,10 +7630,12 @@ Repeat connection request? You may migrate the exported database. + ę‚ØåÆä»„čæē§»åÆ¼å‡ŗēš„ę•°ę®åŗ“ć€‚ No comment provided by engineer. You may save the exported archive. + ę‚ØåÆä»„äæå­˜åÆ¼å‡ŗēš„ę”£ę”ˆć€‚ No comment provided by engineer. @@ -7346,6 +7645,7 @@ Repeat connection request? You need to allow your contact to call to be able to call them. + ę‚Øéœ€č¦å…č®øę‚Øēš„č”ē³»äŗŗå‘¼å«ę‰čƒ½å‘¼å«ä»–ä»¬ć€‚ No comment provided by engineer. @@ -7370,6 +7670,7 @@ Repeat connection request? You will be connected when group link host's device is online, please wait or check later! + 当 Group Link Host ēš„č®¾å¤‡åœØēŗæę—¶ļ¼Œę‚Øå°†č¢«čæžęŽ„ļ¼ŒčÆ·ēØå€™ęˆ–ēØåŽę£€ęŸ„ļ¼ No comment provided by engineer. @@ -7507,6 +7808,7 @@ Repeat connection request? Your profile + ę‚Øēš„äøŖäŗŗčµ„ę–™ No comment provided by engineer. @@ -7615,10 +7917,12 @@ Repeat connection request? and %lld other events + 和 %lld å…¶ä»–äŗ‹ä»¶ No comment provided by engineer. attempts + å°čÆ• No comment provided by engineer. @@ -7663,6 +7967,7 @@ Repeat connection request? call + 呼叫 No comment provided by engineer. @@ -7782,6 +8087,7 @@ Repeat connection request? contact %1$@ changed to %2$@ + 联系人 %1$@ 已曓改为 %2$@ profile update event chat item @@ -7816,6 +8122,7 @@ Repeat connection request? decryption errors + 解密错误 No comment provided by engineer. @@ -7870,6 +8177,7 @@ Repeat connection request? duplicates + å¤ęœ¬ No comment provided by engineer. @@ -7954,6 +8262,7 @@ Repeat connection request? expired + čæ‡ęœŸ No comment provided by engineer. @@ -7988,6 +8297,7 @@ Repeat connection request? inactive + ꗠꕈ No comment provided by engineer. @@ -8032,6 +8342,7 @@ Repeat connection request? invite + 邀请 No comment provided by engineer. @@ -8081,6 +8392,7 @@ Repeat connection request? member %1$@ changed to %2$@ + ęˆå‘˜ %1$@ 已曓改为 %2$@ profile update event chat item @@ -8090,6 +8402,7 @@ Repeat connection request? message + 消息 No comment provided by engineer. @@ -8124,6 +8437,7 @@ Repeat connection request? mute + 静音 No comment provided by engineer. @@ -8180,10 +8494,12 @@ Repeat connection request? other + 其他 No comment provided by engineer. other errors + 其他错误 No comment provided by engineer. @@ -8253,10 +8569,12 @@ Repeat connection request? saved from %@ + äæå­˜č‡Ŗ %@ No comment provided by engineer. search + 搜瓢 No comment provided by engineer. @@ -8288,6 +8606,9 @@ Repeat connection request? server queue info: %1$@ last received msg: %2$@ + ęœåŠ”å™Øé˜Ÿåˆ—äæ”ęÆļ¼š %1$@ + +äøŠę¬”ę”¶åˆ°ēš„ę¶ˆęÆļ¼š %2$@ queue info @@ -8322,6 +8643,7 @@ last received msg: %2$@ unblocked %@ + 未阻止 %@ rcv group event chat item @@ -8331,6 +8653,7 @@ last received msg: %2$@ unknown servers + ęœŖēŸ„ęœåŠ”å™Ø No comment provided by engineer. @@ -8340,10 +8663,12 @@ last received msg: %2$@ unmute + å–ę¶ˆé™éŸ³ No comment provided by engineer. unprotected + ęœŖå—äæęŠ¤ No comment provided by engineer. @@ -8358,6 +8683,7 @@ last received msg: %2$@ v%@ + v%@ No comment provided by engineer. @@ -8387,6 +8713,7 @@ last received msg: %2$@ video + 视频 No comment provided by engineer. @@ -8416,6 +8743,7 @@ last received msg: %2$@ when IP hidden + 当 IP éšč—ę—¶ No comment provided by engineer. @@ -8440,6 +8768,7 @@ last received msg: %2$@ you blocked %@ + ä½ é˜»ę­¢äŗ†%@ snd group event chat item @@ -8484,6 +8813,7 @@ last received msg: %2$@ you unblocked %@ + 您解封了 %@ snd group event chat item @@ -8520,6 +8850,7 @@ last received msg: %2$@ SimpleX uses local network access to allow using user chat profile via desktop app on the same network. + SimpleX ä½æē”Øęœ¬åœ°ē½‘ē»œč®æé—®ļ¼Œå…č®øé€ščæ‡åŒäø€ē½‘ē»œäøŠēš„ę”Œé¢åŗ”ē”ØēØ‹åŗä½æē”Øē”Øęˆ·čŠå¤©é…ē½®ę–‡ä»¶ć€‚ Privacy - Local Network Usage Description @@ -8563,14 +8894,17 @@ last received msg: %2$@ SimpleX SE + SimpleX SE Bundle display name SimpleX SE + SimpleX SE Bundle name Copyright Ā© 2024 SimpleX Chat. All rights reserved. + ē‰ˆęƒę‰€ęœ‰ Ā© 2024 SimpleX Chatć€‚äæē•™ę‰€ęœ‰ęƒåˆ©ć€‚ Copyright (human-readable) @@ -8582,150 +8916,187 @@ last received msg: %2$@ %@ + %@ No comment provided by engineer. App is locked! + åŗ”ē”ØēØ‹åŗå·²é”å®šļ¼ No comment provided by engineer. Cancel + å–ę¶ˆ No comment provided by engineer. Cannot access keychain to save database password + ę— ę³•č®æé—®é’„åŒ™äø²ä»„äæå­˜ę•°ę®åŗ“åÆ†ē  No comment provided by engineer. Cannot forward message + ę— ę³•č½¬å‘ę¶ˆęÆ No comment provided by engineer. Comment + 评论 No comment provided by engineer. Currently maximum supported file size is %@. + å½“å‰ę”ÆęŒēš„ęœ€å¤§ę–‡ä»¶å¤§å°äøŗ %@怂 No comment provided by engineer. Database downgrade required + éœ€č¦ę•°ę®åŗ“é™ēŗ§ No comment provided by engineer. Database encrypted! + ę•°ę®åŗ“å·²åŠ åÆ†ļ¼ No comment provided by engineer. Database error + ę•°ę®åŗ“é”™čÆÆ No comment provided by engineer. Database passphrase is different from saved in the keychain. + ę•°ę®åŗ“åÆ†ē äøŽäæå­˜åœØé’„åŒ™äø²äø­ēš„åÆ†ē äøåŒć€‚ No comment provided by engineer. Database passphrase is required to open chat. + éœ€č¦ę•°ę®åŗ“åÆ†ē ę‰čƒ½ę‰“å¼€čŠå¤©ć€‚ No comment provided by engineer. Database upgrade required + éœ€č¦å‡ēŗ§ę•°ę®åŗ“ No comment provided by engineer. Error preparing file + 准备文件时出错 No comment provided by engineer. Error preparing message + å‡†å¤‡ę¶ˆęÆę—¶å‡ŗé”™ No comment provided by engineer. Error: %@ + é”™čÆÆļ¼š%@ No comment provided by engineer. File error + 文件错误 No comment provided by engineer. Incompatible database version + äøå…¼å®¹ēš„ę•°ę®åŗ“ē‰ˆęœ¬ No comment provided by engineer. Invalid migration confirmation + ę— ę•ˆēš„čæē§»ē”®č®¤ No comment provided by engineer. Keychain error + é’„åŒ™äø²é”™čÆÆ No comment provided by engineer. Large file! + 大文件! No comment provided by engineer. No active profile + ę— ę“»åŠØé…ē½®ę–‡ä»¶ No comment provided by engineer. Ok + å„½ēš„ No comment provided by engineer. Open the app to downgrade the database. + ę‰“å¼€åŗ”ē”ØēØ‹åŗä»„é™ēŗ§ę•°ę®åŗ“ć€‚ No comment provided by engineer. Open the app to upgrade the database. + ę‰“å¼€åŗ”ē”ØēØ‹åŗä»„å‡ēŗ§ę•°ę®åŗ“ć€‚ No comment provided by engineer. Passphrase + 密码 No comment provided by engineer. Please create a profile in the SimpleX app + 请在 SimpleX åŗ”ē”ØēØ‹åŗäø­åˆ›å»ŗé…ē½®ę–‡ä»¶ No comment provided by engineer. Selected chat preferences prohibit this message. + é€‰å®šēš„čŠå¤©é¦–é€‰é”¹ē¦ę­¢ę­¤ę¶ˆęÆć€‚ No comment provided by engineer. Sending a message takes longer than expected. + å‘é€ę¶ˆęÆę‰€éœ€ēš„ę—¶é—“ęÆ”é¢„ęœŸēš„č¦é•æć€‚ No comment provided by engineer. Sending message… + ę­£åœØå‘é€ę¶ˆęÆā€¦ No comment provided by engineer. Share + 共享 No comment provided by engineer. Slow network? + ē½‘ē»œé€Ÿåŗ¦ę…¢ļ¼Ÿ No comment provided by engineer. Unknown database error: %@ + ęœŖēŸ„ę•°ę®åŗ“é”™čÆÆļ¼š %@ No comment provided by engineer. Unsupported format + äøę”ÆęŒēš„ę ¼å¼ No comment provided by engineer. Wait + 等待 No comment provided by engineer. Wrong database passphrase + ę•°ę®åŗ“åÆ†ē é”™čÆÆ No comment provided by engineer. You can allow sharing in Privacy & Security / SimpleX Lock settings. + ę‚ØåÆä»„åœØ "éšē§äøŽå®‰å…Ø"/"SimpleX Lock "设置中允许共享。 No comment provided by engineer. diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings index 388ac01f7f..760be62885 100644 --- a/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX SE/zh-Hans.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ -/* - InfoPlist.strings - SimpleX +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX SE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX SE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "ē‰ˆęƒę‰€ęœ‰ Ā© 2024 SimpleX Chatć€‚äæē•™ę‰€ęœ‰ęƒåˆ©ć€‚"; - Created by EP on 30/07/2024. - Copyright Ā© 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings index 5ef592ec70..362e2edb74 100644 --- a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings @@ -1,7 +1,111 @@ -/* - Localizable.strings - SimpleX +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"App is locked!" = "åŗ”ē”ØēØ‹åŗå·²é”å®šļ¼"; + +/* No comment provided by engineer. */ +"Cancel" = "å–ę¶ˆ"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "ę— ę³•č®æé—®é’„åŒ™äø²ä»„äæå­˜ę•°ę®åŗ“åÆ†ē "; + +/* No comment provided by engineer. */ +"Cannot forward message" = "ę— ę³•č½¬å‘ę¶ˆęÆ"; + +/* No comment provided by engineer. */ +"Comment" = "评论"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "å½“å‰ę”ÆęŒēš„ęœ€å¤§ę–‡ä»¶å¤§å°äøŗ %@怂"; + +/* No comment provided by engineer. */ +"Database downgrade required" = "éœ€č¦ę•°ę®åŗ“é™ēŗ§"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "ę•°ę®åŗ“å·²åŠ åÆ†ļ¼"; + +/* No comment provided by engineer. */ +"Database error" = "ę•°ę®åŗ“é”™čÆÆ"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "ę•°ę®åŗ“åÆ†ē äøŽäæå­˜åœØé’„åŒ™äø²äø­ēš„åÆ†ē äøåŒć€‚"; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "éœ€č¦ę•°ę®åŗ“åÆ†ē ę‰čƒ½ę‰“å¼€čŠå¤©ć€‚"; + +/* No comment provided by engineer. */ +"Database upgrade required" = "éœ€č¦å‡ēŗ§ę•°ę®åŗ“"; + +/* No comment provided by engineer. */ +"Error preparing file" = "准备文件时出错"; + +/* No comment provided by engineer. */ +"Error preparing message" = "å‡†å¤‡ę¶ˆęÆę—¶å‡ŗé”™"; + +/* No comment provided by engineer. */ +"Error: %@" = "é”™čÆÆļ¼š%@"; + +/* No comment provided by engineer. */ +"File error" = "文件错误"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "äøå…¼å®¹ēš„ę•°ę®åŗ“ē‰ˆęœ¬"; + +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "ę— ę•ˆēš„čæē§»ē”®č®¤"; + +/* No comment provided by engineer. */ +"Keychain error" = "é’„åŒ™äø²é”™čÆÆ"; + +/* No comment provided by engineer. */ +"Large file!" = "大文件!"; + +/* No comment provided by engineer. */ +"No active profile" = "ę— ę“»åŠØé…ē½®ę–‡ä»¶"; + +/* No comment provided by engineer. */ +"Ok" = "å„½ēš„"; + +/* No comment provided by engineer. */ +"Open the app to downgrade the database." = "ę‰“å¼€åŗ”ē”ØēØ‹åŗä»„é™ēŗ§ę•°ę®åŗ“ć€‚"; + +/* No comment provided by engineer. */ +"Open the app to upgrade the database." = "ę‰“å¼€åŗ”ē”ØēØ‹åŗä»„å‡ēŗ§ę•°ę®åŗ“ć€‚"; + +/* No comment provided by engineer. */ +"Passphrase" = "密码"; + +/* No comment provided by engineer. */ +"Please create a profile in the SimpleX app" = "请在 SimpleX åŗ”ē”ØēØ‹åŗäø­åˆ›å»ŗé…ē½®ę–‡ä»¶"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "é€‰å®šēš„čŠå¤©é¦–é€‰é”¹ē¦ę­¢ę­¤ę¶ˆęÆć€‚"; + +/* No comment provided by engineer. */ +"Sending a message takes longer than expected." = "å‘é€ę¶ˆęÆę‰€éœ€ēš„ę—¶é—“ęÆ”é¢„ęœŸēš„č¦é•æć€‚"; + +/* No comment provided by engineer. */ +"Sending message…" = "ę­£åœØå‘é€ę¶ˆęÆā€¦"; + +/* No comment provided by engineer. */ +"Share" = "共享"; + +/* No comment provided by engineer. */ +"Slow network?" = "ē½‘ē»œé€Ÿåŗ¦ę…¢ļ¼Ÿ"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "ęœŖēŸ„ę•°ę®åŗ“é”™čÆÆļ¼š %@"; + +/* No comment provided by engineer. */ +"Unsupported format" = "äøę”ÆęŒēš„ę ¼å¼"; + +/* No comment provided by engineer. */ +"Wait" = "等待"; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "ę•°ę®åŗ“åÆ†ē é”™čÆÆ"; + +/* No comment provided by engineer. */ +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "ę‚ØåÆä»„åœØ \"éšē§äøŽå®‰å…Ø\"/\"SimpleX Lock \"设置中允许共享。"; - Created by EP on 30/07/2024. - Copyright Ā© 2024 SimpleX Chat. All rights reserved. -*/ diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index aa43902c81..b044c8bc1e 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -697,7 +697,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "ŠŠµ може Га ŠæŠ¾ŠŗŠ°Š½ŃŃ‚ контактите!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "ŠžŃ‚ŠŗŠ°Š·"; /* No comment provided by engineer. */ @@ -1897,9 +1897,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Пълно име (Š½ŠµŠ·Š°Š“ŃŠŠ»Š¶ŠøŃ‚ŠµŠ»Š½Š¾)"; -/* No comment provided by engineer. */ -"Full name:" = "Пълно име:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "ŠŠ°ŠæŃŠŠ»Š½Š¾ Гецентрализирана – виГима е само за членовете."; @@ -2940,12 +2937,6 @@ /* No comment provided by engineer. */ "Profile images" = "ŠŸŃ€Š¾Ń„ŠøŠ»Š½Šø ŠøŠ·Š¾Š±Ń€Š°Š¶ŠµŠ½ŠøŃ"; -/* No comment provided by engineer. */ -"Profile name" = "Име на профила"; - -/* No comment provided by engineer. */ -"Profile name:" = "Име на профила:"; - /* No comment provided by engineer. */ "Profile password" = "ŠŸŃ€Š¾Ń„ŠøŠ»Š½Š° парола"; @@ -3211,10 +3202,11 @@ /* No comment provided by engineer. */ "Safer groups" = "По-безопасни Š³Ń€ŃƒŠæŠø"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Запази"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Запази (Šø увеГоми контактите)"; /* No comment provided by engineer. */ @@ -3229,9 +3221,6 @@ /* No comment provided by engineer. */ "Save archive" = "Запази архив"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Запази настройките за автоматично приемане"; - /* No comment provided by engineer. */ "Save group profile" = "Запази профила на Š³Ń€ŃƒŠæŠ°Ń‚а"; @@ -3253,9 +3242,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Запази ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ?"; -/* No comment provided by engineer. */ -"Save settings?" = "Запази настройките?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Запази ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŠµŃ‚Š¾ при посрещане?"; @@ -4430,7 +4416,7 @@ "Your profile **%@** will be shared." = "Š’Š°ŃˆŠøŃŃ‚ профил **%@** ще бъГе споГелен."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Š’Š°ŃˆŠøŃŃ‚ профил се ŃŃŠŃ…Ń€Š°Š½ŃŠ²Š° на Š²Š°ŃˆŠµŃ‚о ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Š¾ Šø се ŃŠæŠ¾Š“ŠµŠ»Ń само с Š²Š°ŃˆŠøŃ‚Šµ контакти.\nSimpleX ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ не могат Га Š²ŠøŠ“ŃŃ‚ Š²Š°ŃˆŠøŃ профил."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Š’Š°ŃˆŠøŃŃ‚ профил се ŃŃŠŃ…Ń€Š°Š½ŃŠ²Š° на Š²Š°ŃˆŠµŃ‚о ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Š¾ Šø се ŃŠæŠ¾Š“ŠµŠ»Ń само с Š²Š°ŃˆŠøŃ‚Šµ контакти. SimpleX ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ не могат Га Š²ŠøŠ“ŃŃ‚ Š²Š°ŃˆŠøŃ профил."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Š’Š°ŃˆŠøŃŃ‚ профил, контакти Šø Гоставени ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŃ се ŃŃŠŃ…Ń€Š°Š½ŃŠ²Š°Ń‚ на Š²Š°ŃˆŠµŃ‚о ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Š¾."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 220550c682..f46da2d120 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -562,7 +562,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Nelze pozvat kontakty!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "ZruÅ”it"; /* feature offered item */ @@ -1543,9 +1543,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "CelĆ© jmĆ©no (volitelně)"; -/* No comment provided by engineer. */ -"Full name:" = "CelĆ© jmĆ©no:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Plně přepracovĆ”no, prĆ”cuje na pozadĆ­!"; @@ -2602,10 +2599,11 @@ /* No comment provided by engineer. */ "Run chat" = "Spustit chat"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Uložit"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Uložit (a informovat kontakty)"; /* No comment provided by engineer. */ @@ -2620,9 +2618,6 @@ /* No comment provided by engineer. */ "Save archive" = "Uložit archiv"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Uložit nastavenĆ­ automatickĆ©ho přijĆ­mĆ”nĆ­"; - /* No comment provided by engineer. */ "Save group profile" = "UloženĆ­ profilu skupiny"; @@ -2644,9 +2639,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Uložit servery?"; -/* No comment provided by engineer. */ -"Save settings?" = "Uložit nastavenĆ­?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Uložit uvĆ­tacĆ­ zprĆ”vu?"; @@ -3554,7 +3546,7 @@ "Your profile **%@** will be shared." = "VÔŔ profil **%@** bude sdĆ­len."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "VÔŔ profil je uložen ve vaÅ”em zařízenĆ­ a sdĆ­len pouze s vaÅ”imi kontakty.\nServery SimpleX nevidĆ­ vÔŔ profil."; +"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."; /* No comment provided by engineer. */ "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Ć­."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index bab14eda6b..b69b9fa370 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Mitglied kann nicht benachrichtigt werden"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Abbrechen"; /* No comment provided by engineer. */ @@ -921,16 +921,16 @@ "Chunks uploaded" = "Daten-Pakete hochgeladen"; /* swipe action */ -"Clear" = "Lƶschen"; +"Clear" = "Entfernen"; /* No comment provided by engineer. */ -"Clear conversation" = "Chatinhalte lƶschen"; +"Clear conversation" = "Chat-Inhalte entfernen"; /* No comment provided by engineer. */ -"Clear conversation?" = "Unterhaltung lƶschen?"; +"Clear conversation?" = "Chat-Inhalte entfernen?"; /* No comment provided by engineer. */ -"Clear private notes?" = "Private Notizen lƶschen?"; +"Clear private notes?" = "Private Notizen entfernen?"; /* No comment provided by engineer. */ "Clear verification" = "Überprüfung zurücknehmen"; @@ -1167,7 +1167,7 @@ "Continue" = "Weiter"; /* No comment provided by engineer. */ -"Conversation deleted!" = "Unterhaltung gelƶscht!"; +"Conversation deleted!" = "Chat-Inhalte entfernt!"; /* No comment provided by engineer. */ "Copy" = "Kopieren"; @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "VollstƤndiger Name (optional)"; -/* No comment provided by engineer. */ -"Full name:" = "VollstƤndiger Name:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "VollstƤndig dezentralisiert – nur für Mitglieder sichtbar."; @@ -2306,7 +2303,7 @@ "Group will be deleted for all members - this cannot be undone!" = "Die Gruppe wird für alle Mitglieder gelƶscht. Dies kann nicht rückgƤngig gemacht werden!"; /* No comment provided by engineer. */ -"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird für Sie gelƶscht. Dies kann nicht rückgƤngig gemacht werden!"; +"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird nur bei Ihnen gelƶscht. Dies kann nicht rückgƤngig gemacht werden!"; /* No comment provided by engineer. */ "Help" = "Hilfe"; @@ -2630,7 +2627,7 @@ "Keep" = "Behalten"; /* No comment provided by engineer. */ -"Keep conversation" = "Unterhaltung behalten"; +"Keep conversation" = "Chat-Inhalte beibehalten"; /* No comment provided by engineer. */ "Keep the app open to use it from desktop" = "Die App muss geƶffnet bleiben, um sie vom Desktop aus nutzen zu kƶnnen"; @@ -3115,7 +3112,7 @@ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Nur die EndgerƤte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; /* No comment provided by engineer. */ -"Only delete conversation" = "Nur die Unterhaltung lƶschen"; +"Only delete conversation" = "Nur die Chat-Inhalte lƶschen"; /* No comment provided by engineer. */ "Only group owners can change group preferences." = "Gruppen-PrƤferenzen kƶnnen nur von Gruppen-Eigentümern geƤndert werden."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profil-Bilder"; -/* No comment provided by engineer. */ -"Profile name" = "Profilname"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profilname:"; - /* No comment provided by engineer. */ "Profile password" = "Passwort für Profil"; @@ -3460,7 +3451,7 @@ "Rate the app" = "Bewerten Sie die App"; /* No comment provided by engineer. */ -"Reachable chat toolbar" = "Erreichbare Chat-Symbolleiste"; +"Reachable chat toolbar" = "Chat-Symbolleiste unten"; /* chat item menu */ "React…" = "Reagiere…"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Speichern"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Archiv speichern"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Einstellungen von \"Automatisch akzeptieren\" speichern"; - /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Alle Server speichern?"; -/* No comment provided by engineer. */ -"Save settings?" = "Einstellungen speichern?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Begrüßungsmeldung speichern?"; @@ -3815,7 +3801,7 @@ "Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks akzeptiert."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Suchen oder fügen Sie den SimpleX-Link ein"; +"Search or paste SimpleX link" = "Suchen oder SimpleX-Link einfügen"; /* network option */ "sec" = "sek"; @@ -4385,7 +4371,7 @@ "The message will be marked as moderated for all members." = "Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet."; /* No comment provided by engineer. */ -"The messages will be deleted for all members." = "Die Nachrichten werden für alle Mitglieder gelƶscht werden."; +"The messages will be deleted for all members." = "Die Nachrichten werden für alle Gruppenmitglieder gelƶscht."; /* No comment provided by engineer. */ "The messages will be marked as moderated for all members." = "Die Nachrichten werden für alle Mitglieder als moderiert gekennzeichnet werden."; @@ -4709,7 +4695,7 @@ "Use the app while in the call." = "Die App kann wƤhrend eines Anrufs genutzt werden."; /* No comment provided by engineer. */ -"Use the app with one hand." = "Die App mit einer Hand nutzen."; +"Use the app with one hand." = "Die App mit einer Hand bedienen."; /* No comment provided by engineer. */ "User profile" = "Benutzerprofil"; @@ -5021,7 +5007,7 @@ "You can start chat via app Settings / Database or by restarting the app" = "Sie kƶnnen den Chat über die App-Einstellungen / Datenbank oder durch Neustart der App starten"; /* No comment provided by engineer. */ -"You can still view conversation with %@ in the list of chats." = "Sie kƶnnen in der Chatliste weiterhin die Unterhaltung mit %@ einsehen."; +"You can still view conversation with %@ in the list of chats." = "Sie kƶnnen in der Chat-Liste weiterhin die Unterhaltung mit %@ einsehen."; /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "Sie kƶnnen die SimpleX-Sperre über die Einstellungen aktivieren."; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem GerƤt gespeichert und nur mit Ihren Kontakten geteilt.\nSimpleX-Server kƶnnen Ihr Profil nicht einsehen."; +"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."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem GerƤt gespeichert."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 3dce4e4474..ed5efe14ab 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "No se pueden enviar mensajes al miembro"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Cancelar"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nombre completo (opcional)"; -/* No comment provided by engineer. */ -"Full name:" = "Nombre completo:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Completamente descentralizado y sólo visible para los miembros."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Forma de los perfiles"; -/* No comment provided by engineer. */ -"Profile name" = "Nombre del perfil"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nombre del perfil:"; - /* No comment provided by engineer. */ "Profile password" = "ContraseƱa del perfil"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Grupos mĆ”s seguros"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Guardar"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Guardar (y notificar contactos)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Guardar archivo"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Guardar configuración de auto aceptar"; - /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "ĀæGuardar servidores?"; -/* No comment provided by engineer. */ -"Save settings?" = "ĀæGuardar configuración?"; - /* No comment provided by engineer. */ "Save welcome message?" = "ĀæGuardar mensaje de bienvenida?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "El perfil **%@** serĆ” compartido."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos.\nLos servidores SimpleX no pueden ver tu perfil."; +"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."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index a7820e69b1..ee7056709e 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -547,7 +547,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Kontakteja ei voi kutsua!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Peruuta"; /* feature offered item */ @@ -1519,9 +1519,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Koko nimi (valinnainen)"; -/* No comment provided by engineer. */ -"Full name:" = "Koko nimi:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "TƤysin uudistettu - toimii taustalla!"; @@ -2572,10 +2569,11 @@ /* No comment provided by engineer. */ "Run chat" = "KƤynnistƤ chat"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Tallenna"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Tallenna (ja ilmoita kontakteille)"; /* No comment provided by engineer. */ @@ -2590,9 +2588,6 @@ /* No comment provided by engineer. */ "Save archive" = "Tallenna arkisto"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Tallenna automaattisen hyvƤksynnƤn asetukset"; - /* No comment provided by engineer. */ "Save group profile" = "Tallenna ryhmƤprofiili"; @@ -2614,9 +2609,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Tallenna palvelimet?"; -/* No comment provided by engineer. */ -"Save settings?" = "Tallenna asetukset?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Tallenna tervetuloviesti?"; @@ -3512,7 +3504,7 @@ "Your profile **%@** will be shared." = "Profiilisi **%@** jaetaan."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa.\nSimpleX-palvelimet eivƤt nƤe profiiliasi."; +"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."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index b4f256762c..e1c4c20461 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossible d'envoyer un message Ć  ce membre"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Annuler"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nom complet (optionnel)"; -/* No comment provided by engineer. */ -"Full name:" = "Nom complet :"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "EntiĆØrement dĆ©centralisĆ© – visible que par ses membres."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Images de profil"; -/* No comment provided by engineer. */ -"Profile name" = "Nom du profil"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nom du profil :"; - /* No comment provided by engineer. */ "Profile password" = "Mot de passe de profil"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Groupes plus sĆ»rs"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Enregistrer"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Enregistrer (et en informer les contacts)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Enregistrer l'archive"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Enregistrer les paramĆØtres de validation automatique"; - /* No comment provided by engineer. */ "Save group profile" = "Enregistrer le profil du groupe"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Enregistrer les serveurs ?"; -/* No comment provided by engineer. */ -"Save settings?" = "Enregistrer les paramĆØtres ?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Enregistrer le message d'accueil ?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Votre profil **%@** sera partagĆ©."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Votre profil est stockĆ© sur votre appareil et est seulement partagĆ© avec vos contacts.\nLes serveurs SimpleX ne peuvent pas voir votre profil."; +"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."; /* No comment provided by engineer. */ "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."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 8c70bcd626..54c0b6bd50 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -29,7 +29,7 @@ "- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- opcionĆ”lis Ć©rtesĆ­tĆ©s a tƶrƶlt kapcsolatokról.\n- profilnevek szókƶzƶkkel.\n- Ć©s mĆ©g sok mĆ”s!"; /* No comment provided by engineer. */ -"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- hangüzenetek legfeljebb 5 perces időtartamig.\n- egyedi eltűnĆ©si időhatĆ”r megadĆ”sa.\n- előzmĆ©nyek szerkesztĆ©se."; +"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 5 perc hosszĆŗsĆ”gĆŗ hangüzenetek.\n- egyedi üzenet-eltűnĆ©si időkorlĆ”t.\n- előzmĆ©nyek szerkesztĆ©se."; /* No comment provided by engineer. */ ", " = ", "; @@ -62,7 +62,7 @@ "[Send us email](mailto:chat@simplex.chat)" = "[Küldjƶn nekünk e-mailt](mailto:chat@simplex.chat)"; /* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat)"; +"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[CsillagozĆ”s a GitHubon](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ "**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzĆ”adĆ”sa**: Ćŗj meghĆ­vó hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz."; @@ -263,7 +263,7 @@ "`a + b`" = "a + b"; /* email text */ -"

Hi!

\n

Connect to me via SimpleX Chat

" = "

Üdvözlöm!

\n

Csatlakozzon hozzƔm a SimpleX Chaten

"; +"

Hi!

\n

Connect to me via SimpleX Chat

" = "

Üdvözlöm!

\n

Csatlakozzon hozzÔm a SimpleX Chaten keresztül

"; /* No comment provided by engineer. */ "~strike~" = "\\~Ć”thĆŗzott~"; @@ -343,7 +343,7 @@ "Accept" = "ElfogadĆ”s"; /* No comment provided by engineer. */ -"Accept connection request?" = "KapcsolódĆ”si kĆ©relem elfogadĆ”sa?"; +"Accept connection request?" = "IsmerőskĆ©relem elfogadĆ”sa?"; /* notification body */ "Accept contact request from %@?" = "Elfogadja %@ kapcsolat kĆ©rĆ©sĆ©t?"; @@ -659,10 +659,10 @@ "Bad desktop address" = "HibĆ”s szĆ”mĆ­tógĆ©p cĆ­m"; /* integrity error chat item */ -"bad message hash" = "hibĆ”s az üzenet ellenőrzőösszege"; +"bad message hash" = "hibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke"; /* No comment provided by engineer. */ -"Bad message hash" = "HibĆ”s az üzenet ellenőrzőösszege"; +"Bad message hash" = "HibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke"; /* integrity error chat item */ "bad message ID" = "tĆ©ves üzenet ID"; @@ -749,7 +749,7 @@ "Call already ended!" = "A hĆ­vĆ”s mĆ”r befejeződƶtt!"; /* call status */ -"call error" = "hiba a hĆ­vĆ”sban"; +"call error" = "hĆ­vĆ”shiba"; /* call status */ "call in progress" = "hĆ­vĆ”s folyamatban"; @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Nem lehet üzenetet küldeni a tagnak"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "MĆ©gse"; /* No comment provided by engineer. */ @@ -1002,7 +1002,7 @@ "Connect incognito" = "KapcsolódĆ”s inkognitóban"; /* No comment provided by engineer. */ -"Connect to desktop" = "KapcsolódĆ”s szĆ”mĆ­tógĆ©phez"; +"Connect to desktop" = "TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel"; /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "KapcsolódĆ”s a SimpleX Chat fejlesztőkhƶz."; @@ -1014,7 +1014,7 @@ "Connect to yourself?" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az egyszer hasznĆ”latos hivatkozĆ”sa!"; +"Connect to yourself?\nThis is your own one-time link!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az ƶn egyszer hasznĆ”latos hivatkozĆ”sa!"; /* No comment provided by engineer. */ "Connect to yourself?\nThis is your own SimpleX address!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az ƶn SimpleX cĆ­me!"; @@ -1038,7 +1038,7 @@ "Connected" = "Kapcsolódva"; /* No comment provided by engineer. */ -"Connected desktop" = "Csatlakoztatott szĆ”mĆ­tógĆ©p"; +"Connected desktop" = "TĆ”rsĆ­tott szĆ”mĆ­tógĆ©p"; /* rcv group event chat item */ "connected directly" = "kƶzvetlenül kapcsolódva"; @@ -1068,7 +1068,7 @@ "connecting (introduction invitation)" = "kapcsolódĆ”s (bemutatkozó meghĆ­vó)"; /* call status */ -"connecting call" = "hĆ­vĆ”s kapcsolódik…"; +"connecting call" = "kapcsolódĆ”si hĆ­vĆ”s…"; /* No comment provided by engineer. */ "Connecting server…" = "KapcsolódĆ”s a kiszolgĆ”lóhoz…"; @@ -1128,7 +1128,7 @@ "Contact allows" = "Ismerős engedĆ©lyezi"; /* No comment provided by engineer. */ -"Contact already exists" = "LĆ©tező ismerős"; +"Contact already exists" = "Az ismerős mĆ”r lĆ©tezik"; /* No comment provided by engineer. */ "Contact deleted!" = "Ismerős tƶrƶlve!"; @@ -1197,7 +1197,7 @@ "Create group" = "Csoport lĆ©trehozĆ”sa"; /* No comment provided by engineer. */ -"Create group link" = "Csoportos hivatkozĆ”s lĆ©trehozĆ”sa"; +"Create group link" = "CsoporthivatkozĆ”s lĆ©trehozĆ”sa"; /* No comment provided by engineer. */ "Create link" = "HivatkozĆ”s lĆ©trehozĆ”sa"; @@ -1233,7 +1233,7 @@ "Created on %@" = "LĆ©trehozva %@"; /* No comment provided by engineer. */ -"Creating archive link" = "ArchĆ­v hivatkozĆ”s lĆ©trehozĆ”sa"; +"Creating archive link" = "ArchĆ­vum hivatkozĆ”s lĆ©trehozĆ”sa"; /* No comment provided by engineer. */ "Creating link…" = "HivatkozĆ”s lĆ©trehozĆ”sa…"; @@ -1450,7 +1450,7 @@ "Delete old database?" = "RĆ©gi adatbĆ”zis tƶrlĆ©se?"; /* No comment provided by engineer. */ -"Delete pending connection?" = "Függő kapcsolatfelvĆ©teli kĆ©rĆ©sek tƶrlĆ©se?"; +"Delete pending connection?" = "Függőben lĆ©vő ismerőskĆ©relem tƶrlĆ©se?"; /* No comment provided by engineer. */ "Delete profile" = "Profil tƶrlĆ©se"; @@ -1654,7 +1654,7 @@ "Downloading link details" = "LetƶltĆ©si hivatkozĆ”s rĆ©szletei"; /* No comment provided by engineer. */ -"Duplicate display name!" = "DuplikĆ”lt megjelenĆ­tĆ©si nĆ©v!"; +"Duplicate display name!" = "DuplikĆ”lt megjelenĆ­tett nĆ©v!"; /* integrity error chat item */ "duplicate message" = "duplikĆ”lt üzenet"; @@ -1852,7 +1852,7 @@ "Error accessing database file" = "Hiba az adatbĆ”zisfĆ”jl elĆ©rĆ©sekor"; /* No comment provided by engineer. */ -"Error adding member(s)" = "Hiba a tag(-ok) hozzĆ”adĆ”sakor"; +"Error adding member(s)" = "Hiba a tag(ok) hozzĆ”adĆ”sakor"; /* No comment provided by engineer. */ "Error changing address" = "Hiba a cĆ­m megvĆ”ltoztatĆ”sakor"; @@ -1873,7 +1873,7 @@ "Error creating group" = "Hiba a csoport lĆ©trehozĆ”sakor"; /* No comment provided by engineer. */ -"Error creating group link" = "Hiba a csoport hivatkozĆ”sĆ”nak lĆ©trehozĆ”sakor"; +"Error creating group link" = "Hiba a csoporthivatkozĆ”s lĆ©trehozĆ”sakor"; /* No comment provided by engineer. */ "Error creating member contact" = "Hiba az ismerőssel tƶrtĆ©nő kapcsolat lĆ©trehozĆ”sĆ”ban"; @@ -2002,7 +2002,7 @@ "Error synchronizing connection" = "Hiba a kapcsolat szinkronizĆ”lĆ”sa kƶzben"; /* No comment provided by engineer. */ -"Error updating group link" = "Hiba a csoport hivatkozĆ”s frissĆ­tĆ©sekor"; +"Error updating group link" = "Hiba a csoporthivatkozĆ”s frissĆ­tĆ©sekor"; /* No comment provided by engineer. */ "Error updating message" = "Hiba az üzenet frissĆ­tĆ©sekor"; @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Teljes nĆ©v (opcionĆ”lis)"; -/* No comment provided by engineer. */ -"Full name:" = "Teljes nĆ©v:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Teljesen decentralizĆ”lt - kizĆ”rólag tagok szĆ”mĆ”ra lĆ”tható."; @@ -2255,10 +2252,10 @@ "Group invitation is no longer valid, it was removed by sender." = "A csoport meghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője tƶrƶlte."; /* No comment provided by engineer. */ -"Group link" = "Csoport hivatkozĆ”s"; +"Group link" = "CsoporthivatkozĆ”s"; /* No comment provided by engineer. */ -"Group links" = "Csoport hivatkozĆ”sok"; +"Group links" = "CsoporthivatkozĆ”sok"; /* No comment provided by engineer. */ "Group members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzĆ”."; @@ -2303,7 +2300,7 @@ "Group welcome message" = "Csoport üdvƶzlő üzenete"; /* No comment provided by engineer. */ -"Group will be deleted for all members - this cannot be undone!" = "Csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza!"; +"Group will be deleted for all members - this cannot be undone!" = "A csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ "Group will be deleted for you - this cannot be undone!" = "A csoport tƶrlĆ©sre kerül az ƶn szĆ”mĆ”ra - ez a művelet nem vonható vissza!"; @@ -2444,10 +2441,10 @@ "incognito via contact address link" = "inkognitó a kapcsolattartĆ”si hivatkozĆ”son keresztül"; /* chat list item description */ -"incognito via group link" = "inkognitó a csoportos hivatkozĆ”son keresztül"; +"incognito via group link" = "inkognitó a csoporthivatkozĆ”son keresztül"; /* chat list item description */ -"incognito via one-time link" = "inkognitó az egyszer hasznĆ”latos hivatkozĆ”son keresztül"; +"incognito via one-time link" = "inkognitó egy egyszer hasznĆ”latos hivatkozĆ”son keresztül"; /* notification */ "Incoming audio call" = "Bejƶvő hanghĆ­vĆ”s"; @@ -2501,13 +2498,13 @@ "invalid chat data" = "Ć©rvĆ©nytelen csevegĆ©s adat"; /* No comment provided by engineer. */ -"Invalid connection link" = "ƉrvĆ©nytelen kapcsolati hivatkozĆ”s"; +"Invalid connection link" = "ƉrvĆ©nytelen kapcsolattartĆ”si hivatkozĆ”s"; /* invalid chat item */ "invalid data" = "Ć©rvĆ©nytelen adat"; /* No comment provided by engineer. */ -"Invalid display name!" = "ƉrvĆ©nytelen megjelenĆ­tendő felhaszĆ”lónĆ©v!"; +"Invalid display name!" = "ƉrvĆ©nytelen megjelenĆ­tendő nĆ©v!"; /* No comment provided by engineer. */ "Invalid link" = "ƉrvĆ©nytelen hivatkozĆ”s"; @@ -2558,7 +2555,7 @@ "invited to connect" = "meghĆ­vta, hogy csatlakozzon"; /* rcv group event chat item */ -"invited via your group link" = "meghĆ­va az ƶn csoport hivatkozĆ”sĆ”n keresztül"; +"invited via your group link" = "meghĆ­va az ƶn csoporthivatkozĆ”sĆ”n keresztül"; /* No comment provided by engineer. */ "iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonsĆ”gos tĆ”rolĆ”sĆ”ra szolgĆ”l - lehetővĆ© teszi a push-Ć©rtesĆ­tĆ©sek fogadĆ”sĆ”t."; @@ -2666,7 +2663,7 @@ "left" = "elhagyta a csoportot"; /* email subject */ -"Let's talk in SimpleX Chat" = "BeszĆ©lgessünk a SimpleX Chat-ben"; +"Let's talk in SimpleX Chat" = "BeszĆ©lgessünk a SimpleX Chatben"; /* No comment provided by engineer. */ "Light" = "VilĆ”gos"; @@ -2675,13 +2672,13 @@ "Limitations" = "KorlĆ”tozĆ”sok"; /* No comment provided by engineer. */ -"Link mobile and desktop apps! šŸ”—" = "TĆ”rsĆ­tsa ƶssze a mobil Ć©s az asztali alkalmazĆ”sokat! šŸ”—"; +"Link mobile and desktop apps! šŸ”—" = "TĆ”rsĆ­tsa ƶssze a mobil Ć©s asztali alkalmazĆ”sokat! šŸ”—"; /* No comment provided by engineer. */ -"Linked desktop options" = "Ɩsszekapcsolt szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok"; +"Linked desktop options" = "TĆ”rsĆ­tott szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok"; /* No comment provided by engineer. */ -"Linked desktops" = "Ɩsszekapcsolt szĆ”mĆ­tógĆ©pek"; +"Linked desktops" = "TĆ”rsĆ­tott szĆ”mĆ­tógĆ©pek"; /* No comment provided by engineer. */ "LIVE" = "ƉLŐ"; @@ -2723,7 +2720,7 @@ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjƶn meg arról, hogy a WebRTC ICE-kiszolgĆ”ló cĆ­mei megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva."; /* No comment provided by engineer. */ -"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kĆ©rdeztĆ©k: *ha a SimpleX-nek nincsenek felhasznĆ”lói azonosĆ­tói, akkor hogyan tud üzeneteket kĆ©zbesĆ­teni?*"; +"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kĆ©rdeztĆ©k: *ha a SimpleX Chatnek nincsenek felhasznĆ”lói azonosĆ­tói, akkor hogyan tud üzeneteket kĆ©zbesĆ­teni?*"; /* No comment provided by engineer. */ "Mark deleted for everyone" = "Jelƶlje meg mindenki szĆ”mĆ”ra tƶrƶltkĆ©nt"; @@ -2738,7 +2735,7 @@ "Markdown in messages" = "Markdown az üzenetekben"; /* marked deleted chat item preview text */ -"marked deleted" = "tƶrƶltnek jelƶlve"; +"marked deleted" = "tƶrlĆ©sre jelƶlve"; /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "Max. 30 mĆ”sodperc, azonnal Ć©rkezett."; @@ -2903,10 +2900,10 @@ "moderated" = "moderĆ”lt"; /* No comment provided by engineer. */ -"Moderated at" = "ModerĆ”lva lett ekkor:"; +"Moderated at" = "ModerĆ”lva ekkor:"; /* copied message info */ -"Moderated at: %@" = "ModerĆ”lva lett ekkor: %@"; +"Moderated at: %@" = "ModerĆ”lva ekkor: %@"; /* marked deleted chat item preview text */ "moderated by %@" = "moderĆ”lva lett %@ Ć”ltal"; @@ -2966,7 +2963,7 @@ "New chat experience šŸŽ‰" = "Új csevegĆ©si Ć©lmĆ©ny šŸŽ‰"; /* notification */ -"New contact request" = "Új kapcsolattartĆ”si kĆ©relem"; +"New contact request" = "Új ismerőskĆ©relem"; /* notification */ "New contact:" = "Új kapcsolat:"; @@ -3011,7 +3008,7 @@ "No app password" = "Nincs alkalmazĆ”s jelszó"; /* No comment provided by engineer. */ -"No contacts selected" = "Nem kerültek ismerősƶk kivĆ”lasztĆ”sra"; +"No contacts selected" = "Nincs kivĆ”lasztva ismerős"; /* No comment provided by engineer. */ "No contacts to add" = "Nincs hozzĆ”adandó ismerős"; @@ -3056,7 +3053,7 @@ "Not compatible!" = "Nem kompatibilis!"; /* No comment provided by engineer. */ -"Nothing selected" = "Semmi sincs kivĆ”lasztva"; +"Nothing selected" = "Nincs kivĆ”lasztva semmi"; /* No comment provided by engineer. */ "Notifications" = "ƉrtesĆ­tĆ©sek"; @@ -3094,7 +3091,7 @@ "Old database" = "RĆ©gi adatbĆ”zis"; /* No comment provided by engineer. */ -"Old database archive" = "RĆ©gi adatbĆ”zis archĆ­vum"; +"Old database archive" = "RĆ©gi adatbĆ”zis-archĆ­vum"; /* group pref value */ "on" = "bekapcsolva"; @@ -3112,7 +3109,7 @@ "Onion hosts will not be used." = "Onion kiszolgĆ”lók nem lesznek hasznĆ”lva."; /* No comment provided by engineer. */ -"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak a klienseszkƶzƶk tĆ”roljĆ”k a felhasznĆ”lói profilokat, nĆ©vjegyeket, csoportokat Ć©s a **2 rĆ©tegű vĆ©gponttól-vĆ©gpontig titkosĆ­tĆ”ssal** küldƶtt üzeneteket."; +"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Csak a klienseszkƶzƶk tĆ”roljĆ”k a felhasznĆ”lói profilokat, nĆ©vjegyeket, csoportokat Ć©s a **2 rĆ©tegű vĆ©gpontok kƶzƶtti titkosĆ­tĆ”ssal** küldƶtt üzeneteket."; /* No comment provided by engineer. */ "Only delete conversation" = "Csak a beszĆ©lgetĆ©s tƶrlĆ©se"; @@ -3193,7 +3190,7 @@ "Or scan QR code" = "Vagy QR-kód beolvasĆ”sa"; /* No comment provided by engineer. */ -"Or securely share this file link" = "Vagy a fĆ”jl hivĆ­tkozĆ”sĆ”nak biztonsĆ”gos megosztĆ”sa"; +"Or securely share this file link" = "Vagy ossza meg biztonsĆ”gosan ezt a fĆ”jlhivatkozĆ”st"; /* No comment provided by engineer. */ "Or show this code" = "Vagy mutassa meg ezt a kódot"; @@ -3235,7 +3232,7 @@ "Password to show" = "Jelszó megjelenĆ­tĆ©se"; /* past/unknown group member */ -"Past member %@" = "MĆ”r nem tag - %@"; +"Past member %@" = "%@ (mĆ”r nem tag)"; /* No comment provided by engineer. */ "Paste desktop address" = "SzĆ”mĆ­tógĆ©p cĆ­mĆ©nek beillesztĆ©se"; @@ -3247,13 +3244,13 @@ "Paste link to connect!" = "HivatkozĆ”s beillesztĆ©se a kapcsolódĆ”shoz!"; /* No comment provided by engineer. */ -"Paste the link you received" = "Fogadott hivatkozĆ”s beillesztĆ©se"; +"Paste the link you received" = "Kapott hivatkozĆ”s beillesztĆ©se"; /* No comment provided by engineer. */ "peer-to-peer" = "ponttól-pontig"; /* No comment provided by engineer. */ -"Pending" = "Függő"; +"Pending" = "Függőben"; /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Az emberek csak az ƶn Ć”ltal megosztott hivatkozĆ”son keresztül kapcsolódhatnak."; @@ -3286,7 +3283,7 @@ "Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Ellenőrizze, hogy a mobil Ć©s az asztali szĆ”mĆ­tógĆ©p ugyanahhoz a helyi hĆ”lózathoz csatlakozik-e, valamint az asztali szĆ”mĆ­tógĆ©p tűzfalĆ”ban engedĆ©lyezve van-e a kapcsolat.\nMinden tovĆ”bbi problĆ©mĆ”t osszon meg a fejlesztőkkel."; /* No comment provided by engineer. */ -"Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg ismerősĆ©t, hogy küldjƶn egy mĆ”sikat."; +"Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg az ismerősĆ©t, hogy küldjƶn egy mĆ”sikat."; /* No comment provided by engineer. */ "Please check your network connection with %@ and try again." = "Ellenőrizze hĆ”lózati kapcsolatĆ”t a(z) %@ segĆ­tsĆ©gĆ©vel, Ć©s próbĆ”lja Ćŗjra."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "ProfilkĆ©pek"; -/* No comment provided by engineer. */ -"Profile name" = "ProfilnĆ©v"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profil neve:"; - /* No comment provided by engineer. */ "Profile password" = "Profiljelszó"; @@ -3493,7 +3484,7 @@ "Receive errors" = "ÜzenetfogadĆ”si hibĆ”k"; /* No comment provided by engineer. */ -"received answer…" = "fogadott vĆ”lasz…"; +"received answer…" = "vĆ”lasz fogadĆ”sa…"; /* No comment provided by engineer. */ "Received at" = "Fogadva ekkor:"; @@ -3647,7 +3638,7 @@ "Required" = "SzüksĆ©ges"; /* No comment provided by engineer. */ -"Reset" = "Alaphelyzetbe Ć”llĆ­tĆ”s"; +"Reset" = "VisszaĆ”llĆ­tĆ”s"; /* No comment provided by engineer. */ "Reset all hints" = "Tippek visszaĆ”llĆ­tĆ”sa"; @@ -3659,13 +3650,13 @@ "Reset all statistics?" = "Minden statisztika visszaĆ”llĆ­tĆ”sa?"; /* No comment provided by engineer. */ -"Reset colors" = "SzĆ­nek alaphelyzetbe Ć”llĆ­tĆ”sa"; +"Reset colors" = "SzĆ­nek visszaĆ”llĆ­tĆ”sa"; /* No comment provided by engineer. */ "Reset to app theme" = "AlkalmazĆ”s tĆ©mĆ”jĆ”nak visszaĆ”llĆ­tĆ”sa"; /* No comment provided by engineer. */ -"Reset to defaults" = "Alaphelyzetbe Ć”llĆ­tĆ”s"; +"Reset to defaults" = "VisszaĆ”llĆ­tĆ”s alaphelyzetbe"; /* No comment provided by engineer. */ "Reset to user theme" = "FelhasznĆ”ló Ć”ltal lĆ©trehozott tĆ©ma visszaĆ”llĆ­tĆ”sa"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "BiztonsĆ”gosabb csoportok"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "MentĆ©s"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "MentĆ©s (Ć©s az ismerősƶk Ć©rtesĆ­tĆ©se)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "ArchĆ­vum mentĆ©se"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Automatikus elfogadĆ”si beĆ”llĆ­tĆ”sok mentĆ©se"; - /* No comment provided by engineer. */ "Save group profile" = "Csoportprofil elmentĆ©se"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "KiszolgĆ”lók mentĆ©se?"; -/* No comment provided by engineer. */ -"Save settings?" = "BeĆ”llĆ­tĆ”sok mentĆ©se?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Üdvƶzlőszƶveg mentĆ©se?"; @@ -4013,10 +3999,10 @@ "Servers" = "KiszolgĆ”lók"; /* No comment provided by engineer. */ -"Servers info" = "informĆ”ciók a kiszolgĆ”lókról"; +"Servers info" = "InformĆ”ciók a kiszolgĆ”lókról"; /* No comment provided by engineer. */ -"Servers statistics will be reset - this cannot be undone!" = "A kiszolgĆ”lók statisztikĆ”i visszaĆ”llnak - ez nem vonható vissza!"; +"Servers statistics will be reset - this cannot be undone!" = "A kiszolgĆ”lók statisztikĆ”i visszaĆ”llnak - ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ "Session code" = "Munkamenet kód"; @@ -4136,7 +4122,7 @@ "SimpleX encrypted message or connection event" = "SimpleX titkosĆ­tott üzenet vagy kapcsolati esemĆ©ny"; /* simplex link type */ -"SimpleX group link" = "SimpleX csoport hivatkozĆ”s"; +"SimpleX group link" = "SimpleX csoporthivatkozĆ”s"; /* chat feature */ "SimpleX links" = "SimpleX hivatkozĆ”sok"; @@ -4274,7 +4260,7 @@ "Subscriptions ignored" = "ElutasĆ­tott feliratkozĆ”sok"; /* No comment provided by engineer. */ -"Support SimpleX Chat" = "TĆ”mogassa a SimpleX Chatet"; +"Support SimpleX Chat" = "SimpleX Chat tĆ”mogatĆ”sa"; /* No comment provided by engineer. */ "System" = "Rendszer"; @@ -4343,7 +4329,7 @@ "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Kƶszƶnet a felhasznĆ”lóknak – [hozzĆ”jĆ”rulĆ”s a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; /* No comment provided by engineer. */ -"Thanks to the users – contribute via Weblate!" = "Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten!"; +"Thanks to the users – contribute via Weblate!" = "Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en!"; /* No comment provided by engineer. */ "The 1st platform without any user identifiers – private by design." = "Az első csevegĆ©si rendszer bĆ”rmifĆ©le felhasznĆ”ló azonosĆ­tó nĆ©lkül - privĆ”tra lett tervezre."; @@ -4358,10 +4344,10 @@ "The attempt to change database passphrase was not completed." = "Az adatbĆ”zis jelmondatĆ”nak megvĆ”ltoztatĆ”sĆ”ra tett kĆ­sĆ©rlet nem fejeződƶtt be."; /* No comment provided by engineer. */ -"The code you scanned is not a SimpleX link QR code." = "A beolvasott kód nem egy SimpleX hivatkozĆ”s QR-kód."; +"The code you scanned is not a SimpleX link QR code." = "A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozĆ”s."; /* No comment provided by engineer. */ -"The connection you accepted will be cancelled!" = "Az ƶn Ć”ltal elfogadott kapcsolat vissza lesz vonva!"; +"The connection you accepted will be cancelled!" = "Az ƶn Ć”ltal elfogadott kĆ©relem vissza lesz vonva!"; /* No comment provided by engineer. */ "The contact you shared this link with will NOT be able to connect!" = "Ismerőse, akivel megosztotta ezt a hivatkozĆ”st, NEM fog tudni kapcsolódni!"; @@ -4373,7 +4359,7 @@ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosĆ­tĆ”s műkƶdik, Ć©s Ćŗj titkosĆ­tĆ”si egyezmĆ©nyre nincs szüksĆ©g. Ez kapcsolati hibĆ”kat eredmĆ©nyezhet!"; /* No comment provided by engineer. */ -"The hash of the previous message is different." = "Az előző üzenet ellenőrzőösszege külƶnbƶzik."; +"The hash of the previous message is different." = "Az előző üzenet hasĆ­tó Ć©rtĆ©ke külƶnbƶzik."; /* No comment provided by engineer. */ "The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A kƶvetkező üzenet azonosĆ­tója hibĆ”s (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő."; @@ -4442,7 +4428,7 @@ "This device name" = "Ennek az eszkƶznek a neve"; /* No comment provided by engineer. */ -"This display name is invalid. Please choose another name." = "Ez a megjelenĆ­tett felhasznĆ”lónĆ©v Ć©rvĆ©nytelen. VĆ”lasszon egy mĆ”sik nevet."; +"This display name is invalid. Please choose another name." = "Ez a megjelenĆ­tett nĆ©v Ć©rvĆ©nytelen. VĆ”lasszon egy mĆ”sik nevet."; /* No comment provided by engineer. */ "This group has over %lld members, delivery receipts are not sent." = "Ennek a csoportnak tƶbb mint %lld tagja van, a kĆ©zbesĆ­tĆ©si jelentĆ©sek nem kerülnek elküldĆ©sre."; @@ -4451,13 +4437,13 @@ "This group no longer exists." = "Ez a csoport mĆ”r nem lĆ©tezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az egyszer hasznĆ”latos hivatkozĆ”sa!"; +"This is your own one-time link!" = "Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa!"; /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Ez az ƶn SimpleX cĆ­me!"; /* No comment provided by engineer. */ -"This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozĆ”st egy mĆ”sik mobilleszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n."; +"This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozĆ”st egy mĆ”sik mobileszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n."; /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Ez a beĆ”llĆ­tĆ”s a jelenlegi **%@** profiljĆ”ban lĆ©vő üzenetekre Ć©rvĆ©nyes."; @@ -4520,10 +4506,10 @@ "Transport sessions" = "Munkamenetek Ć”tvitele"; /* No comment provided by engineer. */ -"Trying to connect to the server used to receive messages from this contact (error: %@)." = "KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l (hiba: %@)."; +"Trying to connect to the server used to receive messages from this contact (error: %@)." = "KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l (hiba: %@)."; /* No comment provided by engineer. */ -"Trying to connect to the server used to receive messages from this contact." = "KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l."; +"Trying to connect to the server used to receive messages from this contact." = "KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l."; /* No comment provided by engineer. */ "Turkish interface" = "Tƶrƶk kezelőfelület"; @@ -4598,7 +4584,7 @@ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hĆ­vĆ”si felületĆ©t hasznĆ”lja, engedĆ©lyezze a Ne zavarjanak módot a megszakĆ­tĆ”sok elkerülĆ©se Ć©rdekĆ©ben."; /* No comment provided by engineer. */ -"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerőse nem tƶrƶlte a kapcsolatot, vagy ez a hivatkozĆ”s mĆ”r hasznĆ”latban volt, lehet hogy ez egy hiba – jelentse a problĆ©mĆ”t.\nA kapcsolódĆ”shoz kĆ©rje meg ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsolati hivatkozĆ”st, Ć©s ellenőrizze, hogy a hĆ”lózati kapcsolat stabil-e."; +"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerőse nem tƶrƶlte a kapcsolatot, vagy ez a hivatkozĆ”s mĆ”r hasznĆ”latban volt egyszer, lehet hogy ez egy hiba – jelentse a problĆ©mĆ”t.\nA kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsolattartĆ”si hivatkozĆ”st, Ć©s ellenőrizze, hogy a hĆ”lózati kapcsolat stabil-e."; /* No comment provided by engineer. */ "Unlink" = "SzĆ©tkapcsolĆ”s"; @@ -4682,7 +4668,7 @@ "Use for new connections" = "AlkalmazĆ”s Ćŗj kapcsolatokhoz"; /* No comment provided by engineer. */ -"Use from desktop" = "HasznĆ”lat szĆ”mĆ­tógĆ©pről"; +"Use from desktop" = "TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel"; /* No comment provided by engineer. */ "Use iOS call interface" = "Az iOS hĆ­vófelület hasznĆ”lata"; @@ -4754,10 +4740,10 @@ "via contact address link" = "kapcsolattartĆ”si cĆ­m-hivatkozĆ”son keresztül"; /* chat list item description */ -"via group link" = "csoport hivatkozĆ”son keresztül"; +"via group link" = "a csoporthivatkozĆ”son keresztül"; /* chat list item description */ -"via one-time link" = "egyszer hasznĆ”latos hivatkozĆ”son keresztül"; +"via one-time link" = "egy egyszer hasznĆ”latos hivatkozĆ”son keresztül"; /* No comment provided by engineer. */ "via relay" = "Ć”tjĆ”tszón keresztül"; @@ -4934,7 +4920,7 @@ "You already have a chat profile with the same display name. Please choose another name." = "MĆ”r van egy csevegĆ©si profil ugyanezzel a megjelenĆ­tett nĆ©vvel. VĆ”lasszon egy mĆ”sik nevet."; /* No comment provided by engineer. */ -"You are already connected to %@." = "MĆ”r kapcsolódva van hozzĆ”: %@."; +"You are already connected to %@." = "Ɩn mĆ”r kapcsolódva van ehhez: %@."; /* No comment provided by engineer. */ "You are already connecting to %@." = "MĆ”r folyamatban van a kapcsolódĆ”s ehhez: %@."; @@ -4958,7 +4944,7 @@ "You are already joining the group!\nRepeat join request?" = "CsatlakozĆ”s folyamatban!\nCsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se?"; /* No comment provided by engineer. */ -"You are connected to the server used to receive messages from this contact." = "MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l."; +"You are connected to the server used to receive messages from this contact." = "MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l."; /* No comment provided by engineer. */ "you are invited to group" = "meghĆ­vĆ”st kapott a csoportba"; @@ -4973,7 +4959,7 @@ "you are observer" = "megfigyelő szerep"; /* snd group event chat item */ -"you blocked %@" = "ƶn letiltotta %@-t"; +"you blocked %@" = "ƶn letiltotta őt: %@"; /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "HĆ­vĆ”sokat fogadhat a lezĆ”rĆ”si kĆ©pernyőről, eszkƶz- Ć©s alkalmazĆ”shitelesĆ­tĆ©s nĆ©lkül."; @@ -4997,7 +4983,7 @@ "You can hide or mute a user profile - swipe it to the right." = "Elrejtheti vagy lenĆ©mĆ­thatja a felhasznĆ”ló profiljait - csĆŗsztassa jobbra a profilt."; /* No comment provided by engineer. */ -"You can make it visible to your SimpleX contacts via Settings." = "LĆ”thatóvĆ” teheti SimpleX ismerősƶk szĆ”mĆ”ra a BeĆ”llĆ­tĆ”sokban."; +"You can make it visible to your SimpleX contacts via Settings." = "LĆ”thatóvĆ” teheti a SimpleXbeli ismerősei szĆ”mĆ”ra a ā€žBeĆ”llĆ­tĆ”sokbanā€."; /* notification body */ "You can now chat with %@" = "Mostantól küldhet üzeneteket %@ szĆ”mĆ”ra"; @@ -5111,7 +5097,7 @@ "You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; /* No comment provided by engineer. */ -"You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoportos hivatkozĆ”s tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; +"You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoporthivatkozĆ”s tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; /* No comment provided by engineer. */ "You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolódĆ”si kĆ©relme elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja megosztĆ”sra fog kerülni."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profilja az eszkƶzƶn van tĆ”rolva, Ć©s csak az ismerősƶkkel kerül megosztĆ”sra.\nA SimpleX kiszolgĆ”lók nem lĆ”tjhatjĆ”k profiljĆ”t."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profilja az eszkƶzƶn van tĆ”rolva, Ć©s csak az ismerősƶkkel kerül megosztĆ”sra. A SimpleX kiszolgĆ”lók nem lĆ”tjhatjĆ”k profiljĆ”t."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profilja, ismerősei Ć©s az elküldƶtt üzenetei az eszkƶzƶn kerülnek tĆ”rolĆ”sra."; @@ -5207,7 +5193,7 @@ "Your settings" = "BeĆ”llĆ­tĆ”sok"; /* No comment provided by engineer. */ -"Your SimpleX address" = "Az ƶn SimpleX cĆ­me"; +"Your SimpleX address" = "Profil SimpleX cĆ­me"; /* No comment provided by engineer. */ "Your SMP servers" = "SMP kiszolgĆ”lók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index f3fa0424cc..c60c1537a6 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Impossibile inviare un messaggio al membro"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Annulla"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Nome completo (facoltativo)"; -/* No comment provided by engineer. */ -"Full name:" = "Nome completo:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Completamente decentralizzato: visibile solo ai membri."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Immagini del profilo"; -/* No comment provided by engineer. */ -"Profile name" = "Nome del profilo"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nome del profilo:"; - /* No comment provided by engineer. */ "Profile password" = "Password del profilo"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Gruppi più sicuri"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Salva"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Salva archivio"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Salva le impostazioni di accettazione automatica"; - /* No comment provided by engineer. */ "Save group profile" = "Salva il profilo del gruppo"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Salvare i server?"; -/* No comment provided by engineer. */ -"Save settings?" = "Salvare le impostazioni?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Salvare il messaggio di benvenuto?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "VerrĆ  condiviso il tuo profilo **%@**."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Il tuo profilo ĆØ memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti.\nI server di SimpleX non possono vedere il tuo profilo."; +"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."; /* No comment provided by engineer. */ "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."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 2a924539c8..8a6c48532c 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -619,7 +619,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "é€£ēµ”å…ˆć‚’ę‹›å¾…ć§ćć¾ć›ć‚“ļ¼"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "äø­ę­¢"; /* feature offered item */ @@ -1594,9 +1594,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "ćƒ•ćƒ«ćƒćƒ¼ćƒ  (ä»»ę„):"; -/* No comment provided by engineer. */ -"Full name:" = "ćƒ•ćƒ«ćƒćƒ¼ćƒ ļ¼š"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "å®Œå…Øć«å†å®Ÿč£…ć•ć‚Œć¾ć—ćŸ - ćƒćƒƒć‚Æć‚°ćƒ©ć‚¦ćƒ³ćƒ‰ć§å‹•ä½œć—ć¾ć™!"; @@ -2647,10 +2644,11 @@ /* No comment provided by engineer. */ "Run chat" = "ćƒćƒ£ćƒƒćƒˆčµ·å‹•"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "äæå­˜"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "äæå­˜ļ¼ˆé€£ēµ”å…ˆć«é€šēŸ„ļ¼‰"; /* No comment provided by engineer. */ @@ -2665,9 +2663,6 @@ /* No comment provided by engineer. */ "Save archive" = "ć‚¢ćƒ¼ć‚«ć‚¤ćƒ–ć‚’äæå­˜"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "č‡Ŗå‹•å—ć‘å…„ć‚ŒčØ­å®šć‚’äæå­˜ć™ć‚‹"; - /* No comment provided by engineer. */ "Save group profile" = "ć‚°ćƒ«ćƒ¼ćƒ—ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ć®äæå­˜"; @@ -2689,9 +2684,6 @@ /* No comment provided by engineer. */ "Save servers?" = "ć‚µćƒ¼ćƒć‚’äæå­˜ć—ć¾ć™ć‹ļ¼Ÿ"; -/* No comment provided by engineer. */ -"Save settings?" = "čØ­å®šć‚’äæå­˜ć—ć¾ć™ć‹ļ¼Ÿ"; - /* No comment provided by engineer. */ "Save welcome message?" = "ć‚¦ć‚§ćƒ«ć‚«ćƒ ćƒ”ćƒƒć‚»ćƒ¼ć‚øć‚’äæå­˜ć—ć¾ć™ć‹ļ¼Ÿ"; @@ -3566,7 +3558,7 @@ "Your profile **%@** will be shared." = "ć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚”ć‚¤ćƒ« **%@** ćŒå…±ęœ‰ć•ć‚Œć¾ć™ć€‚"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ćÆćƒ‡ćƒć‚¤ć‚¹ć«äæå­˜ć•ć‚Œć€é€£ēµ”å…ˆćØć®ćæå…±ęœ‰ć•ć‚Œć¾ć™ć€‚\nSimpleX ć‚µćƒ¼ćƒćƒ¼ćÆć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚”ć‚¤ćƒ«ć‚’å‚ē…§ć§ćć¾ć›ć‚“ć€‚"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ćÆćƒ‡ćƒć‚¤ć‚¹ć«äæå­˜ć•ć‚Œć€é€£ēµ”å…ˆćØć®ćæå…±ęœ‰ć•ć‚Œć¾ć™ć€‚ SimpleX ć‚µćƒ¼ćƒćƒ¼ćÆć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚”ć‚¤ćƒ«ć‚’å‚ē…§ć§ćć¾ć›ć‚“ć€‚"; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "ć‚ćŖćŸć®ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ć€é€£ēµ”å…ˆć€é€äæ”ć—ćŸćƒ”ćƒƒć‚»ćƒ¼ć‚øćŒć”č‡Ŗåˆ†ć®ē«Æęœ«ć«äæå­˜ć•ć‚Œć¾ć™ć€‚"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 7d452743c6..6c566fa7da 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Kan geen bericht sturen naar lid"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Annuleren"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Volledige naam (optioneel)"; -/* No comment provided by engineer. */ -"Full name:" = "Volledige naam:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Volledig gedecentraliseerd – alleen zichtbaar voor leden."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profiel afbeeldingen"; -/* No comment provided by engineer. */ -"Profile name" = "Profielnaam"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profielnaam:"; - /* No comment provided by engineer. */ "Profile password" = "Profiel wachtwoord"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Veiligere groepen"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Opslaan"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Bewaar (en informeer contacten)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Bewaar archief"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Sla instellingen voor automatisch accepteren op"; - /* No comment provided by engineer. */ "Save group profile" = "Groep profiel opslaan"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Servers opslaan?"; -/* No comment provided by engineer. */ -"Save settings?" = "Instellingen opslaan?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Welkom bericht opslaan?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Uw profiel **%@** wordt gedeeld."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten.\nSimpleX servers kunnen uw profiel niet zien."; +"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."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 179b2d3848..7def1c7e02 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "Nie można wysłać wiadomości do członka"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Anuluj"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Pełna nazwa (opcjonalna)"; -/* No comment provided by engineer. */ -"Full name:" = "Pełna nazwa:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "W pełni zdecentralizowana – widoczna tylko dla członków."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Zdjęcia profilowe"; -/* No comment provided by engineer. */ -"Profile name" = "Nazwa profilu"; - -/* No comment provided by engineer. */ -"Profile name:" = "Nazwa profilu:"; - /* No comment provided by engineer. */ "Profile password" = "Hasło profilu"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Bezpieczniejsze grupy"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Zapisz"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Zapisz (i powiadom kontakty)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Zapisz archiwum"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Zapisz ustawienia automatycznej akceptacji"; - /* No comment provided by engineer. */ "Save group profile" = "Zapisz profil grupy"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Zapisać serwery?"; -/* No comment provided by engineer. */ -"Save settings?" = "Zapisać ustawienia?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Zapisać wiadomość powitalną?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Twój profil **%@** zostanie udostępniony."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom.\nSerwery SimpleX nie mogą zobaczyć Twojego profilu."; +"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."; /* No comment provided by engineer. */ "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."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 5d74647d07..038041d0e6 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "ŠŠµ ŃƒŠ“Š°ŠµŃ‚ŃŃ Š½Š°ŠæŠøŃŠ°Ń‚ŃŒ Ń‡Š»ŠµŠ½Ńƒ Š³Ń€ŃƒŠæŠæŃ‹"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "ŠžŃ‚Š¼ŠµŠ½ŠøŃ‚ŃŒ"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Полное ŠøŠ¼Ń (не Š¾Š±ŃŠ·Š°Ń‚ŠµŠ»ŃŒŠ½Š¾)"; -/* No comment provided by engineer. */ -"Full name:" = "Полное ŠøŠ¼Ń:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Š“Ń€ŃƒŠæŠæŠ° ŠæŠ¾Š»Š½Š¾ŃŃ‚ŃŒŃŽ Гецентрализована – она виГна Ń‚Š¾Š»ŃŒŠŗŠ¾ членам."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "ŠšŠ°Ń€Ń‚ŠøŠ½ŠŗŠø профилей"; -/* No comment provided by engineer. */ -"Profile name" = "Š˜Š¼Ń ŠæŃ€Š¾Ń„ŠøŠ»Ń"; - -/* No comment provided by engineer. */ -"Profile name:" = "Š˜Š¼Ń ŠæŃ€Š¾Ń„ŠøŠ»Ń:"; - /* No comment provided by engineer. */ "Profile password" = "ŠŸŠ°Ń€Š¾Š»ŃŒ ŠæŃ€Š¾Ń„ŠøŠ»Ń"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Более безопасные Š³Ń€ŃƒŠæŠæŃ‹"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ (Šø ŃƒŠ²ŠµŠ“Š¾Š¼ŠøŃ‚ŃŒ контакты)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ архив"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ настройки автоприема"; - /* No comment provided by engineer. */ "Save group profile" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ Š³Ń€ŃƒŠæŠæŃ‹"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ серверы?"; -/* No comment provided by engineer. */ -"Save settings?" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ настройки?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Š”Š¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ приветственное сообщение?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Š‘ŃƒŠ“ŠµŃ‚ отправлен Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ **%@**."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ Ń…Ń€Š°Š½ŠøŃ‚ŃŃ на Š’Š°ŃˆŠµŠ¼ ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Šµ Šø Š¾Ń‚ŠæŃ€Š°Š²Š»ŃŠµŃ‚ŃŃ Ń‚Š¾Š»ŃŒŠŗŠ¾ Š’Š°ŃˆŠøŠ¼ контактам.\nSimpleX серверы не Š¼Š¾Š³ŃƒŃ‚ ŠæŠ¾Š»ŃƒŃ‡ŠøŃ‚ŃŒ Š“Š¾ŃŃ‚ŃƒŠæ Šŗ Š’Š°ŃˆŠµŠ¼Ńƒ ŠæŃ€Š¾Ń„ŠøŠ»ŃŽ."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ Ń…Ń€Š°Š½ŠøŃ‚ŃŃ на Š’Š°ŃˆŠµŠ¼ ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Šµ Šø Š¾Ń‚ŠæŃ€Š°Š²Š»ŃŠµŃ‚ŃŃ Ń‚Š¾Š»ŃŒŠŗŠ¾ Š’Š°ŃˆŠøŠ¼ контактам. SimpleX серверы не Š¼Š¾Š³ŃƒŃ‚ ŠæŠ¾Š»ŃƒŃ‡ŠøŃ‚ŃŒ Š“Š¾ŃŃ‚ŃƒŠæ Šŗ Š’Š°ŃˆŠµŠ¼Ńƒ ŠæŃ€Š¾Ń„ŠøŠ»ŃŽ."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„ŠøŠ»ŃŒ, контакты Šø Гоставленные ŃŠ¾Š¾Š±Ń‰ŠµŠ½ŠøŃ Ń…Ń€Š°Š½ŃŃ‚ŃŃ на Š’Š°ŃˆŠµŠ¼ ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²Šµ."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 9726441a1f..994a10c9db 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -523,7 +523,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ą¹€ąøŠąø“ąøąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ą¹„ąø”ą¹‰!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "ยกเคณก"; /* feature offered item */ @@ -1468,9 +1468,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "ąøŠąø·ą¹ˆąø­ą¹€ąø•ą¹‡ąø” (ą¹„ąø”ą¹ˆąøšąø±ąø‡ąø„ąø±ąøš)"; -/* No comment provided by engineer. */ -"Full name:" = "ąøŠąø·ą¹ˆąø­ą¹€ąø•ą¹‡ąø”:"; - /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "ąø”ąø³ą¹€ąø™ąø“ąø™ąøąø²ąø£ą¹ƒąø«ąø”ą¹ˆąø­ąø¢ą¹ˆąø²ąø‡ąøŖąø”ąøšąø¹ąø£ąø“ą¹Œ - ąø—ąø³ąø‡ąø²ąø™ą¹ƒąø™ąøžąø·ą¹‰ąø™ąø«ąø„ąø±ąø‡!"; @@ -2503,10 +2500,11 @@ /* No comment provided by engineer. */ "Run chat" = "ą¹€ąø£ąøµąø¢ąøą¹ƒąøŠą¹‰ą¹ąøŠąø—"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "ąøšąø±ąø™ąø—ąø¶ąø"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "ąøšąø±ąø™ąø—ąø¶ąø (ą¹ąø„ąø°ą¹ąøˆą¹‰ąø‡ąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­)"; /* No comment provided by engineer. */ @@ -2521,9 +2519,6 @@ /* No comment provided by engineer. */ "Save archive" = "ąøšąø±ąø™ąø—ąø¶ąøą¹„ąøŸąø„ą¹Œą¹€ąøą¹‡ąøšąø–ąø²ąø§ąø£"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "ąøšąø±ąø™ąø—ąø¶ąøąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø²ąøąø²ąø£ąø¢ąø­ąø”ąø£ąø±ąøšąø­ąø±ąø•ą¹‚ąø™ąø”ąø±ąø•ąø“"; - /* No comment provided by engineer. */ "Save group profile" = "ąøšąø±ąø™ąø—ąø¶ąøą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąøąø„ąøøą¹ˆąø”"; @@ -2545,9 +2540,6 @@ /* No comment provided by engineer. */ "Save servers?" = "ąøšąø±ąø™ąø—ąø¶ąøą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ?"; -/* No comment provided by engineer. */ -"Save settings?" = "ąøšąø±ąø™ąø—ąø¶ąøąøąø²ąø£ąø•ąø±ą¹‰ąø‡ąø„ą¹ˆąø²?"; - /* No comment provided by engineer. */ "Save welcome message?" = "ąøšąø±ąø™ąø—ąø¶ąøąø‚ą¹‰ąø­ąø„ąø§ąø²ąø”ąø•ą¹‰ąø­ąø™ąø£ąø±ąøš?"; @@ -3413,7 +3405,7 @@ "Your privacy" = "ąø„ąø§ąø²ąø”ą¹€ąø›ą¹‡ąø™ąøŖą¹ˆąø§ąø™ąø•ąø±ąø§ąø‚ąø­ąø‡ąø„ąøøąø“"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø–ąø¹ąøąøˆąø±ąø”ą¹€ąøą¹‡ąøšą¹„ąø§ą¹‰ą¹ƒąø™ąø­ąøøąø›ąøąø£ąø“ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹ąø„ąø°ą¹ąøŠąø£ą¹Œąøąø±ąøšąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ąø‚ąø­ąø‡ąø„ąøøąø“ą¹€ąø—ą¹ˆąø²ąø™ąø±ą¹‰ąø™\ną¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ SimpleX ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąø”ąø¹ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹„ąø”ą¹‰"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø–ąø¹ąøąøˆąø±ąø”ą¹€ąøą¹‡ąøšą¹„ąø§ą¹‰ą¹ƒąø™ąø­ąøøąø›ąøąø£ąø“ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹ąø„ąø°ą¹ąøŠąø£ą¹Œąøąø±ąøšąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ąø‚ąø­ąø‡ąø„ąøøąø“ą¹€ąø—ą¹ˆąø²ąø™ąø±ą¹‰ąø™ ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ SimpleX ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąø”ąø¹ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“ą¹„ąø”ą¹‰"; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "ą¹‚ąø›ąø£ą¹„ąøŸąø„ą¹Œ ąø£ąø²ąø¢ąøŠąø·ą¹ˆąø­ąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ ą¹ąø„ąø°ąø‚ą¹‰ąø­ąø„ąø§ąø²ąø”ąø—ąøµą¹ˆąøŖą¹ˆąø‡ąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø–ąø¹ąøąøˆąø±ąø”ą¹€ąøą¹‡ąøšą¹„ąø§ą¹‰ą¹ƒąø™ąø­ąøøąø›ąøąø£ąø“ą¹Œąø‚ąø­ąø‡ąø„ąøøąø“"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 892f38fcbc..f39140340b 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -703,7 +703,7 @@ /* No comment provided by engineer. */ "Can't invite contacts!" = "Kişiler davet edilemiyor!"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "İptal et"; /* No comment provided by engineer. */ @@ -1930,9 +1930,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Bütün isim (opsiyonel)"; -/* No comment provided by engineer. */ -"Full name:" = "Bütün isim:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "Tamamiyle merkezi olmayan - sadece kişilere gƶrünür."; @@ -2991,12 +2988,6 @@ /* No comment provided by engineer. */ "Profile images" = "Profil resimleri"; -/* No comment provided by engineer. */ -"Profile name" = "Profil ismi"; - -/* No comment provided by engineer. */ -"Profile name:" = "Profil ismi:"; - /* No comment provided by engineer. */ "Profile password" = "Profil parolası"; @@ -3271,10 +3262,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Daha güvenli gruplar"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Kaydet"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Kaydet (ve kişilere bildir)"; /* No comment provided by engineer. */ @@ -3289,9 +3281,6 @@ /* No comment provided by engineer. */ "Save archive" = "Arşivi kaydet"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Otomatik kabul et ayarlarını kaydet"; - /* No comment provided by engineer. */ "Save group profile" = "Grup profilini kaydet"; @@ -3313,9 +3302,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Sunucular kaydedilsin mi?"; -/* No comment provided by engineer. */ -"Save settings?" = "Ayarlar kaydedilsin mi?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Hoşgeldin mesajı kaydedilsin mi?"; @@ -4544,7 +4530,7 @@ "Your profile **%@** will be shared." = "Profiliniz **%@** paylaşılacaktır."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır.\nSimpleX sunucuları profilinizi gƶremez."; +"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."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profiliniz, kişileriniz ve gƶnderilmiş mesajlar cihazınızda saklanır."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 1908d386a3..4218065a2e 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -781,7 +781,7 @@ /* No comment provided by engineer. */ "Can't message member" = "ŠŠµ можу наГіслати ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń ŠŗŠ¾Ń€ŠøŃŃ‚ŃƒŠ²Š°Ń‡ŠµŠ²Ń–"; -/* No comment provided by engineer. */ +/* alert button */ "Cancel" = "Š”ŠŗŠ°ŃŃƒŠ²Š°Ń‚Šø"; /* No comment provided by engineer. */ @@ -2203,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "Повне ім'я (необов'ŃŠ·ŠŗŠ¾Š²Š¾)"; -/* No comment provided by engineer. */ -"Full name:" = "Повне ім'я:"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "ŠŸŠ¾Š²Š½Ń–ŃŃ‚ŃŽ Гецентралізована - виГима лише Š“Š»Ń ŃƒŃ‡Š°ŃŠ½ŠøŠŗŃ–Š²."; @@ -3378,12 +3375,6 @@ /* No comment provided by engineer. */ "Profile images" = "Š—Š¾Š±Ń€Š°Š¶ŠµŠ½Š½Ń ŠæŃ€Š¾Ń„Ń–Š»ŃŽ"; -/* No comment provided by engineer. */ -"Profile name" = "ŠŠ°Š·Š²Š° ŠæŃ€Š¾Ń„Ń–Š»ŃŽ"; - -/* No comment provided by engineer. */ -"Profile name:" = "Ім'я ŠæŃ€Š¾Ń„Ń–Š»ŃŽ:"; - /* No comment provided by engineer. */ "Profile password" = "ŠŸŠ°Ń€Š¾Š»ŃŒ Го ŠæŃ€Š¾Ń„Ń–Š»ŃŽ"; @@ -3715,10 +3706,11 @@ /* No comment provided by engineer. */ "Safer groups" = "Š‘ŠµŠ·ŠæŠµŃ‡Š½Ń–ŃˆŃ– Š³Ń€ŃƒŠæŠø"; -/* chat item action */ +/* alert button + chat item action */ "Save" = "Зберегти"; -/* No comment provided by engineer. */ +/* alert button */ "Save (and notify contacts)" = "Зберегти (і повіГомити контактам)"; /* No comment provided by engineer. */ @@ -3736,9 +3728,6 @@ /* No comment provided by engineer. */ "Save archive" = "Зберегти архів"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "Зберегти Š½Š°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń Š°Š²Ń‚Š¾ŠæŃ€ŠøŠ¹Š¾Š¼Ńƒ"; - /* No comment provided by engineer. */ "Save group profile" = "Зберегти ŠæŃ€Š¾Ń„Ń–Š»ŃŒ Š³Ń€ŃƒŠæŠø"; @@ -3760,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "Зберегти сервери?"; -/* No comment provided by engineer. */ -"Save settings?" = "Зберегти Š½Š°Š»Š°ŃˆŃ‚ŃƒŠ²Š°Š½Š½Ń?"; - /* No comment provided by engineer. */ "Save welcome message?" = "Зберегти Š²Ń–Ń‚Š°Š»ŃŒŠ½Šµ ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń?"; @@ -5189,7 +5175,7 @@ "Your profile **%@** will be shared." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ **%@** буГе Š¾ŠæŃƒŠ±Š»Ń–кований."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ Š·Š±ŠµŃ€Ń–Š³Š°Ń”Ń‚ŃŒŃŃ на вашому пристрої і Š“Š¾ŃŃ‚ŃƒŠæŠ½ŠøŠ¹ лише вашим контактам.\nДервери SimpleX не Š±Š°Ń‡Š°Ń‚ŃŒ ваш ŠæŃ€Š¾Ń„Ń–Š»ŃŒ."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ Š·Š±ŠµŃ€Ń–Š³Š°Ń”Ń‚ŃŒŃŃ на вашому пристрої і Š“Š¾ŃŃ‚ŃƒŠæŠ½ŠøŠ¹ лише вашим контактам. Дервери SimpleX не Š±Š°Ń‡Š°Ń‚ŃŒ ваш ŠæŃ€Š¾Ń„Ń–Š»ŃŒ."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Š’Š°Ńˆ ŠæŃ€Š¾Ń„Ń–Š»ŃŒ, контакти та Гоставлені ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń Š·Š±ŠµŃ€Ń–Š³Š°ŃŽŃ‚ŃŒŃŃ на вашому пристрої."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 1d37572498..0f78665b83 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -46,6 +46,12 @@ /* No comment provided by engineer. */ "(" = "("; +/* No comment provided by engineer. */ +"(new)" = "(ꖰ)"; + +/* No comment provided by engineer. */ +"(this device v%@)" = "(此设备 v%@)"; + /* No comment provided by engineer. */ ")" = ")"; @@ -58,9 +64,15 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat)"; +/* No comment provided by engineer. */ +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**ę·»åŠ č”ē³»äŗŗ**: åˆ›å»ŗę–°ēš„é‚€čÆ·é“¾ęŽ„ļ¼Œęˆ–é€ščæ‡ę‚Øę”¶åˆ°ēš„é“¾ęŽ„čæ›č”ŒčæžęŽ„."; + /* No comment provided by engineer. */ "**Add new contact**: to create your one-time QR Code for your contact." = "**ę·»åŠ ę–°č”ē³»äŗŗ**ļ¼šäøŗę‚Øēš„č”ē³»äŗŗåˆ›å»ŗäø€ę¬”ę€§äŗŒē»“ē ęˆ–č€…é“¾ęŽ„ć€‚"; +/* No comment provided by engineer. */ +"**Create group**: to create a new group." = "**åˆ›å»ŗē¾¤ē»„**: åˆ›å»ŗäø€äøŖę–°ē¾¤ē»„."; + /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**ē«Æåˆ°ē«ÆåŠ åÆ†** čÆ­éŸ³é€ščÆ"; @@ -73,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**ęœ€ē§åÆ†**ļ¼šäøä½æē”Ø SimpleX Chat é€šēŸ„ęœåŠ”å™Øļ¼ŒåœØåŽå°å®šęœŸę£€ęŸ„ę¶ˆęÆļ¼ˆå–å†³äŗŽę‚Øå¤šē»åøøä½æē”Øåŗ”ē”ØēØ‹åŗļ¼‰ć€‚"; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**čÆ·ę³Øę„**: åœØäø¤å°č®¾å¤‡äøŠä½æē”Øē›øåŒēš„ę•°ę®åŗ“å°†ē “åę„č‡Ŗę‚Øēš„čæžęŽ„ēš„ę¶ˆęÆč§£åÆ†ļ¼Œä½œäøŗäø€ē§å®‰å…ØäæęŠ¤."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**čÆ·ę³Øę„**ļ¼šå¦‚ęžœę‚Øäø¢å¤±åÆ†ē ļ¼Œę‚Øå°†ę— ę³•ę¢å¤ęˆ–č€…ę›“ę”¹åÆ†ē ć€‚"; @@ -82,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**č­¦å‘Š**ļ¼šåŠę—¶ęŽØé€é€šēŸ„éœ€č¦äæå­˜åœØé’„åŒ™äø²ēš„åÆ†ē ć€‚"; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**č­¦å‘Š**: å­˜ę”£å°†č¢«åˆ é™¤."; + /* No comment provided by engineer. */ "*bold*" = "\\*åŠ ē²—*"; @@ -124,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ å·²čæžęŽ„"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ 已下载"; + /* notification title */ "%@ is connected!" = "%@ å·²čæžęŽ„ļ¼"; @@ -133,6 +154,9 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ 已认证"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ 已上传"; + /* notification title */ "%@ wants to connect!" = "%@ č¦čæžęŽ„ļ¼"; @@ -187,6 +211,15 @@ /* No comment provided by engineer. */ "%lld messages blocked" = "%lld ę”ę¶ˆęÆå·²å±č”½"; +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld č¢«ē®”ē†å‘˜é˜»ę­¢ēš„ę¶ˆęÆ"; + +/* No comment provided by engineer. */ +"%lld messages marked deleted" = "%lld ę ‡č®°äøŗå·²åˆ é™¤ēš„ę¶ˆęÆ"; + +/* No comment provided by engineer. */ +"%lld messages moderated by %@" = "%lld å®”ę øēš„ē•™čØ€ by %@"; + /* No comment provided by engineer. */ "%lld minutes" = "%lld 分钟"; @@ -235,6 +268,9 @@ /* No comment provided by engineer. */ "~strike~" = "\\~删去~"; +/* time to disappear */ +"0 sec" = "0 ē§’"; + /* No comment provided by engineer. */ "0s" = "0ē§’"; @@ -298,6 +334,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "äøŠé¢ļ¼Œē„¶åŽé€‰ę‹©ļ¼š"; +/* No comment provided by engineer. */ +"Accent" = "强调"; + /* accept contact request via notification accept incoming call via notification swipe action */ @@ -316,6 +355,15 @@ /* call status */ "accepted call" = "å·²ęŽ„å—é€ščÆ"; +/* No comment provided by engineer. */ +"Acknowledged" = "甮认"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "甮认错误"; + +/* No comment provided by engineer. */ +"Active connections" = "ę“»åŠØčæžęŽ„"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "å°†åœ°å€ę·»åŠ åˆ°ę‚Øēš„äøŖäŗŗčµ„ę–™ļ¼Œä»„ä¾æę‚Øēš„č”ē³»äŗŗåÆä»„äøŽå…¶ä»–äŗŗå…±äŗ«ć€‚äøŖäŗŗčµ„ę–™ę›“ę–°å°†å‘é€ē»™ę‚Øēš„č”ē³»äŗŗć€‚"; @@ -340,6 +388,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "ę·»åŠ ę¬¢čæŽäæ”ęÆ"; +/* No comment provided by engineer. */ +"Additional accent" = "附加重音"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "附加重音 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "é™„åŠ äŗŒēŗ§"; + /* No comment provided by engineer. */ "Address" = "地址"; @@ -361,6 +418,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "é«˜ēŗ§ē½‘ē»œč®¾ē½®"; +/* No comment provided by engineer. */ +"Advanced settings" = "高级设置"; + /* chat item text */ "agreeing encryption for %@…" = "ę­£åœØåå•†å°†åŠ åÆ†åŗ”ē”ØäŗŽ %@…"; @@ -376,6 +436,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "ę‰€ęœ‰ę•°ę®åœØč¾“å…„åŽå°†č¢«åˆ é™¤ć€‚"; +/* No comment provided by engineer. */ +"All data is private to your device." = "ę‰€ęœ‰ę•°ę®éƒ½ę˜Æę‚Øč®¾å¤‡ēš„ē§ęœ‰ę•°ę®."; + /* No comment provided by engineer. */ "All group members will remain connected." = "ę‰€ęœ‰ē¾¤ē»„ęˆå‘˜å°†äæęŒčæžęŽ„ć€‚"; @@ -388,6 +451,12 @@ /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "ę‰€ęœ‰čŠå¤©č®°å½•å’Œę¶ˆęÆå°†č¢«åˆ é™¤ā€”ā€”čæ™äø€č”Œäøŗę— ę³•ę’¤é”€ļ¼åŖęœ‰ę‚Øēš„ę¶ˆęÆä¼šč¢«åˆ é™¤ć€‚"; +/* No comment provided by engineer. */ +"All new messages from %@ will be hidden!" = "ę„č‡Ŗ %@ ēš„ę‰€ęœ‰ę–°ę¶ˆęÆéƒ½å°†č¢«éšč—!"; + +/* No comment provided by engineer. */ +"All profiles" = "ę‰€ęœ‰é…ē½®ę–‡ä»¶"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "ę‰€ęœ‰č”ē³»äŗŗä¼šäæęŒčæžęŽ„ć€‚"; @@ -403,11 +472,17 @@ /* No comment provided by engineer. */ "Allow calls only if your contact allows them." = "ä»…å½“ę‚Øēš„č”ē³»äŗŗå…č®øę—¶ę‰å…č®øå‘¼å«ć€‚"; +/* No comment provided by engineer. */ +"Allow calls?" = "å…č®øé€ščÆļ¼Ÿ"; + /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "ä»…å½“ę‚Øēš„č”ē³»äŗŗå…č®øę—¶ę‰å…č®øé™ę—¶ę¶ˆęÆć€‚"; /* No comment provided by engineer. */ -"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "ä»…ęœ‰ę‚Øēš„č”ē³»äŗŗč®øåÆåŽę‰å…č®øäøåÆę’¤å›žę¶ˆęÆē§»é™¤ć€‚"; +"Allow downgrade" = "å…č®øé™ēŗ§"; + +/* No comment provided by engineer. */ +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "ä»…ęœ‰ę‚Øēš„č”ē³»äŗŗč®øåÆåŽę‰å…č®øäøåÆę’¤å›žę¶ˆęÆē§»é™¤"; /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "åŖęœ‰ę‚Øēš„č”ē³»äŗŗå…č®øę—¶ę‰å…č®øę¶ˆęÆå›žåŗ”ć€‚"; @@ -422,7 +497,10 @@ "Allow sending disappearing messages." = "å…č®øå‘é€é™ę—¶ę¶ˆęÆć€‚"; /* No comment provided by engineer. */ -"Allow to irreversibly delete sent messages. (24 hours)" = "å…č®øäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆć€‚"; +"Allow sharing" = "允许共享"; + +/* No comment provided by engineer. */ +"Allow to irreversibly delete sent messages. (24 hours)" = "å…č®øäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆ"; /* No comment provided by engineer. */ "Allow to send files and media." = "å…č®øå‘é€ę–‡ä»¶å’ŒåŖ’ä½“ć€‚"; @@ -446,7 +524,7 @@ "Allow your contacts to call you." = "å…č®øę‚Øēš„č”ē³»äŗŗē»™ę‚Øę‰“ē”µčÆć€‚"; /* No comment provided by engineer. */ -"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "å…č®øę‚Øēš„č”ē³»äŗŗäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆć€‚"; +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "å…č®øę‚Øēš„č”ē³»äŗŗäøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ę¶ˆęÆ"; /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "å…č®øę‚Øēš„č”ē³»äŗŗå‘é€é™ę—¶ę¶ˆęÆć€‚"; @@ -466,12 +544,18 @@ /* pref value */ "always" = "å§‹ē»ˆ"; +/* No comment provided by engineer. */ +"Always use private routing." = "å§‹ē»ˆä½æē”Øē§ęœ‰č·Æē”±ć€‚"; + /* No comment provided by engineer. */ "Always use relay" = "一盓使用中继"; /* No comment provided by engineer. */ "An empty chat profile with the provided name is created, and the app opens as usual." = "å·²åˆ›å»ŗäø€äøŖåŒ…å«ę‰€ęä¾›åå­—ēš„ē©ŗē™½čŠå¤©čµ„ę–™ļ¼Œåŗ”ē”ØēØ‹åŗē…§åøøę‰“å¼€ć€‚"; +/* No comment provided by engineer. */ +"and %lld other events" = "和 %lld å…¶ä»–äŗ‹ä»¶"; + /* No comment provided by engineer. */ "Answer call" = "ęŽ„å¬ę„ē”µ"; @@ -505,15 +589,27 @@ /* No comment provided by engineer. */ "Apply" = "应用"; +/* No comment provided by engineer. */ +"Apply to" = "åŗ”ē”ØäŗŽ"; + /* No comment provided by engineer. */ "Archive and upload" = "å­˜ę”£å’ŒäøŠä¼ "; +/* No comment provided by engineer. */ +"Archive contacts to chat later." = "å­˜ę”£č”ē³»äŗŗä»„ä¾æēØåŽčŠå¤©."; + +/* No comment provided by engineer. */ +"Archived contacts" = "å·²å­˜ę”£ēš„č”ē³»äŗŗ"; + /* No comment provided by engineer. */ "Archiving database" = "ę­£åœØå­˜ę”£ę•°ę®åŗ“"; /* No comment provided by engineer. */ "Attach" = "附件"; +/* No comment provided by engineer. */ +"attempts" = "å°čÆ•"; + /* No comment provided by engineer. */ "Audio & video calls" = "čÆ­éŸ³å’Œč§†é¢‘é€ščÆ"; @@ -556,6 +652,9 @@ /* No comment provided by engineer. */ "Back" = "čæ”å›ž"; +/* No comment provided by engineer. */ +"Background" = "čƒŒę™Æ"; + /* No comment provided by engineer. */ "Bad desktop address" = "ē³Ÿē³•ēš„ę”Œé¢åœ°å€"; @@ -577,6 +676,12 @@ /* No comment provided by engineer. */ "Better messages" = "ę›“å„½ēš„ę¶ˆęÆ"; +/* No comment provided by engineer. */ +"Better networking" = "ę›“å„½ēš„ē½‘ē»œ"; + +/* No comment provided by engineer. */ +"Black" = "黑色"; + /* No comment provided by engineer. */ "Block" = "封禁"; @@ -607,6 +712,12 @@ /* No comment provided by engineer. */ "Blocked by admin" = "ē”±ē®”ē†å‘˜å°ē¦"; +/* No comment provided by engineer. */ +"Blur for better privacy." = "ęØ”ē³Šå¤„ē†ļ¼Œęé«˜ē§åÆ†ę€§."; + +/* No comment provided by engineer. */ +"Blur media" = "ęØ”ē³ŠåŖ’ä½“"; + /* No comment provided by engineer. */ "bold" = "åŠ ē²—"; @@ -614,7 +725,7 @@ "Both you and your contact can add message reactions." = "ę‚Øå’Œę‚Øēš„č”ē³»äŗŗéƒ½åÆä»„ę·»åŠ ę¶ˆęÆå›žåŗ”ć€‚"; /* No comment provided by engineer. */ -"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "ę‚Øå’Œę‚Øēš„č”ē³»äŗŗéƒ½åÆä»„äøåÆé€†č½¬åœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆć€‚"; +"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "ę‚Øå’Œę‚Øēš„č”ē³»äŗŗéƒ½åÆä»„äøåÆé€†č½¬åœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆ"; /* No comment provided by engineer. */ "Both you and your contact can make calls." = "ę‚Øå’Œę‚Øēš„č”ē³»äŗŗéƒ½åÆä»„ę‹Øę‰“ē”µčÆć€‚"; @@ -631,6 +742,9 @@ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "é€ščæ‡čŠå¤©čµ„ę–™ļ¼ˆé»˜č®¤ļ¼‰ęˆ–č€…[é€ščæ‡čæžęŽ„](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)怂"; +/* No comment provided by engineer. */ +"call" = "呼叫"; + /* No comment provided by engineer. */ "Call already ended!" = "é€ščÆå·²ē»“ęŸļ¼"; @@ -646,9 +760,18 @@ /* No comment provided by engineer. */ "Calls" = "é€ščÆ"; +/* No comment provided by engineer. */ +"Calls prohibited!" = "ē¦ę­¢ę„ē”µļ¼"; + /* No comment provided by engineer. */ "Camera not available" = "ē›øęœŗäøåÆē”Ø"; +/* No comment provided by engineer. */ +"Can't call contact" = "ę— ę³•å‘¼å«č”ē³»äŗŗ"; + +/* No comment provided by engineer. */ +"Can't call member" = "ę— ę³•å‘¼å«ęˆå‘˜"; + /* No comment provided by engineer. */ "Can't invite contact!" = "无法邀请联系人!"; @@ -656,6 +779,9 @@ "Can't invite contacts!" = "无法邀请联系人!"; /* No comment provided by engineer. */ +"Can't message member" = "ę— ę³•å‘ęˆå‘˜å‘é€ę¶ˆęÆ"; + +/* alert button */ "Cancel" = "å–ę¶ˆ"; /* No comment provided by engineer. */ @@ -667,9 +793,15 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "ę— ę³•č®æé—®é’„åŒ™äø²ä»„äæå­˜ę•°ę®åŗ“åÆ†ē "; +/* No comment provided by engineer. */ +"Cannot forward message" = "ę— ę³•č½¬å‘ę¶ˆęÆ"; + /* No comment provided by engineer. */ "Cannot receive file" = "ę— ę³•ęŽ„ę”¶ę–‡ä»¶"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "č¶…å‡ŗå®¹é‡-ę”¶ä»¶äŗŗęœŖę”¶åˆ°ä»„å‰å‘é€ēš„é‚®ä»¶ć€‚"; + /* No comment provided by engineer. */ "Cellular" = "ē§»åŠØē½‘ē»œ"; @@ -722,6 +854,9 @@ /* No comment provided by engineer. */ "Chat archive" = "聊天攣攈"; +/* No comment provided by engineer. */ +"Chat colors" = "čŠå¤©é¢œč‰²"; + /* No comment provided by engineer. */ "Chat console" = "čŠå¤©ęŽ§åˆ¶å°"; @@ -731,6 +866,9 @@ /* No comment provided by engineer. */ "Chat database deleted" = "čŠå¤©ę•°ę®åŗ“å·²åˆ é™¤"; +/* No comment provided by engineer. */ +"Chat database exported" = "åÆ¼å‡ŗēš„čŠå¤©ę•°ę®åŗ“"; + /* No comment provided by engineer. */ "Chat database imported" = "čŠå¤©ę•°ę®åŗ“å·²åÆ¼å…„"; @@ -743,12 +881,18 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "čŠå¤©å·²åœę­¢ć€‚å¦‚ęžœä½ å·²ē»åœØå¦äø€å°č®¾å¤‡å•†ä½æē”Øčæ‡ę­¤ę•°ę®åŗ“ļ¼Œä½ åŗ”čÆ„åœØåÆåŠØčŠå¤©å‰å°†ę•°ę®åŗ“ä¼ č¾“å›žę„ć€‚"; +/* No comment provided by engineer. */ +"Chat list" = "čŠå¤©åˆ—č”Ø"; + /* No comment provided by engineer. */ "Chat migrated!" = "已迁移聊天!"; /* No comment provided by engineer. */ "Chat preferences" = "čŠå¤©åå„½č®¾ē½®"; +/* No comment provided by engineer. */ +"Chat theme" = "聊天主题"; + /* No comment provided by engineer. */ "Chats" = "聊天"; @@ -758,12 +902,24 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "äø­ę–‡å’Œč„æē­ē‰™ę–‡ē•Œé¢"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "åœØę–°č®¾å¤‡äøŠé€‰ę‹©ā€œä»Žå¦äø€äøŖč®¾å¤‡čæē§»ā€å¹¶ę‰«ęäŗŒē»“ē ć€‚"; + /* No comment provided by engineer. */ "Choose file" = "选ꋩꖇ件"; /* No comment provided by engineer. */ "Choose from library" = "ä»Žåŗ“äø­é€‰ę‹©"; +/* No comment provided by engineer. */ +"Chunks deleted" = "å·²åˆ é™¤ēš„å—"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "äø‹č½½ēš„å—"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "å·²äø‹č½½ēš„åŒŗå—"; + /* swipe action */ "Clear" = "清除"; @@ -779,6 +935,12 @@ /* No comment provided by engineer. */ "Clear verification" = "ęø…é™¤éŖŒčÆ"; +/* No comment provided by engineer. */ +"Color chats with the new themes." = "ä½æē”Øę–°äø»é¢˜äøŗčŠå¤©ē€č‰²ć€‚"; + +/* No comment provided by engineer. */ +"Color mode" = "é¢œč‰²ęØ”å¼"; + /* No comment provided by engineer. */ "colored" = "彩色"; @@ -791,15 +953,27 @@ /* No comment provided by engineer. */ "complete" = "å®Œę•“ēš„"; +/* No comment provided by engineer. */ +"Completed" = "已完成"; + /* No comment provided by engineer. */ "Configure ICE servers" = "é…ē½® ICE ęœåŠ”å™Ø"; +/* No comment provided by engineer. */ +"Configured %@ servers" = "å·²é…ē½® %@ ęœåŠ”å™Ø"; + /* No comment provided by engineer. */ "Confirm" = "甮认"; +/* No comment provided by engineer. */ +"Confirm contact deletion?" = "ē”®č®¤åˆ é™¤č”ē³»äŗŗļ¼Ÿ"; + /* No comment provided by engineer. */ "Confirm database upgrades" = "ē”®č®¤ę•°ę®åŗ“å‡ēŗ§"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "ē”®č®¤ę„č‡ŖęœŖēŸ„ęœåŠ”å™Øēš„ę–‡ä»¶ć€‚"; + /* No comment provided by engineer. */ "Confirm network settings" = "ē”®č®¤ē½‘ē»œč®¾ē½®"; @@ -833,30 +1007,54 @@ /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "čæžęŽ„åˆ° SimpleX Chat å¼€å‘č€…ć€‚"; +/* No comment provided by engineer. */ +"Connect to your friends faster." = "ę›“åæ«åœ°äøŽę‚Øēš„ęœ‹å‹č”ē³»ć€‚"; + /* No comment provided by engineer. */ "Connect to yourself?" = "čæžęŽ„åˆ°ä½ č‡Ŗå·±ļ¼Ÿ"; +/* No comment provided by engineer. */ +"Connect to yourself?\nThis is your own one-time link!" = "äøŽč‡Ŗå·±å»ŗē«‹č”ē³»ļ¼Ÿ\nčæ™ę˜Æę‚Øč‡Ŗå·±ēš„äø€ę¬”ę€§é“¾ęŽ„ļ¼"; + +/* No comment provided by engineer. */ +"Connect to yourself?\nThis is your own SimpleX address!" = "äøŽč‡Ŗå·±å»ŗē«‹č”ē³»ļ¼Ÿ\nčæ™ę˜Æę‚Øč‡Ŗå·±ēš„ SimpleX åœ°å€ļ¼"; + +/* No comment provided by engineer. */ +"Connect via contact address" = "é€ščæ‡č”ē³»åœ°å€čæžęŽ„"; + /* No comment provided by engineer. */ "Connect via link" = "é€ščæ‡é“¾ęŽ„čæžęŽ„"; /* No comment provided by engineer. */ "Connect via one-time link" = "é€ščæ‡äø€ę¬”ę€§é“¾ęŽ„čæžęŽ„"; +/* No comment provided by engineer. */ +"Connect with %@" = "äøŽ %@čæžęŽ„"; + /* No comment provided by engineer. */ "connected" = "å·²čæžęŽ„"; +/* No comment provided by engineer. */ +"Connected" = "å·²čæžęŽ„"; + /* No comment provided by engineer. */ "Connected desktop" = "å·²čæžęŽ„ēš„ę”Œé¢"; /* rcv group event chat item */ "connected directly" = "å·²ē›“čæž"; +/* No comment provided by engineer. */ +"Connected servers" = "å·²čæžęŽ„ēš„ęœåŠ”å™Ø"; + /* No comment provided by engineer. */ "Connected to desktop" = "å·²čæžęŽ„åˆ°ę”Œé¢"; /* No comment provided by engineer. */ "connecting" = "čæžęŽ„äø­"; +/* No comment provided by engineer. */ +"Connecting" = "ę­£åœØčæžęŽ„"; + /* No comment provided by engineer. */ "connecting (accepted)" = "čæžęŽ„äø­ļ¼ˆå·²ęŽ„å—ļ¼‰"; @@ -878,6 +1076,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "čæžęŽ„ęœåŠ”å™Øäø­ā€¦ā€¦ļ¼ˆé”™čÆÆļ¼š%@)"; +/* No comment provided by engineer. */ +"Connecting to contact, please wait or check later!" = "ę­£åœØčæžęŽ„åˆ°č”ē³»äŗŗļ¼ŒčÆ·ēØå€™ęˆ–ēØåŽę£€ęŸ„ļ¼"; + /* No comment provided by engineer. */ "Connecting to desktop" = "ę­£čæžęŽ„åˆ°ę”Œé¢"; @@ -887,6 +1088,9 @@ /* No comment provided by engineer. */ "Connection" = "čæžęŽ„"; +/* No comment provided by engineer. */ +"Connection and servers status." = "čæžęŽ„å’ŒęœåŠ”å™ØēŠ¶ę€ć€‚"; + /* No comment provided by engineer. */ "Connection error" = "čæžęŽ„é”™čÆÆ"; @@ -896,6 +1100,9 @@ /* chat list item title (it should not be shown */ "connection established" = "čæžęŽ„å·²å»ŗē«‹"; +/* No comment provided by engineer. */ +"Connection notifications" = "čæžęŽ„é€šēŸ„"; + /* No comment provided by engineer. */ "Connection request sent!" = "å·²å‘é€čæžęŽ„čÆ·ę±‚ļ¼"; @@ -905,15 +1112,27 @@ /* No comment provided by engineer. */ "Connection timeout" = "čæžęŽ„č¶…ę—¶"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "äøŽę”Œé¢ēš„čæžęŽ„å·²åœę­¢"; + /* connection information */ "connection:%@" = "čæžęŽ„ļ¼š%@"; +/* No comment provided by engineer. */ +"Connections" = "čæžęŽ„"; + +/* profile update event chat item */ +"contact %@ changed to %@" = "联系人 %1$@ 已曓改为 %2$@"; + /* No comment provided by engineer. */ "Contact allows" = "联系人允许"; /* No comment provided by engineer. */ "Contact already exists" = "č”ē³»äŗŗå·²å­˜åœØ"; +/* No comment provided by engineer. */ +"Contact deleted!" = "č”ē³»äŗŗå·²åˆ é™¤ļ¼"; + /* No comment provided by engineer. */ "contact has e2e encryption" = "č”ē³»äŗŗå…·ęœ‰ē«Æåˆ°ē«ÆåŠ åÆ†"; @@ -926,12 +1145,18 @@ /* notification */ "Contact is connected" = "č”ē³»å·²čæžęŽ„"; +/* No comment provided by engineer. */ +"Contact is deleted." = "č”ē³»äŗŗč¢«åˆ é™¤ć€‚"; + /* No comment provided by engineer. */ "Contact name" = "č”ē³»äŗŗå§“å"; /* No comment provided by engineer. */ "Contact preferences" = "č”ē³»äŗŗåå„½č®¾ē½®"; +/* No comment provided by engineer. */ +"Contact will be deleted - this cannot be undone!" = "č”ē³»äŗŗå°†č¢«åˆ é™¤-čæ™ę˜Æę— ę³•ę’¤ę¶ˆēš„ļ¼"; + /* No comment provided by engineer. */ "Contacts" = "联系人"; @@ -941,17 +1166,26 @@ /* No comment provided by engineer. */ "Continue" = "ē»§ē»­"; +/* No comment provided by engineer. */ +"Conversation deleted!" = "åÆ¹čÆå·²åˆ é™¤ļ¼"; + /* No comment provided by engineer. */ "Copy" = "复制"; +/* No comment provided by engineer. */ +"Copy error" = "å¤åˆ¶é”™čÆÆ"; + /* No comment provided by engineer. */ "Core version: v%@" = "ę øåæƒē‰ˆęœ¬: v%@"; +/* No comment provided by engineer. */ +"Correct name to %@?" = "å°†åē§°ę›“ę­£äøŗ %@?"; + /* No comment provided by engineer. */ "Create" = "åˆ›å»ŗ"; /* No comment provided by engineer. */ -"Create a group using a random profile." = "ä½æē”Øéšęœŗčŗ«ä»½åˆ›å»ŗē¾¤ē»„"; +"Create a group using a random profile." = "ä½æē”Øéšęœŗčŗ«ä»½åˆ›å»ŗē¾¤ē»„."; /* No comment provided by engineer. */ "Create an address to let people connect with you." = "åˆ›å»ŗäø€äøŖåœ°å€ļ¼Œč®©äŗŗä»¬äøŽę‚Øč”ē³»ć€‚"; @@ -986,9 +1220,15 @@ /* No comment provided by engineer. */ "Create your profile" = "åˆ›å»ŗę‚Øēš„čµ„ę–™"; +/* No comment provided by engineer. */ +"Created" = "å·²åˆ›å»ŗ"; + /* No comment provided by engineer. */ "Created at" = "åˆ›å»ŗäŗŽ"; +/* copied message info */ +"Created at: %@" = "åˆ›å»ŗäŗŽļ¼š%@"; + /* No comment provided by engineer. */ "Created on %@" = "åˆ›å»ŗäŗŽ %@"; @@ -1007,6 +1247,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "ēŽ°ęœ‰åÆ†ē ā€¦ā€¦"; +/* No comment provided by engineer. */ +"Current profile" = "å½“å‰é…ē½®ę–‡ä»¶"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "ē›®å‰ę”ÆęŒēš„ęœ€å¤§ę–‡ä»¶å¤§å°äøŗ %@怂"; @@ -1016,9 +1259,15 @@ /* No comment provided by engineer. */ "Custom time" = "č‡Ŗå®šä¹‰ę—¶é—“"; +/* No comment provided by engineer. */ +"Customize theme" = "č‡Ŗå®šä¹‰äø»é¢˜"; + /* No comment provided by engineer. */ "Dark" = "深色"; +/* No comment provided by engineer. */ +"Dark mode colors" = "ę·±č‰²ęØ”å¼é¢œč‰²"; + /* No comment provided by engineer. */ "Database downgrade" = "ę•°ę®åŗ“é™ēŗ§"; @@ -1079,12 +1328,18 @@ /* time unit */ "days" = "天"; +/* No comment provided by engineer. */ +"Debug delivery" = "č°ƒčÆ•äŗ¤ä»˜"; + /* No comment provided by engineer. */ "Decentralized" = "åˆ†ę•£å¼"; /* message decrypt error item */ "Decryption error" = "解密错误"; +/* No comment provided by engineer. */ +"decryption errors" = "解密错误"; + /* pref value */ "default (%@)" = "默认 (%@)"; @@ -1098,6 +1353,12 @@ swipe action */ "Delete" = "删除"; +/* No comment provided by engineer. */ +"Delete %lld messages of members?" = "åˆ é™¤ęˆå‘˜ēš„ %lld 消息?"; + +/* No comment provided by engineer. */ +"Delete %lld messages?" = "删除 %lld 消息?"; + /* No comment provided by engineer. */ "Delete address" = "删除地址"; @@ -1131,6 +1392,9 @@ /* No comment provided by engineer. */ "Delete contact" = "åˆ é™¤č”ē³»äŗŗ"; +/* No comment provided by engineer. */ +"Delete contact?" = "åˆ é™¤č”ē³»äŗŗļ¼Ÿ"; + /* No comment provided by engineer. */ "Delete database" = "åˆ é™¤ę•°ę®åŗ“"; @@ -1194,12 +1458,21 @@ /* server test step */ "Delete queue" = "删除队列"; +/* No comment provided by engineer. */ +"Delete up to 20 messages at once." = "äø€ę¬”ęœ€å¤šåˆ é™¤ 20 ę”äæ”ęÆć€‚"; + /* No comment provided by engineer. */ "Delete user profile?" = "åˆ é™¤ē”Øęˆ·čµ„ę–™ļ¼Ÿ"; +/* No comment provided by engineer. */ +"Delete without notification" = "åˆ é™¤č€Œäøé€šēŸ„"; + /* deleted chat item */ "deleted" = "已删除"; +/* No comment provided by engineer. */ +"Deleted" = "已删除"; + /* No comment provided by engineer. */ "Deleted at" = "å·²åˆ é™¤äŗŽ"; @@ -1212,6 +1485,9 @@ /* rcv group event chat item */ "deleted group" = "å·²åˆ é™¤ē¾¤ē»„"; +/* No comment provided by engineer. */ +"Deletion errors" = "åˆ é™¤é”™čÆÆ"; + /* No comment provided by engineer. */ "Delivery" = "传送"; @@ -1227,12 +1503,33 @@ /* No comment provided by engineer. */ "Desktop address" = "ę”Œé¢åœ°å€"; +/* No comment provided by engineer. */ +"Desktop app version %@ is not compatible with this app." = "ę”Œé¢åŗ”ē”ØēØ‹åŗē‰ˆęœ¬ %@ äøŽę­¤åŗ”ē”ØēØ‹åŗäøå…¼å®¹ć€‚"; + /* No comment provided by engineer. */ "Desktop devices" = "ę”Œé¢č®¾å¤‡"; +/* No comment provided by engineer. */ +"Destination server address of %@ is incompatible with forwarding server %@ settings." = "ē›®ę ‡ęœåŠ”å™Øåœ°å€ %@ äøŽč½¬å‘ęœåŠ”å™Ø %@ č®¾ē½®äøå…¼å®¹ć€‚"; + +/* snd error text */ +"Destination server error: %@" = "ē›®ę ‡ęœåŠ”å™Øé”™čÆÆļ¼š%@"; + +/* No comment provided by engineer. */ +"Destination server version of %@ is incompatible with forwarding server %@." = "ē›®ę ‡ęœåŠ”å™Øē‰ˆęœ¬ %@ äøŽč½¬å‘ęœåŠ”å™Ø %@ äøå…¼å®¹ć€‚"; + +/* No comment provided by engineer. */ +"Detailed statistics" = "čÆ¦ē»†ēš„ē»Ÿč®”ę•°ę®"; + +/* No comment provided by engineer. */ +"Details" = "详细俔息"; + /* No comment provided by engineer. */ "Develop" = "开发"; +/* No comment provided by engineer. */ +"Developer options" = "å¼€å‘č€…é€‰é”¹"; + /* No comment provided by engineer. */ "Developer tools" = "å¼€å‘č€…å·„å…·"; @@ -1272,6 +1569,9 @@ /* No comment provided by engineer. */ "disabled" = "关闭"; +/* No comment provided by engineer. */ +"Disabled" = "禁用"; + /* No comment provided by engineer. */ "Disappearing message" = "é™ę—¶ę¶ˆęÆ"; @@ -1308,6 +1608,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "äøē»™ę–°ęˆå‘˜å‘é€åŽ†å²ę¶ˆęÆć€‚"; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "čÆ·å‹æē›“ęŽ„å‘é€ę¶ˆęÆļ¼Œå³ä½æę‚Øēš„ęœåŠ”å™Øęˆ–ē›®ę ‡ęœåŠ”å™Øäøę”ÆęŒē§ęœ‰č·Æē”±ć€‚"; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "äøč¦ä½æē”Øē§ęœ‰č·Æē”±ć€‚"; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "请勿使用 SimpleX čæ›č”Œē“§ę€„é€ščÆć€‚"; @@ -1326,12 +1632,21 @@ /* chat item action */ "Download" = "äø‹č½½"; +/* No comment provided by engineer. */ +"Download errors" = "下载错误"; + /* No comment provided by engineer. */ "Download failed" = "下载失蓄了"; /* server test step */ "Download file" = "下载文件"; +/* No comment provided by engineer. */ +"Downloaded" = "已下载"; + +/* No comment provided by engineer. */ +"Downloaded files" = "äø‹č½½ēš„ę–‡ä»¶"; + /* No comment provided by engineer. */ "Downloading archive" = "ę­£åœØäø‹č½½å­˜ę”£"; @@ -1344,6 +1659,9 @@ /* integrity error chat item */ "duplicate message" = "é‡å¤ēš„ę¶ˆęÆ"; +/* No comment provided by engineer. */ +"duplicates" = "å¤ęœ¬"; + /* No comment provided by engineer. */ "Duration" = "时长"; @@ -1401,6 +1719,9 @@ /* enabled status */ "enabled" = "已启用"; +/* No comment provided by engineer. */ +"Enabled" = "已启用"; + /* No comment provided by engineer. */ "Enabled for" = "启用对豔"; @@ -1428,6 +1749,9 @@ /* notification */ "Encrypted message or another event" = "åŠ åÆ†ę¶ˆęÆęˆ–å…¶ä»–äŗ‹ä»¶"; +/* notification */ +"Encrypted message: app is stopped" = "åŠ åÆ†ę¶ˆęÆļ¼šåŗ”ē”ØēØ‹åŗå·²åœę­¢"; + /* notification */ "Encrypted message: database error" = "åŠ åÆ†ę¶ˆęÆļ¼šę•°ę®åŗ“é”™čÆÆ"; @@ -1482,6 +1806,9 @@ /* No comment provided by engineer. */ "Enter correct passphrase." = "输兄正甮密码。"; +/* No comment provided by engineer. */ +"Enter group name…" = "č¾“å…„ē»„åē§°ā€¦"; + /* No comment provided by engineer. */ "Enter Passcode" = "输兄密码"; @@ -1506,6 +1833,9 @@ /* placeholder */ "Enter welcome message… (optional)" = "č¾“å…„ę¬¢čæŽę¶ˆęÆā€¦ā€¦ļ¼ˆåÆé€‰ļ¼‰"; +/* No comment provided by engineer. */ +"Enter your name…" = "čÆ·č¾“å…„ę‚Øēš„å§“åā€¦"; + /* No comment provided by engineer. */ "error" = "错误"; @@ -1533,6 +1863,9 @@ /* No comment provided by engineer. */ "Error changing setting" = "曓改设置错误"; +/* No comment provided by engineer. */ +"Error connecting to forwarding server %@. Please try later." = "čæžęŽ„åˆ°č½¬å‘ęœåŠ”å™Ø %@ ę—¶å‡ŗé”™ć€‚čÆ·ēØåŽå°čÆ•ć€‚"; + /* No comment provided by engineer. */ "Error creating address" = "åˆ›å»ŗåœ°å€é”™čÆÆ"; @@ -1590,6 +1923,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "åÆ¼å‡ŗčŠå¤©ę•°ę®åŗ“é”™čÆÆ"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "åÆ¼å‡ŗäø»é¢˜ę—¶å‡ŗé”™ļ¼š %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "åÆ¼å…„čŠå¤©ę•°ę®åŗ“é”™čÆÆ"; @@ -1599,12 +1935,24 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "加载 %@ ęœåŠ”å™Øé”™čÆÆ"; +/* No comment provided by engineer. */ +"Error opening chat" = "ę‰“å¼€čŠå¤©ę—¶å‡ŗé”™"; + /* No comment provided by engineer. */ "Error receiving file" = "ęŽ„ę”¶ę–‡ä»¶é”™čÆÆ"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "é‡ę–°čæžęŽ„ęœåŠ”å™Øę—¶å‡ŗé”™"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "é‡ę–°čæžęŽ„ęœåŠ”å™Øę—¶å‡ŗé”™"; + /* No comment provided by engineer. */ "Error removing member" = "åˆ é™¤ęˆå‘˜é”™čÆÆ"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "é‡ē½®ē»Ÿč®”äæ”ęÆę—¶å‡ŗé”™"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "äæå­˜ %@ ęœåŠ”å™Øé”™čÆÆ"; @@ -1626,6 +1974,9 @@ /* No comment provided by engineer. */ "Error saving user password" = "äæå­˜ē”Øęˆ·åÆ†ē ę—¶å‡ŗé”™"; +/* No comment provided by engineer. */ +"Error scanning code: %@" = "ę‰«ęä»£ē ę—¶å‡ŗé”™ļ¼š%@"; + /* No comment provided by engineer. */ "Error sending email" = "å‘é€ē”µé‚®é”™čÆÆ"; @@ -1681,6 +2032,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "é”™čÆÆļ¼šURL ꗠꕈ"; +/* No comment provided by engineer. */ +"Errors" = "错误"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "å³ä½æåœØåÆ¹čÆäø­č¢«ē¦ē”Øć€‚"; @@ -1693,12 +2047,18 @@ /* chat item action */ "Expand" = "展开"; +/* No comment provided by engineer. */ +"expired" = "čæ‡ęœŸ"; + /* No comment provided by engineer. */ "Export database" = "åÆ¼å‡ŗę•°ę®åŗ“"; /* No comment provided by engineer. */ "Export error:" = "åÆ¼å‡ŗé”™čÆÆļ¼š"; +/* No comment provided by engineer. */ +"Export theme" = "åÆ¼å‡ŗäø»é¢˜"; + /* No comment provided by engineer. */ "Exported database archive." = "åÆ¼å‡ŗę•°ę®åŗ“å½’ę”£ć€‚"; @@ -1720,6 +2080,21 @@ /* swipe action */ "Favorite" = "ęœ€å–œę¬¢"; +/* No comment provided by engineer. */ +"File error" = "文件错误"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "ę‰¾äøåˆ°ę–‡ä»¶ - å¾ˆåÆčƒ½ę–‡ä»¶å·²č¢«åˆ é™¤ęˆ–å–ę¶ˆć€‚"; + +/* file error text */ +"File server error: %@" = "ę–‡ä»¶ęœåŠ”å™Øé”™čÆÆļ¼š%@"; + +/* No comment provided by engineer. */ +"File status" = "ę–‡ä»¶ēŠ¶ę€"; + +/* copied message info */ +"File status: %@" = "ę–‡ä»¶ēŠ¶ę€ļ¼š%@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "ę–‡ä»¶å°†ä»ŽęœåŠ”å™Øäø­åˆ é™¤ć€‚"; @@ -1732,6 +2107,9 @@ /* No comment provided by engineer. */ "File: %@" = "ę–‡ä»¶ļ¼š%@"; +/* No comment provided by engineer. */ +"Files" = "ꖇ件"; + /* No comment provided by engineer. */ "Files & media" = "ę–‡ä»¶å’ŒåŖ’ä½“"; @@ -1754,7 +2132,7 @@ "Finalize migration" = "完成迁移"; /* No comment provided by engineer. */ -"Finalize migration on another device." = "åœØå¦äø€éƒØč®¾å¤‡äøŠå®Œęˆčæē§»"; +"Finalize migration on another device." = "åœØå¦äø€éƒØč®¾å¤‡äøŠå®Œęˆčæē§»."; /* No comment provided by engineer. */ "Finally, we have them! šŸš€" = "ē»ˆäŗŽęˆ‘ä»¬ęœ‰å®ƒä»¬äŗ†ļ¼ šŸš€"; @@ -1798,6 +2176,21 @@ /* No comment provided by engineer. */ "Forwarded from" = "č½¬å‘č‡Ŗ"; +/* No comment provided by engineer. */ +"Forwarding server %@ failed to connect to destination server %@. Please try later." = "č½¬å‘ęœåŠ”å™Ø %@ ę— ę³•čæžęŽ„åˆ°ē›®ę ‡ęœåŠ”å™Ø %@ć€‚čÆ·ēØåŽå°čÆ•ć€‚"; + +/* No comment provided by engineer. */ +"Forwarding server address is incompatible with network settings: %@." = "č½¬å‘ęœåŠ”å™Øåœ°å€äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ļ¼š%@怂"; + +/* No comment provided by engineer. */ +"Forwarding server version is incompatible with network settings: %@." = "č½¬å‘ęœåŠ”å™Øē‰ˆęœ¬äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ļ¼š%@怂"; + +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "č½¬å‘ęœåŠ”å™Øļ¼š %1$@\nē›®ę ‡ęœåŠ”å™Øé”™čÆÆļ¼š %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "č½¬å‘ęœåŠ”å™Øļ¼š %1$@\né”™čÆÆļ¼š %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "ę‰¾åˆ°äŗ†ę”Œé¢"; @@ -1810,9 +2203,6 @@ /* No comment provided by engineer. */ "Full name (optional)" = "å…Øåļ¼ˆåÆé€‰ļ¼‰"; -/* No comment provided by engineer. */ -"Full name:" = "å…Øåļ¼š"; - /* No comment provided by engineer. */ "Fully decentralized – visible only to members." = "å®Œå…ØåŽ»äø­åæƒåŒ– - ä»…åÆ¹ęˆå‘˜åÆč§ć€‚"; @@ -1825,9 +2215,18 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GIF å’Œč““ēŗø"; +/* message preview */ +"Good afternoon!" = "äø‹åˆå„½ļ¼"; + +/* message preview */ +"Good morning!" = "ę—©äøŠå„½ļ¼"; + /* No comment provided by engineer. */ "Group" = "群组"; +/* No comment provided by engineer. */ +"Group already exists" = "ē¾¤ē»„å·²å­˜åœØ"; + /* No comment provided by engineer. */ "Group already exists!" = "群已存在!"; @@ -1862,7 +2261,7 @@ "Group members can add message reactions." = "ē¾¤ē»„ęˆå‘˜åÆä»„ę·»åŠ äæ”ęÆå›žåŗ”ć€‚"; /* No comment provided by engineer. */ -"Group members can irreversibly delete sent messages. (24 hours)" = "ē¾¤ē»„ęˆå‘˜åÆä»„äøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆć€‚"; +"Group members can irreversibly delete sent messages. (24 hours)" = "ē¾¤ē»„ęˆå‘˜åÆä»„äøåÆę’¤å›žåœ°åˆ é™¤å·²å‘é€ēš„ę¶ˆęÆ"; /* No comment provided by engineer. */ "Group members can send direct messages." = "ē¾¤ē»„ęˆå‘˜åÆä»„ē§äæ”ć€‚"; @@ -1954,6 +2353,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "å¦‚ä½•ä½æē”Øę‚Øēš„ęœåŠ”å™Ø"; +/* No comment provided by engineer. */ +"Hungarian interface" = "åŒˆē‰™åˆ©čÆ­ē•Œé¢"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE ęœåŠ”å™Øļ¼ˆęÆč”Œäø€äøŖļ¼‰"; @@ -1996,6 +2398,9 @@ /* No comment provided by engineer. */ "Import failed" = "导兄失蓄了"; +/* No comment provided by engineer. */ +"Import theme" = "åÆ¼å…„äø»é¢˜"; + /* No comment provided by engineer. */ "Importing archive" = "ę­£åœØåÆ¼å…„å­˜ę”£"; @@ -2017,6 +2422,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "é€ščÆå£°éŸ³"; +/* No comment provided by engineer. */ +"inactive" = "ꗠꕈ"; + /* No comment provided by engineer. */ "Incognito" = "隐身聊天"; @@ -2080,6 +2488,9 @@ /* No comment provided by engineer. */ "Interface" = "ē•Œé¢"; +/* No comment provided by engineer. */ +"Interface colors" = "ē•Œé¢é¢œč‰²"; + /* invalid chat data */ "invalid chat" = "ę— ę•ˆčŠå¤©"; @@ -2107,6 +2518,9 @@ /* No comment provided by engineer. */ "Invalid QR code" = "ę— ę•ˆēš„äŗŒē»“ē "; +/* No comment provided by engineer. */ +"Invalid response" = "ę— ę•ˆēš„å“åŗ”"; + /* No comment provided by engineer. */ "Invalid server address!" = "ę— ę•ˆēš„ęœåŠ”å™Øåœ°å€ļ¼"; @@ -2119,6 +2533,9 @@ /* group name */ "invitation to group %@" = "é‚€čÆ·ę‚ØåŠ å…„ē¾¤ē»„ %@"; +/* No comment provided by engineer. */ +"invite" = "邀请"; + /* No comment provided by engineer. */ "Invite friends" = "é‚€čÆ·ęœ‹å‹"; @@ -2164,6 +2581,9 @@ /* No comment provided by engineer. */ "It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "å®ƒåÆčƒ½åœØä»„äø‹ęƒ…å†µå‘ē”Ÿļ¼š\n1. ę¶ˆęÆåœØå‘é€å®¢ęˆ·ē«Æ 2 å¤©åŽęˆ–åœØęœåŠ”å™ØäøŠ 30 å¤©åŽčæ‡ęœŸć€‚\n2. ę¶ˆęÆč§£åÆ†å¤±č“„ļ¼Œå› äøŗę‚Øęˆ–ę‚Øēš„č”ē³»äŗŗä½æē”Øäŗ†ę—§ēš„ę•°ę®åŗ“å¤‡ä»½ć€‚\n3.čæžęŽ„č¢«ē “åć€‚"; +/* No comment provided by engineer. */ +"It protects your IP address and connections." = "å®ƒåÆä»„äæęŠ¤ę‚Øēš„ IP åœ°å€å’ŒčæžęŽ„ć€‚"; + /* No comment provided by engineer. */ "It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "ę‚Øä¼¼ä¹Žå·²ē»é€ščæ‡ę­¤é“¾ęŽ„čæžęŽ„ć€‚å¦‚ęžœäøę˜Æčæ™ę ·ļ¼Œåˆ™ęœ‰äø€äøŖé”™čÆÆ (%@)怂"; @@ -2194,12 +2614,24 @@ /* No comment provided by engineer. */ "Join incognito" = "åŠ å…„éščŗ«čŠå¤©"; +/* No comment provided by engineer. */ +"Join with current profile" = "ä½æē”Øå½“å‰ę”£ę”ˆåŠ å…„"; + +/* No comment provided by engineer. */ +"Join your group?\nThis is your link for group %@!" = "åŠ å…„ę‚Øēš„ē¾¤ē»„ļ¼Ÿ\nčæ™ę˜Æę‚Øē»„ %@ ēš„é“¾ęŽ„ļ¼"; + /* No comment provided by engineer. */ "Joining group" = "åŠ å…„ē¾¤ē»„äø­"; /* No comment provided by engineer. */ "Keep" = "äæē•™"; +/* No comment provided by engineer. */ +"Keep conversation" = "äæęŒåÆ¹čÆ"; + +/* No comment provided by engineer. */ +"Keep the app open to use it from desktop" = "äæęŒåŗ”ē”ØēØ‹åŗę‰“å¼€ēŠ¶ę€ä»„ä»Žę”Œé¢ä½æē”Øå®ƒ"; + /* No comment provided by engineer. */ "Keep unused invitation?" = "äæē•™ęœŖä½æē”Øēš„é‚€čÆ·å—ļ¼Ÿ"; @@ -2308,15 +2740,27 @@ /* No comment provided by engineer. */ "Max 30 seconds, received instantly." = "ęœ€é•æ30ē§’ļ¼Œē«‹å³ęŽ„ę”¶ć€‚"; +/* No comment provided by engineer. */ +"Media & file servers" = "Media & file servers"; + +/* blur media */ +"Medium" = "äø­ē­‰"; + /* member role */ "member" = "ęˆå‘˜"; /* No comment provided by engineer. */ "Member" = "ęˆå‘˜"; +/* profile update event chat item */ +"member %@ changed to %@" = "ęˆå‘˜ %1$@ 已曓改为 %2$@"; + /* rcv group event chat item */ "member connected" = "å·²čæžęŽ„"; +/* item status text */ +"Member inactive" = "ęˆå‘˜äøę“»č·ƒ"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "ęˆå‘˜č§’č‰²å°†ę›“ę”¹äøŗ \"%@\"ć€‚ę‰€ęœ‰ē¾¤ęˆå‘˜å°†ę”¶åˆ°é€šēŸ„ć€‚"; @@ -2326,15 +2770,33 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "ęˆå‘˜å°†č¢«ē§»å‡ŗē¾¤ē»„ā€”ā€”ę­¤ę“ä½œę— ę³•ę’¤ę¶ˆļ¼"; +/* No comment provided by engineer. */ +"Menus" = "čœå•"; + +/* No comment provided by engineer. */ +"message" = "消息"; + /* item status text */ "Message delivery error" = "ę¶ˆęÆä¼ é€’é”™čÆÆ"; /* No comment provided by engineer. */ "Message delivery receipts!" = "ę¶ˆęÆé€č¾¾å›žę‰§ļ¼"; +/* item status text */ +"Message delivery warning" = "ę¶ˆęÆä¼ é€’č­¦å‘Š"; + /* No comment provided by engineer. */ "Message draft" = "ę¶ˆęÆč‰ēØæ"; +/* item status text */ +"Message forwarded" = "ę¶ˆęÆå·²č½¬å‘"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "å¦‚ęžœ member å˜äøŗę“»åŠØēŠ¶ę€ļ¼Œåˆ™ēØåŽåÆčƒ½ä¼šå‘é€ę¶ˆęÆć€‚"; + +/* No comment provided by engineer. */ +"Message queue info" = "ę¶ˆęÆé˜Ÿåˆ—äæ”ęÆ"; + /* chat feature */ "Message reactions" = "ę¶ˆęÆå›žåŗ”"; @@ -2347,9 +2809,21 @@ /* notification */ "message received" = "ę¶ˆęÆå·²ę”¶åˆ°"; +/* No comment provided by engineer. */ +"Message reception" = "ę¶ˆęÆęŽ„ę”¶"; + +/* No comment provided by engineer. */ +"Message servers" = "ę¶ˆęÆęœåŠ”å™Ø"; + /* No comment provided by engineer. */ "Message source remains private." = "ę¶ˆęÆę„ęŗäæęŒē§åÆ†ć€‚"; +/* No comment provided by engineer. */ +"Message status" = "ę¶ˆęÆēŠ¶ę€"; + +/* copied message info */ +"Message status: %@" = "ę¶ˆęÆēŠ¶ę€ļ¼š%@"; + /* No comment provided by engineer. */ "Message text" = "ę¶ˆęÆę­£ę–‡"; @@ -2362,6 +2836,21 @@ /* No comment provided by engineer. */ "Messages & files" = "消息"; +/* No comment provided by engineer. */ +"Messages from %@ will be shown!" = "å°†ę˜¾ē¤ŗę„č‡Ŗ %@ ēš„ę¶ˆęÆļ¼"; + +/* No comment provided by engineer. */ +"Messages received" = "ę”¶åˆ°ēš„ę¶ˆęÆ"; + +/* No comment provided by engineer. */ +"Messages sent" = "å·²å‘é€ēš„ę¶ˆęÆ"; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "ę¶ˆęÆć€ę–‡ä»¶å’Œé€ščÆå—åˆ° **ē«Æåˆ°ē«ÆåŠ åÆ†** ēš„äæęŠ¤ļ¼Œå…·ęœ‰å®Œå…Øę­£å‘äæåÆ†ć€å¦č®¤å’Œé—Æå…„ę¢å¤ć€‚"; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "ę¶ˆęÆć€ę–‡ä»¶å’Œé€ščÆå—åˆ° **ęŠ—é‡å­ e2e åŠ åÆ†** ēš„äæęŠ¤ļ¼Œå…·ęœ‰å®Œå…Øę­£å‘äæåÆ†ć€å¦č®¤å’Œé—Æå…„ę¢å¤ć€‚"; + /* No comment provided by engineer. */ "Migrate device" = "迁移设备"; @@ -2434,6 +2923,9 @@ /* No comment provided by engineer. */ "Multiple chat profiles" = "å¤šäøŖčŠå¤©čµ„ę–™"; +/* No comment provided by engineer. */ +"mute" = "静音"; + /* swipe action */ "Mute" = "静音"; @@ -2449,6 +2941,9 @@ /* No comment provided by engineer. */ "Network connection" = "ē½‘ē»œčæžęŽ„"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "ē½‘ē»œé—®é¢˜ - ę¶ˆęÆåœØå¤šę¬”å°čÆ•å‘é€åŽčæ‡ęœŸć€‚"; + /* No comment provided by engineer. */ "Network management" = "ē½‘ē»œē®”ē†"; @@ -2464,6 +2959,9 @@ /* No comment provided by engineer. */ "New chat" = "ę–°čŠå¤©"; +/* No comment provided by engineer. */ +"New chat experience šŸŽ‰" = "ę–°ēš„čŠå¤©ä½“éŖŒ šŸŽ‰"; + /* notification */ "New contact request" = "新联系人请求"; @@ -2482,6 +2980,9 @@ /* No comment provided by engineer. */ "New in %@" = "%@ ēš„ę–°å†…å®¹"; +/* No comment provided by engineer. */ +"New media options" = "新媒体选锹"; + /* No comment provided by engineer. */ "New member role" = "ę–°ęˆå‘˜č§’č‰²"; @@ -2518,6 +3019,9 @@ /* No comment provided by engineer. */ "No device token!" = "ę— č®¾å¤‡ä»¤ē‰Œļ¼"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "čæ˜ę²”ęœ‰ē›“ęŽ„čæžęŽ„ļ¼Œę¶ˆęÆē”±ē®”ē†å‘˜č½¬å‘ć€‚"; + /* No comment provided by engineer. */ "no e2e encryption" = "ę— ē«Æåˆ°ē«ÆåŠ åÆ†"; @@ -2530,6 +3034,9 @@ /* No comment provided by engineer. */ "No history" = "ę— åŽ†å²č®°å½•"; +/* No comment provided by engineer. */ +"No info, try to reload" = "ę— äæ”ęÆļ¼Œå°čÆ•é‡ę–°åŠ č½½"; + /* No comment provided by engineer. */ "No network connection" = "ę— ē½‘ē»œčæžęŽ„"; @@ -2545,6 +3052,9 @@ /* No comment provided by engineer. */ "Not compatible!" = "äøå…¼å®¹ļ¼"; +/* No comment provided by engineer. */ +"Nothing selected" = "ęœŖé€‰äø­ä»»ä½•å†…å®¹"; + /* No comment provided by engineer. */ "Notifications" = "é€šēŸ„"; @@ -2590,10 +3100,10 @@ "One-time invitation link" = "äø€ę¬”ę€§é‚€čÆ·é“¾ęŽ„"; /* No comment provided by engineer. */ -"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion äø»ęœŗå°†ē”ØäŗŽčæžęŽ„ć€‚éœ€č¦åÆē”Ø VPN怂"; +"Onion hosts will be **required** for connection.\nRequires compatible VPN." = "Onion äø»ęœŗå°†ę˜ÆčæžęŽ„ę‰€åæ…éœ€ēš„ć€‚\néœ€č¦å…¼å®¹ēš„ VPN怂"; /* No comment provided by engineer. */ -"Onion hosts will be used when available.\nRequires compatible VPN." = "å½“åÆē”Øę—¶ļ¼Œå°†ä½æē”Ø Onion äø»ęœŗć€‚éœ€č¦åÆē”Ø VPN怂"; +"Onion hosts will be used when available.\nRequires compatible VPN." = "å¦‚ęžœåÆē”Øļ¼Œå°†ä½æē”Øę“‹č‘±äø»ęœŗć€‚\néœ€č¦å…¼å®¹ēš„ VPN怂"; /* No comment provided by engineer. */ "Onion hosts will not be used." = "å°†äøä¼šä½æē”Ø Onion äø»ęœŗć€‚"; @@ -2601,6 +3111,9 @@ /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "åŖęœ‰å®¢ęˆ·ē«Æč®¾å¤‡å­˜å‚Øē”Øęˆ·čµ„ę–™ć€č”ē³»äŗŗć€ē¾¤ē»„å’Œ**åŒå±‚ē«Æåˆ°ē«ÆåŠ åÆ†**å‘é€ēš„ę¶ˆęÆć€‚"; +/* No comment provided by engineer. */ +"Only delete conversation" = "ä»…åˆ é™¤åÆ¹čÆ"; + /* No comment provided by engineer. */ "Only group owners can change group preferences." = "åŖęœ‰ē¾¤äø»åÆä»„ę”¹å˜ē¾¤ē»„åå„½č®¾ē½®ć€‚"; @@ -2614,7 +3127,7 @@ "Only you can add message reactions." = "åŖęœ‰ę‚ØåÆä»„ę·»åŠ ę¶ˆęÆå›žåŗ”ć€‚"; /* No comment provided by engineer. */ -"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "åŖęœ‰ę‚ØåÆä»„äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚Øēš„č”ē³»äŗŗåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰ć€‚"; +"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "åŖęœ‰ę‚ØåÆä»„äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚Øēš„č”ē³»äŗŗåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰"; /* No comment provided by engineer. */ "Only you can make calls." = "åŖęœ‰ę‚ØåÆä»„ę‹Øę‰“ē”µčÆć€‚"; @@ -2629,7 +3142,7 @@ "Only your contact can add message reactions." = "åŖęœ‰ę‚Øēš„č”ē³»äŗŗåÆä»„ę·»åŠ ę¶ˆęÆå›žåŗ”ć€‚"; /* No comment provided by engineer. */ -"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "åŖęœ‰ę‚Øēš„č”ē³»äŗŗę‰čƒ½äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚ØåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰ć€‚"; +"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "åŖęœ‰ę‚Øēš„č”ē³»äŗŗę‰čƒ½äøåÆę’¤å›žåœ°åˆ é™¤ę¶ˆęÆļ¼ˆę‚ØåÆä»„å°†å®ƒä»¬ę ‡č®°äøŗåˆ é™¤ļ¼‰"; /* No comment provided by engineer. */ "Only your contact can make calls." = "åŖęœ‰ę‚Øēš„č”ē³»äŗŗåÆä»„ę‹Øę‰“ē”µčÆć€‚"; @@ -2652,6 +3165,12 @@ /* No comment provided by engineer. */ "Open group" = "打开群"; +/* authentication reason */ +"Open migration to another device" = "ę‰“å¼€čæē§»åˆ°å¦äø€å°č®¾å¤‡"; + +/* No comment provided by engineer. */ +"Open server settings" = "ę‰“å¼€ęœåŠ”å™Øč®¾ē½®"; + /* No comment provided by engineer. */ "Open Settings" = "打开设置"; @@ -2661,6 +3180,9 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "å¼€ęŗåč®®å’Œä»£ē ā€”ā€”ä»»ä½•äŗŗéƒ½åÆä»„čæč”ŒęœåŠ”å™Øć€‚"; +/* No comment provided by engineer. */ +"Opening app…" = "ę­£åœØę‰“å¼€åŗ”ē”ØēØ‹åŗā€¦"; + /* No comment provided by engineer. */ "Or paste archive link" = "ęˆ–ē²˜č““å­˜ę”£é“¾ęŽ„"; @@ -2673,9 +3195,18 @@ /* No comment provided by engineer. */ "Or show this code" = "ęˆ–č€…ę˜¾ē¤ŗę­¤ē "; +/* No comment provided by engineer. */ +"other" = "其他"; + /* No comment provided by engineer. */ "Other" = "其他"; +/* No comment provided by engineer. */ +"Other %@ servers" = "其他 %@ ęœåŠ”å™Ø"; + +/* No comment provided by engineer. */ +"other errors" = "其他错误"; + /* member role */ "owner" = "群主"; @@ -2700,6 +3231,9 @@ /* No comment provided by engineer. */ "Password to show" = "ę˜¾ē¤ŗåÆ†ē "; +/* past/unknown group member */ +"Past member %@" = "å‰ä»»ęˆå‘˜ %@"; + /* No comment provided by engineer. */ "Paste desktop address" = "ē²˜č““ę”Œé¢åœ°å€"; @@ -2715,6 +3249,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "点对点"; +/* No comment provided by engineer. */ +"Pending" = "待定"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "äŗŗä»¬åŖčƒ½é€ščæ‡ę‚Øå…±äŗ«ēš„é“¾ęŽ„äøŽę‚Øå»ŗē«‹č”ē³»ć€‚"; @@ -2733,9 +3270,18 @@ /* No comment provided by engineer. */ "PING interval" = "PING é—“éš”"; +/* No comment provided by engineer. */ +"Play from the chat list." = "ä»ŽčŠå¤©åˆ—č”Øę’­ę”¾ć€‚"; + +/* No comment provided by engineer. */ +"Please ask your contact to enable calls." = "čÆ·č¦ę±‚ę‚Øēš„č”ē³»äŗŗå¼€é€šé€ščÆåŠŸčƒ½ć€‚"; + /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "čÆ·č®©ę‚Øēš„č”ē³»äŗŗåÆē”Øå‘é€čÆ­éŸ³ę¶ˆęÆć€‚"; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "čÆ·ę£€ęŸ„ē§»åŠØč®¾å¤‡å’Œę”Œé¢ę˜Æå¦čæžęŽ„åˆ°åŒäø€ęœ¬åœ°ē½‘ē»œļ¼Œä»„åŠę”Œé¢é˜²ē«å¢™ę˜Æå¦å…č®øčæžęŽ„ć€‚\nčÆ·äøŽå¼€å‘äŗŗå‘˜åˆ†äŗ«ä»»ä½•å…¶ä»–é—®é¢˜ć€‚"; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "čÆ·ę£€ęŸ„ę‚Øä½æē”Øēš„é“¾ęŽ„ę˜Æå¦ę­£ē”®ļ¼Œęˆ–č€…č®©ę‚Øēš„č”ē³»äŗŗē»™ę‚Øå‘é€å¦äø€äøŖé“¾ęŽ„ć€‚"; @@ -2748,6 +3294,9 @@ /* No comment provided by engineer. */ "Please confirm that network settings are correct for this device." = "čÆ·ē”®č®¤ē½‘ē»œč®¾ē½®åÆ¹ę­¤čæ™å°č®¾å¤‡ę­£ē”®ę— čÆÆć€‚"; +/* No comment provided by engineer. */ +"Please contact developers.\nError: %@" = "čÆ·č”ē³»å¼€å‘äŗŗå‘˜ć€‚\né”™čÆÆļ¼š%@"; + /* No comment provided by engineer. */ "Please contact group admin." = "čÆ·č”ē³»ē¾¤ē»„ē®”ē†å‘˜ć€‚"; @@ -2790,6 +3339,9 @@ /* No comment provided by engineer. */ "Preview" = "é¢„č§ˆ"; +/* No comment provided by engineer. */ +"Previously connected servers" = "ä»„å‰čæžęŽ„ēš„ęœåŠ”å™Ø"; + /* No comment provided by engineer. */ "Privacy & security" = "éšē§å’Œå®‰å…Ø"; @@ -2799,9 +3351,21 @@ /* No comment provided by engineer. */ "Private filenames" = "ē§åÆ†ę–‡ä»¶å"; +/* No comment provided by engineer. */ +"Private message routing" = "ē§ęœ‰ę¶ˆęÆč·Æē”±"; + +/* No comment provided by engineer. */ +"Private message routing šŸš€" = "ē§ęœ‰ę¶ˆęÆč·Æē”± šŸš€"; + /* name of notes to self */ "Private notes" = "私密笔记"; +/* No comment provided by engineer. */ +"Private routing" = "专用路由"; + +/* No comment provided by engineer. */ +"Private routing error" = "专用路由错误"; + /* No comment provided by engineer. */ "Profile and server connections" = "čµ„ę–™å’ŒęœåŠ”å™ØčæžęŽ„"; @@ -2812,10 +3376,10 @@ "Profile images" = "个人资料图"; /* No comment provided by engineer. */ -"Profile name:" = "ę˜¾ē¤ŗåļ¼š"; +"Profile password" = "个人资料密码"; /* No comment provided by engineer. */ -"Profile password" = "个人资料密码"; +"Profile theme" = "äøŖäŗŗčµ„ę–™äø»é¢˜"; /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "äøŖäŗŗčµ„ę–™ę›“ę–°å°†č¢«å‘é€ē»™ę‚Øēš„č”ē³»äŗŗć€‚"; @@ -2841,24 +3405,42 @@ /* No comment provided by engineer. */ "Prohibit sending files and media." = "ē¦ę­¢å‘é€ę–‡ä»¶å’ŒåŖ’ä½“ć€‚"; +/* No comment provided by engineer. */ +"Prohibit sending SimpleX links." = "ē¦ę­¢å‘é€ SimpleX é“¾ęŽ„ć€‚"; + /* No comment provided by engineer. */ "Prohibit sending voice messages." = "ē¦ę­¢å‘é€čÆ­éŸ³ę¶ˆęÆć€‚"; /* No comment provided by engineer. */ "Protect app screen" = "äæęŠ¤åŗ”ē”ØēØ‹åŗå±å¹•"; +/* No comment provided by engineer. */ +"Protect IP address" = "äæęŠ¤ IP 地址"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "ä½æē”ØåÆ†ē äæęŠ¤ę‚Øēš„čŠå¤©čµ„ę–™ļ¼"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "äæęŠ¤ę‚Øēš„ IP åœ°å€å…å—č”ē³»äŗŗé€‰ę‹©ēš„ę¶ˆęÆäø­ē»§ēš„ę”»å‡»ć€‚\n在*ē½‘ē»œå’ŒęœåŠ”å™Ø*设置中启用。"; + /* No comment provided by engineer. */ "Protocol timeout" = "åč®®č¶…ę—¶"; /* No comment provided by engineer. */ "Protocol timeout per KB" = "ęÆ KB åč®®č¶…ę—¶"; +/* No comment provided by engineer. */ +"Proxied" = "代理"; + +/* No comment provided by engineer. */ +"Proxied servers" = "ä»£ē†ęœåŠ”å™Ø"; + /* No comment provided by engineer. */ "Push notifications" = "ęŽØé€é€šēŸ„"; +/* No comment provided by engineer. */ +"Push server" = "ęŽØé€ęœåŠ”å™Ø"; + /* chat item text */ "quantum resistant e2e encryption" = "ęŠ—é‡å­ē«Æåˆ°ē«ÆåŠ åÆ†"; @@ -2868,6 +3450,9 @@ /* No comment provided by engineer. */ "Rate the app" = "čÆ„ä»·ę­¤åŗ”ē”ØēØ‹åŗ"; +/* No comment provided by engineer. */ +"Reachable chat toolbar" = "åÆč®æé—®ēš„čŠå¤©å·„å…·ę "; + /* chat item menu */ "React…" = "å›žåŗ”ā€¦"; @@ -2895,6 +3480,9 @@ /* No comment provided by engineer. */ "Receipts are disabled" = "å›žę‰§å·²ē¦ē”Ø"; +/* No comment provided by engineer. */ +"Receive errors" = "ęŽ„ę”¶é”™čÆÆ"; + /* No comment provided by engineer. */ "received answer…" = "å·²ę”¶åˆ°å›žå¤ā€¦ā€¦"; @@ -2913,6 +3501,15 @@ /* message info title */ "Received message" = "ę”¶åˆ°ēš„äæ”ęÆ"; +/* No comment provided by engineer. */ +"Received messages" = "ę”¶åˆ°ēš„ę¶ˆęÆ"; + +/* No comment provided by engineer. */ +"Received reply" = "å·²ę”¶åˆ°å›žå¤"; + +/* No comment provided by engineer. */ +"Received total" = "ꎄꔶꀻꕰ"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "ęŽ„ę”¶åœ°å€å°†å˜ę›“åˆ°äøåŒēš„ęœåŠ”å™Øć€‚åœ°å€ę›“ę”¹å°†åœØå‘ä»¶äŗŗäøŠēŗæåŽå®Œęˆć€‚"; @@ -2922,15 +3519,33 @@ /* No comment provided by engineer. */ "Receiving via" = "ęŽ„ę”¶é€ščæ‡"; +/* No comment provided by engineer. */ +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "ęœ€čæ‘ēš„åŽ†å²č®°å½•å’Œę”¹čæ›ēš„ [ē›®å½•ęœŗå™Øäŗŗ](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; + /* No comment provided by engineer. */ "Recipient(s) can't see who this message is from." = "ę”¶ä»¶äŗŗēœ‹äøåˆ°čæ™ę”ę¶ˆęÆę„č‡Ŗä½•äŗŗć€‚"; /* No comment provided by engineer. */ "Recipients see updates as you type them." = "åÆ¹ę–¹ä¼šåœØę‚Øé”®å…„ę—¶ēœ‹åˆ°ę›“ę–°ć€‚"; +/* No comment provided by engineer. */ +"Reconnect" = "é‡ę–°čæžęŽ„"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "é‡ę–°čæžęŽ„ę‰€ęœ‰å·²čæžęŽ„ēš„ęœåŠ”å™Øä»„å¼ŗåˆ¶å‘é€äæ”ęÆć€‚čæ™ä¼šč€—č“¹ę›“å¤šęµé‡ć€‚"; +/* No comment provided by engineer. */ +"Reconnect all servers" = "é‡ę–°čæžęŽ„ę‰€ęœ‰ęœåŠ”å™Ø"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "é‡ę–°čæžęŽ„ę‰€ęœ‰ęœåŠ”å™Øļ¼Ÿ"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "é‡ę–°čæžęŽ„ęœåŠ”å™Øä»„å¼ŗåˆ¶å‘é€äæ”ęÆć€‚å®ƒä½æē”Øé¢å¤–ēš„ęµé‡ć€‚"; + +/* No comment provided by engineer. */ +"Reconnect server?" = "é‡ę–°čæžęŽ„ęœåŠ”å™Øļ¼Ÿ"; + /* No comment provided by engineer. */ "Reconnect servers?" = "ę˜Æå¦é‡ę–°čæžęŽ„ęœåŠ”å™Øļ¼Ÿ"; @@ -2965,6 +3580,9 @@ /* No comment provided by engineer. */ "Remove" = "移除"; +/* No comment provided by engineer. */ +"Remove image" = "移除图片"; + /* No comment provided by engineer. */ "Remove member" = "åˆ é™¤ęˆå‘˜"; @@ -3022,12 +3640,27 @@ /* No comment provided by engineer. */ "Reset" = "é‡ē½®"; +/* No comment provided by engineer. */ +"Reset all hints" = "é‡ē½®ę‰€ęœ‰ęē¤ŗ"; + +/* No comment provided by engineer. */ +"Reset all statistics" = "é‡ē½®ę‰€ęœ‰ē»Ÿč®”äæ”ęÆ"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "é‡ē½®ę‰€ęœ‰ē»Ÿč®”äæ”ęÆļ¼Ÿ"; + /* No comment provided by engineer. */ "Reset colors" = "é‡ē½®é¢œč‰²"; +/* No comment provided by engineer. */ +"Reset to app theme" = "é‡ē½®äøŗåŗ”ē”ØēØ‹åŗäø»é¢˜"; + /* No comment provided by engineer. */ "Reset to defaults" = "é‡ē½®äøŗé»˜č®¤"; +/* No comment provided by engineer. */ +"Reset to user theme" = "é‡ē½®äøŗē”Øęˆ·äø»é¢˜"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "é‡ę–°åÆåŠØåŗ”ē”ØēØ‹åŗä»„åˆ›å»ŗę–°ēš„čŠå¤©čµ„ę–™"; @@ -3068,12 +3701,16 @@ "Run chat" = "čæč”ŒčŠå¤©ēØ‹åŗ"; /* No comment provided by engineer. */ -"Safer groups" = "ę›“å®‰å…Øēš„ē¾¤ē»„"; - -/* chat item action */ -"Save" = "äæå­˜"; +"Safely receive files" = "å®‰å…ØęŽ„ę”¶ę–‡ä»¶"; /* No comment provided by engineer. */ +"Safer groups" = "ę›“å®‰å…Øēš„ē¾¤ē»„"; + +/* alert button + chat item action */ +"Save" = "äæå­˜"; + +/* alert button */ "Save (and notify contacts)" = "äæå­˜ļ¼ˆå¹¶é€šēŸ„č”ē³»äŗŗļ¼‰"; /* No comment provided by engineer. */ @@ -3082,15 +3719,15 @@ /* No comment provided by engineer. */ "Save and notify group members" = "äæå­˜å¹¶é€šēŸ„ē¾¤ē»„ęˆå‘˜"; +/* No comment provided by engineer. */ +"Save and reconnect" = "äæå­˜å¹¶é‡ę–°čæžęŽ„"; + /* No comment provided by engineer. */ "Save and update group profile" = "äæå­˜å’Œę›“ę–°ē»„é…ē½®ę–‡ä»¶"; /* No comment provided by engineer. */ "Save archive" = "äæå­˜å­˜ę”£"; -/* No comment provided by engineer. */ -"Save auto-accept settings" = "äæå­˜č‡ŖåŠØęŽ„å—č®¾ē½®"; - /* No comment provided by engineer. */ "Save group profile" = "äæå­˜ē¾¤ē»„čµ„ę–™"; @@ -3112,9 +3749,6 @@ /* No comment provided by engineer. */ "Save servers?" = "äæå­˜ęœåŠ”å™Øļ¼Ÿ"; -/* No comment provided by engineer. */ -"Save settings?" = "äæå­˜č®¾ē½®ļ¼Ÿ"; - /* No comment provided by engineer. */ "Save welcome message?" = "äæå­˜ę¬¢čæŽäæ”ęÆļ¼Ÿ"; @@ -3127,12 +3761,21 @@ /* No comment provided by engineer. */ "Saved from" = "äæå­˜č‡Ŗ"; +/* No comment provided by engineer. */ +"saved from %@" = "äæå­˜č‡Ŗ %@"; + /* message info title */ "Saved message" = "å·²äæå­˜ēš„ę¶ˆęÆ"; /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "å·²äæå­˜ēš„WebRTC ICEęœåŠ”å™Øå°†č¢«åˆ é™¤"; +/* No comment provided by engineer. */ +"Scale" = "规樔"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "ę‰«ę / ē²˜č““é“¾ęŽ„"; + /* No comment provided by engineer. */ "Scan code" = "扫码"; @@ -3148,6 +3791,9 @@ /* No comment provided by engineer. */ "Scan server QR code" = "ę‰«ęęœåŠ”å™ØäŗŒē»“ē "; +/* No comment provided by engineer. */ +"search" = "搜瓢"; + /* No comment provided by engineer. */ "Search" = "搜瓢"; @@ -3160,6 +3806,9 @@ /* network option */ "sec" = "ē§’"; +/* No comment provided by engineer. */ +"Secondary" = "二级"; + /* time unit */ "seconds" = "ē§’"; @@ -3169,6 +3818,9 @@ /* server test step */ "Secure queue" = "äæęŠ¤é˜Ÿåˆ—"; +/* No comment provided by engineer. */ +"Secured" = "ę‹…äæ"; + /* No comment provided by engineer. */ "Security assessment" = "安全评估"; @@ -3181,6 +3833,12 @@ /* chat item action */ "Select" = "选ꋩ"; +/* No comment provided by engineer. */ +"Selected %lld" = "é€‰å®šēš„ %lld"; + +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "é€‰å®šēš„čŠå¤©é¦–é€‰é”¹ē¦ę­¢ę­¤ę¶ˆęÆć€‚"; + /* No comment provided by engineer. */ "Self-destruct" = "自毁"; @@ -3211,12 +3869,24 @@ /* No comment provided by engineer. */ "Send disappearing message" = "å‘é€é™ę—¶ę¶ˆęÆäø­"; +/* No comment provided by engineer. */ +"Send errors" = "å‘é€é”™čÆÆ"; + /* No comment provided by engineer. */ "Send link previews" = "å‘é€é“¾ęŽ„é¢„č§ˆ"; /* No comment provided by engineer. */ "Send live message" = "å‘é€å®žę—¶ę¶ˆęÆ"; +/* No comment provided by engineer. */ +"Send message to enable calls." = "å‘é€ę¶ˆęÆä»„åÆē”Øå‘¼å«ć€‚"; + +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "当 IP åœ°å€å—åˆ°äæęŠ¤å¹¶äø”ę‚Øęˆ–ē›®ę ‡ęœåŠ”å™Øäøę”ÆęŒē§ęœ‰č·Æē”±ę—¶ļ¼Œē›“ęŽ„å‘é€ę¶ˆęÆć€‚"; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "å½“ę‚Øęˆ–ē›®ę ‡ęœåŠ”å™Øäøę”ÆęŒē§ęœ‰č·Æē”±ę—¶ļ¼Œē›“ęŽ„å‘é€ę¶ˆęÆć€‚"; + /* No comment provided by engineer. */ "Send notifications" = "å‘é€é€šēŸ„"; @@ -3271,15 +3941,42 @@ /* copied message info */ "Sent at: %@" = "å·²å‘é€äŗŽļ¼š%@"; +/* No comment provided by engineer. */ +"Sent directly" = "ē›“ęŽ„å‘é€"; + /* notification */ "Sent file event" = "å·²å‘é€ę–‡ä»¶é”¹ē›®"; /* message info title */ "Sent message" = "å·²å‘äæ”ęÆ"; +/* No comment provided by engineer. */ +"Sent messages" = "å·²å‘é€ēš„ę¶ˆęÆ"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "å·²å‘é€ēš„ę¶ˆęÆå°†åœØč®¾å®šēš„ę—¶é—“åŽč¢«åˆ é™¤ć€‚"; +/* No comment provided by engineer. */ +"Sent reply" = "已发送回复"; + +/* No comment provided by engineer. */ +"Sent total" = "å‘é€ę€»ę•°"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "é€ščæ‡ä»£ē†å‘é€"; + +/* No comment provided by engineer. */ +"Server address" = "ęœåŠ”å™Øåœ°å€"; + +/* No comment provided by engineer. */ +"Server address is incompatible with network settings: %@." = "ęœåŠ”å™Øåœ°å€äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ļ¼š%@怂"; + +/* srv error text. */ +"Server address is incompatible with network settings." = "ęœåŠ”å™Øåœ°å€äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ć€‚"; + +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "ęœåŠ”å™Øé˜Ÿåˆ—äæ”ęÆļ¼š %1$@\n\näøŠę¬”ę”¶åˆ°ēš„ę¶ˆęÆļ¼š %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "ęœåŠ”å™Øéœ€č¦ęŽˆęƒę‰čƒ½åˆ›å»ŗé˜Ÿåˆ—ļ¼Œę£€ęŸ„åÆ†ē "; @@ -3289,9 +3986,24 @@ /* No comment provided by engineer. */ "Server test failed!" = "ęœåŠ”å™Øęµ‹čÆ•å¤±č“„ļ¼"; +/* No comment provided by engineer. */ +"Server type" = "ęœåŠ”å™Øē±»åž‹"; + +/* srv error text */ +"Server version is incompatible with network settings." = "ęœåŠ”å™Øē‰ˆęœ¬äøŽē½‘ē»œč®¾ē½®äøå…¼å®¹ć€‚"; + +/* No comment provided by engineer. */ +"Server version is incompatible with your app: %@." = "ęœåŠ”å™Øē‰ˆęœ¬äøŽä½ ēš„åŗ”ē”ØēØ‹åŗäøå…¼å®¹ļ¼š%@怂"; + /* No comment provided by engineer. */ "Servers" = "ęœåŠ”å™Ø"; +/* No comment provided by engineer. */ +"Servers info" = "ęœåŠ”å™Øäæ”ęÆ"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "ęœåŠ”å™Øē»Ÿč®”äæ”ęÆå°†č¢«é‡ē½® - ę­¤ę“ä½œę— ę³•ę’¤ę¶ˆļ¼"; + /* No comment provided by engineer. */ "Session code" = "ä¼ščÆē "; @@ -3301,6 +4013,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "č®¾ē½®č”ē³»äŗŗå§“åā€¦ā€¦"; +/* No comment provided by engineer. */ +"Set default theme" = "设置默认主题"; + /* No comment provided by engineer. */ "Set group preferences" = "č®¾ē½®ē¾¤ē»„åå„½č®¾ē½®"; @@ -3346,15 +4061,24 @@ /* No comment provided by engineer. */ "Share address with contacts?" = "äøŽč”ē³»äŗŗåˆ†äŗ«åœ°å€ļ¼Ÿ"; +/* No comment provided by engineer. */ +"Share from other apps." = "ä»Žå…¶ä»–åŗ”ē”ØēØ‹åŗå…±äŗ«ć€‚"; + /* No comment provided by engineer. */ "Share link" = "åˆ†äŗ«é“¾ęŽ„"; /* No comment provided by engineer. */ "Share this 1-time invite link" = "åˆ†äŗ«ę­¤äø€ę¬”ę€§é‚€čÆ·é“¾ęŽ„"; +/* No comment provided by engineer. */ +"Share to SimpleX" = "åˆ†äŗ«åˆ° SimpleX"; + /* No comment provided by engineer. */ "Share with contacts" = "äøŽč”ē³»äŗŗåˆ†äŗ«"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "显示 → é€ščæ‡äø“ē”Øč·Æē”±å‘é€ēš„äæ”ęÆ."; + /* No comment provided by engineer. */ "Show calls in phone history" = "åœØē”µčÆåŽ†å²č®°å½•äø­ę˜¾ē¤ŗé€ščÆ"; @@ -3364,6 +4088,12 @@ /* No comment provided by engineer. */ "Show last messages" = "ę˜¾ē¤ŗęœ€čæ‘ēš„ę¶ˆęÆ"; +/* No comment provided by engineer. */ +"Show message status" = "ę˜¾ē¤ŗę¶ˆęÆēŠ¶ę€"; + +/* No comment provided by engineer. */ +"Show percentage" = "ę˜¾ē¤ŗē™¾åˆ†ęÆ”"; + /* No comment provided by engineer. */ "Show preview" = "ę˜¾ē¤ŗé¢„č§ˆ"; @@ -3373,6 +4103,9 @@ /* No comment provided by engineer. */ "Show:" = "显示:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "SimpleX 地址"; @@ -3418,6 +4151,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "ē®€åŒ–ēš„éščŗ«ęØ”å¼"; +/* No comment provided by engineer. */ +"Size" = "大小"; + /* No comment provided by engineer. */ "Skip" = "跳过"; @@ -3427,14 +4163,26 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "å°ē¾¤ē»„ļ¼ˆęœ€å¤š 20 人)"; +/* No comment provided by engineer. */ +"SMP server" = "SMP ęœåŠ”å™Ø"; + +/* blur media */ +"Soft" = "软"; + +/* No comment provided by engineer. */ +"Some file(s) were not exported:" = "ęŸäŗ›ę–‡ä»¶ęœŖåÆ¼å‡ŗļ¼š"; + /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "åÆ¼å…„čæ‡ēØ‹äø­å‘ē”Ÿäŗ†äø€äŗ›éžč‡“å‘½é”™čÆÆā€”ā€”ę‚ØåÆä»„ęŸ„ēœ‹čŠå¤©ęŽ§åˆ¶å°äŗ†č§£ę›“å¤ščÆ¦ē»†äæ”ęÆć€‚"; +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import:" = "åÆ¼å…„čæ‡ēØ‹äø­å‡ŗēŽ°äø€äŗ›éžč‡“å‘½é”™čÆÆļ¼š"; + /* notification title */ "Somebody" = "某人"; /* No comment provided by engineer. */ -"Square, circle, or anything in between." = "ę–¹å½¢ć€åœ†å½¢ć€ęˆ–äø¤č€…ä¹‹é—“ēš„ä»»ę„å½¢ēŠ¶"; +"Square, circle, or anything in between." = "ę–¹å½¢ć€åœ†å½¢ć€ęˆ–äø¤č€…ä¹‹é—“ēš„ä»»ę„å½¢ēŠ¶."; /* chat item text */ "standard end-to-end encryption" = "ę ‡å‡†ē«Æåˆ°ē«ÆåŠ åÆ†"; @@ -3448,9 +4196,15 @@ /* No comment provided by engineer. */ "Start migration" = "开始迁移"; +/* No comment provided by engineer. */ +"Starting from %@." = "从 %@ 开始。"; + /* No comment provided by engineer. */ "starting…" = "åÆåŠØäø­ā€¦ā€¦"; +/* No comment provided by engineer. */ +"Statistics" = "统讔"; + /* No comment provided by engineer. */ "Stop" = "停止"; @@ -3490,9 +4244,21 @@ /* No comment provided by engineer. */ "strike" = "删去"; +/* blur media */ +"Strong" = "åŠ ē²—"; + /* No comment provided by engineer. */ "Submit" = "ęäŗ¤"; +/* No comment provided by engineer. */ +"Subscribed" = "å·²č®¢é˜…"; + +/* No comment provided by engineer. */ +"Subscription errors" = "č®¢é˜…é”™čÆÆ"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "åæ½ē•„č®¢é˜…"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "ę”ÆęŒ SimpleX Chat"; @@ -3526,6 +4292,9 @@ /* No comment provided by engineer. */ "Tap to scan" = "č½»ęŒ‰ę‰«ę"; +/* No comment provided by engineer. */ +"TCP connection" = "TCP čæžęŽ„"; + /* No comment provided by engineer. */ "TCP connection timeout" = "TCP čæžęŽ„č¶…ę—¶"; @@ -3538,6 +4307,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; +/* No comment provided by engineer. */ +"Temporary file error" = "专时文件错误"; + /* server test failure */ "Test failed at step %@." = "在歄骤 %@ äøŠęµ‹čÆ•å¤±č“„ć€‚"; @@ -3565,6 +4337,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "čÆ„åŗ”ē”ØåÆä»„åœØę‚Øę”¶åˆ°ę¶ˆęÆęˆ–č”ē³»äŗŗčÆ·ę±‚ę—¶é€šēŸ„ę‚Øā€”ā€”čÆ·ę‰“å¼€č®¾ē½®ä»„åÆē”Øé€šēŸ„ć€‚"; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "čÆ„åŗ”ē”ØēØ‹åŗå°†č¦ę±‚ē”®č®¤ä»ŽęœŖēŸ„ę–‡ä»¶ęœåŠ”å™Øļ¼ˆ.onion 除外)下载。"; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "ę›“ę”¹ę•°ę®åŗ“åÆ†ē ēš„å°čÆ•ęœŖå®Œęˆć€‚"; @@ -3595,6 +4370,12 @@ /* No comment provided by engineer. */ "The message will be marked as moderated for all members." = "čÆ„ę¶ˆęÆå°†åÆ¹ę‰€ęœ‰ęˆå‘˜ę ‡č®°äøŗå·²č¢«ē®”ē†å‘˜ē§»é™¤ć€‚"; +/* No comment provided by engineer. */ +"The messages will be deleted for all members." = "å°†åˆ é™¤ę‰€ęœ‰ęˆå‘˜ēš„ę¶ˆęÆć€‚"; + +/* No comment provided by engineer. */ +"The messages will be marked as moderated for all members." = "åÆ¹äŗŽę‰€ęœ‰ęˆå‘˜ļ¼Œčæ™äŗ›ę¶ˆęÆå°†č¢«ę ‡č®°äøŗå·²å®”ę øć€‚"; + /* No comment provided by engineer. */ "The next generation of private messaging" = "äø‹äø€ä»£ē§åÆ†é€šč®Æč½Æä»¶"; @@ -3616,6 +4397,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "ę‚Øē²˜č““ēš„ę–‡ęœ¬äøę˜Æ SimpleX é“¾ęŽ„ć€‚"; +/* No comment provided by engineer. */ +"Themes" = "主题"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "čæ™äŗ›č®¾ē½®é€‚ē”ØäŗŽę‚Øå½“å‰ēš„é…ē½®ę–‡ä»¶ **%@**怂"; @@ -3658,9 +4442,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "čæ™ę˜Æä½ č‡Ŗå·±ēš„ SimpleX åœ°å€ļ¼"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "ę­¤é“¾ęŽ„å·²åœØå…¶ä»–ē§»åŠØč®¾å¤‡äøŠä½æē”Øļ¼ŒčÆ·åœØę”Œé¢äøŠåˆ›å»ŗę–°é“¾ęŽ„ć€‚"; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "ę­¤č®¾ē½®é€‚ē”ØäŗŽę‚Øå½“å‰čŠå¤©čµ„ę–™ **%@** äø­ēš„ę¶ˆęÆć€‚"; +/* No comment provided by engineer. */ +"Title" = "ę ‡é¢˜"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "č¦ęå‡ŗä»»ä½•é—®é¢˜å¹¶ęŽ„ę”¶ę›“ę–°ļ¼ŒčÆ·ļ¼š"; @@ -3682,6 +4472,9 @@ /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "äøŗäæęŠ¤ę‚Øēš„äæ”ęÆļ¼ŒčÆ·ę‰“å¼€ SimpleX é”å®šć€‚\nåœØåÆē”Øę­¤åŠŸčƒ½ä¹‹å‰ļ¼Œē³»ē»Ÿå°†ęē¤ŗę‚Øå®Œęˆčŗ«ä»½éŖŒčÆć€‚"; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "äøŗäŗ†äæęŠ¤ę‚Øēš„ IP åœ°å€ļ¼Œē§ęœ‰č·Æē”±ä½æē”Øę‚Øēš„ SMP ęœåŠ”å™Øę„ä¼ é€’é‚®ä»¶ć€‚"; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "čÆ·ęŽˆęƒä½æē”Øéŗ¦å…‹é£Žä»„å½•åˆ¶čÆ­éŸ³ę¶ˆęÆć€‚"; @@ -3694,18 +4487,33 @@ /* No comment provided by engineer. */ "To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "č¦äøŽę‚Øēš„č”ē³»äŗŗéŖŒčÆē«Æåˆ°ē«ÆåŠ åÆ†ļ¼ŒčÆ·ęÆ”č¾ƒļ¼ˆęˆ–ę‰«ęļ¼‰ę‚Øč®¾å¤‡äøŠēš„ä»£ē ć€‚"; +/* No comment provided by engineer. */ +"Toggle chat list:" = "åˆ‡ę¢čŠå¤©åˆ—č”Øļ¼š"; + /* No comment provided by engineer. */ "Toggle incognito when connecting." = "åœØčæžęŽ„ę—¶åˆ‡ę¢éščŗ«ęØ”å¼ć€‚"; +/* No comment provided by engineer. */ +"Toolbar opacity" = "å·„å…·ę äøé€ę˜Žåŗ¦"; + +/* No comment provided by engineer. */ +"Total" = "共讔"; + /* No comment provided by engineer. */ "Transport isolation" = "ä¼ č¾“éš”ē¦»"; +/* No comment provided by engineer. */ +"Transport sessions" = "ä¼ č¾“ä¼ščÆ"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "ę­£åœØå°čÆ•čæžęŽ„åˆ°ē”ØäŗŽä»ŽčÆ„č”ē³»äŗŗęŽ„ę”¶ę¶ˆęÆēš„ęœåŠ”å™Øļ¼ˆé”™čÆÆļ¼š%@)。"; /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact." = "ę­£åœØå°čÆ•čæžęŽ„åˆ°ē”ØäŗŽä»ŽčÆ„č”ē³»äŗŗęŽ„ę”¶ę¶ˆęÆēš„ęœåŠ”å™Øć€‚"; +/* No comment provided by engineer. */ +"Turkish interface" = "åœŸč€³å…¶čÆ­ē•Œé¢"; + /* No comment provided by engineer. */ "Turn off" = "关闭"; @@ -3730,6 +4538,9 @@ /* No comment provided by engineer. */ "Unblock member?" = "č§£å°ęˆå‘˜å—ļ¼Ÿ"; +/* rcv group event chat item */ +"unblocked %@" = "未阻止 %@"; + /* No comment provided by engineer. */ "Unexpected migration state" = "ęœŖé¢„ę–™ēš„čæē§»ēŠ¶ę€"; @@ -3760,6 +4571,12 @@ /* No comment provided by engineer. */ "Unknown error" = "ęœŖēŸ„é”™čÆÆ"; +/* No comment provided by engineer. */ +"unknown servers" = "ęœŖēŸ„ęœåŠ”å™Ø"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "ęœŖēŸ„ęœåŠ”å™Øļ¼"; + /* No comment provided by engineer. */ "unknown status" = "ęœŖēŸ„ēŠ¶ę€"; @@ -3781,9 +4598,15 @@ /* authentication reason */ "Unlock app" = "č§£é”åŗ”ē”ØēØ‹åŗ"; +/* No comment provided by engineer. */ +"unmute" = "å–ę¶ˆé™éŸ³"; + /* swipe action */ "Unmute" = "å–ę¶ˆé™éŸ³"; +/* No comment provided by engineer. */ +"unprotected" = "ęœŖå—äæęŠ¤"; + /* swipe action */ "Unread" = "未读"; @@ -3799,6 +4622,9 @@ /* No comment provided by engineer. */ "Update network settings?" = "ę›“ę–°ē½‘ē»œč®¾ē½®ļ¼Ÿ"; +/* No comment provided by engineer. */ +"Update settings?" = "ę›“ę–°č®¾ē½®ļ¼Ÿ"; + /* rcv group event chat item */ "updated group profile" = "å·²ę›“ę–°ēš„ē¾¤ē»„čµ„ę–™"; @@ -3811,12 +4637,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "å‡ēŗ§å¹¶ę‰“å¼€čŠå¤©"; +/* No comment provided by engineer. */ +"Upload errors" = "äøŠä¼ é”™čÆÆ"; + /* No comment provided by engineer. */ "Upload failed" = "äøŠä¼ å¤±č“„äŗ†"; /* server test step */ "Upload file" = "äøŠä¼ ę–‡ä»¶"; +/* No comment provided by engineer. */ +"Uploaded" = "已上传"; + +/* No comment provided by engineer. */ +"Uploaded files" = "å·²äøŠä¼ ēš„ę–‡ä»¶"; + /* No comment provided by engineer. */ "Uploading archive" = "正在上传存攣"; @@ -3841,6 +4676,15 @@ /* No comment provided by engineer. */ "Use new incognito profile" = "ä½æē”Øę–°ēš„éščŗ«é…ē½®ę–‡ä»¶"; +/* No comment provided by engineer. */ +"Use only local notifications?" = "ä»…ä½æē”Øęœ¬åœ°é€šēŸ„ļ¼Ÿ"; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "当 IP åœ°å€äøå—äæęŠ¤ę—¶ļ¼ŒåÆ¹ęœŖēŸ„ęœåŠ”å™Øä½æē”Øē§ęœ‰č·Æē”±ć€‚"; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "åÆ¹ęœŖēŸ„ęœåŠ”å™Øä½æē”Øē§ęœ‰č·Æē”±ć€‚"; + /* No comment provided by engineer. */ "Use server" = "ä½æē”ØęœåŠ”å™Ø"; @@ -3848,14 +4692,23 @@ "Use SimpleX Chat servers?" = "使用 SimpleX Chat ęœåŠ”å™Øļ¼Ÿ"; /* No comment provided by engineer. */ -"Use the app while in the call." = "é€ščÆę—¶ä½æē”Øęœ¬åŗ”ē”Ø"; +"Use the app while in the call." = "é€ščÆę—¶ä½æē”Øęœ¬åŗ”ē”Ø."; + +/* No comment provided by engineer. */ +"Use the app with one hand." = "ē”Øäø€åŖę‰‹ä½æē”Øåŗ”ē”ØēØ‹åŗć€‚"; /* No comment provided by engineer. */ "User profile" = "ē”Øęˆ·čµ„ę–™"; +/* No comment provided by engineer. */ +"User selection" = "ē”Øęˆ·é€‰ę‹©"; + /* No comment provided by engineer. */ "Using SimpleX Chat servers." = "使用 SimpleX Chat ęœåŠ”å™Øć€‚"; +/* No comment provided by engineer. */ +"v%@" = "v%@"; + /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; @@ -3898,6 +4751,9 @@ /* No comment provided by engineer. */ "Via secure quantum resistant protocol." = "é€ščæ‡å®‰å…Øēš„ć€ęŠ—é‡å­č®”ē®—ęœŗē “č§£ēš„åč®®ć€‚"; +/* No comment provided by engineer. */ +"video" = "视频"; + /* No comment provided by engineer. */ "Video call" = "č§†é¢‘é€ščÆ"; @@ -3943,6 +4799,9 @@ /* No comment provided by engineer. */ "waiting for confirmation…" = "等待甮认中……"; +/* No comment provided by engineer. */ +"Waiting for desktop..." = "ę­£åœØē­‰å¾…ę”Œé¢..."; + /* No comment provided by engineer. */ "Waiting for file" = "等待文件中"; @@ -3952,11 +4811,17 @@ /* No comment provided by engineer. */ "Waiting for video" = "等待视频中"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "壁纸装鄰"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "å£ēŗøčƒŒę™Æ"; + /* No comment provided by engineer. */ "wants to connect to you!" = "ęƒ³č¦äøŽę‚ØčæžęŽ„ļ¼"; /* No comment provided by engineer. */ -"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "č­¦å‘Šļ¼šäøę”ÆęŒåœØå¤šéƒØč®¾å¤‡äøŠåÆåŠØčŠå¤©ļ¼Œčæ™ä¹ˆåšä¼šåÆ¼č‡“ę¶ˆęÆä¼ é€å¤±č“„ć€‚"; +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "č­¦å‘Šļ¼šäøę”ÆęŒåœØå¤šéƒØč®¾å¤‡äøŠåÆåŠØčŠå¤©ļ¼Œčæ™ä¹ˆåšä¼šåÆ¼č‡“ę¶ˆęÆä¼ é€å¤±č“„"; /* No comment provided by engineer. */ "Warning: you may lose some data!" = "č­¦å‘Šļ¼šę‚ØåÆčƒ½ä¼šäø¢å¤±éƒØåˆ†ę•°ę®ļ¼"; @@ -3985,6 +4850,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "čæžęŽ„éŸ³é¢‘å’Œč§†é¢‘é€ščÆę—¶ć€‚"; +/* No comment provided by engineer. */ +"when IP hidden" = "当 IP éšč—ę—¶"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "å½“äŗŗä»¬čÆ·ę±‚čæžęŽ„ę—¶ļ¼Œę‚ØåÆä»„ęŽ„å—ęˆ–ę‹’ē»å®ƒć€‚"; @@ -4009,12 +4877,27 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "é™ä½Žäŗ†ē”µé‡ä½æē”Øć€‚"; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ę–‡ä»¶ęœåŠ”å™ØåÆč§ć€‚"; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ä»„äø‹ XFTP äø­ē»§åÆč§ļ¼š%@怂"; + /* No comment provided by engineer. */ "Wrong database passphrase" = "ę•°ę®åŗ“åÆ†ē é”™čÆÆ"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "åÆ†é’„é”™čÆÆęˆ–čæžęŽ„ęœŖēŸ„ - å¾ˆåÆčƒ½ę­¤čæžęŽ„å·²č¢«åˆ é™¤ć€‚"; + +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "åÆ†é’„é”™čÆÆęˆ–ę–‡ä»¶å—åœ°å€ęœŖēŸ„ - å¾ˆåÆčƒ½ę–‡ä»¶å·²åˆ é™¤ć€‚"; + /* No comment provided by engineer. */ "Wrong passphrase!" = "密码错误!"; +/* No comment provided by engineer. */ +"XFTP server" = "XFTP ęœåŠ”å™Ø"; + /* pref value */ "yes" = "是"; @@ -4024,6 +4907,9 @@ /* No comment provided by engineer. */ "You" = "您"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "您 **äøå¾—** åœØäø¤å°č®¾å¤‡äøŠä½æē”Øē›øåŒēš„ę•°ę®åŗ“ć€‚"; + /* No comment provided by engineer. */ "You accepted connection" = "ę‚Øå·²ęŽ„å—čæžęŽ„"; @@ -4036,12 +4922,27 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "ę‚Øå·²ē»čæžęŽ„åˆ° %@怂"; +/* No comment provided by engineer. */ +"You are already connecting to %@." = "ę‚Øå·²čæžęŽ„åˆ° %@怂"; + /* No comment provided by engineer. */ "You are already connecting via this one-time link!" = "ä½ å·²ē»åœØé€ščæ‡čæ™äøŖäø€ę¬”ę€§é“¾ęŽ„čæ›č”ŒčæžęŽ„ļ¼"; +/* No comment provided by engineer. */ +"You are already in group %@." = "ę‚Øå·²åœØē»„ %@ 中。"; + +/* No comment provided by engineer. */ +"You are already joining the group %@." = "ę‚Øå·²åŠ å…„ē»„ %@怂"; + +/* No comment provided by engineer. */ +"You are already joining the group via this link!" = "ę‚Øå·²ē»é€ščæ‡ę­¤é“¾ęŽ„åŠ å…„ē¾¤ē»„ļ¼"; + /* No comment provided by engineer. */ "You are already joining the group via this link." = "ä½ å·²ē»åœØé€ščæ‡ę­¤é“¾ęŽ„åŠ å…„čÆ„ē¾¤ć€‚"; +/* No comment provided by engineer. */ +"You are already joining the group!\nRepeat join request?" = "ę‚Øå·²ē»åŠ å…„äŗ†čæ™äøŖē¾¤ē»„ļ¼\né‡å¤åŠ å…„čÆ·ę±‚ļ¼Ÿ"; + /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "ę‚Øå·²čæžęŽ„åˆ°ē”ØäŗŽęŽ„ę”¶čÆ„č”ē³»äŗŗę¶ˆęÆēš„ęœåŠ”å™Øć€‚"; @@ -4051,12 +4952,21 @@ /* No comment provided by engineer. */ "You are invited to group" = "ę‚Øč¢«é‚€čÆ·åŠ å…„ē¾¤ē»„"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "ę‚ØęœŖčæžęŽ„åˆ°čæ™äŗ›ęœåŠ”å™Øć€‚ē§ęœ‰č·Æē”±ē”ØäŗŽå‘ä»–ä»¬å‘é€ę¶ˆęÆć€‚"; + /* No comment provided by engineer. */ "you are observer" = "ę‚Øę˜Æč§‚åÆŸč€…"; +/* snd group event chat item */ +"you blocked %@" = "ä½ é˜»ę­¢äŗ†%@"; + /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "ę‚ØåÆä»„ä»Žé”å±äøŠęŽ„å¬ē”µčÆļ¼Œę— éœ€č®¾å¤‡å’Œåŗ”ē”ØēØ‹åŗēš„č®¤čÆć€‚"; +/* No comment provided by engineer. */ +"You can change it in Appearance settings." = "ę‚ØåÆä»„åœØå¤–č§‚č®¾ē½®äø­ę›“ę”¹å®ƒć€‚"; + /* No comment provided by engineer. */ "You can create it later" = "ę‚ØåÆä»„ä»„åŽåˆ›å»ŗå®ƒ"; @@ -4078,6 +4988,9 @@ /* notification body */ "You can now chat with %@" = "ę‚ØēŽ°åœØåÆä»„ē»™ %@ å‘é€ę¶ˆęÆ"; +/* No comment provided by engineer. */ +"You can send messages to %@ from Archived contacts." = "ę‚ØåÆä»„ä»Žå­˜ę”£ēš„č”ē³»äŗŗå‘%@å‘é€ę¶ˆęÆć€‚"; + /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "ę‚ØåÆä»„é€ščæ‡č®¾ē½®ę„č®¾ē½®é”å±é€šēŸ„é¢„č§ˆć€‚"; @@ -4093,6 +5006,9 @@ /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "ę‚ØåÆä»„é€ščæ‡åŗ”ē”ØēØ‹åŗč®¾ē½®/ę•°ę®åŗ“ęˆ–é‡ę–°åÆåŠØåŗ”ē”ØēØ‹åŗå¼€å§‹čŠå¤©"; +/* No comment provided by engineer. */ +"You can still view conversation with %@ in the list of chats." = "ę‚Øä»ē„¶åÆä»„åœØčŠå¤©åˆ—č”Øäø­ęŸ„ēœ‹äøŽ %@ēš„åÆ¹čÆć€‚"; + /* No comment provided by engineer. */ "You can turn on SimpleX Lock via Settings." = "ę‚ØåÆä»„é€ščæ‡č®¾ē½®å¼€åÆ SimpleX é”å®šć€‚"; @@ -4126,6 +5042,9 @@ /* No comment provided by engineer. */ "You have already requested connection via this address!" = "ä½ å·²ē»čÆ·ę±‚é€ščæ‡ę­¤åœ°å€čæ›č”ŒčæžęŽ„ļ¼"; +/* No comment provided by engineer. */ +"You have already requested connection!\nRepeat connection request?" = "ę‚Øå·²ē»čÆ·ę±‚čæžęŽ„äŗ†ļ¼\né‡å¤čæžęŽ„čÆ·ę±‚ļ¼Ÿ"; + /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "ę‚Øåæ…é”»åœØęÆę¬”åŗ”ē”ØēØ‹åŗåÆåŠØę—¶č¾“å…„åÆ†ē ā€”ā€”å®ƒäøå­˜å‚ØåœØč®¾å¤‡äøŠć€‚"; @@ -4141,9 +5060,18 @@ /* snd group event chat item */ "you left" = "您已离开"; +/* No comment provided by engineer. */ +"You may migrate the exported database." = "ę‚ØåÆä»„čæē§»åÆ¼å‡ŗēš„ę•°ę®åŗ“ć€‚"; + +/* No comment provided by engineer. */ +"You may save the exported archive." = "ę‚ØåÆä»„äæå­˜åÆ¼å‡ŗēš„ę”£ę”ˆć€‚"; + /* No comment provided by engineer. */ "You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "ę‚ØåŖčƒ½åœØäø€å°č®¾å¤‡äøŠä½æē”Øęœ€ę–°ē‰ˆęœ¬ēš„čŠå¤©ę•°ę®åŗ“ļ¼Œå¦åˆ™ę‚ØåÆčƒ½ä¼šåœę­¢ęŽ„ę”¶ę„č‡ŖęŸäŗ›č”ē³»äŗŗēš„ę¶ˆęÆć€‚"; +/* No comment provided by engineer. */ +"You need to allow your contact to call to be able to call them." = "ę‚Øéœ€č¦å…č®øę‚Øēš„č”ē³»äŗŗå‘¼å«ę‰čƒ½å‘¼å«ä»–ä»¬ć€‚"; + /* No comment provided by engineer. */ "You need to allow your contact to send voice messages to be able to send them." = "ę‚Øéœ€č¦å…č®øę‚Øēš„č”ē³»äŗŗå‘é€čÆ­éŸ³ę¶ˆęÆļ¼Œä»„ä¾æę‚Øčƒ½å¤Ÿå‘é€čÆ­éŸ³ę¶ˆęÆć€‚"; @@ -4162,9 +5090,15 @@ /* chat list item description */ "you shared one-time link incognito" = "ę‚Øåˆ†äŗ«äŗ†äø€ę¬”ę€§é“¾ęŽ„éščŗ«čŠå¤©"; +/* snd group event chat item */ +"you unblocked %@" = "您解封了 %@"; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "ę‚Øå°†åœØē»„äø»č®¾å¤‡äøŠēŗæę—¶čæžęŽ„åˆ°čÆ„ē¾¤ē»„ļ¼ŒčÆ·ēØē­‰ęˆ–ēØåŽå†ę£€ęŸ„ļ¼"; +/* No comment provided by engineer. */ +"You will be connected when group link host's device is online, please wait or check later!" = "当 Group Link Host ēš„č®¾å¤‡åœØēŗæę—¶ļ¼Œę‚Øå°†č¢«čæžęŽ„ļ¼ŒčÆ·ēØå€™ęˆ–ēØåŽę£€ęŸ„ļ¼"; + /* No comment provided by engineer. */ "You will be connected when your connection request is accepted, please wait or check later!" = "å½“ę‚Øēš„čæžęŽ„čÆ·ę±‚č¢«ęŽ„å—åŽļ¼Œę‚Øå°†åÆä»„čæžęŽ„ļ¼ŒčÆ·ēØē­‰ęˆ–ēØåŽę£€ęŸ„ļ¼"; @@ -4234,11 +5168,14 @@ /* No comment provided by engineer. */ "Your privacy" = "ę‚Øēš„éšē§č®¾ē½®"; +/* No comment provided by engineer. */ +"Your profile" = "ę‚Øēš„äøŖäŗŗčµ„ę–™"; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "ę‚Øēš„äøŖäŗŗčµ„ę–™ **%@** 将被共享。"; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "ę‚Øēš„čµ„ę–™å­˜å‚ØåœØę‚Øēš„č®¾å¤‡äøŠå¹¶ä»…äøŽę‚Øēš„č”ē³»äŗŗå…±äŗ«ć€‚\nSimpleX ęœåŠ”å™Øę— ę³•ēœ‹åˆ°ę‚Øēš„čµ„ę–™ć€‚"; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "ę‚Øēš„čµ„ę–™å­˜å‚ØåœØę‚Øēš„č®¾å¤‡äøŠå¹¶ä»…äøŽę‚Øēš„č”ē³»äŗŗå…±äŗ«ć€‚ SimpleX ęœåŠ”å™Øę— ę³•ēœ‹åˆ°ę‚Øēš„čµ„ę–™ć€‚"; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "ę‚Øēš„čµ„ę–™ć€č”ē³»äŗŗå’Œå‘é€ēš„ę¶ˆęÆå­˜å‚ØåœØę‚Øēš„č®¾å¤‡äøŠć€‚"; diff --git a/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings index b3192851c8..199d5faf7c 100644 --- a/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/zh-Hans.lproj/SimpleX--iOS--InfoPlist.strings @@ -7,6 +7,9 @@ /* Privacy - Face ID Usage Description */ "NSFaceIDUsageDescription" = "SimpleX 使用Face IDčæ›č”Œęœ¬åœ°čŗ«ä»½éŖŒčÆ"; +/* Privacy - Local Network Usage Description */ +"NSLocalNetworkUsageDescription" = "SimpleX ä½æē”Øęœ¬åœ°ē½‘ē»œč®æé—®ļ¼Œå…č®øé€ščæ‡åŒäø€ē½‘ē»œäøŠēš„ę”Œé¢åŗ”ē”ØēØ‹åŗä½æē”Øē”Øęˆ·čŠå¤©é…ē½®ę–‡ä»¶ć€‚"; + /* Privacy - Microphone Usage Description */ "NSMicrophoneUsageDescription" = "SimpleX éœ€č¦éŗ¦å…‹é£Žč®æé—®ęƒé™ę‰čƒ½čæ›č”ŒéŸ³é¢‘å’Œč§†é¢‘é€ščÆļ¼Œä»„åŠå½•åˆ¶čÆ­éŸ³ę¶ˆęÆć€‚"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 0c4e78a269..595545213a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -224,7 +224,7 @@ Jak používat servery VaÅ”e servery ICE Konfigurace serverÅÆ ICE - NastavenĆ­ sĆ­tě + PokročilĆ© nastavenĆ­ Použít proxy server SOCKS\? Použít přímĆ© připojenĆ­ k internetu\? Ne @@ -434,7 +434,7 @@ Upravit obrĆ”zek Smazat obrĆ”zek chyba volĆ”nĆ­ - Protokol a kód s otevřeným zdrojovým kódem - servery může provozovat kdokoli. + Servery může provozovat kdokoli. Vytvořte si svÅÆj profil Vytvořte si soukromĆ© připojenĆ­ Videohovor Å”ifrovaný e2e @@ -799,7 +799,7 @@ pozval %1$s připojen změnil roli %s na %s - změnil svou roli na %s + změnil vaÅ”i roli na %s odstraněn %1$s odstranil vĆ”s skupina odstraněna @@ -1861,4 +1861,5 @@ VolĆ”nĆ­ zakĆ”zĆ”no! Nelze zavolat člena skupiny ArchivovanĆ© kontakty + Archivujte kontakty pro pozdějŔí chatovĆ”nĆ­. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 897ea64be8..e7b327c4ff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -277,11 +277,11 @@ Inkognito akzeptieren Ablehnen - Chatinhalte lƶschen? + Chat-Inhalte entfernen? Es werden alle Nachrichten gelƶscht. Dies kann nicht rückgƤngig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelƶscht. - Lƶschen - Chatinhalte lƶschen - Chatinhalte lƶschen + Entfernen + Chat-Inhalte entfernen + Chat-Inhalte entfernen Lƶschen Lƶschen Als gelesen markieren @@ -753,7 +753,7 @@ Mitgliedereinladungen überspringen Kontakte auswƤhlen Kontakt geprüft - Lƶschen + Entfernen %d Kontakt(e) ausgewƤhlt Keine Kontakte ausgewƤhlt Kontakt kann nicht eingeladen werden! @@ -765,7 +765,7 @@ Gruppe lƶschen Gruppe lƶschen? Die Gruppe wird für alle Mitglieder gelƶscht. Dies kann nicht rückgƤngig gemacht werden! - Die Gruppe wird für Sie gelƶscht. Dies kann nicht rückgƤngig gemacht werden! + Die Gruppe wird nur bei Ihnen gelƶscht. Dies kann nicht rückgƤngig gemacht werden! Gruppe verlassen Gruppenprofil bearbeiten Gruppen-Link @@ -1397,7 +1397,7 @@ Wir haben das zweite HƤkchen vermisst! āœ… Reparatur der Verschlüsselung nach Wiedereinspielen von Backups. Ein paar weitere Dinge - Auch wenn sie im Chat deaktiviert sind. + Auch wenn sie in den Unterhaltungen deaktiviert sind. - stabilere Zustellung von Nachrichten. \n- ein bisschen verbesserte Gruppen. \n- und mehr! @@ -1630,7 +1630,7 @@ Zum Scannen tippen Behalten Zum Link einfügen tippen - Suchen oder fügen Sie den SimpleX-Link ein + Suchen oder SimpleX-Link einfügen Der Chat wurde gestoppt. Wenn diese Datenbank bereits auf einem anderen GerƤt von Ihnen verwendet wurde, sollten Sie diese dorthin zurück übertragen, bevor Sie den Chat starten. Chat starten? Interne Fehler anzeigen @@ -1673,7 +1673,7 @@ Mit verschlüsselten Dateien und Medien. Private Notizen Es werden alle Nachrichten gelƶscht. Dies kann nicht rückgƤngig gemacht werden! - Private Notizen lƶschen? + Private Notizen entfernen? %s wurde blockiert %s wurde freigegeben Sie haben %s blockiert @@ -2081,16 +2081,16 @@ Verbinden Nachricht Ɩffnen - Unterhaltung gelƶscht! - Nur die Unterhaltung lƶschen + Chat-Inhalte gelƶscht! + Nur die Chat-Inhalte lƶschen Suchen Video - Sie kƶnnen in der Chatliste weiterhin die Unterhaltung mit %1$s einsehen. + Sie kƶnnen in der Chat-Liste weiterhin die Unterhaltung mit %1$s einsehen. Link einfügen Archivierte Kontakte Keine gefilterten Kontakte Ihre Kontakte - Erreichbare Chat-Symbolleiste + Chat-Symbolleiste unten Bitten Sie Ihren Kontakt darum, Anrufe zu aktivieren. Sie müssen Ihrem Kontakt Anrufe zu Ihnen erlauben, bevor Sie ihn selbst anrufen kƶnnen. Anrufe erlauben? @@ -2106,11 +2106,11 @@ Kontakt wird gelƶscht. Dies kann nicht rückgƤngig gemacht werden! Ohne Benachrichtigung lƶschen Einladen - Unterhaltung behalten + Chat-Inhalte beibehalten Nachricht senden, um Anrufe zu aktivieren. Sie kƶnnen aus den archivierten Kontakten heraus Nachrichten an %1$s versenden. Einstellungen - Die Nachrichten werden für alle Mitglieder gelƶscht. + Die Nachrichten werden für alle Gruppenmitglieder gelƶscht. Die Nachrichten werden für alle Mitglieder als moderiert markiert. %d Nachrichten der Mitglieder lƶschen? Nachricht @@ -2120,7 +2120,7 @@ AuswƤhlen Einladen TCP-Verbindung - Schriftgröße erhƶhen. + Schriftgröße anpassen. Neue Chat-Erfahrung šŸŽ‰ Die App automatisch aktualisieren Verbindungs- und Server-Status. @@ -2136,8 +2136,8 @@ Kontakte für spƤtere Chats archivieren. Ihre IP-Adresse und Verbindungen werden geschützt. Lƶschen Sie bis zu 20 Nachrichten auf einmal. - Erreichbare Chat-Symbolleiste - Die App mit einer Hand nutzen. + Chat-Symbolleiste unten + Die App mit einer Hand bedienen. Schneller mit Ihren Freunden verbinden. Chat-Datenbank wurde exportiert Weiter diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index db06d74378..3ec6bc021b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -15,10 +15,10 @@ 30 mĆ”sodperc Egyszer hasznĆ”latos hivatkozĆ”s %1$s szeretne kapcsolatba lĆ©pni ƶnnel ezen keresztül: - A SimpleX Chat-ről + A SimpleX Chatről 1 nap CĆ­mvĆ”ltoztatĆ”s megszakĆ­tĆ”sa - A SimpleX-ről + A SimpleXről KiemelĆ©s fogadott hĆ­vĆ”s HozzĆ”fĆ©rĆ©s a kiszolgĆ”lókhoz SOCKS proxy segĆ­tsĆ©gĆ©vel a %d porton? A proxyt el kell indĆ­tani, mielőtt engedĆ©lyeznĆ© ezt az opciót. @@ -26,25 +26,25 @@ ElfogadĆ”s gombra fent, majd: ElfogadĆ”s inkognĆ­tóban - KapcsolódĆ”si kĆ©relem elfogadĆ”sa? + IsmerőskĆ©relem elfogadĆ”sa? ElfogadĆ”s ElfogadĆ”s CĆ­m hozzĆ”adĆ”sa a profilhoz, hogy az ismerősei megoszthassĆ”k mĆ”sokkal. A profilfrissĆ­tĆ©s elküldĆ©sre kerül az ismerősƶk szĆ”mĆ”ra. TovĆ”bbi kiemelĆ©s - hiba a hĆ­vĆ”sban + hĆ­vĆ”shiba Csoporttagok letiltĆ”sa HitelesĆ­tĆ©s Egy üres csevegĆ©si profil jƶn lĆ©tre a megadott nĆ©vvel, Ć©s az alkalmazĆ”s a szokĆ”sos módon megnyĆ­lik. %s visszavonva Előre beĆ”llĆ­tott kiszolgĆ”lók hozzĆ”adĆ”sa A hĆ­vĆ”sok kezdemĆ©nyezĆ©se le van tiltva ebben a csevegĆ©sben. - Külƶn TCP kapcsolat (Ć©s SOCKS bejelentkezĆ©si adatok) lesz hasznĆ”lva minden ismerős Ć©s csoporttag szĆ”mĆ”ra. -\nFigyelem: ha sok ismerőse van, az akkumulĆ”tor- Ć©s adathasznĆ”lat jelentősen megnƶvekedhet Ć©s nĆ©hĆ”ny kapcsolódĆ”si kĆ­sĆ©rlet sikertelen lehet. - hivatkozĆ”s előnĆ©zet visszavonĆ”sa + Külƶn TCP kapcsolat (Ć©s SOCKS bejelentkezĆ©si adatok) lesz hasznĆ”lva minden ismerős Ć©s csoporttag szĆ”mĆ”ra. \u0020 +\nFigyelem: ha sok ismerőse van, az akkumulĆ”tor- Ć©s az adathasznĆ”lat jelentősen megnƶvekedhet, Ć©s nĆ©hĆ”ny kapcsolódĆ”si kĆ­sĆ©rlet sikertelen lehet. + hivatkozĆ”s előnĆ©zetĆ©nek visszavonĆ”sa az alkalmazĆ”sban minden csevegĆ©si profiljĆ”hoz .]]> MindkĆ©t fĆ©l küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonsĆ”gos tĆ”rolĆ”sĆ”ra hasznĆ”ljĆ”k - lehetővĆ© teszi az Ć©rtesĆ­tĆ©si szolgĆ”ltatĆ”s műkƶdĆ©sĆ©t. - HibĆ”s az üzenet ellenőrzőösszege + HibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke HĆ”ttĆ©r Tudnivaló: az üzenet- Ć©s fĆ”jl Ć”tjĆ”tszók SOCKS proxy Ć”ltal vannak kapcsolatban. A hĆ­vĆ”sok Ć©s URL hivatkozĆ”s előnĆ©zetek kƶzvetlen kapcsolatot hasznĆ”lnak.]]> AlkalmazĆ”sadatok biztonsĆ”gi mentĆ©se @@ -114,7 +114,7 @@ hanghĆ­vĆ”s fĆ©lkƶvĆ©r Az alkalmazĆ”s jelkód helyettesĆ­tĆ©sre kerül egy ƶnmegsemmisĆ­tő jelkóddal. - Arab, bulgĆ”r, finn, hĆ©ber, thai Ć©s ukrĆ”n - kƶszƶnet a felhasznĆ”lóknak Ć©s a Weblate-nek! + Arab, bulgĆ”r, finn, hĆ©ber, thai Ć©s ukrĆ”n - kƶszƶnet a felhasznĆ”lóknak Ć©s a Weblate-nek. Hangüzenetek engedĆ©lyezĆ©se? Mindig hasznĆ”ljon Ć”tjĆ”tszó kiszolgĆ”lót mindig @@ -124,9 +124,9 @@ Ɖlő csevegĆ©si üzenet visszavonĆ”sa Az üzenetek vĆ©gleges tƶrlĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra) Hang- Ć©s videóhĆ­vĆ”sok - hibĆ”s az üzenet ellenőrzőösszege + hibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke Mindig fut - Az Android Keystore biztonsĆ”gosan fogja tĆ”rolni a jelmondatot az alkalmazĆ”s ĆŗjraindĆ­tĆ”sa, vagy a jelmondat megvĆ”ltoztatĆ”s utĆ”n - lehetővĆ© tĆ©ve az Ć©rtesĆ­tĆ©sek fogadĆ”sĆ”t. + Az Android Keystore biztonsĆ”gosan fogja tĆ”rolni a jelmondatot az alkalmazĆ”s ĆŗjraindĆ­tĆ”sa, vagy a jelmondat megvĆ”ltoztatĆ”s utĆ”n - lehetővĆ© teszi az Ć©rtesĆ­tĆ©sek fogadĆ”sĆ”t. Minden alkalmazĆ”sadat tƶrƶlve. Legjobb akkumulĆ”toridő. Csak akkor kap Ć©rtesĆ­tĆ©seket, amikor az alkalmazĆ”s meg van nyitva. (NINCS hĆ”ttĆ©rszolgĆ”ltatĆ”s.)]]> MegjelenĆ©s @@ -137,7 +137,7 @@ szerző Az elküldƶtt üzenetek vĆ©gleges tƶrlĆ©se engedĆ©lyezve van az ismerősei szĆ”mĆ”ra. (24 óra) MĆ©gse - Az alkalmazĆ”s csak akkor tud Ć©rtesĆ­tĆ©seket fogadni, amikor meg van nyitva. A hĆ”ttĆ©rszolgĆ”ltatĆ”s nem indul el. + Az alkalmazĆ”s csak akkor tud Ć©rtesĆ­tĆ©seket fogadni, amikor meg van nyitva. A hĆ”ttĆ©rszolgĆ”ltatĆ”s nem indul el Jobb üzenetek A cĆ­m módosĆ­tĆ”sa megszakad. A rĆ©gi fogadĆ”si cĆ­m kerül felhasznĆ”lĆ”sra. EngedĆ©lyezĆ©s @@ -147,7 +147,7 @@ AlkalmazĆ”s jelkód FelkĆ©rtĆ©k a kĆ©p fogadĆ”sĆ”ra Kamera - A Keystore-hoz nem sikerül hozzĆ”fĆ©rni az adatbĆ”zis jelszó mentĆ©se vĆ©gett + Nem Ć©rhető el a Keystore az adatbĆ”zis jelszavĆ”nak mentĆ©sĆ©hez hĆ­vĆ”s folyamatban KĆ©pek automatikus elfogadĆ”sa A hĆ­vĆ”sok kezdemĆ©nyezĆ©se engedĆ©lyezve van az ismerősei szĆ”mĆ”ra. @@ -183,7 +183,7 @@ kapcsolódott KapcsolódĆ”s kapcsolódott - Csatlakoztatott telefon + TĆ”rsĆ­tott mobil eszkƶz kapcsolódva Szerepkƶr megvĆ”ltoztatĆ”sa Kapcsolódva @@ -202,7 +202,7 @@ Az ismerős Ć©s az ƶsszes üzenet tƶrlĆ©sre kerül - ez a művelet nem vonható vissza! Az ismerősei tƶrlĆ©sre jelƶlhetnek üzeneteket; ƶn majd meg tudja nĆ©zni azokat. KapcsolódĆ”s egyszer hasznĆ”latos hivatkozĆ”ssal? - KapcsolódĆ”s egy hivatkozĆ”s / QR-kód Ć”ltal + KapcsolódĆ”s egy hivatkozĆ”son vagy QR-kódon keresztül KapcsolódĆ”si hiba (AUTH) Ismerős neve Kapcsolódik a kapcsolattartĆ”si cĆ­men keresztül? @@ -210,7 +210,7 @@ MĆ”solĆ”s FolytatĆ”s KapcsolódĆ”s egy hivatkozĆ”son keresztül? - LĆ©tező ismerős + Az ismerős mĆ”r lĆ©tezik Fő verzió: v%s Ismerős ellenőrizve KapcsolódĆ”s sajĆ”t magĆ”hoz? @@ -229,7 +229,7 @@ az ismerősnek nincs e2e titkosĆ­tĆ”sa Ismerős engedĆ©lyezi Ismerős elrejtve: - KapcsolódĆ”s szĆ”mĆ­tógĆ©phez + TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel Kƶrnyezeti ikon KapcsolódĆ”s egy hivatkozĆ”son keresztül Ismerősƶk @@ -271,7 +271,7 @@ kapcsolódĆ”s… CsevegĆ©si profil tƶrlĆ©se egyedi - hĆ­vĆ”s kapcsolódik… + kapcsolódĆ”si hĆ­vĆ”s… TĆ©ma szemĆ©lyre szabĆ”sa Jelenleg tĆ”mogatott legnagyobb fĆ”jl mĆ©ret: %1$s. FĆ”jl tƶrlĆ©se @@ -325,14 +325,14 @@ kapcsolódĆ”s… CsevegĆ©si adatbĆ”zis tƶrƶlve kapcsolódĆ”s (bejelentve) - Csoportos hivatkozĆ”s lĆ©trehozĆ”sa + CsoporthivatkozĆ”s lĆ©trehozĆ”sa CsevegĆ©si konzol FĆ”jlok tƶrlĆ©se minden csevegĆ©si profilból VĆ”rólista tƶrlĆ©se Ismerős tƶrlĆ©se LĆ©trehozva ekkor: %1$s cĆ­m megvĆ”ltoztatĆ”sa… - Csatlakoztatva a mobilhoz + TĆ”rsĆ­tva a mobil eszkƶzzel Jelenlegi jelmondat… FĆ”jl kivĆ”lasztĆ”s KĆ©p tƶrlĆ©se @@ -346,7 +346,7 @@ FĆ”jl ƶsszehasonlĆ­tĆ”s CsevegĆ©sek Üzenet tƶrlĆ©se? - Függő kapcsolatfelvĆ©teli kĆ©rĆ©sek tƶrlĆ©se? + Függőben lĆ©vő ismerőskĆ©relem tƶrlĆ©se? AdatbĆ”zis titkosĆ­tva! Üzenetek kiürĆ­tĆ©se? AdatbĆ”zis visszafejlesztĆ©se @@ -358,7 +358,7 @@ AdatbĆ”zis ID AdatbĆ”zis ID: %d AdatbĆ”zis azonosĆ­tók Ć©s Ć”tviteli izolĆ”ciós beĆ”llĆ­tĆ”sok. - Az adatbĆ”zis titkosĆ­tĆ”s jelmondata megvĆ”ltoztatĆ”sra Ć©s mentĆ©sre kerül a Keystore-ban. + Az adatbĆ”zis-titkosĆ­tĆ”si jelmondat megvĆ”ltoztatĆ”sra Ć©s mentĆ©sre kerül a Keystore-ban. Az adatbĆ”zis titkosĆ­tĆ”sra kerül Ć©s a jelmondat eltĆ”rolĆ”sra a beĆ”llĆ­tĆ”sokban. KiszolgĆ”ló tƶrlĆ©se A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r ki van kapcsolva. @@ -381,7 +381,7 @@ %d hónap CĆ­m tƶrlĆ©se? Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek letiltĆ”sa? - Az adatbĆ”zis jelmondata eltĆ©r a Keystore-ban lĆ©vőtől. + Az adatbĆ”zis jelmondat eltĆ©r a Keystore-ban lĆ©vőtől. Kƶzvetlen üzenetek E-mail LetiltĆ”s mindenki szĆ”mĆ”ra @@ -415,7 +415,7 @@ nap %d nap CsevegĆ©si archĆ­vum tƶrlĆ©se? - DuplikĆ”lt megjelenĆ­tĆ©si nĆ©v! + DuplikĆ”lt megjelenĆ­tett nĆ©v! LetiltĆ”s (felülĆ­rĆ”sok megtartĆ”sĆ”val) AdatbĆ”zis fejlesztĆ©se %d üzenet letiltva @@ -430,7 +430,7 @@ Minden fĆ”jl tƶrlĆ©se Az adatbĆ”zis titkosĆ­tĆ”sra kerül. AdatbĆ”zis jelmondat Ć©s -exportĆ”lĆ”s - Az adatbĆ”zis titkosĆ­tĆ”sra kerül Ć©s a jelmondat eltĆ”rolĆ”sra a Keystore-ban. + Az adatbĆ”zis titkosĆ­tĆ”sra kerül Ć©s a jelmondat a Keystore-ban lesz tĆ”rolva. Automatikus üzenet tƶrlĆ©s engedĆ©lyezĆ©se? TƶrlĆ©s az adatbĆ”zis verziója Ćŗjabb, mint az alkalmazĆ”sĆ©, visszafelĆ© Ć”tkƶltƶztetĆ©s nem lehetsĆ©ges a kƶvetkezőhƶz: %s @@ -450,7 +450,7 @@ KĆ©p szerkesztĆ©se ƉrtesĆ­tĆ©sek letiltĆ”sa Eszkƶzƶk - LĆ”tható helyi hĆ”lózaton + LĆ”tható a helyi hĆ”lózaton Ne engedĆ©lyezze ArchĆ­vum tƶrlĆ©se Az eltűnő üzenetek küldĆ©se le van tiltva ebben a csevegĆ©sben. @@ -483,8 +483,8 @@ Hiba a hĆ”lózat konfigurĆ”ciójĆ”nak frissĆ­tĆ©sekor TCP Ć©letben tartĆ”sa Kamera vĆ”ltĆ”s - Üdv! -\nCsatlakozzon hozzĆ”m SimpleX Chat-en keresztül: %s + Üdvƶzlƶm! +\nCsatlakozzon hozzĆ”m a SimpleX Chaten keresztül: %s A megjelenĆ­tett nĆ©v nem tartalmazhat szókƶzƶket. Csoport Üdvƶzlő üzenet megadĆ”sa… (opcionĆ”lis) @@ -499,7 +499,7 @@ A csevegĆ©sek betƶltĆ©se sikertelen A csoport mĆ”r lĆ©tezik! Francia kezelőfelület - Csoport hivatkozĆ”sok + CsoporthivatkozĆ”sok VĆ©gre, megvannak! šŸš€ Hiba a csevegĆ©s elindĆ­tĆ”sakor A csoport profilja a tagok eszkƶzein tĆ”rolódik, nem a kiszolgĆ”lókon. @@ -515,7 +515,7 @@ Kedvenc Csoport moderĆ”ció FĆ”jl - Csoport hivatkozĆ”s + CsoporthivatkozĆ”s titkosĆ­tĆ”s ĆŗjraegyeztetĆ©s szüksĆ©ges %s szĆ”mĆ”ra Hiba a profil vĆ”ltĆ”sakor! KĆ­sĆ©rleti funkciók @@ -536,7 +536,7 @@ Teljesen decentralizĆ”lt - kizĆ”rólag tagok szĆ”mĆ”ra lĆ”tható. FĆ”jl: %s HĆ­vĆ”s befejezĆ©se - Hiba a csoport hivatkozĆ”sĆ”nak tƶrlĆ©sekor + Hiba a csoporthivatkozĆ”s tƶrlĆ©sekor FĆ”jl elmentve Kapcsolat javĆ­tĆ”sa? FĆ”jlok Ć©s mĆ©diatartalom @@ -552,8 +552,8 @@ A csevegĆ©s betƶltĆ©se sikertelen KiszolgĆ”ló megadĆ”sa kĆ©zzel A fĆ”jl akkor Ć©rkezik meg, amikor a küldője elĆ©rhető lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! - Hiba a csoport hivatkozĆ”sĆ”nak lĆ©trehozĆ”sakor - A GalĆ©riĆ”ból + Hiba a csoporthivatkozĆ”s lĆ©trehozĆ”sakor + A galĆ©riĆ”ból EngedĆ©lyezĆ©s (csoport felülĆ­rĆ”sok megtartĆ”sĆ”val) Hiba az ismerős tƶrlĆ©sekor A csoport tagjai vĆ©glegesen tƶrƶlhetik az elküldƶtt üzeneteiket. (24 óra) @@ -562,12 +562,12 @@ A csoport tagjai küldhetnek eltűnő üzeneteket. Kapcsolat javĆ­tĆ”sa Hiba a profil lĆ©trehozĆ”sakor! - Hiba a tag(-ok) hozzĆ”adĆ”sakor + Hiba a tag(ok) hozzĆ”adĆ”sakor FĆ”jl A csoport tagjai küldhetnek fĆ”jlokat Ć©s mĆ©diatartalmakat. TƶrlĆ©s ennyi idő utĆ”n Hiba a beĆ”llĆ­tĆ”s megvĆ”ltoztatĆ”sakor - Hiba a csoport hivatkozĆ”s frissĆ­tĆ©sekor + Hiba a csoporthivatkozĆ”s frissĆ­tĆ©sekor a csoport tƶrƶlve csoportprofil frissĆ­tve Hiba a függőben lĆ©vő ismerős kapcsolatĆ”nak tƶrlĆ©sekor @@ -593,7 +593,7 @@ Hiba a cĆ­m megvĆ”ltoztatĆ”sĆ”nak megszakĆ­tĆ”sakor Hiba a fĆ”jl fogadĆ”sakor titkosĆ­tĆ”s rendben - Hiba az ismerős kĆ©relem tƶrlĆ©sekor + Hiba az ismerőskĆ©relem tƶrlĆ©sekor Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©seket engedĆ©lyezĆ©se csoportok szĆ”mĆ”ra? Ismerős Ć”ltali javĆ­tĆ”s nem tĆ”mogatott FĆ”jl nem talĆ”lható @@ -604,7 +604,7 @@ TovĆ”bb csƶkkentett akkumulĆ”tor hasznĆ”lat Hiba a csevegĆ©s megĆ”llĆ­tĆ”sakor titkosĆ­tĆ”s rendben %s szĆ”mĆ”ra - Csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza! + A csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza! TitkosĆ­tĆ”s javĆ­tĆ”sa az adatmentĆ©sek helyreĆ”llĆ­tĆ”sa utĆ”n. Hiba a csevegĆ©si adatbĆ”zis tƶrlĆ©sekor Teljes hivatkozĆ”s @@ -623,7 +623,7 @@ titkosĆ­tĆ”s ĆŗjraegyeztetĆ©s szüksĆ©ges Rejtett csevegĆ©si profilok FĆ”jlok Ć©s mĆ©dia - A kĆ©p mentve a GalĆ©riĆ”ba + A kĆ©p mentve a ā€žGalĆ©riĆ”baā€ Elrejt Azonnal A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva! @@ -648,7 +648,7 @@ Figyelmen kĆ­vül hagyĆ”s KĆ©p elküldve Rejtett - HĆ”zigazda + KiszolgĆ”ló Kezdeti szerepkƶr Ć©rvĆ©nytelen csevegĆ©s óra @@ -678,12 +678,12 @@ MegerősĆ­tĆ©s esetĆ©n az üzenetküldő kiszolgĆ”lók lĆ”tni fogjĆ”k az IP-cĆ­mĆ©t Ć©s a szolgĆ”ltatójĆ”t – azt, hogy mely kiszolgĆ”lókhoz kapcsolódik. A kĆ©p akkor Ć©rkezik meg, amikor a küldője befejezte annak feltƶltĆ©sĆ©t. QR kód beolvasĆ”sĆ”val.]]> - A kapott SimpleX Chat meghĆ­vó hivatkozĆ”sĆ”t megnyithatja bƶngĆ©szőjĆ©ben: + A kapott SimpleX Chat meghĆ­vó hivatkozĆ”sĆ”t megnyithatja a bƶngĆ©szőjĆ©ben: Ha az alkalmazĆ”s megnyitĆ”sakor megadja az ƶnmegsemmisĆ­tő jelkódot: MegtalĆ”lt szĆ”mĆ­tógĆ©p SzĆ”mĆ­tógĆ©pek A markdown hasznĆ”lata - Csevegő profil lĆ©trehozĆ”sa + CsevegĆ©si profil lĆ©trehozĆ”sa LevĆ©lszemĆ©t elleni vĆ©delem Mobilok levĆ”lasztĆ”sa Külƶnbƶző nevek, avatarok Ć©s Ć”tviteli izolĆ”ció. @@ -691,7 +691,7 @@ Szerepkƶr kivĆ”lasztĆ”sĆ”nak bővĆ­tĆ©se A kĆ©p akkor Ć©rkezik meg, amikor a küldője elĆ©rhető lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! meghĆ­va - ƉrvĆ©nytelen kapcsolati hivatkozĆ”s + ƉrvĆ©nytelen kapcsolattartĆ”si hivatkozĆ”s NĆ©mĆ­tĆ”s nincsenek rĆ©szletek Nem fogadott hĆ­vĆ”s @@ -699,13 +699,13 @@ Az üzenet tƶrlĆ©sre kerül - ez a művelet nem vonható vissza! Markdown segĆ­tsĆ©g Ćŗj üzenet - RĆ©gi adatbĆ”zis archĆ­vum + RĆ©gi adatbĆ”zis-archĆ­vum Haladó beĆ”llĆ­tĆ”sok Nincs kĆ©zbesĆ­tĆ©si informĆ”ció moderĆ”lt A tag eltĆ”volĆ­tĆ”sa a csoportból - ez a művelet nem vonható vissza! Győződjƶn meg róla, hogy az XFTP kiszolgĆ”ló cĆ­mei megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva. - Nem kerültek ismerősƶk kivĆ”lasztĆ”sra + Nincs kivĆ”lasztva ismerős Nincsenek fogadott, vagy küldƶtt fĆ”jlok MegnyitĆ”s mobil alkalmazĆ”sban, majd koppintson a KapcsolódĆ”s gombra az alkalmazĆ”sban.]]> Markdown az üzenetekben @@ -721,7 +721,7 @@ Helyi nĆ©v HĆ”lózat Ć©s kiszolgĆ”lók ƉrtesĆ­tĆ©s előnĆ©zet - TĆ”rsĆ­tsa ƶssze a mobil Ć©s az asztali alkalmazĆ”sokat! šŸ”— + TĆ”rsĆ­tsa ƶssze a mobil Ć©s asztali alkalmazĆ”sokat! šŸ”— kƶzvetett (%1$s) Hamarosan tovĆ”bbi fejlesztĆ©sek Ć©rkeznek! Az üzenetreakciók küldĆ©se le van tiltva ebben a csevegĆ©sben. @@ -749,12 +749,12 @@ Onion kiszolgĆ”lók nem lesznek hasznĆ”lva. perc Tudjon meg tƶbbet - Új kapcsolattartĆ”si kĆ©relem + Új ismerőskĆ©relem CsatlakozĆ”s a csoporthoz - Ɩsszekapcsolt szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok - meghĆ­va az ƶn csoport hivatkozĆ”sĆ”n keresztül + TĆ”rsĆ­tott szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok + meghĆ­va az ƶn csoporthivatkozĆ”sĆ”n keresztül elhagyta a csoportot - Ɩsszekapcsolt szĆ”mĆ­tógĆ©pek + TĆ”rsĆ­tott szĆ”mĆ­tógĆ©pek Nincs alkalmazĆ”s jelkód NĆ©mĆ­tĆ”s, ha inaktĆ­v! A meghĆ­vó lejĆ”rt! @@ -779,7 +779,7 @@ Olasz kezelőfelület Nincsenek hĆ”ttĆ©rhĆ­vĆ”sok Üzenetek - Ɩsszekapcsolt mobil eszkƶzƶk + TĆ”rsĆ­tott mobil eszkƶzƶk LehetővĆ© teszi, hogy egyetlen csevegőprofilon belül tƶbb anonim kapcsolat legyen, anĆ©lkül, hogy megosztott adatok lennĆ©nek kƶzƶttük. Az üzenet tƶrlĆ©sre lesz jelƶlve. A cĆ­mzett(ek) kĆ©pes(ek) lesz(nek) felfedni ezt az üzenetet. ElhagyĆ”s @@ -800,12 +800,12 @@ Tegye privĆ”ttĆ” a profiljĆ”t! ÜzenetkĆ©zbesĆ­tĆ©si hiba Tƶbb csevegőprofil - tƶrƶltnek jelƶlve + tƶrlĆ©sre jelƶlve NĆ©mĆ­tĆ”s - Egy mobil ƶsszekapcsolĆ”sa + Egy mobil eszkƶz tĆ”rsĆ­tĆ”sa ƉrtesĆ­tĆ©si szolgĆ”ltatĆ”s Csak a csoporttulajdonosok engedĆ©lyezhetik a hangüzenetek küldĆ©sĆ©t. - 2 rĆ©tegű vĆ©gponttól-vĆ©gpontig titkosĆ­tĆ”ssal küldƶtt üzeneteket.]]> + 2 rĆ©tegű vĆ©gpontok kƶzƶtti titkosĆ­tĆ”ssal küldƶtt üzeneteket.]]> ƉrvĆ©nytelen Ć”tkƶltƶztetĆ©si visszaigazolĆ”s Csak a csoporttulajdonosok módosĆ­thatjĆ”k a csoportbeĆ”llĆ­tĆ”sokat. Nincsenek előzmĆ©nyek @@ -823,26 +823,26 @@ ajĆ”nlott %s Csoport elhagyĆ”sa Minden %s Ć”ltal Ć­rt üzenet megjelenik! - Ha a SimpleX Chat-nek nincs felhasznĆ”lói azonosĆ­tója, hogyan lehet mĆ©gis üzeneteket küldeni?]]> + Ha a SimpleX Chatnek nincs felhasznĆ”lói azonosĆ­tója, hogyan lehet mĆ©gis üzeneteket küldeni?]]> Ez akkor fordulhat elő, ha: \n1. Az üzenetek 2 nap utĆ”n, vagy a kiszolgĆ”lón 30 nap utĆ”n lejĆ”rtak. \n2. Az üzenet visszafejtĆ©se sikertelen volt, mert ƶn, vagy az ismerőse rĆ©gebbi adatbĆ”zis biztonsĆ”gi mentĆ©st hasznĆ”lt. \n3. A kapcsolat sĆ©rült. megfigyelő - inkognitó a csoportos hivatkozĆ”son keresztül + inkognitó a csoporthivatkozĆ”son keresztül Onion kiszolgĆ”lók hasznĆ”lata, ha azok rendelkezĆ©sre Ć”llnak. Ismerősƶk meghĆ­vĆ”sa Menük Ć©s figyelmeztetĆ©sek Tagok meghĆ­vĆ”sa csatlakozĆ”s mint %s - Nincs kivĆ”lasztott csevegĆ©s + Nincs kivĆ”lasztva csevegĆ©s Csak helyi profiladatok - inkognitó az egyszer hasznĆ”latos hivatkozĆ”son keresztül + inkognitó egy egyszer hasznĆ”latos hivatkozĆ”son keresztül ModerĆ”lva lett ekkor: %s Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s ƉrvĆ©nytelen nĆ©v! - BeszĆ©lgessünk a SimpleX Chat-ben - ModerĆ”lva lett ekkor: + BeszĆ©lgessünk a SimpleX Chatben + ModerĆ”lva ekkor: Ɖlő üzenetek HitelesĆ­tĆ©s ÜzenetkĆ©zbesĆ­tĆ©si bizonylatok! @@ -864,7 +864,7 @@ tag PrivĆ”t kapcsolat lĆ©trehozĆ”sa moderĆ”lva lett %s Ć”ltal - Győződjƶn meg arról, hogy a fĆ”jl helyes YAML-szintaxist tartalmaz. ExportĆ”lja a tĆ©mĆ”t, hogy legyen egy pĆ©lda a tĆ©ma fĆ”jl szerkezetĆ©re. + Győződjƶn meg arról, hogy a fĆ”jl helyes YAML-szintaxist tartalmaz. ExportĆ”lja a tĆ©mĆ”t, hogy legyen egy pĆ©lda a tĆ©mafĆ”jl szerkezetĆ©re. dőlt ƉrvĆ©nytelen fĆ”jl elĆ©rĆ©si Ćŗtvonal Csatlakozik a csoporthoz? @@ -904,13 +904,13 @@ ElőnĆ©zet megjelenĆ­tĆ©se vĆ”rakozĆ”s a visszaigazolĆ”sra… FĆ”jl megĆ”llĆ­tĆ”sa - csoport hivatkozĆ”son keresztül + a csoporthivatkozĆ”son keresztül PING időkƶze Eltűnő üzenet küldĆ©se ƖnmegsemmisĆ­tĆ©si jelkód MentĆ©s Ć©s csoportprofil frissĆ­tĆ©se AdatvĆ©delem - Az ƶn SimpleX cĆ­me + Profil SimpleX cĆ­me Jelentse a fejlesztőknek. Ɩn dƶnti el, hogy kivel beszĆ©lget. Az eltűnő üzenetek küldĆ©se le van tiltva. @@ -946,7 +946,7 @@ (beolvasĆ”s, vagy beillesztĆ©s a vĆ”gólapról) Videóra vĆ”rakozĆ”s VĆ”lasz - Ez az egyszer hasznĆ”latos hivatkozĆ”sa! + Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! SimpleX Chat hĆ­vĆ”sok Új inkognĆ­tó profil hasznĆ”lata FrissĆ­tse az alkalmazĆ”st, Ć©s lĆ©pjen kapcsolatba a fejlesztőkkel. @@ -964,10 +964,10 @@ BiztonsĆ”gi kód beolvasĆ”sa az ismerősĆ©nek alkalmazĆ”sĆ”ból. LĆ©pjen kapcsolatba a csoport adminnal. Videó bekapcsolva - Profil neve: + ProfilnĆ©v: BeillesztĆ©s Kƶszƶnjük, hogy telepĆ­tette a SimpleX Chatet! - CsillagozĆ”s a GitHub-on + CsillagozĆ”s a GitHubon EltĆ”volĆ­tĆ”s KeresĆ©s TitkosĆ­tĆ”s ĆŗjraegyeztetĆ©se? @@ -1001,7 +1001,7 @@ VĆ©letlen MegosztĆ”s az ismerősƶkkel ƶn - Nincsenek csevegĆ©si üzenetek + Nincsenek csevegĆ©sei KüldĆ©s %s mĆ”sodperc %s: %s @@ -1020,7 +1020,7 @@ Profiljelszó TĆ©ma Jelmondat eltĆ”volĆ­tĆ”sa a beĆ”llĆ­tĆ”sokból? - SimpleX csoport hivatkozĆ”s + SimpleX csoporthivatkozĆ”s KĆ©pre vĆ”rakozĆ”s ƖnmegsemmisĆ­tĆ©s vĆ”rakozĆ”s a vĆ”laszra… @@ -1041,11 +1041,11 @@ VĆ©letlenszerű jelmondat hasznĆ”lata egyenrangĆŗ CSEVEGƉSI SZOLGƁLTATƁS INDƍTƁSA - Fogadott hivatkozĆ”s beillesztĆ©se + Kapott hivatkozĆ”s beillesztĆ©se KiszolgĆ”lók mentĆ©se? A SimpleX Chat biztonsĆ”ga a Trail of Bits Ć”ltal lett auditĆ”lva. frissĆ­tette a csoport profiljĆ”t - TƁMOGASSA A SIMPLEX CHATET + SIMPLEX CHAT TƁMOGATƁSA SimpleX Chat szolgĆ”ltatĆ”s Nem lehet üzeneteket küldeni! %s ellenőrzƶtt @@ -1122,13 +1122,13 @@ SMP kiszolgĆ”lók Az üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva AdatbĆ”zis mappa megnyitĆ”sa - egyszer hasznĆ”latos hivatkozĆ”son keresztül + egy egyszer hasznĆ”latos hivatkozĆ”son keresztül CsoportbeĆ”llĆ­tĆ”sok megadĆ”sa ezen keresztül: %1$s igen Hangüzenet - HasznĆ”lat szĆ”mĆ­tógĆ©pről - ƖN + TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel + PROFIL port %d KapcsolódĆ”s hivatkozĆ”son keresztül CĆ­m megosztĆ”sa @@ -1144,7 +1144,7 @@ eltĆ”volĆ­tottak SimpleX cĆ­m MegjelenĆ­tĆ©s: - fogadott vĆ”lasz… + vĆ”lasz fogadĆ”sa… AdatbĆ”zismentĆ©s visszaĆ”llĆ­tĆ”sa? Üzenetek fogadĆ”sa… %s Ć©s %s kapcsolódott @@ -1169,7 +1169,7 @@ Az üzenetreakciók küldĆ©se le van tiltva. Rendszer olvasatlan - Függő + Függőben Üdvƶzƶljük %1$s! Jelmondat eltĆ”volĆ­tĆ”sa a Keystrore-ból? FeloldĆ”s @@ -1185,10 +1185,10 @@ KĆ©p/videó megoszĆ”sa… ƶn: %1$s BeĆ”llĆ­tĆ”sok - SzĆ­nek alaphelyzetbe Ć”llĆ­tĆ”sa + SzĆ­nek visszaĆ”llĆ­tĆ”sa MentĆ©s VĆ”ltĆ”s - A kapott hivatkozĆ”s beillesztĆ©se az ismerősƶkhƶz tƶrtĆ©nő kapcsolódĆ”shoz… + A kapott hivatkozĆ”s beillesztĆ©se az ismerőshƶz való kapcsolódĆ”shoz… BeolvasĆ”s Port megnyitĆ”sa a tűzfalon indĆ­tĆ”s… @@ -1254,7 +1254,7 @@ Fogadva ekkor: %s SimpleX zĆ”r MentĆ©s Ć©s csoporttagok Ć©rtesĆ­tĆ©se - Alaphelyzetbe Ć”llĆ­tĆ”s + VisszaĆ”llĆ­tĆ”s Csak az ismerőse tud üzenetreakciókat küldeni. Hangüzenetek elhagyta a csoportot @@ -1287,9 +1287,9 @@ Videók Ć©s fĆ”jlok 1Gb mĆ©retig TCP kapcsolat időtĆŗllĆ©pĆ©s A(z) %1$s nevű profiljĆ”nak SimpleX cĆ­me megosztĆ”sra fog kerülni. - Ɩn mĆ”r kapcsolódott ehhez: %1$s. + Ɩn mĆ”r kapcsolódva van ehhez: %1$s. Jelenlegi csevegĆ©si adatbĆ”zis TƖRLƉSRE Ć©s FELCSERƉLƉSRE kerül az importĆ”lt Ć”ltal! -\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegĆ©si üzenetei Ć©s fĆ”jljai vĆ©glegesen tƶrƶlve lesznek! +\nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegĆ©si üzenetei Ć©s fĆ”jljai vĆ©glegesen tƶrƶlve lesznek.
Ɩtletek Ć©s javaslatok FigyelmeztetĆ©s: nĆ©hĆ”ny adat elveszhet! Koppintson az Ćŗj csevegĆ©s indĆ­tĆ”sĆ”hoz @@ -1303,23 +1303,23 @@ cĆ­m megvĆ”ltoztatva nĆ”la: %s fĆ”jlok fogadĆ”sa egyelőre mĆ©g nem tĆ”mogatott Csoportprofil mentĆ©se - Alaphelyzetbe Ć”llĆ­tĆ”s - Hacsak az ismerőse nem tƶrƶlte a kapcsolatot, vagy ez a hivatkozĆ”s mĆ”r hasznĆ”latban volt, lehet hogy ez egy hiba – jelentse a problĆ©mĆ”t. -\nA kapcsolódĆ”shoz kĆ©rje meg ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsolati hivatkozĆ”st, Ć©s ellenőrizze, hogy a hĆ”lózati kapcsolat stabil-e. + VisszaĆ”llĆ­tĆ”s alaphelyzetbe + Hacsak az ismerőse nem tƶrƶlte a kapcsolatot, vagy ez a hivatkozĆ”s mĆ”r hasznĆ”latban volt egyszer, lehet hogy ez egy hiba – jelentse a problĆ©mĆ”t. +\nA kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapcsolattartĆ”si hivatkozĆ”st, Ć©s ellenőrizze, hogy a hĆ”lózati kapcsolat stabil-e. videóhĆ­vĆ”s (nem e2e titkosĆ­tott) AlkalmazĆ”s Ćŗj kapcsolatokhoz Az Ćŗj üzenetek rendszeresen letƶltĆ©sre kerülnek az alkalmazĆ”s Ć”ltal – naponta nĆ©hĆ”ny szĆ”zalĆ©kot hasznĆ”l az akkumulĆ”torból. Az alkalmazĆ”s nem hasznĆ”l push Ć©rtesĆ­tĆ©seket – az eszkƶzről szĆ”rmazó adatok nem kerülnek elküldĆ©sre a kiszolgĆ”lóknak. SzĆ”mĆ­tógĆ©p cĆ­mĆ©nek beillesztĆ©se kapcsolattartĆ”si cĆ­m-hivatkozĆ”son keresztül - SimpleX hĆ”ttĆ©rszolgĆ”ltatĆ”st hasznĆ”lja - az akkumulĆ”tor nĆ©hĆ”ny szĆ”zalĆ©kĆ”t hasznĆ”lja naponta.]]> + SimpleX hĆ”ttĆ©rszolgĆ”ltatĆ”st hasznĆ”lja - az akkumulĆ”tornak csak nĆ©hĆ”ny szĆ”zalĆ©kĆ”t hasznĆ”lja naponta.]]> Az ismerősĆ©nek online kell lennie ahhoz, hogy a kapcsolat lĆ©trejƶjjƶn. -\nVisszavonhatja ezt a kapcsolatfelvĆ©telt Ć©s tƶrƶlheti az ismerőst (ezt kĆ©sőbb ismĆ©t megpróbĆ”lhatja egy Ćŗj hivatkozĆ”ssal). - A jelszó nem talĆ”lható a Keystore-ban, ezĆ©rt kĆ©zzel szüksĆ©ges megadni. Ez akkor tƶrtĆ©nhetett meg, ha visszaĆ”llĆ­totta az alkalmazĆ”s adatait egy biztonsĆ”gi mentĆ©si eszkƶzzel. Ha nem Ć­gy tƶrtĆ©nt, akkor lĆ©pjen kapcsolatba a fejlesztőkkel. +\nVisszavonhatja ezt az ismerőskĆ©relmet Ć©s tƶrƶlheti az ismerőst (ezt kĆ©sőbb ismĆ©t megpróbĆ”lhatja egy Ćŗj hivatkozĆ”ssal). + A jelszó nem talĆ”lható a Keystore-ban, ezĆ©rt kĆ©zzel szüksĆ©ges megadni. Ez akkor tƶrtĆ©nhetett meg, ha visszaĆ”llĆ­totta az alkalmazĆ”s adatait egy biztonsĆ”gimentĆ©si eszkƶzzel. Ha nem Ć­gy tƶrtĆ©nt, akkor lĆ©pjen kapcsolatba a fejlesztőkkel. Az ismerősei tovĆ”bbra is kapcsolódva maradnak. A kiszolgĆ”lónak engedĆ©lyre van szüksĆ©ge a vĆ”rólistĆ”k lĆ©trehozĆ”sĆ”hoz, ellenőrizze jelszavĆ”t Az adatbĆ”zis nem műkƶdik megfelelően. Koppintson tovĆ”bbi informĆ”cióért A fĆ”jl küldĆ©se leĆ”llt. - KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. + KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. Nem lehetett ellenőrizni; próbĆ”lja meg Ćŗjra. Az üzenet minden tag szĆ”mĆ”ra moderĆ”ltkĆ©nt lesz megjelƶlve. ƉrtesĆ­tĆ©sek fogadĆ”sĆ”hoz adja meg az adatbĆ”zis jelmondatĆ”t @@ -1327,8 +1327,8 @@ Az alkalmazĆ”s indĆ­tĆ”sakor, vagy 30 mĆ”sodpercnyi hĆ”ttĆ©rben tƶltƶtt idő utĆ”n az alkalmazĆ”shoz visszatĆ©rve hitelesĆ­tĆ©s szüksĆ©ges. Az üzenet minden tag szĆ”mĆ”ra tƶrlĆ©sre kerül. A videó nem dekódolható. PróbĆ”lja ki egy mĆ”sik videóval, vagy lĆ©pjen kapcsolatba a fejlesztőkkel. - Ez a szƶveg a beĆ”llĆ­tĆ”sok kƶzƶtt Ć©rhető el - Profilja elküldĆ©sre kerül ismerőse szĆ”mĆ”ra, akitől ezt a hivatkozĆ”st kapta. + Ez a szƶveg a ā€žBeĆ”llĆ­tĆ”sokbanā€ Ć©rhető el + Profilja elküldĆ©sre kerül az ismerőse szĆ”mĆ”ra, akitől ezt a hivatkozĆ”st kapta. Az alkalmazĆ”s 1 perc utĆ”n bezĆ”rható a hĆ”ttĆ©rben. meghĆ­vĆ”st kapott a csoportba engedĆ©lyezze a SimpleX hĆ”ttĆ©rben tƶrtĆ©nő futĆ”sĆ”t a kƶvetkező pĆ”rbeszĆ©dpanelen. Ellenkező esetben az Ć©rtesĆ­tĆ©sek letiltĆ”sra kerülnek.]]> @@ -1341,22 +1341,22 @@ HĆ”lózati kapcsolat ellenőrzĆ©se a kƶvetkezővel: %1$s, Ć©s próbĆ”lja Ćŗjra. A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be. Az alkalmazĆ”s ƶsszeomlott - Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg ismerősĆ©t, hogy küldjƶn egy mĆ”sikat. + Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg az ismerősĆ©t, hogy küldjƶn egy mĆ”sikat. A kĆ©p nem dekódolható. PróbĆ”lja meg egy mĆ”sik kĆ©ppel, vagy lĆ©pjen kapcsolatba a fejlesztőkkel. ƉrvĆ©nytelen fĆ”jl elĆ©rĆ©si Ćŗtvonalat osztott meg. Jelentse a problĆ©mĆ”t az alkalmazĆ”s fejlesztőinek. MĆ”r van egy csevegĆ©si profil ugyanezzel a megjelenĆ­tett nĆ©vvel. VĆ”lasszon egy mĆ”sik nevet. - KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l (hiba: %1$s). + KapcsolódĆ”si kĆ­sĆ©rlet ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l (hiba: %1$s). A fĆ”jl fogadĆ”sa leĆ”llt. Ne felejtse el, vagy tĆ”rolja biztonsĆ”gosan – az elveszett jelszót nem lehet visszaĆ”llĆ­tani! A videó akkor Ć©rkezik meg, amikor a küldője befejezte annak feltƶltĆ©sĆ©t. egyszer hasznĆ”latos hivatkozĆ”st osztott meg inkognitóban - MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerőstől Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. + MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. KĆ©sőbb engedĆ©lyezheti a BeĆ”llĆ­tĆ”sokban Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! külƶnbƶző Ć”tkƶltƶztetĆ©s az alkalmazĆ”sban/adatbĆ”zisban: %s / %s %1$s.]]> Profil felfedĆ©se - Ez a hivatkozĆ”s nem Ć©rvĆ©nyes kapcsolati hivatkozĆ”s! + Ez nem egy Ć©rvĆ©nyes kapcsolattartĆ”si hivatkozĆ”s! A vĆ©gpontok kƶzƶtti titkosĆ­tĆ”s ellenőrzĆ©sĆ©hez hasonlĆ­tsa ƶssze (vagy olvassa be a QR-kódot) az ismerőse eszkƶzĆ©n lĆ©vő kóddal. A csevegĆ©si adatbĆ”zis legfrissebb verziójĆ”t CSAK egy eszkƶzƶn kell hasznĆ”lnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősĆ©től. Ez a beĆ”llĆ­tĆ”s a jelenlegi csevegĆ©si profilban lĆ©vő üzenetekre Ć©rvĆ©nyes @@ -1367,7 +1367,7 @@ Ismerőse a jelenleg megengedett maximĆ”lis mĆ©retű (%1$s) fĆ”jlnĆ”l nagyobbat küldƶtt. Az ismerősei Ć©s az üzenetek (kĆ©zbesĆ­tĆ©s utĆ”n) nem kerülnek tĆ”rolĆ”sra a SimpleX kiszolgĆ”lókon. Üzenetek formĆ”zĆ”sa a szƶvegbe szĆŗrt speciĆ”lis karakterekkel: - MegnyitĆ”s alkalmazĆ”sban gombra.]]> + MegnyitĆ”s az alkalmazĆ”sban gombra.]]> CsevegĆ©si profilja elküldĆ©sre kerül \naz ismerőse szĆ”mĆ”ra Egy olyan ismerősĆ©t próbĆ”lja meghĆ­vni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a sajĆ”t fő profilja van hasznĆ”latban @@ -1379,10 +1379,10 @@ A hangüzenetek küldĆ©se le van tiltva ebben a csoportban. AlkalmazĆ”s akkumulĆ”tor hasznĆ”lata / KorlĆ”tlan módot az alkalmazĆ”s beĆ”llĆ­tĆ”saiban.]]> BiztonsĆ”gos kvantumrezisztens protokollon keresztül. - - hangüzenetek 5 percig. -\n- egyedi eltűnĆ©si időhatĆ”r. -\n- előzmĆ©ny szerkesztĆ©se. - HasznĆ”lat szĆ”mĆ­tógĆ©pről menüt a mobil alkalmazĆ”sban Ć©s olvassa be a QR-kódot.]]> + - 5 perc hosszĆŗsĆ”gĆŗ hangüzenetek. +\n- egyedi üzenet-eltűnĆ©si időkorlĆ”t. +\n- előzmĆ©nyek szerkesztĆ©se. + TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel menüt a mobil alkalmazĆ”sban Ć©s olvassa be a QR-kódot.]]> %s ekkor: %s Akkor lesz kapcsolódva, amikor az ismerősĆ©nek az eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! KĆ©retlen üzenetek elrejtĆ©se. @@ -1411,7 +1411,7 @@ cĆ­m megvĆ”ltoztatva Ismerősei engedĆ©lyezhetik a teljes üzenet tƶrlĆ©st. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazĆ”s elindul - nem az eszkƶzƶn kerül tĆ”rolĆ”sra. - Ha engedĆ©lyezni szeretnĆ©, hogy egy mobilalkalmazĆ”s csatlakozzon a szĆ”mĆ­tógĆ©phez, akkor nyissa meg ezt a portot a tűzfalĆ”ban, ha engedĆ©lyezte azt + Ha engedĆ©lyezni szeretnĆ© a mobilalkalmazĆ”s tĆ”rsĆ­tĆ”sĆ”t a szĆ”mĆ­tógĆ©phez, akkor nyissa meg ezt a portot a tűzfalĆ”ban, ha engedĆ©lyezte azt Profilja, ismerősei Ć©s az elküldƶtt üzenetei az eszkƶzƶn kerülnek tĆ”rolĆ”sra. AlkalmazĆ”s akkumulĆ”tor hasznĆ”lata / KorlĆ”tlan módot az alkalmazĆ”s beĆ”llĆ­tĆ”saiban.]]> Ez a karakterlĆ”nc nem egy meghĆ­vó hivatkozĆ”s! @@ -1419,7 +1419,7 @@ A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”latos hivatkozĆ”son keresztül! Nem veszĆ­ti el az ismerőseit, ha kĆ©sőbb tƶrli a cĆ­mĆ©t. A beĆ”llĆ­tĆ”sok frissĆ­tĆ©se a kiszolgĆ”lókhoz való Ćŗjra kapcsolódĆ”ssal jĆ”r. - kapcsolatba akar lĆ©pni veled! + kapcsolatba akar lĆ©pni ƶnnel! sajĆ”t szerepkƶre erre vĆ”ltozott: %s A csevegĆ©si szolgĆ”ltatĆ”s elindĆ­tható a BeĆ”llĆ­tĆ”sok / AdatbĆ”zis menüben vagy az alkalmazĆ”s ĆŗjraindĆ­tĆ”sĆ”val. Kód ellenőrzĆ©se a mobilon @@ -1431,18 +1431,18 @@ InkognĆ­tó mód kapcsolódĆ”skor. Megoszthat egy hivatkozĆ”st vagy QR-kódot - Ć­gy bĆ”rki csatlakozhat a csoporthoz. Ha a csoport kĆ©sőbb tƶrlĆ©sre kerül, akkor nem fogja elveszĆ­teni annak tagjait. Csatlakozott ehhez a csoporthoz - %1$s csoporthoz!]]> + %1$s csoporthoz!]]> A hangüzenetek küldĆ©se le van tiltva ebben a csevegĆ©sben. Ɩn irĆ”nyĆ­tja csevegĆ©sĆ©t! Kód ellenőrzĆ©se a szĆ”mĆ­tógĆ©pen Az időzóna vĆ©delme Ć©rdekĆ©ben a kĆ©p-/hangfĆ”jlok UTC-t hasznĆ”lnak. - A kapcsolódĆ”si kĆ©relem elküldĆ©sre kerül ezen csoporttag szĆ”mĆ”ra + A kapcsolódĆ”si kĆ©relem elküldĆ©sre kerül ezen csoporttag szĆ”mĆ”ra. Inkognitóprofil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott. MĆ”r kĆ©rt egy kapcsolódĆ”si kĆ©relmet ezen a cĆ­men keresztül! Megoszthatja ezt a SimpleX cĆ­met az ismerőseivel, hogy kapcsolatba lĆ©phessenek vele: %s. Amikor az emberek kapcsolódĆ”st kĆ©relmeznek, ƶn elfogadhatja vagy elutasĆ­thatja azokat. MegjelenĆ­tendő üzenet beĆ”llĆ­tĆ”sa az Ćŗj tagok szĆ”mĆ”ra! - Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten! + Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! A kĆ©zbesĆ­tĆ©si jelentĆ©s küldĆ©se minden ismerős szĆ”mĆ”ra engedĆ©lyezĆ©sre kerül. Protokoll időkorlĆ”t KB-onkĆ©nt Az adatbĆ”zis jelmondatĆ”nak megvĆ”ltoztatĆ”sĆ”ra tett kĆ­sĆ©rlet nem fejeződƶtt be. @@ -1453,7 +1453,7 @@ Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalommal együtt tƶrlĆ©sre kerülnek. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak. KĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %d ismerősnĆ©l KüldĆ©s ezen keresztül: - Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten! + Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezĆ©sre kerül az ƶsszes lĆ”tható csevegĆ©si profilban lĆ©vő minden ismerős szĆ”mĆ”ra. Bluetooth tĆ”mogatĆ”s Ć©s tovĆ”bbi fejlesztĆ©sek. Ez a funkció mĆ©g nem tĆ”mogatott. PróbĆ”lja meg a kƶvetkező kiadĆ”sban. @@ -1467,13 +1467,13 @@ Jelmondat beĆ”llĆ­tĆ”sa az exportĆ”lĆ”shoz KĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva a(z) %d csoportban NĆ©hĆ”ny nem vĆ©gzetes hiba tƶrtĆ©nt az importĆ”lĆ”s kƶzben: - Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten! + Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! Az Ć”tjĆ”tszó kiszolgĆ”ló csak szüksĆ©g esetĆ©n kerül hasznĆ”latra. Egy mĆ”sik fĆ©l megfigyelheti az IP-cĆ­met. RendszerhitelesĆ­tĆ©s helyetti beĆ”llĆ­tĆ”s. A fogadó cĆ­m egy mĆ”sik kiszolgĆ”lóra vĆ”ltozik. A cĆ­mvĆ”ltoztatĆ”s a feladó online Ć”llapotba kerülĆ©se utĆ”n fejeződik be. A csevegĆ©s megĆ”llĆ­tĆ”sa a csevegő adatbĆ”zis exportĆ”lĆ”sĆ”hoz, importĆ”lĆ”sĆ”hoz, vagy tƶrlĆ©sĆ©hez. A csevegĆ©s megĆ”llĆ­tĆ”sa alatt nem tud üzeneteket fogadni Ć©s küldeni. Jelmondat mentĆ©se a Keystore-ba - Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten! + Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! Jelmondat mentĆ©se a beĆ”llĆ­tĆ”sokban Ennek a csoportnak tƶbb mint %1$d tagja van, a kĆ©zbesĆ­tĆ©si jelentĆ©sek nem kerülnek elküldĆ©sre. A mĆ”sodik jelƶlĆ©s, amit kihagytunk! āœ… @@ -1484,18 +1484,18 @@ KĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak a(z) %d csoportban A szerepkƶr meg fog vĆ”ltozni erre: ā€ž%sā€. A csoportban mindenki Ć©rtesĆ­tve lesz. Profil Ć©s kiszolgĆ”lókapcsolatok - Egy üzenetküldő- Ć©s alkalmazĆ”splatform, amely vĆ©di az ƶn adatait Ć©s biztonsĆ”gĆ”t. + Egy üzenetküldő- Ć©s alkalmazĆ”splatform, amely vĆ©di az adatait Ć©s biztonsĆ”gĆ”t. A profil aktivĆ”lĆ”sĆ”hoz koppintson az ikonra. KĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %d ismerősnĆ©l Munkamenet kód - Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblaten! + Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! Kis csoportok (max. 20 tag) - Az ƶn Ć”ltal elfogadott kapcsolat vissza lesz vonva! + Az ƶn Ć”ltal elfogadott kĆ©relem vissza lesz vonva! Ɖlő üzenet küldĆ©se - a cĆ­mzett(ek) szĆ”mĆ”ra frissül, ahogy beĆ­rja A KƉZBESƍTƉSI JELENTƉSEKET A KƖVETKEZŐ CƍMRE KELL KÜLDENI A kƶvetkező üzenet azonosĆ­tója hibĆ”s (kisebb vagy egyenlő az előzővel). \nEz valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. - Az eszkƶz neve megosztĆ”sra kerül a csatlakoztatott mobil klienssel. + Az eszkƶz neve megosztĆ”sra kerül a tĆ”rsĆ­tott mobil klienssel. A cĆ­mzettek a beĆ­rĆ”s kƶzben lĆ”tjĆ”k a frissĆ­tĆ©seket. TĆ”rolja el biztonsĆ”gosan jelmondatĆ”t, mert ha elveszĆ­ti azt, NEM tudja megvĆ”ltoztatni. A jelmondat a beĆ”llĆ­tĆ”sok kƶzƶtt egyszerű szƶvegkĆ©nt kerül tĆ”rolĆ”sra, miutĆ”n megvĆ”ltoztatta vagy ĆŗjraindĆ­totta az alkalmazĆ”st. @@ -1510,15 +1510,15 @@ HasznĆ”lati Ćŗtmutatóban olvasható.]]> A jelmondat a beĆ”llĆ­tĆ”sokban egyszerű szƶvegkĆ©nt van tĆ”rolva. Konzol megjelenĆ­tĆ©se Ćŗj ablakban - Az előző üzenet ellenőrzőösszege külƶnbƶzik. + Az előző üzenet hasĆ­tó Ć©rtĆ©ke külƶnbƶzik. Ezek a beĆ”llĆ­tĆ”sok a jelenlegi profiljĆ”ra vonatkoznak - VĆ”rjon, amĆ­g a fĆ”jl betƶltődik a csatolt mobilról + VĆ”rjon, amĆ­g a fĆ”jl betƶltődik a tĆ”rsĆ­tott mobilról GitHub tĆ”rolónkban.]]> - hiba a tartalom megjelenĆ­tĆ©se kƶzben + hiba a tartalom megjelenĆ­tĆ©sekor hiba az üzenet megjelenĆ­tĆ©sekor - LĆ”thatóvĆ” teheti SimpleX-beli ismerősei szĆ”mĆ”ra a BeĆ”llĆ­tĆ”sokban. + LĆ”thatóvĆ” teheti a SimpleXbeli ismerősei szĆ”mĆ”ra a ā€žBeĆ”llĆ­tĆ”sokbanā€. Legfeljebb az utolsó 100 üzenet kerül elküldĆ©sre az Ćŗj tagok szĆ”mĆ”ra. - A beolvasott kód nem egy SimpleX hivatkozĆ”s QR-kód. + A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozĆ”s. A beillesztett szƶveg nem egy SimpleX hivatkozĆ”s. A meghĆ­vó hivatkozĆ”sĆ”t Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l. CsevegĆ©s indĆ­tĆ”sa? @@ -1567,9 +1567,9 @@ \n%s %s mobil eszkƶz verziója nem tĆ”mogatott. Győződjƶn meg arról, hogy mindkĆ©t eszkƶzƶn ugyanazt a verziót hasznĆ”lja]]> %s mobil eszkƶzzel]]> - ƉrvĆ©nytelen megjelenĆ­tendő felhaszĆ”lónĆ©v! - Ez a megjelenĆ­tett felhasznĆ”lónĆ©v Ć©rvĆ©nytelen. VĆ”lasszon egy mĆ”sik nevet. - %s mobil eszkƶzzel, a(z) %s problĆ©ma miatt]]> + ƉrvĆ©nytelen megjelenĆ­tendő nĆ©v! + Ez a megjelenĆ­tett nĆ©v Ć©rvĆ©nytelen. VĆ”lasszon egy mĆ”sik nevet. + %s mobil eszkƶzzel, a(z) %s problĆ©ma miatt]]> %s problĆ©ma miatt megszakadt a kapcsolat %s mobil eszkƶz nem talĆ”lható]]> %s mobil eszkƶzzel rossz Ć”llapotban van]]> @@ -1581,7 +1581,7 @@ Fejlesztői beĆ”llĆ­tĆ”sok A funkció vĆ©grehajtĆ”sa tĆŗl sokĆ”ig tart: %1$d mĆ”sodperc: %2$s %s mobil eszkƶz elfoglalt]]> - MĆ”r nem tag - %1$s + %1$s (mĆ”r nem tag) ismeretlen stĆ”tusz %1$s megvĆ”ltoztatta a nevĆ©t erre: %2$s tƶrƶlt kapcsolattartĆ”si cĆ­m @@ -1612,13 +1612,13 @@ letiltva letiltva az admin Ć”ltal Letiltva az admin Ć”ltal - letiltotta %s-t + letiltotta őt: %s LetiltĆ”s mindenki szĆ”mĆ”ra Mindenki szĆ”mĆ”ra letiltja ezt a tagot? %d üzenet letiltva az admin Ć”ltal LetiltĆ”s feloldĆ”sa mindenki szĆ”mĆ”ra Mindenki szĆ”mĆ”ra feloldja a tag letiltĆ”sĆ”t? - ƶn letiltotta %s-t + ƶn letiltotta őt: %s Hiba a tag mindenki szĆ”mĆ”ra való letiltĆ”sa kƶzben Az üzenet tĆŗl nagy Az üdvƶzlő üzenet tĆŗl hosszĆŗ @@ -1642,7 +1642,7 @@ ƁtkƶltƶztetĆ©s visszavonĆ”sa A csevegĆ©s Ć”tkƶltƶztetve! Ellenőrizze az internetkapcsolatot, Ć©s próbĆ”lja Ćŗjra - ArchĆ­v hivatkozĆ”s lĆ©trehozĆ”sa + ArchĆ­vum hivatkozĆ”s lĆ©trehozĆ”sa AdatbĆ”zis tƶrlĆ©se erről az eszkƶzről Sikertelen letƶltĆ©s ArchĆ­vum letƶltĆ©se @@ -1671,7 +1671,7 @@ Ellenőrizze, hogy a hĆ”lózati beĆ”llĆ­tĆ”sok megfelelőek-e ehhez az eszkƶzhƶz. A folytatĆ”shoz a csevegĆ©st meg kell szakĆ­tani. CsevegĆ©s megĆ”llĆ­tĆ”sa folyamatban - Vagy a fĆ”jl hivĆ­tkozĆ”sĆ”nak biztonsĆ”gos megosztĆ”sa + Vagy ossza meg biztonsĆ”gosan ezt a fĆ”jlhivatkozĆ”st CsevegĆ©s indĆ­tĆ”sa Nem szabad ugyanazt az adatbĆ”zist hasznĆ”lni egyszerre kĆ©t eszkƶzƶn.]]> ErősĆ­tse meg, hogy emlĆ©kszik az adatbĆ”zis jelmondatĆ”ra az Ć”tkƶltƶztetĆ©shez. @@ -1726,7 +1726,7 @@ tulajdonosok adminok minden tag - SimpleX hivatkozĆ”s + SimpleX hivatkozĆ”sok A hangüzenetek küldĆ©se le van tiltva A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban. A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva @@ -1840,14 +1840,14 @@ PrivĆ”t üzenet ĆŗtvĆ”lasztĆ”s šŸš€ FĆ”jlok biztonsĆ”gos fogadĆ”sa Csƶkkentett akkumulĆ”tor-hasznĆ”lattal. - Hiba a WebView inicializĆ”lĆ”sĆ”ban. FrissĆ­tse rendszerĆ©t az Ćŗj verzióra. KĆ©rjük, lĆ©pjen kapcsolatba a fejlesztőkkel. + Hiba a WebView inicializĆ”lĆ”sĆ”ban. FrissĆ­tse rendszerĆ©t az Ćŗj verzióra. LĆ©pjen kapcsolatba a fejlesztőkkel. \nHiba: %s FelhasznĆ”ló Ć”ltal lĆ©trehozott tĆ©ma visszaĆ”llĆ­tĆ”sa Üzenet vĆ”rakoztatĆ”si informĆ”ció nincs KĆ©zbesĆ­tĆ©si hibĆ”k felderĆ­tĆ©se - KiszolgĆ”ló vĆ”rakoztatĆ”si infó: %1$s -\nUtoljĆ”ra kĆ©zbesĆ­tett üzenet: %2$s + kiszolgĆ”ló vĆ”rakoztatĆ”si infó: %1$s +\nutoljĆ”ra kĆ©zbesĆ­tett üzenet: %2$s HibĆ”s kulcs vagy ismeretlen fĆ”jltƶredĆ©k cĆ­m - valószĆ­nűleg a fĆ”jl tƶrlődƶtt. Ideiglenes fĆ”jlhiba ÜzenetĆ”llapot @@ -1858,7 +1858,7 @@ FĆ”jlĆ”llapot FĆ”jlĆ”llapot: %s MĆ”solĆ”si hiba - Ezt a hivatkozĆ”st egy mĆ”sik mobilleszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n. + Ezt a hivatkozĆ”st egy mĆ”sik mobileszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n. Ellenőrizze, hogy a mobil Ć©s az asztali szĆ”mĆ­tógĆ©p ugyanahhoz a helyi hĆ”lózathoz csatlakozik-e, valamint az asztali szĆ”mĆ­tógĆ©p tűzfalĆ”ban engedĆ©lyezve van-e a kapcsolat. \nMinden tovĆ”bbi problĆ©mĆ”t osszon meg a fejlesztőkkel. Nem lehet üzenetet küldeni @@ -1870,13 +1870,13 @@ Az üzenet kĆ©sőbb is kĆ©zbesĆ­thető, ha a tag aktĆ­vvĆ” vĆ”lik. MĆ©g nincs kƶzvetlen kapcsolat, az üzenetet az admin tovĆ”bbĆ­tja. HivatkozĆ”s beolvasĆ”sa / beillesztĆ©se - BeĆ”llĆ­tott SMP-kiszolgĆ”lók + KonfigurĆ”lt SMP-kiszolgĆ”lók EgyĆ©b SMP-kiszolgĆ”lók EgyĆ©b XFTP-kiszolgĆ”lók letiltva inaktĆ­v NagyĆ­tĆ”s - informĆ”ciók a kiszolgĆ”lókról + InformĆ”ciók a kiszolgĆ”lókról KapcsolódĆ”s HibĆ”k Függőben @@ -1892,7 +1892,7 @@ VisszaĆ”llĆ­tĆ”s Minden statisztika visszaĆ”llĆ­tĆ”sa Minden statisztika visszaĆ”llĆ­tĆ”sa? - A kiszolgĆ”lók statisztikĆ”i visszaĆ”llnak - ez nem vonható vissza! + A kiszolgĆ”lók statisztikĆ”i visszaĆ”llnak - ez a művelet nem vonható vissza! RĆ©szletes statisztikĆ”k Letƶltve lejĆ”rt @@ -1930,7 +1930,7 @@ Feltƶltƶtt fĆ”jltƶredĆ©kek ElkĆ©szült Kapcsolódott kiszolgĆ”lók - BeĆ”llĆ­tott XFTP-kiszolgĆ”lók + KonfigurĆ”lt XFTP-kiszolgĆ”lók Kapcsolódva Jelenlegi profil RĆ©szletek @@ -2023,7 +2023,7 @@ EngedĆ©lyeznie kell a hĆ­vĆ”sokat az ismerőse szĆ”mĆ”ra, hogy fel tudjĆ”k hĆ­vni egymĆ”st. A(z) %1$s nevű ismerősĆ©vel folytatott beszĆ©lgetĆ©seit tovĆ”bbra is megtekintheti a csevegĆ©sek listĆ”jĆ”ban. Üzenet - KivĆ”laszt + KivĆ”lasztĆ”s Az üzenetek minden tag szĆ”mĆ”ra moderĆ”ltkĆ©nt lesznek megjelƶlve. Nincs kivĆ”lasztva semmi Az üzenetek tƶrlĆ©sre lesznek jelƶlve. A cĆ­mzett(ek) kĆ©pes(ek) lesz(nek) felfedni ezt az üzenetet. @@ -2032,7 +2032,7 @@ Az üzenetek minden tag szĆ”mĆ”ra tƶrlĆ©sre kerülnek. CsevegĆ©si adatbĆ”zis exportĆ”lva Kapcsolatok- Ć©s kiszolgĆ”lók Ć”llapotĆ”nak megjelenĆ­tĆ©se. - Kapcsolódjon gyorsabban az ismerőseihez + Kapcsolódjon gyorsabban az ismerőseihez. FolytatĆ”s EllenőrĆ­zze a hĆ”lózatĆ”t MĆ©dia- Ć©s fĆ”jlkiszolgĆ”lók @@ -2052,11 +2052,11 @@ Csevegőlista Ć”tvĆ”ltĆ”sa: Ezt a ā€žMegjelenĆ©sā€ menüben módosĆ­thatja. Új mĆ©diabeĆ”llĆ­tĆ”sok - LejĆ”tszĆ”s a csevegĆ©si listĆ”ból - ElhomĆ”lyosĆ­tĆ”s a jobb adatvĆ©delemĆ©rt + LejĆ”tszĆ”s a csevegĆ©si listĆ”ból. + ElhomĆ”lyosĆ­tĆ”s a jobb adatvĆ©delemĆ©rt. Automatikus frissĆ­tĆ©s LĆ©trehozĆ”s - Új verziók letƶltĆ©se a GitHub-ról + Új verziók letƶltĆ©se a GitHubról BetűmĆ©ret nƶvelĆ©se. MeghĆ­vĆ”s Új csevegĆ©si Ć©lmĆ©ny šŸŽ‰ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index b6d6770250..9f6fa799b7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -2,16 +2,16 @@ %1$s ANGGOTA Alamat - %1$d pesan dihapus admin %2$s + %1$d pesan dimoderasi oleh %2$s 1 menit 30 detik 5 menit Semua pesan akan dihapus - ini tidak bisa dikembalikan! Pesan akan HANYA dihapus untukmu. - Terima permintaan relasi? + Terima permintaan koneksi? Tentang alamat SimpleX Tambah kontak Tentang SimpleX - %1$d gagal mendekripsi pesan. + %1$d pesan gagal terdekripsi. Tolong laporkan hal ini ke pengembang. 1 bulan 1 minggu @@ -19,7 +19,7 @@ %1$s.]]> Panggilan suara Batal - izinkan pesan suara? + Izinkan pesan suara? Terima Versi aplikasi Versi aplikasi: v%s @@ -30,13 +30,13 @@ Tambah profil Corak Tambahan sekunder - Motif aplikasi + Tema aplikasi selalu - diizinkan mengirim pesan suara. + Izinkan mengirim pesan suara. semua anggota Panggilan suara dan video - Hal-hal lain - Sudah menghubungi! + Beberapa hal lainnya + Sudah terhubungkan! Sudah bergabung dengan grup! %1$s ingin menghubungimu lewat Batalkan penggantian alamat? @@ -54,7 +54,7 @@ tebal menelepon… Bluetooth - Panggilan ditutup + Panggilan diakhiri Ubah Peringatan: arsip akan dihapus.]]> Buat grup: untuk membuat grup baru.]]> @@ -112,4 +112,154 @@ Tampilan macet Anda membagikan lokasi file yang tidak valid. Laporkan masalah ini ke pengembang aplikasi. error + Tuatan 1 kali pakai + %1$d pesan yang terlewati + %1$d pesan yang dilewati + %s tidak didukung. Harap pastikan kamu menggunakan versi yang sama pada kedua perangkat.]]> + %s sedang sibuk]]> + %s tidak terhubung]]> + %s tidak aktif]]> + %s tidak di temukan]]> + Terima penyamaran + panggilan diterima + 6 bahasa antarmuka baru + %s tidak terhubung]]> + Tidak ada kode sandi aplikasi + Belum ada koneksi langsung, pesan diteruskan oleh admin. + Tidak ada obrolan yang difilter + Tidak ada yang dipilih + Hanya 10 gambar dapat dikirim pada saat bersamaan + Notifikasi + Hanya menghapus percakapan + Koneksi jaringan yang lebih handal. + Pengalaman obrolan yang baru šŸŽ‰ + Pilihan media baru + Izinkan panggilan? + Pesan baru + Hanya kamu yang dapat mengirim pesan menghilang. + Tidak ada pengidentifikasi pengguna. + Hanya pemilik grup yang dapat mengubah preferensi grup. + dan %d peristiwa lainnya + Peran baru anggota + Tidak ada kontak di pilih + Tidak ada kontak untuk ditambahkan + Tidak ada teks + Semua pesan baru dari %s akan disembunyikan! + tidak ada + Status jaringan + Seluruh obrolan dan pesan akan dihapus - ini tidak bisa dibatalkan! + Tidak ada perangkat terkoneksi + Tidak ada panggilan latar + Pratinjau notifikasi + Layanan notifikasi + Selalu aktif + Permintaan kontak baru + Pesan baru + Kemungkinan besar kontak ini telah menghapus koneksi dengan kamu. + Masalah jaringan - pesan kadaluwarsa setelah beberapa kali mencoba mengirim. + Tidak ada obrolan dipilih + Hanya pemilik grup yang dapat mengaktifkan file dan media. + (hanya disimpan oleh anggota grup) + Lagi + Tautan undangan satu kali + Obrolan baru + Jaringan & server + Pengaturan tingkat lanjut + Host Onion akan diperlukan untuk koneksi. +\nHarap diperhatikan: Anda tidak akan dapat terhubung ke server tanpa alamat .onion. + Host Onion tidak akan digunakan. + Selalu gunakan perutean pribadi. + Pemberitahuan akan berhenti bekerja sampai kamu meluncurkan ulang aplikasi + Semua kontak kamu akan tetap terhubung. Pembaruan profil akan dikirim ke kontak kamu. + Tidak ada enkripsi ujung-ujung + Kode sandi baru + Mati + Seluruh data aplikasi dihapus. + Arsip database baru + Arsip database lama + tidak pernah + Tidak ada file yang diterima atau dikirim + Menyetujui enkripsi… + Bisukan ketika tidak aktif! + Seluruh mode warna + mati` + tidak + on + mati + Izinkan kontak kamu menghapus pesan terkirim secara permanen. (24 jam) + Izinkan penghapusan pesan yang tidak dapat diubah hanya jika kontak kamu mengizinkannya. (24 jam) + Izinkan kontak kamu mengirim pesan suara. + Hanya kamu yang dapat menghapus pesan secara permanen (kontak kamu dapat menandainya untuk dihapus). (24 jam) + Hanya kontak kamu yang dapat menghapus pesan secara permanen (kamu dapat menandainya untuk dihapus). (24 jam) + Hanya kamu yang dapat menambahkan reaksi pesan. + Hanya kamu yang dapat melakukan panggilan. + Hanya kontak kamu yang dapat menambahkan reaksi pesan. + Izinkan pengiriman pesan langsung ke anggota. + Izinkan untuk mengirim pesan menghilang. + ditawarkan %s: %2s + Beberapa profil obrolan + Lebih banyak peningkatan akan segera hadir! + Tidak ada admin yang dapat: +\n- menghapus pesan anggota. +\n- menonaktifkan anggota (peran ā€œpengamatā€) + Lebih banyak peningkatan akan segera hadir! + - pengiriman pesan yang lebih stabil. +\n- group yang sedikit lebih baik. +\n- dan lainnya! + Aplikasi desktop baru! + Manajemen jaringan + bulan + Semua kontak, percakapan, dan file kamu akan dienkripsi dengan aman dan diunggah dalam beberapa bagian ke relay XFTP yang telah dikonfigurasi. + Tidak ada koneksi jaringan + Bisu + Koneksi jaringan + TIdak pernah + Mati + Hanya 10 video dapat dikirim pada saat bersamaan + enkripsi ujung-ke-ujung 2 lapis.]]> + Hanya pemilik grup yang dapat mengaktifkan pesan suara. + Seluruh pesan akan dihapus - ini tidak bisa dibatalkan! + Izinkan turun versi + Tidak ada informasi pengiriman + Boleh + OK + Tidak ada rincian + Tautan undangan satu kali + Host Onion akan di pakai jika tersedia. + Selalu gunakan relay + Nama tampilan baru: + Frasa sandi baru + Pemberitahuan akan dikirim hanya sampai saat aplikasi berhenti! + Pengaturan tingkat lanjut + Izinkan reaksi pesan hanya jika kontak Anda mengizinkannya. + Izinkan pesan suara hanya jika kontak kamu mengizinkannya. + Izinkan kontak kamu menambahkan reaksi pesan. + Izinkan kontak kamu untuk menghubungimu. + Izinkan kontak kamu untuk mengirim pesan menghilang. + Izinkan panggilan hanya jika kontak kamu mengizinkannya. + Izinkan pesan menghilang hanya jika kontak kamu mengizinkannya. + Izinkan reaksi pesan. + Izinkan untuk menghapus pesan terkirim secara permanen. (24 jam) + Bisu + Tidak ada riwayat + Tema obrolan yang baru + Semua profil + Izinkan untuk mengirim tautan SimpleX. + Semua data akan terhapus jika ini dimasukkan. + Tidak kompatibel! + Perangkat seluler baru + Hanya satu perangkat yang dapat bekerja pada saat bersamaan + Tidak + Tidak ada kontak yang di filter + pengamat + menyetujui enkripsi untuk %s… + Semua anggota grup akan tetap terhubung. + Tidak ada info, coba muat ulang + Tidak + Baru di %s + ditawarkan %s + Hanya kamu yang dapat mengirim pesan suara. + Hanya kontak kamu yang dapat melakukan panggilan. + Izinkan untuk mengirim file dan media. + Semua kontak kamu akan tetap terhubung. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 3c639cf777..92055493fb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -74,7 +74,7 @@ Verifique o endereƧo do servidor e tente novamente. para cada perfil de bate-papo que vocĆŖ tiver no aplicativo.]]> Melhor para bateria. VocĆŖ receberĆ” notificaƧƵes apenas quando o aplicativo estiver em execução (SEM o serviƧo em segundo plano).]]> - Consome mais bateria! O serviƧo em segundo plano estĆ” sempre em execução - as notificaƧƵes sĆ£o exibidas assim que as mensagens estiverem disponĆ­veis.]]> + Consome mais bateria! O aplicativo em segundo plano estĆ” sempre em execução - as notificaƧƵes sĆ£o exibidas instantaneamente.]]> BATE-PAPOS ƍCONE DO APLICATIVO BANCO DE DADOS DE BATE-PAPO @@ -91,7 +91,7 @@ O serviƧo em segundo plano estĆ” sempre em execução - as notificaƧƵes serĆ£o exibidas assim que as mensagens estiverem disponĆ­veis. Uma conexĆ£o TCP separada (e credencial SOCKS) serĆ” usada para cada contato e membro do grupo. \nAtenção: se vocĆŖ tiver muitas conexƵes, o consumo de bateria e trĆ”fego pode ser substancialmente maior e algumas conexƵes podem falhar. - Bom para bateria. O serviƧo em segundo plano procura por mensagens a cada 10 minutos. VocĆŖ pode perder chamadas ou mensagens urgentes.]]> + Bom para bateria. O aplicativo procura por mensagens a cada 10 minutos. VocĆŖ pode perder chamadas ou mensagens urgentes.]]> chamda encerrada %1$s Converse com os desenvolvedores Criar link de grupo @@ -256,12 +256,12 @@ DISPOSITIVO Ferramentas de desenvolvedor conectando (introduzido) - RealƧe + Tonalidade Erro ao remover membro Erro ao alterar cargo direto erro - Falha ao carregar o bate-papo + Falha ao carregar a conversa Erro ao atualizar a configuração de conexĆ£o Erro ao enviar mensagem Erro ao adicionar membro(s) @@ -317,8 +317,8 @@ Expandir seleção de cargo Erro ao salvar o perfil do grupo Mensagens diretas - habilitado - habilitado para contato + ativado + ativado para contato ativado para vocĆŖ %dm %d min @@ -379,7 +379,7 @@ Nome completo do grupo: Links de grupo Privacidade e seguranƧa aprimoradas - Falha ao carregar o bate-papo + Falha ao carregar as conversas Arquivo: %s Arquivo salvo Os membros do grupo podem enviar mensagens de voz. @@ -403,7 +403,7 @@ Código de seguranƧa incorreto! Instale o SimpleX para terminal Como funciona - Imune a spam e abuso + Imune a spam Vire a cĆ¢mera Desligar Modo anĆ“nimo @@ -551,7 +551,7 @@ formato de mensagem invĆ”lido AO VIVO moderado por %s - chat invĆ”lido + conversa invĆ”lida Abrir o link no navegador pode reduzir a privacidade e a seguranƧa da conexĆ£o. Links SimpleX nĆ£o confiĆ”veis ficarĆ£o vermelhos. Link de conexĆ£o invĆ”lido Certifique-se de que os endereƧos do servidor SMP estejam no formato correto, separados por linhas e nĆ£o estejam duplicados. @@ -562,7 +562,7 @@ Executa quando o aplicativo estĆ” aberto enviado o envio falhou - Bate-papos + Conversas Colar Link de convite de uso Ćŗnico Enviar perguntas e idĆ©ias @@ -764,7 +764,7 @@ Privacidade redefinida NotificaƧƵes privadas Fazer uma conexĆ£o privada - Pessoas podem se conectar com vocĆŖ somente via links compartilhados. + VocĆŖ decide quem pode se conectar. Pode acontecer quando: \n1. As mensagens expiraram no remetente após 2 dias ou no servidor após 30 dias. \n2. A descriptografia da mensagem falhou porque vocĆŖ ou seu contato usou o backup do banco de dados antigo. @@ -785,7 +785,7 @@ Gravar mensagem de voz Certifique-se de que os endereƧos do servidor WebRTC ICE estĆ£o em formato correto, separados por linha e nĆ£o estejam duplicados. ConexĆ£o e servidores - ConfiguraƧƵes de conexĆ£o + ConfiguraƧƵes avanƧadas sem detalhes Link de convite de uso Ćŗnico Seu endereƧo de servidor @@ -803,7 +803,7 @@ NĆ£o Usar conexĆ£o direta com a internet\? Ocultar: - Seu perfil Ć© guardado no seu diapositivo e Ć© compartilhado somente com seus contatos. Servidores SimpleX nĆ£o podem ver seu perfil. + Seu perfil Ć© guardado no seu dispositivo e Ć© compartilhado somente com seus contatos. Servidores SimpleX nĆ£o podem ver seu perfil. Salvar preferĆŖncias\? Salvar e notificar membros do grupo Erro ao salvar a senha do usuĆ”rio @@ -857,7 +857,8 @@ Para comeƧar um novo bate-papo Ligar Bem-vindo(a)! - A próxima geração de mensageiros privados + A próxima geração +\nde mensageiros privados PROXY SOCKS A tentativa de alterar a senha do banco de dados nĆ£o foi concluĆ­da. Pare o bate-papo para exportar, importar ou excluir o banco de dados do chat. VocĆŖ nĆ£o poderĆ” receber e enviar mensagens enquanto o chat estiver interrompido. @@ -883,7 +884,7 @@ Inicia periodicamente envio nĆ£o autorizado Toque para iniciar um novo bate-papo - VocĆŖ nĆ£o tem bate-papos + VocĆŖ nĆ£o tem conversas aguardando resposta… Seu banco de dados de bate-papo atual serĆ” EXCLUƍDO e SUBSTITUƍDO pelo importado. \nEsta ação nĆ£o pode ser desfeita - seu perfil, contatos, mensagens e arquivos serĆ£o perdidos de forma irreversĆ­vel. @@ -902,12 +903,12 @@ Atualizar O app busca novas mensagens periodicamente – ele usa alguns por cento da bateria por dia. O aplicativo nĆ£o usa notificaƧƵes por push – os dados do seu dispositivo nĆ£o sĆ£o enviados para os servidores. Para receber notificaƧƵes, por favor, digite a senha do banco de dados - ServiƧo SimpleX + ServiƧo de Chat SimpleX Mostrar prĆ©via Mostrar contato e mensagem Mostrar somente contato Compartilhar - Parar bate-papo + Parar conversa Desbloquear A mensagem serĆ” excluĆ­da para todos os membros. A mensagem serĆ” marcada como moderada para todos os membros. @@ -925,7 +926,7 @@ simplexmq: v%s (%2s) Isolamento de transporte VocĆŖ controla sua conversa! - A 1ĀŖ plataforma sem nenhum identificador de usuĆ”rio – privada por design. + Sem identificadores de usuĆ”rio. Alto-falante ligado VĆ­deo desativado Alto-falante desligado @@ -976,7 +977,7 @@ iniciando… aguardando confirmação… NĆ£o armazenamos nenhum dos seus contatos ou mensagens (uma vez entregues) nos servidores. - Mensagens omitidas + Mensagens ignoradas Toque para ativar o perfil. Mostrar perfil de chat Mostrar perfil @@ -994,7 +995,7 @@ imagem de prĆ©-visualização do link Para proteger suas informaƧƵes, ative o bloqueio SimpleX. \nVocĆŖ serĆ” solicitado a completar a autenticação antes que este recurso seja ativado. - Protocolo de código aberto – qualquer um pode hospedar os servidores. + Qualquer um pode hospedar os servidores. Claro O contato permite ativado @@ -1109,7 +1110,7 @@ Revogar Sobre o endereƧo SimpleX SecundĆ”ria adicional - RealƧe adicional + Tonalidade adicional Link de uso Ćŗnico Adicione o endereƧo ao seu perfil, para que seus contatos possam compartilhĆ”-lo com outras pessoas. A atualização do perfil serĆ” enviada aos seus contatos. Crie um endereƧo para permitir que as pessoas se conectem com vocĆŖ. @@ -1139,7 +1140,7 @@ VocĆŖ nĆ£o perderĆ” seus contatos se, posteriormente, excluir seu endereƧo. EndereƧo SimpleX Quando as pessoas solicitam uma conexĆ£o, vocĆŖ pode aceitĆ”-la ou rejeitĆ”-la. - CORES DO TEMA + CORES DA INTERFACE compartilhar com os contatos A atualização do perfil serĆ” enviada aos seus contatos. Salvar configuraƧƵes\? @@ -1238,7 +1239,7 @@ Alterar senha de auto-destruição Se vocĆŖ digitar sua senha de auto-destruição ao abrir o aplicativo: sem texto - Alguns erros nĆ£o-fatais ocurreram durante importação - pode ver o console de Chat para mais detalhes. + Alguns erros nĆ£o fatais ocorreram durante importação: Pesquisar Desativado Arquivos e mĆ­dia @@ -1281,7 +1282,7 @@ Desligar Corrigir conexĆ£o Corrigir conexĆ£o\? - Sem bate-papo filtrados + Sem conversas filtradas Renegociar Desfavoritar Renegociar a criptografia\? @@ -1335,7 +1336,7 @@ Enviar recibos de entrega serĆ£o habilitados para todos os contatos em todos os perfis visĆ­veis. %s e %s conectados Conectar diretamente\? - Nenhum bate-papo selecionado + Nenhuma conversa selecionada Rascunho de mensagem desativado SimpleX nĆ£o pode ser executado em segundo plano. VocĆŖ receberĆ” as notificaƧƵes somente quando o aplicativo estiver em execução. @@ -1562,7 +1563,7 @@ Erro de renegociação de criptografia Erro ao abrir o navegador Erro ao enviar o convite - Carregando bate-papos… + Carregando conversas… %s foi desconectado]]> %s foi desconectado]]> Apenas um dispositivo pode funcionar ao mesmo tempo @@ -1659,4 +1660,412 @@ Servidores XFTP configurados Verifique sua conexĆ£o de internet e tente novamente Verificar atualizaƧƵes + Modo de cor + Apagar banco de dados desse dispositivo + O endereƧo do servidor de destino de %1$s Ć© incompatĆ­vel com o as configuraƧƵes %2$s do servidor de encaminhamento. + Capacidade excedida - o destinatĆ”rio nĆ£o recebeu as mensagens enviadas anteriormente. + Erro do servidor de destino: %1$s + Inativo + Migre de outro dispositivono novo dispositivo e escaneie o QR code.]]> + Confirme se vocĆŖ se lembra da senha do banco de dados para migrĆ”-lo. + Borrar conteĆŗdo + Borrar para melhor privacidade. + Confirmar exclusĆ£o do contato? + conectar + Apagar sem notificar + Tonalidade adicional 2 + Confirmar configuraƧƵes de rede + Todos os modos de cor + Modo escuro + NĆ£o Ć© possĆ­vel enviar mensagem + Cores do chat + Criar + Confirmar upload + administradores + O contato foi apagado. + Permitir chamadas? + NĆ£o Ć© possĆ­vel chamar o contato + Conectando ao contato, por favor aguarde ou volte depois! + Chamadas proibidas! + NĆ£o Ć© possĆ­vel chamar membro do grupo + NĆ£o Ć© possĆ­vel mandar mensagem para o membro do grupo + Controle sua rede + Apague atĆ© 20 mensagens por vez. + Arquivar contatos para conversar depois. + Conecte aos seus amigos mais rapidamente. + Escuro + Confirmar arquivos de servidores desconhecidos. + Copiar erro + Conversa migrada! + chamar + O contato serĆ” apagado - essa ação nĆ£o pode ser desfeita! + Beta + A atualização do aplicativo foi baixada + Tema da conversa + Entrega de depuração + Cores do modo escuro + Criando link de arquivo + Permitir downgrade + Todos os usuĆ”rios + Conectado + Conectando + Perfil atual + Servidores conectados + ConexƵes ativas + tentativas + Reconhecido + Erros conhecidos + ConexƵes + Criado + erros de decriptação + Apagado + Erros de exclusĆ£o + PedaƧos excluĆ­dos + PedaƧos baixados + PedaƧos carregados + Apagar %d mensagens dos membros? + Contato apagado! + Conversa apagada! + Contatos arquivados + Banco de dados da conversa exportado + Continuar + ConexĆ£o e status dos servidores. + Repetir download + Recebendo simultaneidade + Redefinir para o tema do usuĆ”rio + IU Persa + Aviso de entrega de mensagem + Erro: %1$s + Chave incorreta ou conexĆ£o desconhecida - provavelmente esta conexĆ£o foi excluĆ­da. + Essa conversa Ć© protegida por criptografia ponta a ponta + Repetir upload + Erro de conexĆ£o ao servidor de encaminhamento %1$s. Por favor tente mais tarde. + A versĆ£o do servidor de encaminhamento Ć© incompatĆ­vel com as configuraƧƵes de rede: %1$s. + O servidor de encaminhamento %1$s falhou ao se conectar ao servidor de destino %2$s. Por favor tente mais tarde. + O endereƧo do servidor de encaminhamento Ć© incompatĆ­vel com as configuraƧƵes de rede: %1$s. + A versĆ£o do servidor de destino de %1$s Ć© incompatĆ­vel com o servidor de encaminhamento %2$s. + Problemas de rede - a mensagem expirou após muitas tentativas de envio. + Servidor de encaminhamento: %1$s +\nErro: %2$s + DestinatĆ”rio(s) nĆ£o podem ver de onde essa mensagem veio. + A mensagem poderĆ” ser entregue mais tarde se o membro se tornar ativo. + Outros servidores SMP + Para proteger seu endereƧo IP, roteamento privado usa seus servidores SMP para entregar mensagens. + Pular essa versĆ£o + Baixar %s (%s) + Download da atualização cancelado + Mostrar lista de conversas em nova janela + Tamanho da fonte + Fundo do papel de parede + Tonalidade do papel de parede + Remover imagem + Zoom + Definir tema padrĆ£o + Proibido enviar links SimpleX + Erro ao carregar o arquivo + Falha ao carregar + Carregando arquivo + EstatĆ­sticas detalhadas + Erro no servidor de arquivo: %1$s + Salvo de + Baixar + Encaminhar + Status de arquivo: %s + Convidar + Aumentar tamanho da fonte. + Aprimorar aplicativo automaticamente + Redefinir todas as estatĆ­sticas? + As estatĆ­sticas dos servidores serĆ£o redefinidas - isso nĆ£o poderĆ” ser desfeito! + Link invĆ”lido + Por favor cheque se o link SimpleX estĆ” correto + criptografia quantum resistant e2e com perfeito sigilo direto, repĆŗdio e recuperação de vazamento.]]> + Essa conversa Ć© protegida por criptografia quantum resistant ponta a ponta + Por favor tente mais tarde. + Selecionado %d + SimpleX links nĆ£o permitidos + Arquivos e mĆ­dia nĆ£o permitidos + Mensagem + Erro de arquivo temporĆ”rio + mensagem + pesquisar + vĆ­deo + Roteamento privado + NƃO use roteamento privado. + Abrir configuraƧƵes + Fone de ouvido + Erro ao iniciar o WebView. Atualize seu sistema para a nova versĆ£o. Por favor contate os desenvolvedores. +\nErro: %s + Desativado + Forte + Ativar em conversas diretas (BETA)! + Finalizar migração em outro dispositivo. + Claro + A origem da mensagem permanece privada. + Migrar aqui + Migrando + Nova experiĆŖncia de conversa šŸŽ‰ + Novas opƧƵes de mĆ­dia + Ou cole o link do arquivo + Reproduzir da lista de conversa. + Redefinir todas as estatĆ­sticas + Aviso: iniciar conversa em mĆŗltiplos dispositivos nĆ£o Ć© suportado e pode causar falhas na entrega de mensagens + Internet cabeada + nĆ£o deve usar a mesma base de dados em dois dispositivos.]]> + Membros do grupo podem enviar link SimpleX + Importando arquivo + Modo claro + Ativado para + Ao conectar em chamadas de Ć”udio de vĆ­deo. + Migrar dispositivo + Erro ao salvar configuraƧƵes + Arquivo exportado nĆ£o existe + %s carregados + Para continuar, a conversa precisa ser interrompida. + Outro + Sem conexĆ£o de rede + As preferĆŖncias de conversa selecionadas proĆ­bem essa mensagem. + Erro de arquivo + Redefinir cor + Sons de chamada + Formato das imagens de perfil + IU Lituana + Roteamento de mensagem privada šŸš€ + Novos temas de conversa + Com uso de bateria reduzida. + Falha no download + Falha na importação + Baixando arquivo + Repetir importação + VocĆŖ pode tentar novamente. + Erro ao exportar banco de dados de conversa + Erro ao verificar a palavra-chave: + salvo + salvo de %s + Encaminhado de + Mensagens de voz nĆ£o permitidas + Nova mensagem + Colar link + Links SimpleX + Encaminhar e salvar mensagens + Suave + MĆ©dio + VocĆŖ precisa permitir seu contato ligue para poder ligar para ele. + Redefinir para o tema do aplicativo + Resposta recebida + Boa tarde! + Bom dia! + Baixe novas versƵes no GitHub. + encaminhado + O arquivo foi deletado ou o link estĆ” invĆ”lido + Abrir tela de migração + Grupos seguros + Preparando upload + ConexĆ£o de rede + Servidores desconhecidos! + Sem Tor ou VPN, seu endereƧo de IP ficarĆ” visĆ­vel para esses relays XFTP +\n%1$s. + Erro ao exibir notificação, contate os desenvolvedores. + Salvo + Encaminhado + Servidores desconhecidos + Desprotegido + Nunca + Modo de roteamento de mensagens + Conceder permissƵes + Alto falante + Headphones + Sem Tor ou VPN, seu endereƧo de IP ficarĆ” visĆ­vel para servidores de arquivo. + ARQUIVOS + Fotos de perfil + ROTEAMENTO DE MENSAGEM PRIVADA + criptografia padrĆ£o ponta a ponta + proprietĆ”rios + Migrar para outro dispositivo + Verificar palavra-passe + WiFi + Alternar lista de conversa: + VocĆŖ pode mudar isso em configuraƧƵes de AparĆŖncia. + desativado + nenhum + informaƧƵes da fila do servidor: %1$s +\n +\nĆŗltima mensagem recebida: %2$s + Por favor peƧa para seu contato ativar as chamadas. + Enviar mensagem para ativar chamadas. + Salvar e reconectar + ConexĆ£o TCP + Barra de ferramentas de conversa acessĆ­vel + Isso protege seu endereƧo de IP e conexƵes. + Use o aplicativo com uma mĆ£o. + Arquivos carregados + Baixado + Erro ao redefinir estatĆ­sticas + Redefinir + Status de arquivo + InformaƧƵes da fila de mensagens + Sistema + Repetir + Escala + Preencher + Ajustar + Links SimpleX sĆ£o proibidos neste grupo. + Migrar para outro dispositivo via QR code. + Chamadas picture-in-picture + Use o aplicativo enquanto estĆ” em chamada. + SerĆ” ativado em conversas diretas! + Receber arquivos de forma segura + Gerenciamento de rede + FaƧa suas conversas terem uma aparĆŖncia diferente! + ConexĆ£o de rede mais confiĆ”vel. + Preparando download + %s baixados + Colar link de arquivo + Insira a palavra-chave + Erro ao baixar o arquivo + Ou de forma segura compartilhe esse link de arquivo + Verifique a palavra-passe do banco de dados + criptografia ponta-a-ponta com perfeito sigilo direto, repĆŗdio e recuperação de vazamento.]]> + Arquivo nĆ£o encontrado - provavelmente o arquivo foi excluĆ­do ou cancelado. + Chave incorreta ou arquivo de pedaƧo de endereƧo - provavelmente o arquivo foi excluĆ­do. + Mande mensagens diretamente quando o seu endereƧo de IP estĆ” protegido e o servidor de destino nĆ£o suporta roteamento privado. + Use roteamento privado em servidores desconhecidos. + Escanear / Colar link + Baixando detalhes de link + Finalizar migração + Criptografia Quantum resistant + Migração concluĆ­da + Proteja seu endereƧo de IP dos retransmissores de mensagem escolhidos por seus contatos. +\nAtive nas configuraƧƵes *Redes e servidores* . + Iniciar conversa + ConfiguraƧƵes + abrir + Manter conversa + Apenas excluir conversa + Outros servidores XFTP + Use roteamento privado em servidores desconhecidos quando o endereƧo de IP nĆ£o estĆ” protegido. + Sim + Instalar atualização + Atualização disponĆ­vel: %s + Abrir local do arquivo + Desativar + Microfone + Conceder nas configuraƧƵes + Encontre essa permissĆ£o nas configuraƧƵes do Android e conceda-a manualmente. + O aplicativo irĆ” perguntar para confirmar os downloads de servidores de arquivo desconhecidos (exceto .onion ou quando o proxy SOCKS estiver habilitado). + Tema de perfil + Defina uma palavra-chave + criptografia quantum resistant e2e + Convidar + Status da mensagem + Entrega de mensagens aprimorada + Quadrado, circulo, ou qualquer coisa entre eles. + Por favor verifique se o celular e o computador estĆ£o conectados na mesma rede local e o firewall do computador permite a conexĆ£o. +\nPor favor compartilhe qualquer outro problema com os desenvolvedores. + Esse link foi usado em outros dispositivo móvel, por favor crie um novo link no computador. + Erro ao excluir banco de dados + Por favor confirme que as configuraƧƵes de rede estĆ£o corretas para este dispositivo. + Parando conversa + VocĆŖ pode tentar novamente. + A versĆ£o do servidor Ć© incompatĆ­vel com seu aplicativo: %1$s. + Erro de roteamento privado + O endereƧo do servidor Ć© incompatĆ­vel com as configuraƧƵes de rede: %1$s. + Servidor de encaminhamento: %1$s +\nErro no servidor de destino: %2$s + EndereƧo do servidor Ć© incompatĆ­vel com as configuraƧƵes de rede. + A versĆ£o do servidor Ć© incompatĆ­vel com as configuraƧƵes de rede. + Encaminhar mensagem… + Quando IP oculto + Proteger endereƧo IP + Enviar resposta + Arquivos + NĆ£o + Baixando atualização do aplicativo, nĆ£o feche o aplicativo + Conceder permissĆ£o para fazer chamadas + Link invĆ”lido + Detalhes + Erros + Mensagens recebidas + Mensagens enviadas + Sem informação, tente recarregar + Informação dos servidores + Mostrando informação para + EstatĆ­sticas + SessƵes de transporte + Recepção de mensagem + Pendente + ComeƧando de %s. +\nTodos os dados sĆ£o privados do seu dispositivo. + Total + Servidores proxiados + Servidores conectados anteriormente + Reconecte todos os servidores conectados para forƧar entrega de mensagem. Isso usa trĆ”fego adicional. + Reconectar servidor? + Reconectar servidores? + Reconectar servidor para forƧar entrega de mensagem. Isso usa trĆ”fego adicional. + VocĆŖ nĆ£o estĆ” conectado nesses servidores. Roteamento privado Ć© usado para entregar mensagens para eles. + Erro + Erro ao reconectar servidor + Erro ao reconectar servidores + Reconectar todos os servidores + Reconectar + Enviar diretamente + Enviar mensagens + Enviar total + Enviar via proxy + Servidor SMP + Mensagens recebidas + Total recebido + Receber erros + ComeƧando de %s. + Servidor XFTP + Seguro + Enviar erros + Inscrito + duplicatas + expirada + outro + Erros de inscrição + outros erros + Proxied + InscriƧƵes ignoradas + Erros de download + Arquivos baixados + EndereƧo do servidor + Tamanho + Erros de upload + Abrir configuraƧƵes de servidor + Selecione + As mensagens serĆ£o excluĆ­das para todos os membros. + As mensagens serĆ£o marcadas como moderadas para todos os membros. + Mensagem encaminhada + Ainda nĆ£o hĆ” conexĆ£o direta, a mensagem Ć© encaminhada pelo administrador. + Membro inativo + Nada selecionado + Mensagens serĆ£o marcadas para exclusĆ£o. O(s) destinatĆ”rio(s) poderĆ”(Ć£o) revelar essas mensagens. + VocĆŖ ainda pode ver a conversa com %1$s na lista de conversas. + Nenhum contato filtrado + Seus contatos + NƃO envie mensagens diretamente, mesmo que o seu servidor ou o servidor de destino nĆ£o suporte roteamento privado. + Mande mensagens diretamente quando o seu servidor ou o servidor de destino nĆ£o suporta roteamento privado. + Retorno de roteamento de mensagens + Mostrar status da mensagem + Migrar de outro dispositivo + Status da mensagem: %s + Carregado + Servidores de mensagem + Servidores de mĆ­dia e arquivo + Mostrar porcentagem + Proxy SOCKS + VocĆŖ pode salvar o arquivo exportado. + VocĆŖ pode migrar o banco de dados exportado. + Alguns arquivos nĆ£o foram exportados + VocĆŖ pode enviar mensagens para %1$s de Contatos arquivados. + Redefinir todas as dicas + Desativado + EstĆ”vel + Instalado com sucesso + Por favor reinicie o aplicativo. + Me lembre mais tarde + Para ser notificado sobre os novos lanƧamentos, habilite a checagem periódica de versƵes EstĆ”veis e Beta. + Barra de ferramentas de conversa acessĆ­vel \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index cd34f2efff..b16c96b636 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -724,4 +724,61 @@ Lį»—i kįŗæt nối lįŗ”i mĆ”y chį»§ Lį»—i Lį»—i khĆ“i phỄc thống kĆŖ + Lį»—i cįŗ­p nhįŗ­t liĆŖn kįŗæt nhóm + lį»—i hiển thị tin nhįŗÆn + lį»—i hiển thị nį»™i dung + Lį»—i lʰu mĆ”y chį»§ ICE + Lį»—i lʰu mĆ”y chį»§ XFTP + Lį»—i lʰu mĆ”y chį»§ SMP + Lį»—i gį»­i tin nhįŗÆn + Lį»—i khởi động ứng dỄng + Lį»—i dừng ứng dỄng + Lį»—i hiển thị thĆ“ng bĆ”o, liĆŖn hệ vį»›i nhĆ  phĆ”t triển. + Lį»—i lʰu mįŗ­t khįŗ©u ngĘ°į»i dùng + Lį»—i lʰu cĆ i đặt + Lį»—i gį»­i lį»i mį»i + Lį»—i chuyển đổi hồ sĘ”! + Lį»—i đồng bį»™ kįŗæt nối + Lį»—i cĆ i đặt địa chỉ + Dù đã tįŗÆt trong cuį»™c trò chuyện. + Lį»—i cįŗ­p nhįŗ­t cįŗ„u hƬnh mįŗ”ng + Lį»—i cįŗ­p nhįŗ­t quyền riĆŖng tʰ ngĘ°į»i dùng + Mở rį»™ng chį»n quyền hįŗ”n + THỬ NGHIỆM + Mở rį»™ng + ThoĆ”t mĆ  khĆ“ng lʰu + đã hįŗæt hįŗ”n + Lį»—i xĆ”c thį»±c mįŗ­t khįŗ©u: + QuĆ” trƬnh thį»±c hiện chức năng mįŗ„t quĆ” nhiều thį»i gian: %1$dgiĆ¢y: %2$s + TĆ­nh năng thį»­ nghiệm + Xuįŗ„t cĘ” sở dữ liệu + Lį»—i tįŗ£i lĆŖn kho lʰu trữ + Tįŗ­p tin đã xuįŗ„t khĆ“ng tồn tįŗ”i + Tįŗ¬P TIN + KhĆ“ng thể tįŗ£i tin nhįŗÆn + KhĆ“ng tƬm thįŗ„y tįŗ­p tin - có thể tįŗ­p tin đã bị xóa vĆ  hį»§y bį». + Lį»—i tįŗ­p tin + Xuįŗ„t chį»§ đề + KhĆ“ng thể tįŗ£i tin nhįŗÆn + Tįŗ­p tin + Nhanh chóng vĆ  khĆ“ng cįŗ§n phįŗ£i đợi ngĘ°į»i gį»­i hoįŗ”t động! + KhĆ“ng tƬm thįŗ„y tįŗ­p tin + Tįŗ­p tin: %s + Tham gia nhanh chóng hĘ”n vĆ  xį»­ lý tin nhįŗÆn ổn định hĘ”n. + Tįŗ­p tin + YĆŖu thĆ­ch + Tįŗ­p tin + Lį»—i mĆ”y chį»§ tệp: %1$s + Trįŗ”ng thĆ”i tệp + Tệp vĆ  phʰʔng tiện truyền thĆ“ng khĆ“ng được cho phĆ©p + Tệp vĆ  phʰʔng tiện truyền thĆ“ng bị cįŗ„m trong nhóm nĆ y. + Tệp sįŗ½ bị xóa khį»i mĆ”y chį»§. + Tệp & phʰʔng tiện truyền thĆ“ng + Tệp vĆ  phʰʔng tiện truyền thĆ“ng bị cįŗ„m! + Tệp sįŗ½ được nhįŗ­n khi liĆŖn hệ cį»§a bįŗ”n hoĆ n tįŗ„t quĆ” trƬnh tįŗ£i lĆŖn. + Tệp vĆ  phʰʔng tiện truyền thĆ“ng + Tệp đã bị xóa hoįŗ·c liĆŖn kįŗæt khĆ“ng hợp lệ + Tệp đã được lʰu + Tệp sįŗ½ được nhįŗ­n khi liĆŖn hệ cį»§a bįŗ”n hoįŗ”t động, vui lòng chį» hoįŗ·c kiểm tra lįŗ”i sau! + Trįŗ”ng thĆ”i tệp: %s \ No newline at end of file From ea320f531fbbd09097ed7fd931e3d2c26946684a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 16 Sep 2024 16:53:11 +0100 Subject: [PATCH 054/704] Translated using Weblate (Spanish) (#4891) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ Co-authored-by: Eraorahan --- website/langs/es.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/langs/es.json b/website/langs/es.json index 443f04b2ac..989d89557c 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -29,7 +29,7 @@ "home": "Inicio", "simplex-explained-tab-3-p-1": "Para cada cola los servidores disponen de credenciales separadas y anónimas, por lo que desconocen a quĆ© usuarios pertenecen.", "hero-p-1": "Las demĆ”s aplicaciones usan ID de usuario: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.
SimpleX no los tiene, ni siquiera nĆŗmeros aleatorios.
Esto mejora radicalmente su privacidad.", - "hero-2-header-desc": "El video muestra cómo se conecta con sus amistades a travĆ©s del código QR de un solo uso, en persona o a travĆ©s de videollamada. TambiĆ©n puede conectarse compartiendo un enlace de invitación.", + "hero-2-header-desc": "El vĆ­deo muestra cómo se conecta con sus amistades a travĆ©s del código QR de un solo uso, en persona o a travĆ©s de videollamada. TambiĆ©n puede conectarse compartiendo un enlace de invitación.", "feature-7-title": "Almacenamiento portable y cifrado — podrĆ” transferir su perfil a otro dispositivo", "simplex-private-card-4-point-2": "Para usar SimpleX a travĆ©s de Tor, instala la aplicación Orbot y activa el proxy SOCKS5 (o VPN en iOS).", "simplex-private-card-3-point-1": "Para las conexiones cliente servidor se usan exclusivamente el protocolo TLS 1.2/1.3 con algoritmos robustos.", @@ -52,7 +52,7 @@ "feature-8-title": "Modo incógnito —
exclusivo de SimpleX Chat", "simplex-private-1-title": "Doble capa de
cifrado de extremo a extremo", "simplex-private-2-title": "Capa de cifrado
adicional en el servidor", - "simplex-private-3-title": "Transporte TLS
seguro y autƩnticado", + "simplex-private-3-title": "Transporte TLS
seguro y autenticado", "simplex-private-4-title": "Acceso opcional
a través de Tor", "simplex-private-7-title": "Verificación de la
integridad del mensaje", "feature-4-title": "Mensajes de voz cifrados E2E", @@ -252,7 +252,7 @@ "hero-overlay-card-3-p-1": "Trail of Bits es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.", "docs-dropdown-9": "Descargas", "please-enable-javascript": "Habilita JavaScript para ver el código QR.", - "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil,", + "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil", "docs-dropdown-10": "Transparencia", "docs-dropdown-11": "FAQ", "docs-dropdown-12": "Seguridad" From 05aab35a1f96ce5b0ca64d543a427ef27930bf44 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 16 Sep 2024 18:22:50 +0100 Subject: [PATCH 055/704] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5d777dd158..dd97170700 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -167,11 +167,6 @@ 648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; }; 649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; }; 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; - 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */; }; - 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085E2C8F2CCB00AE9D01 /* libffi.a */; }; - 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */; }; - 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */; }; - 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; @@ -222,6 +217,11 @@ 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 */; }; + E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CE2C989E12001D165C /* libgmpxx.a */; }; + E55128D42C989E13001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CF2C989E12001D165C /* libffi.a */; }; + E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */; }; + E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */; }; + E55128D72C989E13001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D22C989E13001D165C /* libgmp.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 */; }; @@ -508,11 +508,6 @@ 6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; }; 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; - 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 64A2085E2C8F2CCB00AE9D01 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a"; sourceTree = ""; }; - 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a"; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; @@ -561,6 +556,11 @@ 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 = ""; }; + E55128CE2C989E12001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128CF2C989E12001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a"; sourceTree = ""; }; + E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a"; sourceTree = ""; }; + E55128D22C989E13001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.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 = ""; }; @@ -651,13 +651,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 64A208662C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a in Frameworks */, - 64A208622C8F2CCC00AE9D01 /* libgmpxx.a in Frameworks */, - 64A208652C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a in Frameworks */, + E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E55128D72C989E13001D165C /* libgmp.a in Frameworks */, + E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 64A208632C8F2CCC00AE9D01 /* libffi.a in Frameworks */, - 64A208642C8F2CCC00AE9D01 /* libgmp.a in Frameworks */, + E55128D42C989E13001D165C /* libffi.a in Frameworks */, + E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 64A2085E2C8F2CCB00AE9D01 /* libffi.a */, - 64A2085F2C8F2CCB00AE9D01 /* libgmp.a */, - 64A2085D2C8F2CCB00AE9D01 /* libgmpxx.a */, - 64A208602C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7-ghc9.6.3.a */, - 64A208612C8F2CCC00AE9D01 /* libHSsimplex-chat-6.1.0.0-G2fNFSEVU486aN7U9YSuO7.a */, + E55128CF2C989E12001D165C /* libffi.a */, + E55128D22C989E13001D165C /* libgmp.a */, + E55128CE2C989E12001D165C /* libgmpxx.a */, + E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */, + E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */, ); path = Libraries; sourceTree = ""; From af993529f907337eb313d270743c4c6539d93741 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 17 Sep 2024 12:23:50 +0100 Subject: [PATCH 056/704] core: migrate SOCKS proxy settings (#4894) --- src/Simplex/Chat/AppSettings.hs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs index 6d23a19ba1..863302fad2 100644 --- a/src/Simplex/Chat/AppSettings.hs +++ b/src/Simplex/Chat/AppSettings.hs @@ -28,6 +28,7 @@ data LockScreenCalls = LSCDisable | LSCShow | LSCAccept deriving (Show) data AppSettings = AppSettings { appPlatform :: Maybe AppPlatform, networkConfig :: Maybe NetworkConfig, + networkProxy :: Maybe NetworkProxy, privacyEncryptLocalFiles :: Maybe Bool, privacyAskToApproveRelays :: Maybe Bool, privacyAcceptImages :: Maybe Bool, @@ -57,11 +58,24 @@ data AppSettings = AppSettings } deriving (Show) +data NetworkProxy = NetworkProxy + { host :: Text, + port :: Int, + auth :: NetworkProxyAuth, + username :: Text, + password :: Text + } + deriving (Show) + +data NetworkProxyAuth = NPAUsername | NPAIsolate + deriving (Show) + defaultAppSettings :: AppSettings defaultAppSettings = AppSettings { appPlatform = Nothing, networkConfig = Just defaultNetworkConfig, + networkProxy = Nothing, privacyEncryptLocalFiles = Just True, privacyAskToApproveRelays = Just True, privacyAcceptImages = Just True, @@ -95,6 +109,7 @@ defaultParseAppSettings = AppSettings { appPlatform = Nothing, networkConfig = Nothing, + networkProxy = Nothing, privacyEncryptLocalFiles = Nothing, privacyAskToApproveRelays = Nothing, privacyAcceptImages = Nothing, @@ -128,6 +143,7 @@ combineAppSettings platformDefaults storedSettings = AppSettings { appPlatform = p appPlatform, networkConfig = p networkConfig, + networkProxy = p networkProxy, privacyEncryptLocalFiles = p privacyEncryptLocalFiles, privacyAskToApproveRelays = p privacyAskToApproveRelays, privacyAcceptImages = p privacyAcceptImages, @@ -167,12 +183,17 @@ $(JQ.deriveJSON (enumJSON $ dropPrefix "NPM") ''NotificationPreviewMode) $(JQ.deriveJSON (enumJSON $ dropPrefix "LSC") ''LockScreenCalls) +$(JQ.deriveJSON (enumJSON $ dropPrefix "NPA") ''NetworkProxyAuth) + +$(JQ.deriveJSON defaultJSON ''NetworkProxy) + $(JQ.deriveToJSON defaultJSON ''AppSettings) instance FromJSON AppSettings where parseJSON (J.Object v) = do appPlatform <- p "appPlatform" networkConfig <- p "networkConfig" + networkProxy <- p "networkProxy" privacyEncryptLocalFiles <- p "privacyEncryptLocalFiles" privacyAskToApproveRelays <- p "privacyAskToApproveRelays" privacyAcceptImages <- p "privacyAcceptImages" @@ -203,6 +224,7 @@ instance FromJSON AppSettings where AppSettings { appPlatform, networkConfig, + networkProxy, privacyEncryptLocalFiles, privacyAskToApproveRelays, privacyAcceptImages, From c13c7baaaf44f18c891b3597c4fe479998359462 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:07:29 +0000 Subject: [PATCH 057/704] desktop: less distance from edge to call icon (#4898) --- .../chat/simplex/common/views/chatlist/ChatListView.desktop.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index 9e4eeb0c96..ef5eef3fdc 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -35,7 +35,7 @@ actual fun ActiveCallInteractiveArea(call: Call) { ) { Box( Modifier - .padding(end = 71.dp, bottom = 92.dp) + .padding(end = 15.dp, bottom = 92.dp) .size(67.dp) .combinedClickable(onClick = { val chat = chatModel.getChat(call.contact.id) From 665d9dcd007b4e7bcdd5ad089ffc875c68746f60 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 17 Sep 2024 17:34:24 +0100 Subject: [PATCH 058/704] ios: SOCKS proxy UI (#4893) * ios: SOCKS proxy UI * update network config * proxy * adapt * move, dont default to localhost:9050 * move socks proxy to defaults * sock proxy preference * rename * rename * fix * fix --------- Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../Views/ChatList/ServersSummaryView.swift | 24 +++-- .../Views/Migration/MigrateFromDevice.swift | 6 ++ .../AdvancedNetworkSettings.swift | 97 +++++++++++++++++-- .../Views/UserSettings/AppSettings.swift | 11 ++- .../Views/UserSettings/SettingsView.swift | 3 + apps/ios/SimpleXChat/APITypes.swift | 66 ++++++++++++- apps/ios/SimpleXChat/AppGroup.swift | 7 +- 7 files changed, 197 insertions(+), 17 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 477a78e36d..22ea78f27b 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -407,12 +407,18 @@ struct ServersSummaryView: View { struct SubscriptionStatusIndicatorView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) if #available(iOS 16.0, *) { Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) .foregroundColor(color) @@ -425,26 +431,32 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme var subs: SMPServerSubs var hasSess: Bool var body: some View { - let onionHosts = networkUseOnionHostsGroupDefault.get() - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) + let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage( + online: m.networkInfo.online, + usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil, + subs: subs, + hasSess: hasSess, + primaryColor: theme.colors.primary + ) Text(verbatim: "\(Int(floor(statusPercent * 100)))%") .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) { +func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 : (n * 4).rounded() / 4 } - let activeColor: Color = onionHosts == .require ? .indigo : .accentColor + let activeColor: Color = usesProxy ? .indigo : primaryColor let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0) let activeSubsRounded = roundedToQuarter(subs.shareOfActive) diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index 73e5b97057..829cea0165 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -529,9 +529,15 @@ struct MigrateFromDevice: View { } case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs): let cfg = getNetCfg() + let proxy: NetworkProxy? = if cfg.socksProxy == nil { + nil + } else { + networkProxyDefault.get() + } let data = MigrationFileLinkData.init( networkConfig: MigrationFileLinkData.NetworkConfig( socksProxy: cfg.socksProxy, + networkProxy: proxy, hostMode: cfg.hostMode, requiredHostMode: cfg.requiredHostMode ) diff --git a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift index 99c0a588eb..9884c6e877 100644 --- a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift @@ -36,6 +36,10 @@ struct AdvancedNetworkSettings: View { @State private var showSettingsAlert: NetworkSettingsAlert? @State private var onionHosts: OnionHosts = .no @State private var showSaveDialog = false + @State private var netProxy = networkProxyDefault.get() + @State private var currentNetProxy = networkProxyDefault.get() + @State private var useNetProxy = false + @State private var netProxyAuth = false var body: some View { VStack { @@ -102,6 +106,76 @@ struct AdvancedNetworkSettings: View { .foregroundColor(theme.colors.secondary) } + Section { + Toggle("Use SOCKS proxy", isOn: $useNetProxy) + Group { + TextField("IP address", text: $netProxy.host) + TextField( + "Port", + text: Binding( + get: { netProxy.port > 0 ? "\(netProxy.port)" : "" }, + set: { s in + netProxy.port = if let port = Int(s), port > 0 { + port + } else { + 0 + } + } + ) + ) + Toggle("Proxy requires password", isOn: $netProxyAuth) + if netProxyAuth { + TextField("Username", text: $netProxy.username) + PassphraseField( + key: $netProxy.password, + placeholder: "Password", + valid: NetworkProxy.validCredential(netProxy.password) + ) + } + } + .if(!useNetProxy) { $0.foregroundColor(theme.colors.secondary) } + .disabled(!useNetProxy) + } header: { + HStack { + Text("SOCKS proxy").foregroundColor(theme.colors.secondary) + if useNetProxy && !netProxy.valid { + Spacer() + Image(systemName: "exclamationmark.circle.fill").foregroundColor(.red) + } + } + } footer: { + if netProxyAuth { + Text("Your credentials may be sent unencrypted.") + .foregroundColor(theme.colors.secondary) + } else { + Text("Do not use credentials with proxy.") + .foregroundColor(theme.colors.secondary) + } + } + .onChange(of: useNetProxy) { useNetProxy in + netCfg.socksProxy = useNetProxy && currentNetProxy.valid + ? currentNetProxy.toProxyString() + : nil + netProxy = currentNetProxy + netProxyAuth = netProxy.username != "" || netProxy.password != "" + } + .onChange(of: netProxyAuth) { netProxyAuth in + if netProxyAuth { + netProxy.auth = currentNetProxy.auth + netProxy.username = currentNetProxy.username + netProxy.password = currentNetProxy.password + } else { + netProxy.auth = .username + netProxy.username = "" + netProxy.password = "" + } + } + .onChange(of: netProxy) { netProxy in + netCfg.socksProxy = useNetProxy && netProxy.valid + ? netProxy.toProxyString() + : nil + } + Section { Picker("Use .onion hosts", selection: $onionHosts) { ForEach(OnionHosts.values, id: \.self) { Text($0.text) } @@ -156,19 +230,19 @@ struct AdvancedNetworkSettings: View { Section { Button("Reset to defaults") { - updateNetCfgView(NetCfg.defaults) + updateNetCfgView(NetCfg.defaults, NetworkProxy.def) } .disabled(netCfg == NetCfg.defaults) Button("Set timeouts for proxy/VPN") { - updateNetCfgView(netCfg.withProxyTimeouts) + updateNetCfgView(netCfg.withProxyTimeouts, netProxy) } .disabled(netCfg.hasProxyTimeouts) Button("Save and reconnect") { showSettingsAlert = .update } - .disabled(netCfg == currentNetCfg) + .disabled(netCfg == currentNetCfg || (useNetProxy && !netProxy.valid)) } } } @@ -182,7 +256,8 @@ struct AdvancedNetworkSettings: View { if cfgLoaded { return } cfgLoaded = true currentNetCfg = getNetCfg() - updateNetCfgView(currentNetCfg) + currentNetProxy = networkProxyDefault.get() + updateNetCfgView(currentNetCfg, currentNetProxy) } .alert(item: $showSettingsAlert) { a in switch a { @@ -206,7 +281,7 @@ struct AdvancedNetworkSettings: View { if netCfg == currentNetCfg { dismiss() cfgLoaded = false - } else { + } else if !useNetProxy || netProxy.valid { showSaveDialog = true } }) @@ -221,18 +296,26 @@ struct AdvancedNetworkSettings: View { } } - private func updateNetCfgView(_ cfg: NetCfg) { + private func updateNetCfgView(_ cfg: NetCfg, _ proxy: NetworkProxy) { netCfg = cfg + netProxy = proxy onionHosts = OnionHosts(netCfg: netCfg) enableKeepAlive = netCfg.enableKeepAlive keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults + useNetProxy = netCfg.socksProxy != nil + netProxyAuth = switch netProxy.auth { + case .username: netProxy.username != "" || netProxy.password != "" + case .isolate: false + } } private func saveNetCfg() -> Bool { do { try setNetworkConfig(netCfg) currentNetCfg = netCfg - setNetCfg(netCfg) + setNetCfg(netCfg, networkProxy: useNetProxy ? netProxy : nil) + currentNetProxy = netProxy + networkProxyDefault.set(netProxy) return true } catch let error { let err = responseError(error) diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index bd829552f4..19260ce573 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -19,9 +19,15 @@ extension AppSettings { val.hostMode = .publicHost val.requiredHostMode = true } - val.socksProxy = nil - setNetCfg(val) + if val.socksProxy != nil { + val.socksProxy = networkProxy?.toProxyString() + setNetCfg(val, networkProxy: networkProxy) + } else { + val.socksProxy = nil + setNetCfg(val, networkProxy: nil) + } } + if let val = networkProxy { networkProxyDefault.set(val) } if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) } if let val = privacyAcceptImages { @@ -63,6 +69,7 @@ extension AppSettings { let def = UserDefaults.standard var c = AppSettings.defaults c.networkConfig = getNetCfg() + c.networkProxy = networkProxyDefault.get() c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 463ac4ae07..f1140575b7 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -75,6 +75,8 @@ let DEFAULT_SYSTEM_DARK_THEME = "systemDarkTheme" let DEFAULT_CURRENT_THEME_IDS = "currentThemeIds" let DEFAULT_THEME_OVERRIDES = "themeOverrides" +let DEFAULT_NETWORK_PROXY = "networkProxy" + let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" let defaultChatItemRoundness: Double = 0.75 @@ -251,6 +253,7 @@ public class CodableDefault { } } +let networkProxyDefault: CodableDefault = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_NETWORK_PROXY, withDefault: NetworkProxy.def) struct SettingsView: View { @Environment(\.colorScheme) var colorScheme diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 7f030cb838..3cc2202bff 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI +import Network public let jsonDecoder = getJSONDecoder() public let jsonEncoder = getJSONEncoder() @@ -1497,6 +1498,63 @@ public struct KeepAliveOpts: Codable, Equatable { public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) } +public struct NetworkProxy: Equatable, Codable { + public var host: String = "" + public var port: Int = 0 + public var auth: NetworkProxyAuth = .username + public var username: String = "" + public var password: String = "" + + public static var def: NetworkProxy { + NetworkProxy() + } + + public var valid: Bool { + let hostOk = switch NWEndpoint.Host(host) { + case .ipv4: true + case .ipv6: true + default: false + } + return hostOk && + port > 0 && port <= 65535 && + NetworkProxy.validCredential(username) && NetworkProxy.validCredential(password) + } + + public static func validCredential(_ s: String) -> Bool { + !s.contains(":") && !s.contains("@") + } + + public func toProxyString() -> String? { + if !valid { return nil } + var res = "" + switch auth { + case .username: + let usernameTrimmed = username.trimmingCharacters(in: .whitespaces) + let passwordTrimmed = password.trimmingCharacters(in: .whitespaces) + if usernameTrimmed != "" || passwordTrimmed != "" { + res += usernameTrimmed + ":" + passwordTrimmed + "@" + } else { + res += "@" + } + case .isolate: () + } + if host != "" { + if host.contains(":") { + res += "[\(host.trimmingCharacters(in: [" ", "[", "]"]))]" + } else { + res += host.trimmingCharacters(in: .whitespaces) + } + } + res += ":\(port)" + return res + } +} + +public enum NetworkProxyAuth: String, Codable { + case username + case isolate +} + public enum NetworkStatus: Decodable, Equatable { case unknown case connected @@ -2120,11 +2178,13 @@ public struct MigrationFileLinkData: Codable { public struct NetworkConfig: Codable { let socksProxy: String? + let networkProxy: NetworkProxy? let hostMode: HostMode? let requiredHostMode: Bool? - public init(socksProxy: String?, hostMode: HostMode?, requiredHostMode: Bool?) { + public init(socksProxy: String?, networkProxy: NetworkProxy?, hostMode: HostMode?, requiredHostMode: Bool?) { self.socksProxy = socksProxy + self.networkProxy = networkProxy self.hostMode = hostMode self.requiredHostMode = requiredHostMode } @@ -2133,6 +2193,7 @@ public struct MigrationFileLinkData: Codable { return if let hostMode, let requiredHostMode { NetworkConfig( socksProxy: nil, + networkProxy: nil, hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, requiredHostMode: requiredHostMode ) @@ -2152,6 +2213,7 @@ public struct MigrationFileLinkData: Codable { public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil + public var networkProxy: NetworkProxy? = nil public var privacyEncryptLocalFiles: Bool? = nil public var privacyAskToApproveRelays: Bool? = nil public var privacyAcceptImages: Bool? = nil @@ -2183,6 +2245,7 @@ public struct AppSettings: Codable, Equatable { var empty = AppSettings() let def = AppSettings.defaults if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } + if networkProxy != def.networkProxy { empty.networkProxy = networkProxy } if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } @@ -2215,6 +2278,7 @@ public struct AppSettings: Codable, Equatable { public static var defaults: AppSettings { AppSettings ( networkConfig: NetCfg.defaults, + networkProxy: NetworkProxy.def, privacyEncryptLocalFiles: true, privacyAskToApproveRelays: true, privacyAcceptImages: true, diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index bd38f3568c..455607ddea 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -35,6 +35,7 @@ public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRel // replaces DEFAULT_PROFILE_IMAGE_CORNER_RADIUS public let GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" +public let GROUP_DEFAULT_NETWORK_SOCKS_PROXY = "networkSocksProxy" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode" @@ -327,6 +328,7 @@ public class Default { } public func getNetCfg() -> NetCfg { + let socksProxy = groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) let onionHosts = networkUseOnionHostsGroupDefault.get() let (hostMode, requiredHostMode) = onionHosts.hostMode let sessionMode = networkSessionModeGroupDefault.get() @@ -349,6 +351,7 @@ public func getNetCfg() -> NetCfg { tcpKeepAlive = nil } return NetCfg( + socksProxy: socksProxy, hostMode: hostMode, requiredHostMode: requiredHostMode, sessionMode: sessionMode, @@ -365,11 +368,13 @@ public func getNetCfg() -> NetCfg { ) } -public func setNetCfg(_ cfg: NetCfg) { +public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg)) networkSessionModeGroupDefault.set(cfg.sessionMode) networkSMPProxyModeGroupDefault.set(cfg.smpProxyMode) networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback) + let socksProxy = networkProxy?.toProxyString() + groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB) From 17b55c51c5f3f128b49811ad2d20777b02ab6fa2 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:50:26 +0400 Subject: [PATCH 059/704] core: update statuses of all batched messages on SENT, RCVD (#4888) * core: update statuses of all batched messages on SENT, RCVD * wip * update all * refactor --- src/Simplex/Chat.hs | 119 ++++++++++++++++------------- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Store/Messages.hs | 33 ++++---- src/Simplex/Chat/View.hs | 9 ++- 4 files changed, 90 insertions(+), 73 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9157ac7509..d5f06a326f 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4545,10 +4545,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId - ci_ <- withStore $ \db -> do - ci_ <- updateDirectItemStatus' db ct conn msgId (CISSndSent SSPComplete) - forM ci_ $ \ci -> liftIO $ setDirectSndChatItemViaProxy db user ct ci (isJust proxy) - forM_ ci_ $ \ci -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + cis <- withStore $ \db -> do + cis <- updateDirectItemsStatus' db ct conn msgId (CISSndSent SSPComplete) + liftIO $ forM cis $ \ci -> setDirectSndChatItemViaProxy db user ct ci (isJust proxy) + let acis = map ctItem cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) SWITCH qd phase cStats -> do toView $ CRContactSwitch user ct (SwitchProgress qd phase cStats) when (phase `elem` [SPStarted, SPCompleted]) $ case qd of @@ -4604,7 +4607,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processConnMERR connEntity conn err MERRS msgIds err -> do -- error cannot be AUTH error here - updateDirectItemsStatus ct conn (L.toList msgIds) (CISSndError $ agentSndError err) + updateDirectItemsStatusMsgs ct conn (L.toList msgIds) (CISSndError $ agentSndError err) toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) @@ -4958,7 +4961,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = continued <- continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId - updateGroupItemStatus gInfo m conn msgId GSSSent (Just $ isJust proxy) + updateGroupItemsStatus gInfo m conn msgId GSSSent (Just $ isJust proxy) when continued $ sendPendingGroupMessages user m conn SWITCH qd phase cStats -> do toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) @@ -5002,10 +5005,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn MWARN msgId err -> do - withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (GSSWarning $ agentSndError err) + withStore' $ \db -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) (GSSWarning $ agentSndError err) processConnMWARN connEntity conn err MERR msgId err -> do - withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) + withStore' $ \db -> updateGroupItemsErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) -- group errors are silenced to reduce load on UI event log -- toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err @@ -5013,7 +5016,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let newStatus = GSSError $ agentSndError err -- error cannot be AUTH error here withStore' $ \db -> forM_ msgIds $ \msgId -> - updateGroupItemErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () + updateGroupItemsErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) @@ -5021,10 +5024,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () where - updateGroupItemErrorStatus :: DB.Connection -> AgentMsgId -> GroupMemberId -> GroupSndStatus -> IO () - updateGroupItemErrorStatus db msgId groupMemberId newStatus = do - chatItemId_ <- getChatItemIdByAgentMsgId db connId msgId - forM_ chatItemId_ $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus + updateGroupItemsErrorStatus :: DB.Connection -> AgentMsgId -> GroupMemberId -> GroupSndStatus -> IO () + updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do + itemIds <- getChatItemIdsByAgentMsgId db connId msgId + forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32) agentMsgDecryptError = \case @@ -6696,43 +6699,42 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus updateDirectItemStatus ct conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete - -- TODO [batch send] update status of all messages in batch - -- - this is for when we implement identifying inactive connections - -- - regular messages sent in batch would all be marked as delivered by a single receipt - -- - repeat for directMsgReceived if same logic is applied to direct messages - -- - getChatItemIdByAgentMsgId to return [ChatItemId] groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> CM () groupMsgReceived gInfo m conn@Connection {connId} msgMeta msgRcpts = do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus - updateGroupItemStatus gInfo m conn agentMsgId (GSSRcvd msgRcptStatus) Nothing + updateGroupItemsStatus gInfo m conn agentMsgId (GSSRcvd msgRcptStatus) Nothing - updateDirectItemsStatus :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM () - updateDirectItemsStatus ct conn msgIds newStatus = do - cis_ <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemStatus' db ct conn msgId newStatus - -- only send the last expired item event to view - case reverse $ catMaybes $ rights cis_ of - ci : _ -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) - _ -> pure () + -- Searches chat items for many agent message IDs and updates their status + updateDirectItemsStatusMsgs :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM () + updateDirectItemsStatusMsgs ct conn msgIds newStatus = do + cis <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemsStatus' db ct conn msgId newStatus + let acis = map ctItem $ concat $ rights cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) updateDirectItemStatus :: Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> CM () updateDirectItemStatus ct conn msgId newStatus = do - ci_ <- withStore $ \db -> updateDirectItemStatus' db ct conn msgId newStatus - forM_ ci_ $ \ci -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + cis <- withStore $ \db -> updateDirectItemsStatus' db ct conn msgId newStatus + let acis = map ctItem cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + ctItem = AChatItem SCTDirect SMDSnd (DirectChat ct) - updateDirectItemStatus' :: DB.Connection -> Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> ExceptT StoreError IO (Maybe (ChatItem 'CTDirect 'MDSnd)) - updateDirectItemStatus' db ct@Contact {contactId} Connection {connId} msgId newStatus = - liftIO (getDirectChatItemByAgentMsgId db user contactId connId msgId) >>= \case - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ _}}) -> pure Nothing - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) - | itemStatus == newStatus -> pure Nothing - | otherwise -> Just <$> updateDirectChatItemStatus db user ct itemId newStatus - _ -> pure Nothing - - updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> GroupSndStatus -> CM Bool - updateGroupMemSndStatus itemId groupMemberId newStatus = - withStore' $ \db -> updateGroupMemSndStatus' db itemId groupMemberId newStatus + updateDirectItemsStatus' :: DB.Connection -> Contact -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> ExceptT StoreError IO [ChatItem 'CTDirect 'MDSnd] + updateDirectItemsStatus' db ct@Contact {contactId} Connection {connId} msgId newStatus = do + items <- liftIO $ getDirectChatItemsByAgentMsgId db user contactId connId msgId + catMaybes <$> mapM updateItem items + where + updateItem :: CChatItem 'CTDirect -> ExceptT StoreError IO (Maybe (ChatItem 'CTDirect 'MDSnd)) + updateItem = \case + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ _}}) -> pure Nothing + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) + | itemStatus == newStatus -> pure Nothing + | otherwise -> Just <$> updateDirectChatItemStatus db user ct itemId newStatus + _ -> pure Nothing updateGroupMemSndStatus' :: DB.Connection -> ChatItemId -> GroupMemberId -> GroupSndStatus -> IO Bool updateGroupMemSndStatus' db itemId groupMemberId newStatus = @@ -6743,20 +6745,29 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> updateGroupSndStatus db itemId groupMemberId newStatus $> True _ -> pure False - updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> GroupSndStatus -> Maybe Bool -> CM () - updateGroupItemStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus viaProxy_ = - withStore' (\db -> getGroupChatItemByAgentMsgId db user groupId connId msgId) >>= \case - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure () - Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) -> do - forM_ viaProxy_ $ \viaProxy -> withStore' $ \db -> setGroupSndViaProxy db itemId groupMemberId viaProxy - memStatusChanged <- updateGroupMemSndStatus itemId groupMemberId newMemStatus - when memStatusChanged $ do - memStatusCounts <- withStore' (`getGroupSndStatusCounts` itemId) - let newStatus = membersGroupItemStatus memStatusCounts - when (newStatus /= itemStatus) $ do - chatItem <- withStore $ \db -> updateGroupChatItemStatus db user gInfo itemId newStatus - toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem) - _ -> pure () + updateGroupItemsStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> GroupSndStatus -> Maybe Bool -> CM () + updateGroupItemsStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus viaProxy_ = do + items <- withStore' (\db -> getGroupChatItemsByAgentMsgId db user groupId connId msgId) + cis <- catMaybes <$> withStore (\db -> mapM (updateItem db) items) + let acis = map gItem cis + unless (null acis) $ toView $ CRChatItemsStatusesUpdated user acis + where + gItem = AChatItem SCTGroup SMDSnd (GroupChat gInfo) + updateItem :: DB.Connection -> CChatItem 'CTGroup -> ExceptT StoreError IO (Maybe (ChatItem 'CTGroup 'MDSnd)) + updateItem db = \case + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure Nothing + (CChatItem SMDSnd ChatItem {meta = CIMeta {itemId, itemStatus}}) -> do + forM_ viaProxy_ $ \viaProxy -> liftIO $ setGroupSndViaProxy db itemId groupMemberId viaProxy + memStatusChanged <- liftIO $ updateGroupMemSndStatus' db itemId groupMemberId newMemStatus + if memStatusChanged + then do + memStatusCounts <- liftIO $ getGroupSndStatusCounts db itemId + let newStatus = membersGroupItemStatus memStatusCounts + if newStatus /= itemStatus + then Just <$> updateGroupChatItemStatus db user gInfo itemId newStatus + else pure Nothing + else pure Nothing + _ -> pure Nothing createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (Contact, Connection) createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 1e00172cea..c80dc320f1 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -600,7 +600,7 @@ data ChatResponse | CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text} | CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text} | CRNewChatItems {user :: User, chatItems :: [AChatItem]} - | CRChatItemStatusUpdated {user :: User, chatItem :: AChatItem} + | CRChatItemsStatusesUpdated {user :: User, chatItems :: [AChatItem]} | CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem} | CRChatItemReaction {user :: User, added :: Bool, reaction :: ACIReaction} diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index f6f9588f66..e4715c7b12 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -75,16 +75,16 @@ module Simplex.Chat.Store.Messages getGroupCIReactions, getGroupReactions, setGroupReaction, - getChatItemIdByAgentMsgId, + getChatItemIdsByAgentMsgId, getDirectChatItem, getDirectCIWithReactions, getDirectChatItemBySharedMsgId, - getDirectChatItemByAgentMsgId, + getDirectChatItemsByAgentMsgId, getGroupChatItem, getGroupCIWithReactions, getGroupChatItemBySharedMsgId, getGroupMemberCIBySharedMsgId, - getGroupChatItemByAgentMsgId, + getGroupChatItemsByAgentMsgId, getGroupMemberChatItemLast, getLocalChatItem, updateLocalChatItem', @@ -1624,19 +1624,18 @@ getAllChatItems db vr user@User {userId} pagination search_ = do |] (userId, search, beforeTs, beforeTs, beforeId, count) -getChatItemIdByAgentMsgId :: DB.Connection -> Int64 -> AgentMsgId -> IO (Maybe ChatItemId) -getChatItemIdByAgentMsgId db connId msgId = - fmap join . maybeFirstRow fromOnly $ - DB.query +getChatItemIdsByAgentMsgId :: DB.Connection -> Int64 -> AgentMsgId -> IO [ChatItemId] +getChatItemIdsByAgentMsgId db connId msgId = + map fromOnly + <$> DB.query db [sql| SELECT chat_item_id FROM chat_item_messages - WHERE message_id = ( + WHERE message_id IN ( SELECT message_id FROM msg_deliveries WHERE connection_id = ? AND agent_msg_id = ? - LIMIT 1 ) |] (connId, msgId) @@ -1780,10 +1779,10 @@ getDirectChatItemBySharedMsgId db user@User {userId} contactId sharedMsgId = do itemId <- getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId getDirectChatItem db user contactId itemId -getDirectChatItemByAgentMsgId :: DB.Connection -> User -> ContactId -> Int64 -> AgentMsgId -> IO (Maybe (CChatItem 'CTDirect)) -getDirectChatItemByAgentMsgId db user contactId connId msgId = do - itemId_ <- getChatItemIdByAgentMsgId db connId msgId - maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getDirectChatItem db user contactId) itemId_ +getDirectChatItemsByAgentMsgId :: DB.Connection -> User -> ContactId -> Int64 -> AgentMsgId -> IO [CChatItem 'CTDirect] +getDirectChatItemsByAgentMsgId db user contactId connId msgId = do + itemIds <- getChatItemIdsByAgentMsgId db connId msgId + catMaybes <$> mapM (fmap eitherToMaybe . runExceptT . getDirectChatItem db user contactId) itemIds getDirectChatItemIdBySharedMsgId_ :: DB.Connection -> UserId -> Int64 -> SharedMsgId -> ExceptT StoreError IO Int64 getDirectChatItemIdBySharedMsgId_ db userId contactId sharedMsgId = @@ -2036,10 +2035,10 @@ getGroupMemberCIBySharedMsgId db user@User {userId} groupId memberId sharedMsgId (GCUserMember, userId, groupId, memberId, sharedMsgId) getGroupChatItem db user groupId itemId -getGroupChatItemByAgentMsgId :: DB.Connection -> User -> GroupId -> Int64 -> AgentMsgId -> IO (Maybe (CChatItem 'CTGroup)) -getGroupChatItemByAgentMsgId db user groupId connId msgId = do - itemId_ <- getChatItemIdByAgentMsgId db connId msgId - maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupChatItem db user groupId) itemId_ +getGroupChatItemsByAgentMsgId :: DB.Connection -> User -> GroupId -> Int64 -> AgentMsgId -> IO [CChatItem 'CTGroup] +getGroupChatItemsByAgentMsgId db user groupId connId msgId = do + itemIds <- getChatItemIdsByAgentMsgId db connId msgId + catMaybes <$> mapM (fmap eitherToMaybe . runExceptT . getGroupChatItem db user groupId) itemIds getGroupChatItem :: DB.Connection -> User -> Int64 -> ChatItemId -> ExceptT StoreError IO (CChatItem 'CTGroup) getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index cb686ef2b0..166e12b2c2 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -134,7 +134,14 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] - CRChatItemStatusUpdated u ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts + CRChatItemsStatusesUpdated u chatItems + | length chatItems <= 20 -> + concatMap + (\ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts) + chatItems + | testView && showReceipts -> + ttyUser u [sShow (length chatItems) <> " message statuses updated"] + | otherwise -> [] CRChatItemUpdated u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewItemUpdate chat item liveItems ts tz CRChatItemNotChanged u ci -> ttyUser u $ viewItemNotChanged ci CRChatItemsDeleted u deletions byUser timed -> case deletions of From 0d9ef4e56740e5e7405a0e93a50ffed4501f6057 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:51:18 +0400 Subject: [PATCH 060/704] ui: support CRChatItemsStatusesUpdated response (#4890) * ui: support new CRChatItemsStatusesUpdated api * ios * refactor --- apps/ios/Shared/Model/SimpleXAPI.swift | 36 ++++++++++--------- .../Shared/Views/Call/ActiveCallView.swift | 10 +++--- apps/ios/SimpleX SE/ShareModel.swift | 3 +- apps/ios/SimpleXChat/APITypes.swift | 8 +++-- apps/ios/SimpleXChat/ChatTypes.swift | 14 ++++++++ .../chat/simplex/common/model/SimpleXAPI.kt | 21 +++++------ .../simplex/common/views/call/CallView.kt | 9 ++--- .../simplex/common/views/chat/ChatView.kt | 4 +-- 8 files changed, 64 insertions(+), 41 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 7312b42b1b..6bbacea245 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1821,23 +1821,25 @@ func processReceivedMsg(_ res: ChatResponse) async { NtfManager.shared.notifyMessageReceived(user, cInfo, cItem) } } - case let .chatItemStatusUpdated(user, aChatItem): - let cInfo = aChatItem.chatInfo - let cItem = aChatItem.chatItem - if !cItem.isDeletedContent && active(user) { - await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } - } - if let endTask = m.messageDelivery[cItem.id] { - switch cItem.meta.itemStatus { - case .sndNew: () - case .sndSent: endTask() - case .sndRcvd: endTask() - case .sndErrorAuth: endTask() - case .sndError: endTask() - case .sndWarning: endTask() - case .rcvNew: () - case .rcvRead: () - case .invalid: () + case let .chatItemsStatusesUpdated(user, chatItems): + for chatItem in chatItems { + let cInfo = chatItem.chatInfo + let cItem = chatItem.chatItem + if !cItem.isDeletedContent && active(user) { + await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) } + } + if let endTask = m.messageDelivery[cItem.id] { + switch cItem.meta.itemStatus { + case .sndNew: () + case .sndSent: endTask() + case .sndRcvd: endTask() + case .sndErrorAuth: endTask() + case .sndError: endTask() + case .sndWarning: endTask() + case .rcvNew: () + case .rcvRead: () + case .invalid: () + } } } case let .chatItemUpdated(user, aChatItem): diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index d238c2dbae..049ee95a09 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -214,10 +214,12 @@ struct ActiveCallView: View { ChatReceiver.shared.messagesChannel = nil return } - if case let .chatItemStatusUpdated(_, msg) = msg, - msg.chatInfo.id == call.contact.id, - case .sndCall = msg.chatItem.content, - case .sndRcvd = msg.chatItem.meta.itemStatus { + if case let .chatItemsStatusesUpdated(_, chatItems) = msg, + chatItems.contains(where: { ci in + ci.chatInfo.id == call.contact.id && + ci.chatItem.content.isSndCall && + ci.chatItem.meta.itemStatus.isSndRcvd + }) { CallSoundsPlayer.shared.startInCallSound() ChatReceiver.shared.messagesChannel = nil } diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift index e73aeee13c..807f2c9903 100644 --- a/apps/ios/SimpleX SE/ShareModel.swift +++ b/apps/ios/SimpleX SE/ShareModel.swift @@ -320,7 +320,8 @@ class ShareModel: ObservableObject { } await ch.completeFile() if await !ch.isRunning { break } - case let .chatItemStatusUpdated(_, ci): + case let .chatItemsStatusesUpdated(_, chatItems): + guard let ci = chatItems.last else { continue } guard isMessage(for: ci) else { continue } if let (title, message) = ci.chatItem.meta.itemStatus.statusInfo { // `title` and `message` already localized and interpolated diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 3cc2202bff..c210431d12 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -601,7 +601,7 @@ public enum ChatResponse: Decodable, Error { case groupEmpty(user: UserRef, groupInfo: GroupInfo) case userContactLinkSubscribed case newChatItems(user: UserRef, chatItems: [AChatItem]) - case chatItemStatusUpdated(user: UserRef, chatItem: AChatItem) + case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) case chatItemReaction(user: UserRef, added: Bool, reaction: ACIReaction) @@ -772,7 +772,7 @@ public enum ChatResponse: Decodable, Error { case .groupEmpty: return "groupEmpty" case .userContactLinkSubscribed: return "userContactLinkSubscribed" case .newChatItems: return "newChatItems" - case .chatItemStatusUpdated: return "chatItemStatusUpdated" + case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" case .chatItemReaction: return "chatItemReaction" @@ -943,7 +943,9 @@ public enum ChatResponse: Decodable, Error { case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) - case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .chatItemsStatusesUpdated(u, chatItems): + let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") + return withUser(u, itemsString) case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemNotChanged(u, chatItem): return withUser(u, String(describing: chatItem)) case let .chatItemReaction(u, added, reaction): return withUser(u, "added: \(added)\n\(String(describing: reaction))") diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 84bf445601..6b03833d08 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2856,6 +2856,13 @@ public enum CIStatus: Decodable, Hashable { ) } } + + public var isSndRcvd: Bool { + switch self { + case .sndRcvd: return true + default: return false + } + } } public enum SndError: Decodable, Hashable { @@ -3152,6 +3159,13 @@ public enum CIContent: Decodable, ItemContent, Hashable { default: return false } } + + public var isSndCall: Bool { + switch self { + case .sndCall: return true + default: return false + } + } } public enum MsgDecryptError: String, Decodable, Hashable { 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 452e8a704b..de31229697 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 @@ -2186,15 +2186,16 @@ object ChatController { } } } - is CR.ChatItemStatusUpdated -> { - val cInfo = r.chatItem.chatInfo - val cItem = r.chatItem.chatItem - if (!cItem.isDeletedContent && active(r.user)) { - withChats { - updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + is CR.ChatItemsStatusesUpdated -> + r.chatItems.forEach { chatItem -> + val cInfo = chatItem.chatInfo + val cItem = chatItem.chatItem + if (!cItem.isDeletedContent && active(r.user)) { + withChats { + updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus) + } } } - } is CR.ChatItemUpdated -> chatItemSimpleUpdate(rhId, r.user, r.chatItem) is CR.ChatItemReaction -> { @@ -4831,7 +4832,7 @@ sealed class CR { @Serializable @SerialName("groupEmpty") class GroupEmpty(val user: UserRef, val group: GroupInfo): CR() @Serializable @SerialName("userContactLinkSubscribed") class UserContactLinkSubscribed: CR() @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() - @Serializable @SerialName("chatItemStatusUpdated") class ChatItemStatusUpdated(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR() @@ -5008,7 +5009,7 @@ sealed class CR { is GroupEmpty -> "groupEmpty" is UserContactLinkSubscribed -> "userContactLinkSubscribed" is NewChatItems -> "newChatItems" - is ChatItemStatusUpdated -> "chatItemStatusUpdated" + is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated" is ChatItemUpdated -> "chatItemUpdated" is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" @@ -5177,7 +5178,7 @@ sealed class CR { is GroupEmpty -> withUser(user, json.encodeToString(group)) is UserContactLinkSubscribed -> noDetails() is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) - is ChatItemStatusUpdated -> withUser(user, json.encodeToString(chatItem)) + is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt index e4a6691d49..ae020ea3a1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallView.kt @@ -15,10 +15,11 @@ fun activeCallWaitDeliveryReceipt(scope: CoroutineScope) = scope.launch(Dispatch if (call == null || call.callState > CallState.InvitationSent) break val msg = apiResp.resp if (apiResp.remoteHostId == call.remoteHostId && - msg is CR.ChatItemStatusUpdated && - msg.chatItem.chatInfo.id == call.contact.id && - msg.chatItem.chatItem.content is CIContent.SndCall && - msg.chatItem.chatItem.meta.itemStatus is CIStatus.SndRcvd) { + msg is CR.ChatItemsStatusesUpdated && + msg.chatItems.any { + it.chatInfo.id == call.contact.id && it.chatItem.content is CIContent.SndCall && it.chatItem.meta.itemStatus is CIStatus.SndRcvd + } + ) { CallSoundsPlayer.startInCallSound(scope) break } 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 511230cc83..2bef0bdca7 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 @@ -444,8 +444,8 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - for (apiResp in controller.messagesChannel) { val msg = apiResp.resp if (apiResp.remoteHostId == chatRh && - msg is CR.ChatItemStatusUpdated && - msg.chatItem.chatItem.id == cItem.id + msg is CR.ChatItemsStatusesUpdated && + msg.chatItems.any { it.chatItem.id == cItem.id } ) { ciInfo = loadChatItemInfo() ?: return@withContext initialCiInfo = ciInfo From 69bbe0ae916d02039c0e0f7ae1c5903ebd0bd534 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 17 Sep 2024 20:09:50 +0000 Subject: [PATCH 061/704] android, desktop: proxy configuration includes credentials (#4892) * android, desktop: proxy configuration includes credentials * migration * changes for disabled socks * migration * port * new logic * migration * check validity of fields * validity of host * import changes proxy just in case * send port always * non-nullable * Revert "send port always" This reverts commit 14dd066d80f4c6c6b1061e8e6142bf18f83b97bb. * string --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/SimpleXAPI.kt | 91 ++++-- .../views/migration/MigrateFromDevice.kt | 16 +- .../common/views/migration/MigrateToDevice.kt | 177 ++++++----- .../usersettings/AdvancedNetworkSettings.kt | 32 +- .../views/usersettings/NetworkAndServers.kt | 285 ++++++++++++------ .../commonMain/resources/MR/base/strings.xml | 10 + 6 files changed, 377 insertions(+), 234 deletions(-) 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 de31229697..906ca826ca 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 @@ -129,7 +129,22 @@ class AppPreferences { val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false) val networkShowSubscriptionPercentage = mkBoolPreference(SHARED_PREFS_NETWORK_SHOW_SUBSCRIPTION_PERCENTAGE, false) - val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050") + private val _networkProxy = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, json.encodeToString(NetworkProxy())) + val networkProxy: SharedPreference = SharedPreference( + get = fun(): NetworkProxy { + val value = _networkProxy.get() ?: return NetworkProxy() + return try { + if (value.startsWith("{")) { + json.decodeFromString(value) + } else { + NetworkProxy(host = value.substringBefore(":").ifBlank { "localhost" }, port = value.substringAfter(":").toIntOrNull() ?: 9050) + } + } catch (e: Throwable) { + NetworkProxy() + } + }, + set = fun(proxy: NetworkProxy) { _networkProxy.set(json.encodeToString(proxy)) } + ) private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name) val networkSessionMode: SharedPreference = SharedPreference( get = fun(): TransportSessionMode { @@ -531,7 +546,7 @@ object ChatController { suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? { Log.d(TAG, "startChatWithTemporaryDatabase") val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) - if (!apiSetNetworkConfig(netCfg, ctrl)) { + if (!apiSetNetworkConfig(netCfg, ctrl = ctrl)) { Log.e(TAG, "Error setting network config, stopping migration") return null } @@ -976,16 +991,18 @@ object ChatController { throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } - suspend fun apiSetNetworkConfig(cfg: NetCfg, ctrl: ChatCtrl? = null): Boolean { + suspend fun apiSetNetworkConfig(cfg: NetCfg, showAlertOnError: Boolean = true, ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl) return when (r) { is CR.CmdOk -> true else -> { Log.e(TAG, "apiSetNetworkConfig bad response: ${r.responseType} ${r.details}") - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.error_setting_network_config), - "${r.responseType}: ${r.details}" - ) + if (showAlertOnError) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_setting_network_config), + "${r.responseType}: ${r.details}" + ) + } false } } @@ -2777,13 +2794,9 @@ object ChatController { fun getNetCfg(): NetCfg { val useSocksProxy = appPrefs.networkUseSocksProxy.get() - val proxyHostPort = appPrefs.networkProxyHostPort.get() + val networkProxy = appPrefs.networkProxy.get() val socksProxy = if (useSocksProxy) { - if (proxyHostPort?.startsWith("localhost:") == true) { - proxyHostPort.removePrefix("localhost") - } else { - proxyHostPort ?: ":9050" - } + networkProxy.toProxyString() } else { null } @@ -2825,7 +2838,7 @@ object ChatController { } /** - * [AppPreferences.networkProxyHostPort] is not changed here, use appPrefs to set it + * [AppPreferences.networkProxy] is not changed here, use appPrefs to set it * */ fun setNetCfg(cfg: NetCfg) { appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy) @@ -3569,13 +3582,8 @@ data class NetCfg( val useSocksProxy: Boolean get() = socksProxy != null val enableKeepAlive: Boolean get() = tcpKeepAlive != null - fun withHostPort(hostPort: String?, default: String? = ":9050"): NetCfg { - val socksProxy = if (hostPort?.startsWith("localhost:") == true) { - hostPort.removePrefix("localhost") - } else { - hostPort ?: default - } - return copy(socksProxy = socksProxy) + fun withProxy(proxy: NetworkProxy?, default: String? = ":9050"): NetCfg { + return copy(socksProxy = proxy?.toProxyString() ?: default) } companion object { @@ -3617,6 +3625,39 @@ data class NetCfg( } } +@Serializable +data class NetworkProxy( + val username: String = "", + val password: String = "", + val auth: NetworkProxyAuth = NetworkProxyAuth.ISOLATE, + val host: String = "localhost", + val port: Int = 9050 +) { + fun toProxyString(): String { + var res = "" + if (auth == NetworkProxyAuth.USERNAME && (username.isNotBlank() || password.isNotBlank())) { + res += username.trim() + ":" + password.trim() + "@" + } else if (auth == NetworkProxyAuth.USERNAME) { + res += "@" + } + if (host != "localhost") { + res += if (host.contains(':')) "[${host.trim(' ', '[', ']')}]" else host.trim() + } + if (port != 9050 || res.isEmpty()) { + res += ":$port" + } + return res + } +} + +@Serializable +enum class NetworkProxyAuth { + @SerialName("isolate") + ISOLATE, + @SerialName("username") + USERNAME, +} + enum class OnionHosts { NEVER, PREFER, REQUIRED } @@ -6184,6 +6225,7 @@ enum class NotificationsMode() { @Serializable data class AppSettings( var networkConfig: NetCfg? = null, + var networkProxy: NetworkProxy? = null, var privacyEncryptLocalFiles: Boolean? = null, var privacyAskToApproveRelays: Boolean? = null, var privacyAcceptImages: Boolean? = null, @@ -6215,6 +6257,7 @@ data class AppSettings( val empty = AppSettings() val def = defaults if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig } + if (networkProxy != def.networkProxy) { empty.networkProxy = networkProxy } if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } if (privacyAskToApproveRelays != def.privacyAskToApproveRelays) { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages } @@ -6252,8 +6295,12 @@ data class AppSettings( if (net.hostMode == HostMode.Onion) { net = net.copy(hostMode = HostMode.Public, requiredHostMode = true) } + if (net.socksProxy != null) { + net = net.copy(socksProxy = networkProxy?.toProxyString()) + } setNetCfg(net) } + networkProxy?.let { def.networkProxy.set(it) } privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) } privacyAskToApproveRelays?.let { def.privacyAskToApproveRelays.set(it) } privacyAcceptImages?.let { def.privacyAcceptImages.set(it) } @@ -6286,6 +6333,7 @@ data class AppSettings( val defaults: AppSettings get() = AppSettings( networkConfig = NetCfg.defaults, + networkProxy = null, privacyEncryptLocalFiles = true, privacyAskToApproveRelays = true, privacyAcceptImages = true, @@ -6319,6 +6367,7 @@ data class AppSettings( val def = appPreferences return defaults.copy( networkConfig = getNetCfg(), + networkProxy = def.networkProxy.get(), privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(), privacyAskToApproveRelays = def.privacyAskToApproveRelays.get(), privacyAcceptImages = def.privacyAcceptImages.get(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index a71503e315..4cc7899cc8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -4,7 +4,6 @@ import SectionBottomSpacer import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -17,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase @@ -38,7 +38,6 @@ import kotlinx.serialization.* import java.io.File import java.net.URLEncoder import kotlin.math.max -import kotlin.math.sqrt @Serializable data class MigrationFileLinkData( @@ -46,16 +45,20 @@ data class MigrationFileLinkData( ) { @Serializable data class NetworkConfig( - val socksProxy: String?, + // Legacy. Remove in 2025 + @SerialName("socksProxy") + val legacySocksProxy: String?, + val networkProxy: NetworkProxy?, val hostMode: HostMode?, val requiredHostMode: Boolean? ) { - fun hasOnionConfigured(): Boolean = socksProxy != null || hostMode == HostMode.Onion + fun hasProxyConfigured(): Boolean = networkProxy != null || legacySocksProxy != null || hostMode == HostMode.Onion fun transformToPlatformSupported(): NetworkConfig { return if (hostMode != null && requiredHostMode != null) { NetworkConfig( - socksProxy = if (hostMode == HostMode.Onion) socksProxy ?: NetCfg.proxyDefaults.socksProxy else socksProxy, + legacySocksProxy = if (hostMode == HostMode.Onion) legacySocksProxy ?: NetCfg.proxyDefaults.socksProxy else legacySocksProxy, + networkProxy = if (hostMode == HostMode.Onion) networkProxy ?: NetworkProxy() else networkProxy, hostMode = if (hostMode == HostMode.Onion) HostMode.OnionViaSocks else hostMode, requiredHostMode = requiredHostMode ) @@ -570,7 +573,8 @@ private fun MutableState.startUploading( val cfg = getNetCfg() val data = MigrationFileLinkData( networkConfig = MigrationFileLinkData.NetworkConfig( - socksProxy = cfg.socksProxy, + legacySocksProxy = null, + networkProxy = if (appPrefs.networkUseSocksProxy.get()) appPrefs.networkProxy.get() else null, hostMode = cfg.hostMode, requiredHostMode = cfg.requiredHostMode ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 8312c213ec..415f5cdd57 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -5,16 +5,15 @@ import SectionItemView import SectionSpacer import SectionTextFooter import SectionView -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import chat.simplex.common.model.* import chat.simplex.common.model.AppPreferences.Companion.SHARED_PREFS_MIGRATION_TO_STAGE +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatCtrl @@ -41,10 +40,10 @@ import kotlin.math.max @Serializable sealed class MigrationToDeviceState { - @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() - @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() - @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() - @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg): MigrationToDeviceState() + @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val networkProxy: NetworkProxy?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() + @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() + @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() + @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState() companion object { // Here we check whether it's needed to show migration process after app restart or not @@ -66,10 +65,10 @@ sealed class MigrationToDeviceState { null } else { val archivePath = File(getMigrationTempFilesDirectory(), state.archiveName) - MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg) + MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg, state.networkProxy) } } - is Passphrase -> MigrationToState.Passphrase("", state.netCfg) + is Passphrase -> MigrationToState.Passphrase("", state.netCfg, state.networkProxy) } if (initial == null) { settings.remove(SHARED_PREFS_MIGRATION_TO_STAGE) @@ -91,16 +90,24 @@ sealed class MigrationToDeviceState { @Serializable sealed class MigrationToState { @Serializable object PasteOrScanLink: MigrationToState() - @Serializable data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToState() - @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val ctrl: ChatCtrl?): MigrationToState() - @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg): MigrationToState() - @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() - @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() + @Serializable data class Onion( + val link: String, + // Legacy, remove in 2025 + @SerialName("socksProxy") + val legacySocksProxy: String?, + val networkProxy: NetworkProxy?, + val hostMode: HostMode, + val requiredHostMode: Boolean + ): MigrationToState() + @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?, val ctrl: ChatCtrl?): MigrationToState() + @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() + @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState() } private var MutableState.state: MigrationToState? @@ -175,16 +182,16 @@ private fun ModalData.SectionByState( when (val s = migrationState.value) { null -> {} is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView() - is MigrationToState.Onion -> OnionView(s.link, s.socksProxy, s.hostMode, s.requiredHostMode, migrationState) - is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg) - is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg) + is MigrationToState.Onion -> OnionView(s.link, s.legacySocksProxy, s.networkProxy, s.hostMode, s.requiredHostMode, migrationState) + is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg, s.networkProxy) + is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg, s.networkProxy) is MigrationToState.DownloadProgress -> DownloadProgressView(s.downloadedBytes, totalBytes = s.totalBytes) - is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg) - is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg) - is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg) - is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg) - is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg) - is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, close) + is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg, s.networkProxy) + is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg, s.networkProxy) + is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg, s.networkProxy) + is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, s.networkProxy, close) } } @@ -216,21 +223,24 @@ private fun MutableState.PasteLinkView() { } @Composable -private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { +private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) { val onionHosts = remember { stateGetOrPut("onionHosts") { - getNetCfg().copy(socksProxy = socksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts + getNetCfg().copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts } } - val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { socksProxy != null } } + val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { linkNetworkProxy != null || legacyLinkSocksProxy != null } } val sessionMode = remember { stateGetOrPut("sessionMode") { TransportSessionMode.User} } - val networkProxyHostPort = remember { stateGetOrPut("networkHostProxyPort") { - var proxy = (socksProxy ?: chatModel.controller.appPrefs.networkProxyHostPort.get()) - if (proxy?.startsWith(":") == true) proxy = "localhost$proxy" - proxy - } + val networkProxy = remember { stateGetOrPut("networkProxy") { + linkNetworkProxy + ?: if (legacyLinkSocksProxy != null) { + NetworkProxy(host = legacyLinkSocksProxy.substringBefore(":").ifBlank { "localhost" }, port = legacyLinkSocksProxy.substringAfter(":").toIntOrNull() ?: 9050) + } else { + appPrefs.networkProxy.get() + } + } } val netCfg = rememberSaveable(stateSaver = serializableSaver()) { - mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = socksProxy, sessionMode = sessionMode.value)) + mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, sessionMode = sessionMode.value)) } SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings).uppercase()) { @@ -241,12 +251,12 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos click = { val updated = netCfg.value .withOnionHosts(onionHosts.value) - .withHostPort(if (networkUseSocksProxy.value) networkProxyHostPort.value else null, null) + .withProxy(if (networkUseSocksProxy.value) networkProxy.value else null, null) .copy( sessionMode = sessionMode.value ) withBGApi { - state.value = MigrationToState.DatabaseInit(link, updated) + state.value = MigrationToState.DatabaseInit(link, updated, if (networkUseSocksProxy.value) networkProxy.value else null) } } ){} @@ -255,8 +265,8 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos SectionSpacer() - val networkProxyHostPortPref = SharedPreference(get = { networkProxyHostPort.value }, set = { - networkProxyHostPort.value = it + val networkProxyPref = SharedPreference(get = { networkProxy.value }, set = { + networkProxy.value = it }) SectionView(stringResource(MR.strings.network_settings_title).uppercase()) { OnionRelatedLayout( @@ -264,13 +274,10 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos networkUseSocksProxy, onionHosts, sessionMode, - networkProxyHostPortPref, + networkProxyPref, toggleSocksProxy = { enable -> networkUseSocksProxy.value = enable }, - useOnion = { - onionHosts.value = it - }, updateSessionMode = { sessionMode.value = it } @@ -279,13 +286,13 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos } @Composable -private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg) { +private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) { Box { SectionView(stringResource(MR.strings.migrate_to_device_database_init).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - prepareDatabase(link, tempDatabaseFile, netCfg) + prepareDatabase(link, tempDatabaseFile, netCfg, networkProxy) } } @@ -297,14 +304,15 @@ private fun MutableState.LinkDownloadingView( archivePath: String, tempDatabaseFile: File, chatReceiver: MutableState, - netCfg: NetCfg + netCfg: NetCfg, + networkProxy: NetworkProxy? ) { Box { SectionView(stringResource(MR.strings.migrate_to_device_downloading_details).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg) + startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg, networkProxy) } } @@ -319,14 +327,14 @@ private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) { } @Composable -private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg) { +private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { SectionView(stringResource(MR.strings.migrate_to_device_download_failed).uppercase()) { SettingsActionItemWithContent( icon = painterResource(MR.images.ic_download), text = stringResource(MR.strings.migrate_to_device_repeat_download), textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.DatabaseInit(link, netCfg) + state = MigrationToState.DatabaseInit(link, netCfg, networkProxy) } ) {} SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) @@ -339,25 +347,25 @@ private fun MutableState.DownloadFailedView(link: String, cha } @Composable -private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg) { +private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { Box { SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - importArchive(archivePath, netCfg) + importArchive(archivePath, netCfg, networkProxy) } } @Composable -private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg) { +private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) { SettingsActionItemWithContent( icon = painterResource(MR.images.ic_download), text = stringResource(MR.strings.migrate_to_device_repeat_import), textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.ArchiveImport(archivePath, netCfg) + state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy) } ) {} SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) @@ -365,7 +373,7 @@ private fun MutableState.ArchiveImportFailedView(archivePath: } @Composable -private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg) { +private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { val currentKey = rememberSaveable { mutableStateOf(currentKey) } val verifyingPassphrase = rememberSaveable { mutableStateOf(false) } val useKeychain = rememberSaveable { mutableStateOf(appPreferences.storeDBPassphrase.get()) } @@ -395,9 +403,9 @@ private fun MutableState.PassphraseEnteringView(currentKey: S val (status, _) = chatInitTemporaryDatabase(dbAbsolutePrefixPath, key = currentKey.value, confirmation = MigrationConfirmation.YesUp) val success = status == DBMigrationResult.OK || status == DBMigrationResult.InvalidConfirmation if (success) { - state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg) + state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg, networkProxy) } else if (status is DBMigrationResult.ErrorMigration) { - state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg) + state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg, networkProxy) } else { showErrorOnMigrationIfNeeded(status) } @@ -414,7 +422,7 @@ private fun MutableState.PassphraseEnteringView(currentKey: S } @Composable -private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg) { +private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?) { data class Tuple4(val a: A, val b: B, val c: C, val d: D) val (header: String, button: String?, footer: String, confirmation: MigrationConfirmation?) = when (status) { is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) { @@ -449,7 +457,7 @@ private fun MutableState.MigrationConfirmationView(status: DB text = button, textColor = MaterialTheme.colors.primary, click = { - state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg) + state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg, networkProxy) } ) {} } @@ -458,13 +466,13 @@ private fun MutableState.MigrationConfirmationView(status: DB } @Composable -private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { +private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) { Box { SectionView(stringResource(MR.strings.migrate_to_device_migrating).uppercase()) {} ProgressView() } LaunchedEffect(Unit) { - startChat(passphrase, confirmation, useKeychain, netCfg, close) + startChat(passphrase, confirmation, useKeychain, netCfg, networkProxy, close) } } @@ -476,19 +484,21 @@ private fun ProgressView() { private suspend fun MutableState.checkUserLink(link: String) { if (strHasSimplexFileLink(link.trim())) { val data = MigrationFileLinkData.readFromLink(link) - val hasOnionConfigured = data?.networkConfig?.hasOnionConfigured() ?: false + val hasProxyConfigured = data?.networkConfig?.hasProxyConfigured() ?: false val networkConfig = data?.networkConfig?.transformToPlatformSupported() // If any of iOS or Android had onion enabled, show onion screen - if (hasOnionConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { - state = MigrationToState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode) - MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) + if (hasProxyConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { + state = MigrationToState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode) + MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) } else { val current = getNetCfg() state = MigrationToState.DatabaseInit(link.trim(), current.copy( - socksProxy = networkConfig?.socksProxy, + socksProxy = null, hostMode = networkConfig?.hostMode ?: current.hostMode, requiredHostMode = networkConfig?.requiredHostMode ?: current.requiredHostMode - )) + ), + networkProxy = null + ) } } else { AlertManager.shared.showAlertMsg( @@ -502,6 +512,7 @@ private fun MutableState.prepareDatabase( link: String, tempDatabaseFile: File, netCfg: NetCfg, + networkProxy: NetworkProxy? ) { withLongRunningApi { val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, netCfg) @@ -513,7 +524,7 @@ private fun MutableState.prepareDatabase( } val (ctrl, user) = ctrlAndUser - state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg) + state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg, networkProxy) } } @@ -526,13 +537,14 @@ private fun MutableState.startDownloading( link: String, archivePath: String, netCfg: NetCfg, + networkProxy: NetworkProxy? ) { withBGApi { chatReceiver.value = MigrationToChatReceiver(ctrl, tempDatabaseFile) { msg -> when (msg) { is CR.RcvFileProgressXFTP -> { - state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, ctrl) - MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg)) + state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, networkProxy, ctrl) + MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg, networkProxy)) } is CR.RcvStandaloneFileComplete -> { delay(500) @@ -540,8 +552,8 @@ private fun MutableState.startDownloading( if (state == null) { MigrationToDeviceState.save(null) } else { - state = MigrationToState.ArchiveImport(archivePath, netCfg) - MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg)) + state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy) + MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg, networkProxy)) } } is CR.RcvFileError -> { @@ -549,7 +561,7 @@ private fun MutableState.startDownloading( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } is CR.ChatRespError -> { if (msg.chatError is ChatError.ChatErrorChat && msg.chatError.errorType is ChatErrorType.NoRcvFileUser) { @@ -557,7 +569,7 @@ private fun MutableState.startDownloading( generalGetString(MR.strings.migrate_to_device_download_failed), generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) ) - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) } else { Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.chatError)}") } @@ -569,7 +581,7 @@ private fun MutableState.startDownloading( val (res, error) = controller.downloadStandaloneFile(user, link, CryptoFile.plain(File(archivePath).path), ctrl) if (res == null) { - state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.migrate_to_device_error_downloading_archive), error @@ -578,7 +590,7 @@ private fun MutableState.startDownloading( } } -private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg) { +private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) { withLongRunningApi { try { if (ChatController.ctrl == null || ChatController.ctrl == -1L) { @@ -592,14 +604,14 @@ private fun MutableState.importArchive(archivePath: String, n if (archiveErrors.isNotEmpty()) { showArchiveImportedWithErrorsAlert(archiveErrors) } - state = MigrationToState.Passphrase("", netCfg) - MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg)) + state = MigrationToState.Passphrase("", netCfg, networkProxy) + MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg, networkProxy)) } catch (e: Exception) { - state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_importing_database), e.stackTraceToString()) } } catch (e: Exception) { - state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy) AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_deleting_database), e.stackTraceToString()) } } @@ -609,7 +621,7 @@ private suspend fun stopArchiveDownloading(fileId: Long, ctrl: ChatCtrl) { controller.apiCancelFile(null, fileId, ctrl) } -private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { +private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) { if (useKeychain) { ksDatabasePassword.set(passphrase) } else { @@ -621,7 +633,8 @@ private fun startChat(passphrase: String, confirmation: MigrationConfirmation, u try { initChatController(useKey = passphrase, confirmMigrations = confirmation) { CompletableDeferred(false) } val appSettings = controller.apiGetAppSettings(AppSettings.current.prepareForExport()).copy( - networkConfig = netCfg + networkConfig = netCfg, + networkProxy = networkProxy ) finishMigration(appSettings, close) } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 3c8ab2b70a..6dc0f74df3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -1,10 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue +import SectionTextFooter import SectionView import SectionViewSelectableCards import androidx.compose.desktop.ui.tooling.preview.Preview @@ -40,12 +40,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U val currentCfg = remember { stateGetOrPut("currentCfg") { controller.getNetCfg() } } val currentCfgVal = currentCfg.value // used only on initialization - val onionHosts = remember { mutableStateOf(currentCfgVal.onionHosts) } val sessionMode = remember { mutableStateOf(currentCfgVal.sessionMode) } val smpProxyMode = remember { mutableStateOf(currentCfgVal.smpProxyMode) } val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) } - val networkUseSocksProxy: MutableState = remember { mutableStateOf(currentCfgVal.useSocksProxy) } val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) } val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) } val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) } @@ -90,11 +88,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U tcpKeepAlive = tcpKeepAlive, smpPingInterval = networkSMPPingInterval.value, smpPingCount = networkSMPPingCount.value - ).withOnionHosts(onionHosts.value) + ).withOnionHosts(currentCfg.value.onionHosts) } fun updateView(cfg: NetCfg) { - onionHosts.value = cfg.onionHosts sessionMode.value = cfg.sessionMode smpProxyMode.value = cfg.smpProxyMode smpProxyFallback.value = cfg.smpProxyFallback @@ -148,10 +145,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U ) { AdvancedNetworkSettingsLayout( currentRemoteHost = currentRemoteHost, - networkUseSocksProxy = networkUseSocksProxy, developerTools = developerTools, - onionHosts = onionHosts, - useOnion = { onionHosts.value = it; currentCfg.value = currentCfg.value.withOnionHosts(it) }, sessionMode = sessionMode, smpProxyMode = smpProxyMode, smpProxyFallback = smpProxyFallback, @@ -183,10 +177,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U @Composable fun AdvancedNetworkSettingsLayout( currentRemoteHost: RemoteHostInfo?, - networkUseSocksProxy: State, developerTools: Boolean, - onionHosts: MutableState, - useOnion: (OnionHosts) -> Unit, sessionMode: MutableState, smpProxyMode: MutableState, smpProxyFallback: MutableState, @@ -223,21 +214,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { derivedStateOf { smpProxyMode.value != SMPProxyMode.Never } }) SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) } - SectionCustomFooter { - Text(stringResource(MR.strings.private_routing_explanation)) - } - SectionDividerSpaced(maxTopPadding = true) - } - - if (currentRemoteHost == null && networkUseSocksProxy.value) { - SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) - } - } - } + SectionTextFooter(stringResource(MR.strings.private_routing_explanation)) SectionDividerSpaced(maxTopPadding = true) } @@ -562,7 +539,6 @@ fun PreviewAdvancedNetworkSettingsLayout() { SimpleXTheme { AdvancedNetworkSettingsLayout( currentRemoteHost = null, - networkUseSocksProxy = remember { mutableStateOf(false) }, developerTools = false, sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, @@ -577,8 +553,6 @@ fun PreviewAdvancedNetworkSettingsLayout() { networkTCPKeepIdle = remember { mutableStateOf(10) }, networkTCPKeepIntvl = remember { mutableStateOf(10) }, networkTCPKeepCnt = remember { mutableStateOf(10) }, - onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, - useOnion = {}, updateSessionMode = {}, updateSMPProxyMode = {}, updateSMPProxyFallback = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 5bcb0a545d..5272353c20 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -1,10 +1,10 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import SectionCustomFooter import SectionDividerSpaced import SectionItemView import SectionItemWithValue +import SectionTextFooter import SectionView import SectionViewSelectable import TextIconSpaced @@ -37,10 +37,11 @@ fun NetworkAndServersView() { val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } - val proxyPort = remember { derivedStateOf { chatModel.controller.appPrefs.networkProxyHostPort.state.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } } + val proxyPort = remember { derivedStateOf { appPrefs.networkProxy.state.value.port } } NetworkAndServersLayout( currentRemoteHost = currentRemoteHost, networkUseSocksProxy = networkUseSocksProxy, + onionHosts = remember { mutableStateOf(netCfg.onionHosts) }, toggleSocksProxy = { enable -> val def = NetCfg.defaults val proxyDef = NetCfg.proxyDefaults @@ -51,7 +52,7 @@ fun NetworkAndServersView() { confirmText = generalGetString(MR.strings.confirm_verb), onConfirm = { withBGApi { - var conf = controller.getNetCfg().withHostPort(controller.appPrefs.networkProxyHostPort.get()) + var conf = controller.getNetCfg().withProxy(controller.appPrefs.networkProxy.get()) if (conf.tcpConnectTimeout == def.tcpConnectTimeout) { conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout) } @@ -104,6 +105,7 @@ fun NetworkAndServersView() { @Composable fun NetworkAndServersLayout( currentRemoteHost: RemoteHostInfo?, networkUseSocksProxy: MutableState, + onionHosts: MutableState, toggleSocksProxy: (Boolean) -> Unit, ) { val m = chatModel @@ -120,14 +122,10 @@ fun NetworkAndServersView() { if (currentRemoteHost == null) { UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxyHostPort, false, it) }}) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) }}) SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } }) if (networkUseSocksProxy.value) { - SectionCustomFooter { - Column { - Text(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) - } - } + SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations)) SectionDividerSpaced(maxTopPadding = true) } else { SectionDividerSpaced() @@ -158,16 +156,14 @@ fun NetworkAndServersView() { networkUseSocksProxy: MutableState, onionHosts: MutableState, sessionMode: MutableState, - networkProxyHostPort: SharedPreference, + networkProxy: SharedPreference, toggleSocksProxy: (Boolean) -> Unit, - useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, ) { val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.fullscreen.showModal(content = it) } val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.fullscreen.showCustomModal { close -> it(close) }} UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy) - SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxyHostPort, true, it) } }) - UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) + SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxy, onionHosts, sessionMode.value, true, it) } }) if (developerTools) { SessionModePicker(sessionMode, showModal, updateSessionMode) } @@ -205,46 +201,98 @@ fun UseSocksProxySwitch( @Composable fun SocksProxySettings( networkUseSocksProxy: Boolean, - networkProxyHostPort: SharedPreference = appPrefs.networkProxyHostPort, + networkProxy: SharedPreference, + onionHosts: MutableState, + sessionMode: TransportSessionMode, migration: Boolean, close: () -> Unit ) { - val defaultHostPort = remember { "localhost:9050" } - val hostPortSaved by remember { networkProxyHostPort.state } + val networkProxySaved by remember { networkProxy.state } + val onionHostsSaved = remember { mutableStateOf(onionHosts.value) } + + val usernameUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(networkProxySaved.username)) + } + val passwordUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(networkProxySaved.password)) + } val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost")) + mutableStateOf(TextFieldValue(networkProxySaved.host)) } val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050")) + mutableStateOf(TextFieldValue(networkProxySaved.port.toString())) } - val save = { - val oldValue = networkProxyHostPort.get() - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) - if (networkUseSocksProxy && !migration) { - withBGApi { - if (!controller.apiSetNetworkConfig(controller.getNetCfg())) { - networkProxyHostPort.set(oldValue) - } + val proxyAuthRandomUnsaved = rememberSaveable { mutableStateOf(networkProxySaved.auth == NetworkProxyAuth.ISOLATE) } + LaunchedEffect(proxyAuthRandomUnsaved.value) { + if (!proxyAuthRandomUnsaved.value && onionHosts.value != OnionHosts.NEVER) { + onionHosts.value = OnionHosts.NEVER + } + } + val proxyAuthModeUnsaved = remember(proxyAuthRandomUnsaved.value, usernameUnsaved.value.text, passwordUnsaved.value.text) { + derivedStateOf { + if (proxyAuthRandomUnsaved.value) { + NetworkProxyAuth.ISOLATE + } else { + NetworkProxyAuth.USERNAME } } } - val saveAndClose = { - val oldValue = networkProxyHostPort.get() - networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) + + val save: (Boolean) -> Unit = { closeOnSuccess -> + val oldValue = networkProxy.get() + usernameUnsaved.value = usernameUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) usernameUnsaved.value.text.trim() else "") + passwordUnsaved.value = passwordUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) passwordUnsaved.value.text.trim() else "") + hostUnsaved.value = hostUnsaved.value.copy(hostUnsaved.value.text.trim()) + portUnsaved.value = portUnsaved.value.copy(portUnsaved.value.text.trim()) + + networkProxy.set( + NetworkProxy( + username = usernameUnsaved.value.text, + password = passwordUnsaved.value.text, + host = hostUnsaved.value.text, + port = portUnsaved.value.text.toIntOrNull() ?: 9050, + auth = proxyAuthModeUnsaved.value + ) + ) + val oldCfg = controller.getNetCfg() + val cfg = oldCfg.withOnionHosts(onionHosts.value) + val oldOnionHosts = onionHostsSaved.value + onionHostsSaved.value = onionHosts.value + + if (!migration) { + controller.setNetCfg(cfg) + } if (networkUseSocksProxy && !migration) { withBGApi { - if (controller.apiSetNetworkConfig(controller.getNetCfg())) { - close() + if (controller.apiSetNetworkConfig(cfg, showAlertOnError = false)) { + onionHosts.value = cfg.onionHosts + onionHostsSaved.value = onionHosts.value + if (closeOnSuccess) { + close() + } } else { - networkProxyHostPort.set(oldValue) + controller.setNetCfg(oldCfg) + networkProxy.set(oldValue) + onionHostsSaved.value = oldOnionHosts + showWrongProxyConfigAlert() } } } } - val saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || - remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value || - remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value - val resetDisabled = hostUnsaved.value.text + ":" + portUnsaved.value.text == defaultHostPort + val saveDisabled = + ( + networkProxySaved.username == usernameUnsaved.value.text.trim() && + networkProxySaved.password == passwordUnsaved.value.text.trim() && + networkProxySaved.host == hostUnsaved.value.text.trim() && + networkProxySaved.port.toString() == portUnsaved.value.text.trim() && + networkProxySaved.auth == proxyAuthModeUnsaved.value && + onionHosts.value == onionHostsSaved.value + ) || + !validCredential(usernameUnsaved.value.text) || + !validCredential(passwordUnsaved.value.text) || + !validHost(hostUnsaved.value.text) || + !validPort(portUnsaved.value.text) + val resetDisabled = hostUnsaved.value.text.trim() == "localhost" && portUnsaved.value.text.trim() == "9050" && proxyAuthRandomUnsaved.value && onionHosts.value == NetCfg.defaults.onionHosts ModalView( close = { if (saveDisabled) { @@ -252,7 +300,7 @@ fun SocksProxySettings( } else { showUnsavedSocksHostPortAlert( confirmText = generalGetString(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), - save = saveAndClose, + save = { save(true) }, close = close ) } @@ -263,38 +311,78 @@ fun SocksProxySettings( .fillMaxWidth() ) { AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) { - DefaultConfigurableTextField( - hostUnsaved, - stringResource(MR.strings.host_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validHost, - keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), - keyboardType = KeyboardType.Text, - ) - DefaultConfigurableTextField( - portUnsaved, - stringResource(MR.strings.port_verb), - modifier = Modifier.fillMaxWidth(), - isValid = ::validPort, - keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }), - keyboardType = KeyboardType.Number, - ) + SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + hostUnsaved, + stringResource(MR.strings.host_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validHost, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + portUnsaved, + stringResource(MR.strings.port_verb), + modifier = Modifier.fillMaxWidth(), + isValid = ::validPort, + keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save(false) }), + keyboardType = KeyboardType.Number, + ) + } + + UseOnionHosts(onionHosts, rememberUpdatedState(networkUseSocksProxy && proxyAuthRandomUnsaved.value)) { + onionHosts.value = it + } + SectionTextFooter(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported)) } - SectionDividerSpaced(maxBottomPadding = false) + SectionDividerSpaced(maxTopPadding = true) + + SectionView(stringResource(MR.strings.network_proxy_auth).uppercase()) { + PreferenceToggle( + stringResource(MR.strings.network_proxy_random_credentials), + checked = proxyAuthRandomUnsaved.value, + onChange = { proxyAuthRandomUnsaved.value = it } + ) + if (!proxyAuthRandomUnsaved.value) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + DefaultConfigurableTextField( + usernameUnsaved, + stringResource(MR.strings.network_proxy_username), + modifier = Modifier.fillMaxWidth(), + isValid = ::validCredential, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Text, + ) + DefaultConfigurableTextField( + passwordUnsaved, + stringResource(MR.strings.network_proxy_password), + modifier = Modifier.fillMaxWidth(), + isValid = ::validCredential, + keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }), + keyboardType = KeyboardType.Password, + ) + } + } + SectionTextFooter(proxyAuthFooter(usernameUnsaved.value.text, passwordUnsaved.value.text, proxyAuthModeUnsaved.value, sessionMode)) + } + + SectionDividerSpaced(maxBottomPadding = false, maxTopPadding = true) SectionView { SectionItemView({ - val newHost = defaultHostPort.split(":").first() - val newPort = defaultHostPort.split(":").last() - hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length)) - portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length)) + hostUnsaved.value = hostUnsaved.value.copy("localhost", TextRange(9)) + portUnsaved.value = portUnsaved.value.copy("9050", TextRange(4)) + usernameUnsaved.value = TextFieldValue() + passwordUnsaved.value = TextFieldValue() + proxyAuthRandomUnsaved.value = true + onionHosts.value = NetCfg.defaults.onionHosts }, disabled = resetDisabled) { Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } SectionItemView( - click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save() } else save() }, + click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save(false) } else save(false) }, disabled = saveDisabled ) { Text(stringResource(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) @@ -305,6 +393,12 @@ fun SocksProxySettings( } } +private fun proxyAuthFooter(username: String, password: String, auth: NetworkProxyAuth, sessionMode: TransportSessionMode): String = when { + auth == NetworkProxyAuth.ISOLATE -> generalGetString(if (sessionMode == TransportSessionMode.User) MR.strings.network_proxy_auth_mode_isolate_by_auth_user else MR.strings.network_proxy_auth_mode_isolate_by_auth_entity) + username.isBlank() && password.isBlank() -> generalGetString(MR.strings.network_proxy_auth_mode_no_auth) + else -> generalGetString(MR.strings.network_proxy_auth_mode_username_password) +} + private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, close: () -> Unit) { AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.update_network_settings_question), @@ -319,7 +413,6 @@ private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, fun UseOnionHosts( onionHosts: MutableState, enabled: State, - showModal: (@Composable ModalData.() -> Unit) -> Unit, useOnion: (OnionHosts) -> Unit, ) { val values = remember { @@ -331,36 +424,29 @@ fun UseOnionHosts( } } } - val onSelected = { - showModal { - ColumnWithScrollBar( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_use_onion_hosts)) - SectionViewSelectable(null, onionHosts, values, useOnion) - } - } - } - if (enabled.value) { - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - onionHosts, - values, - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = onSelected - ) - } else { - // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - remember { mutableStateOf(OnionHosts.NEVER) }, - listOf(ValueTitleDesc(OnionHosts.NEVER, generalGetString(MR.strings.network_use_onion_hosts_no), AnnotatedString(generalGetString(MR.strings.network_use_onion_hosts_no_desc)))), - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = {} - ) + Column { + if (enabled.value) { + ExposedDropDownSettingRow( + generalGetString(MR.strings.network_use_onion_hosts), + values.map { it.value to it.title }, + onionHosts, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = useOnion + ) + } else { + // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before + ExposedDropDownSettingRow( + generalGetString(MR.strings.network_use_onion_hosts), + listOf(OnionHosts.NEVER to generalGetString(MR.strings.network_use_onion_hosts_no)), + remember { mutableStateOf(OnionHosts.NEVER) }, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = {} + ) + } + SectionTextFooter(values.first { it.value == onionHosts.value }.description) } } @@ -398,12 +484,8 @@ fun SessionModePicker( ) } -// https://stackoverflow.com/a/106223 -private fun validHost(s: String): Boolean { - val validIp = Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") - val validHostname = Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])[.])*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$"); - return s.matches(validIp) || s.matches(validHostname) -} +private fun validHost(s: String): Boolean = + !s.contains('@') // https://ihateregex.io/expr/port/ fun validPort(s: String): Boolean { @@ -411,6 +493,16 @@ fun validPort(s: String): Boolean { return s.isNotBlank() && s.matches(validPort) } +private fun validCredential(s: String): Boolean = + !s.contains(':') && !s.contains('@') + +fun showWrongProxyConfigAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.network_proxy_incorrect_config_title), + text = generalGetString(MR.strings.network_proxy_incorrect_config_desc), + ) +} + fun showUpdateNetworkSettingsDialog( title: String, startsWith: String = "", @@ -435,6 +527,7 @@ fun PreviewNetworkAndServersLayout() { NetworkAndServersLayout( currentRemoteHost = null, networkUseSocksProxy = remember { mutableStateOf(true) }, + onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, toggleSocksProxy = {}, ) } 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 f3e491245b..55cd86e035 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -769,7 +769,17 @@ SOCKS proxy SOCKS proxy settings Use SOCKS proxy + Proxy authentication + Use random credentials + Use different proxy credentials for each profile. + Use different proxy credentials for each connection. + Do not use credentials with proxy. + Your credentials may be sent unencrypted. + Username + Password port %d + Error saving proxy + Make sure proxy configuration is correct. Host Port Use SOCKS proxy? From 3a61290730d46c9a217335cd67f5547a38df2c1f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 17 Sep 2024 22:30:24 +0100 Subject: [PATCH 062/704] core: 6.1.0.1 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index 3d5422612a..4aecc29a97 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.0 +version: 6.1.0.1 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index b58285a349..2a80cb4ea6 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.0 +version: 6.1.0.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From c13e8e4037c817598393059f60bea5d91cd29088 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 07:28:00 +0100 Subject: [PATCH 063/704] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index dd97170700..3374b5773a 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -217,11 +217,11 @@ 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 */; }; - E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CE2C989E12001D165C /* libgmpxx.a */; }; - E55128D42C989E13001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128CF2C989E12001D165C /* libffi.a */; }; - E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */; }; - E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */; }; - E55128D72C989E13001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D22C989E13001D165C /* libgmp.a */; }; + E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */; }; + E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */; }; + E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DA2C9AA96B001D165C /* libffi.a */; }; + E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DB2C9AA96B001D165C /* libgmpxx.a */; }; + E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DC2C9AA96B001D165C /* libgmp.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 */; }; @@ -556,11 +556,11 @@ 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 = ""; }; - E55128CE2C989E12001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E55128CF2C989E12001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a"; sourceTree = ""; }; - E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a"; sourceTree = ""; }; - E55128D22C989E13001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a"; sourceTree = ""; }; + E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a"; sourceTree = ""; }; + E55128DA2C9AA96B001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128DB2C9AA96B001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128DC2C9AA96B001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.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 = ""; }; @@ -651,14 +651,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E55128D32C989E13001D165C /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E55128D72C989E13001D165C /* libgmp.a in Frameworks */, - E55128D62C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a in Frameworks */, + E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */, + E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */, + E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */, + E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E55128D42C989E13001D165C /* libffi.a in Frameworks */, - E55128D52C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E55128CF2C989E12001D165C /* libffi.a */, - E55128D22C989E13001D165C /* libgmp.a */, - E55128CE2C989E12001D165C /* libgmpxx.a */, - E55128D12C989E13001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj-ghc9.6.3.a */, - E55128D02C989E12001D165C /* libHSsimplex-chat-6.1.0.0-6SG1oRijpxxHpZcw3v92xj.a */, + E55128DA2C9AA96B001D165C /* libffi.a */, + E55128DC2C9AA96B001D165C /* libgmp.a */, + E55128DB2C9AA96B001D165C /* libgmpxx.a */, + E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */, + E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */, ); path = Libraries; sourceTree = ""; From 17a0f3a2105d0a29502f0774eee4ca07a30481be Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 08:56:43 +0100 Subject: [PATCH 064/704] core: 6.1.0.2, update min versions for remote access to 6.1.0.2 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.yaml b/package.yaml index 4aecc29a97..ab4c9790ef 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.1 +version: 6.1.0.2 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 2a80cb4ea6..51602084f4 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.1 +version: 6.1.0.2 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 527b87c010..76d3754f17 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -72,11 +72,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 1, 0, 0] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 2] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 1, 0, 0] +minRemoteHostVersion = AppVersion [6, 1, 0, 2] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 0a4667304c7192f125933d2071d79dd342f966c8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 10:18:06 +0100 Subject: [PATCH 065/704] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3374b5773a..519c825f04 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -217,11 +217,11 @@ 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 */; }; - E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */; }; - E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */; }; - E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DA2C9AA96B001D165C /* libffi.a */; }; - E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DB2C9AA96B001D165C /* libgmpxx.a */; }; - E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128DC2C9AA96B001D165C /* libgmp.a */; }; + E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */; }; + E55128E82C9AD063001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E32C9AD063001D165C /* libgmp.a */; }; + E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */; }; + E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E52C9AD063001D165C /* libgmpxx.a */; }; + E55128EB2C9AD063001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E62C9AD063001D165C /* libffi.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 */; }; @@ -556,11 +556,11 @@ 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 = ""; }; - E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a"; sourceTree = ""; }; - E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a"; sourceTree = ""; }; - E55128DA2C9AA96B001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E55128DB2C9AA96B001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E55128DC2C9AA96B001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a"; sourceTree = ""; }; + E55128E32C9AD063001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a"; sourceTree = ""; }; + E55128E52C9AD063001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128E62C9AD063001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.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 = ""; }; @@ -651,14 +651,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */, + E55128E82C9AD063001D165C /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E55128DF2C9AA96B001D165C /* libffi.a in Frameworks */, - E55128E12C9AA96B001D165C /* libgmp.a in Frameworks */, - E55128DE2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a in Frameworks */, - E55128DD2C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a in Frameworks */, + E55128EB2C9AD063001D165C /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E55128E02C9AA96B001D165C /* libgmpxx.a in Frameworks */, + E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E55128DA2C9AA96B001D165C /* libffi.a */, - E55128DC2C9AA96B001D165C /* libgmp.a */, - E55128DB2C9AA96B001D165C /* libgmpxx.a */, - E55128D82C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63-ghc9.6.3.a */, - E55128D92C9AA96B001D165C /* libHSsimplex-chat-6.1.0.1-2faBJOPOSgP5OStZMK9U63.a */, + E55128E62C9AD063001D165C /* libffi.a */, + E55128E32C9AD063001D165C /* libgmp.a */, + E55128E52C9AD063001D165C /* libgmpxx.a */, + E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */, + E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */, ); path = Libraries; sourceTree = ""; From 529921e16ad93489890af7519b47bdc92d8e0613 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 18 Sep 2024 11:37:32 +0100 Subject: [PATCH 066/704] 6.1-beta.0: ios 237, android 239, desktop 66 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++--- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 519c825f04..b0b838eef8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1891,7 +1891,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1916,7 +1916,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1940,7 +1940,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1965,7 +1965,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1981,11 +1981,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2001,11 +2001,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2026,7 +2026,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2041,7 +2041,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2063,7 +2063,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2078,7 +2078,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2100,7 +2100,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2126,7 +2126,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2151,7 +2151,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2177,7 +2177,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2202,7 +2202,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2217,7 +2217,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2236,7 +2236,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 236; + CURRENT_PROJECT_VERSION = 237; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2251,7 +2251,7 @@ "@executable_path/../../Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 6.0.4; + MARKETING_VERSION = 6.1; 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 ac2fce0e12..6123ce156f 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.0.4 -android.version_code=237 +android.version_name=6.1-beta.0 +android.version_code=239 -desktop.version_name=6.0.4 -desktop.version_code=65 +desktop.version_name=6.1-beta.0 +desktop.version_code=66 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From d47ff3597d2c828b1d2d6c66eae1ea167de50ee5 Mon Sep 17 00:00:00 2001 From: Diogo Date: Wed, 18 Sep 2024 16:16:32 +0100 Subject: [PATCH 067/704] android, desktop: reset incognito on profile picker (#4903) --- .../kotlin/chat/simplex/common/views/newchat/NewChatView.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index a05de0e8b3..0e7a01ddab 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -308,6 +308,7 @@ fun ActiveProfilePicker( switchingProfile.value = true withApi { try { + appPreferences.incognito.set(false) var updatedConn: PendingContactConnection? = null; if (contactConnection != null) { @@ -361,6 +362,7 @@ fun ActiveProfilePicker( switchingProfile.value = true withApi { try { + appPreferences.incognito.set(true) val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true) if (conn != null) { withChats { From acc9be1a5b6c9cd617b7ff3c2ee8b35bae51fd10 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 19 Sep 2024 06:57:37 +0000 Subject: [PATCH 068/704] android, desktop: compose 1.6.11 (#4902) --- apps/multiplatform/gradle.properties | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 6123ce156f..11d7b5e7fb 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -21,8 +21,6 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 @@ -34,4 +32,4 @@ desktop.version_code=66 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 -compose.version=1.6.1 +compose.version=1.6.11 From 255538e5d73405bbc880bbdfb7ad19ee803f1b30 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 19 Sep 2024 10:04:19 +0300 Subject: [PATCH 069/704] ios: bulk forward (#4857) * ios: forward multiple messages * ios: batch previews, when sending media messsages (#4861) --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Arturs Krumins Co-authored-by: Diogo Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 155 ++++++++++++------ .../Views/Chat/ChatItemForwardingView.swift | 21 ++- apps/ios/Shared/Views/Chat/ChatView.swift | 145 ++++++++++++++-- .../Chat/ComposeMessage/ComposeView.swift | 144 ++++++++-------- .../Chat/ComposeMessage/ContextItemView.swift | 46 ++++-- .../Chat/ComposeMessage/SendMessageView.swift | 1 + .../Chat/SelectableChatItemToolbars.swift | 23 ++- .../ios/Shared/Views/Helpers/ShareSheet.swift | 18 +- apps/ios/SimpleXChat/APITypes.swift | 15 +- 9 files changed, 404 insertions(+), 164 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 6bbacea245..fab3e10990 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -357,6 +357,12 @@ func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws - throw r } +func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) { + let r = await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds)) + if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) } + throw r +} + func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? { let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl) return await processSendMessageCmd(toChatType: toChatType, cmd: cmd) @@ -1039,77 +1045,122 @@ func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationF } func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async { - if let chatItem = await apiReceiveFile( - fileId: fileId, - userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), - encrypted: privacyEncryptLocalFilesGroupDefault.get(), + await receiveFiles( + user: user, + fileIds: [fileId], + userApprovedRelays: userApprovedRelays, auto: auto - ) { - await chatItemSimpleUpdate(user, chatItem) - } + ) } -func apiReceiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { - let r = await chatSendCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) - let am = AlertManager.shared - if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } - if case .rcvFileAcceptedSndCancelled = r { - logger.debug("apiReceiveFile error: sender cancelled file transfer") - if !auto { - am.showAlertMsg( - title: "Cannot receive file", - message: "Sender cancelled file transfer." +func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool = false, auto: Bool = false) async { + var fileIdsToApprove = [Int64]() + var srvsToApprove = Set() + var otherFileErrs = [ChatResponse]() + + for fileId in fileIds { + let r = await chatSendCmd( + .receiveFile( + fileId: fileId, + userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), + encrypted: privacyEncryptLocalFilesGroupDefault.get(), + inline: nil ) + ) + switch r { + case let .rcvFileAccepted(_, chatItem): + await chatItemSimpleUpdate(user, chatItem) + default: + if let chatError = chatError(r) { + switch chatError { + case let .fileNotApproved(fileId, unknownServers): + fileIdsToApprove.append(fileId) + srvsToApprove.formUnion(unknownServers) + default: + otherFileErrs.append(r) + } + } } - } else if let networkErrorAlert = networkErrorAlert(r) { - logger.error("apiReceiveFile network error: \(String(describing: r))") - if !auto { - am.showAlert(networkErrorAlert) + } + + if !auto { + let otherErrsStr = if otherFileErrs.isEmpty { + "" + } else if otherFileErrs.count == 1 { + "\(otherFileErrs[0])" + } else if otherFileErrs.count == 2 { + "\(otherFileErrs[0])\n\(otherFileErrs[1])" + } else { + "\(otherFileErrs[0])\n\(otherFileErrs[1])\nand \(otherFileErrs.count - 2) other error(s)" } - } else { - switch chatError(r) { - case .fileCancelled: - logger.debug("apiReceiveFile ignoring fileCancelled error") - case .fileAlreadyReceiving: - logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error") - case let .fileNotApproved(fileId, unknownServers): - logger.debug("apiReceiveFile fileNotApproved error") - if !auto { - let srvs = unknownServers.map { s in + + // If there are not approved files, alert is shown the same way both in case of singular and plural files reception + if !fileIdsToApprove.isEmpty { + let srvs = srvsToApprove + .map { s in if let srv = parseServerAddress(s), !srv.hostnames.isEmpty { srv.hostnames[0] } else { serverHost(s) } } - am.showAlert(Alert( - title: Text("Unknown servers!"), - message: Text("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs.sorted().joined(separator: ", "))."), - primaryButton: .default( - Text("Download"), - action: { - Task { - logger.debug("apiReceiveFile fileNotApproved alert - in Task") - if let user = ChatModel.shared.currentUser { - await receiveFile(user: user, fileId: fileId, userApprovedRelays: true) - } + .sorted() + .joined(separator: ", ") + let fIds = fileIdsToApprove + await MainActor.run { + showAlert( + title: NSLocalizedString("Unknown servers!", comment: "alert title"), + message: ( + NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs).", comment: "alert message") + + (otherErrsStr != "" ? "\n\n" + NSLocalizedString("Other file errors:\n\(otherErrsStr)", comment: "alert message") : "") + ), + buttonTitle: NSLocalizedString("Download", comment: "alert button"), + buttonAction: { + Task { + logger.debug("apiReceiveFile fileNotApproved alert - in Task") + if let user = ChatModel.shared.currentUser { + await receiveFiles(user: user, fileIds: fIds, userApprovedRelays: true) } } - ), - secondaryButton: .cancel() - )) + }, + cancelButton: true + ) } - default: - logger.error("apiReceiveFile error: \(String(describing: r))") - if !auto { - am.showAlertMsg( - title: "Error receiving file", - message: "Error: \(responseError(r))" + } else if otherFileErrs.count == 1 { // If there is a single other error, we differentiate on it + let errorResponse = otherFileErrs.first! + switch errorResponse { + case let .rcvFileAcceptedSndCancelled(_, rcvFileTransfer): + logger.debug("receiveFiles error: sender cancelled file transfer \(rcvFileTransfer.fileId)") + await MainActor.run { + showAlert( + NSLocalizedString("Cannot receive file", comment: "alert title"), + message: NSLocalizedString("Sender cancelled file transfer.", comment: "alert message") + ) + } + default: + if let chatError = chatError(errorResponse) { + switch chatError { + case .fileCancelled, .fileAlreadyReceiving: + logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") + default: + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: responseError(errorResponse) + ) + } + } + } + } + } else if otherFileErrs.count > 1 { // If there are multiple other errors, we show general alert + await MainActor.run { + showAlert( + NSLocalizedString("Error receiving file", comment: "alert title"), + message: NSLocalizedString("File errors:\n\(otherErrsStr)", comment: "alert message") ) } } } - return nil } func cancelFile(user: User, fileId: Int64) async { diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 32993d1a76..79ede14be5 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -14,7 +14,7 @@ struct ChatItemForwardingView: View { @EnvironmentObject var theme: AppTheme @Environment(\.dismiss) var dismiss - var ci: ChatItem + var chatItems: [ChatItem] var fromChatInfo: ChatInfo @Binding var composeState: ComposeState @@ -73,11 +73,14 @@ struct ChatItemForwardingView: View { } @ViewBuilder private func forwardListChatView(_ chat: Chat) -> some View { - let prohibited = chat.prohibitedByPref( - hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), - isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, - isVoice: ci.content.msgContent?.isVoice ?? false - ) + let prohibited = chatItems.map { ci in + chat.prohibitedByPref( + hasSimplexLink: hasSimplexLink(ci.content.msgContent?.text), + isMediaOrFileAttachment: ci.content.msgContent?.isMediaOrFileAttachment ?? false, + isVoice: ci.content.msgContent?.isVoice ?? false + ) + }.contains(true) + Button { if prohibited { alert = SomeAlert( @@ -93,10 +96,10 @@ struct ChatItemForwardingView: View { composeState = ComposeState( message: composeState.message, preview: composeState.linkPreview != nil ? composeState.preview : .noPreview, - contextItem: .forwardingItem(chatItem: ci, fromChatInfo: fromChatInfo) + contextItem: .forwardingItems(chatItems: chatItems, fromChatInfo: fromChatInfo) ) } else { - composeState = ComposeState.init(forwardingItem: ci, fromChatInfo: fromChatInfo) + composeState = ComposeState.init(forwardingItems: chatItems, fromChatInfo: fromChatInfo) ItemsModel.shared.loadOpenChat(chat.id) } } @@ -123,7 +126,7 @@ struct ChatItemForwardingView: View { #Preview { ChatItemForwardingView( - ci: ChatItem.getSample(1, .directSnd, .now, "hello"), + chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello")], fromChatInfo: .direct(contact: Contact.sampleData), composeState: Binding.constant(ComposeState(message: "hello")) ).environmentObject(CurrentColors.toAppTheme()) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 5c11cdc3df..e7359587df 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -42,6 +42,7 @@ struct ChatView: View { @State private var showGroupLinkSheet: Bool = false @State private var groupLink: String? @State private var groupLinkMemberRole: GroupMemberRole = .member + @State private var forwardedChatItems: [ChatItem] = [] @State private var selectedChatItems: Set? = nil @State private var showDeleteSelectedMessages: Bool = false @State private var allowToDeleteSelectedMessagesForAll: Bool = false @@ -98,7 +99,8 @@ struct ChatView: View { if case let .group(groupInfo) = chat.chatInfo { showModerateSelectedMessagesAlert(groupInfo) } - } + }, + forwardItems: forwardSelectedMessages ) } } @@ -135,6 +137,22 @@ struct ChatView: View { } } } + .sheet(isPresented: Binding( + get: { !forwardedChatItems.isEmpty }, + set: { isPresented in + if !isPresented { + forwardedChatItems = [] + selectedChatItems = nil + } + } + )) { + if #available(iOS 16.0, *) { + ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) + .presentationDetents([.fraction(0.8)]) + } else { + ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState) + } + } .onAppear { selectedChatItems = nil initChatView() @@ -411,7 +429,8 @@ struct ChatView: View { composeState: $composeState, selectedMember: $selectedMember, revealedChatItem: $revealedChatItem, - selectedChatItems: $selectedChatItems + selectedChatItems: $selectedChatItems, + forwardedChatItems: $forwardedChatItems ) .id(ci.id) // Required to trigger `onAppear` on iOS15 } loadPage: { @@ -701,6 +720,116 @@ struct ChatView: View { } } + private func forwardSelectedMessages() { + Task { + do { + if let selectedChatItems { + let (validItems, confirmation) = try await apiPlanForwardChatItems( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + itemIds: Array(selectedChatItems) + ) + if let confirmation { + if validItems.count > 0 { + showAlert( + String.localizedStringWithFormat( + NSLocalizedString("Forward %d message(s)?", comment: "alert title"), + validItems.count + ), + message: forwardConfirmationText(confirmation) + "\n" + + NSLocalizedString("Forward messages without files?", comment: "alert message") + ) { + switch confirmation { + case let .filesNotAccepted(fileIds): + [forwardAction(validItems), downloadAction(fileIds), cancelAlertAction] + default: + [forwardAction(validItems), cancelAlertAction] + } + } + } else { + showAlert( + NSLocalizedString("Nothing to forward!", comment: "alert title"), + message: forwardConfirmationText(confirmation) + ) { + switch confirmation { + case let .filesNotAccepted(fileIds): + [downloadAction(fileIds), cancelAlertAction] + default: + [okAlertAction] + } + } + } + } else { + await openForwardingSheet(validItems) + } + } + } catch { + logger.error("Plan forward chat items failed: \(error.localizedDescription)") + } + } + + func forwardConfirmationText(_ fc: ForwardConfirmation) -> String { + switch fc { + case let .filesNotAccepted(fileIds): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) were not downloaded.", comment: "forward confirmation reason"), + fileIds.count + ) + case let .filesInProgress(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) are still being downloaded.", comment: "forward confirmation reason"), + filesCount + ) + case let .filesMissing(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) were deleted.", comment: "forward confirmation reason"), + filesCount + ) + case let .filesFailed(filesCount): + String.localizedStringWithFormat( + NSLocalizedString("%d file(s) failed to download.", comment: "forward confirmation reason"), + filesCount + ) + } + } + + func forwardAction(_ items: [Int64]) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Forward messages", comment: "alert action"), + style: .default, + handler: { _ in Task { await openForwardingSheet(items) } } + ) + } + + func downloadAction(_ fileIds: [Int64]) -> UIAlertAction { + UIAlertAction( + title: NSLocalizedString("Download files", comment: "alert action"), + style: .default, + handler: { _ in + Task { + if let user = ChatModel.shared.currentUser { + await receiveFiles(user: user, fileIds: fileIds) + } + } + } + ) + } + + func openForwardingSheet(_ items: [Int64]) async { + let im = ItemsModel.shared + var items = Set(items) + var fci = [ChatItem]() + for reversedChatItem in im.reversedChatItems { + if items.contains(reversedChatItem.id) { + items.remove(reversedChatItem.id) + fci.insert(reversedChatItem, at: 0) + } + if items.isEmpty { break } + } + await MainActor.run { forwardedChatItems = fci } + } + } + private func loadChatItems(_ cInfo: ChatInfo) { Task { if loadingItems || firstPage { return } @@ -762,10 +891,10 @@ struct ChatView: View { @State private var showDeleteMessages = false @State private var showChatItemInfoSheet: Bool = false @State private var chatItemInfo: ChatItemInfo? - @State private var showForwardingSheet: Bool = false @State private var msgWidth: CGFloat = 0 @Binding var selectedChatItems: Set? + @Binding var forwardedChatItems: [ChatItem] @State private var allowMenu: Bool = true @State private var markedRead = false @@ -1079,14 +1208,6 @@ struct ChatView: View { }) { ChatItemInfoView(ci: ci, chatItemInfo: $chatItemInfo) } - .sheet(isPresented: $showForwardingSheet) { - if #available(iOS 16.0, *) { - ChatItemForwardingView(ci: ci, fromChatInfo: chat.chatInfo, composeState: $composeState) - .presentationDetents([.fraction(0.8)]) - } else { - ChatItemForwardingView(ci: ci, fromChatInfo: chat.chatInfo, composeState: $composeState) - } - } } private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool { @@ -1227,7 +1348,7 @@ struct ChatView: View { var forwardButton: Button { Button { - showForwardingSheet = true + forwardedChatItems = [chatItem] } label: { Label( NSLocalizedString("Forward", comment: "chat item action"), diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 99ab778a0e..8cd851446f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -23,7 +23,7 @@ enum ComposeContextItem { case noContextItem case quotedItem(chatItem: ChatItem) case editingItem(chatItem: ChatItem) - case forwardingItem(chatItem: ChatItem, fromChatInfo: ChatInfo) + case forwardingItems(chatItems: [ChatItem], fromChatInfo: ChatInfo) } enum VoiceMessageRecordingState { @@ -73,10 +73,10 @@ struct ComposeState { } } - init(forwardingItem: ChatItem, fromChatInfo: ChatInfo) { + init(forwardingItems: [ChatItem], fromChatInfo: ChatInfo) { self.message = "" self.preview = .noPreview - self.contextItem = .forwardingItem(chatItem: forwardingItem, fromChatInfo: fromChatInfo) + self.contextItem = .forwardingItems(chatItems: forwardingItems, fromChatInfo: fromChatInfo) self.voiceMessageRecordingState = .noRecording } @@ -112,7 +112,7 @@ struct ComposeState { var forwarding: Bool { switch contextItem { - case .forwardingItem: return true + case .forwardingItems: return true default: return false } } @@ -167,6 +167,13 @@ struct ComposeState { } } + var manyMediaPreviews: Bool { + switch preview { + case let .mediaPreviews(mediaPreviews): return mediaPreviews.count > 1 + default: return false + } + } + var attachmentDisabled: Bool { if editing || forwarding || liveMessage != nil || inProgress { return true } switch preview { @@ -687,7 +694,7 @@ struct ComposeView: View { case let .quotedItem(chatItem: quotedItem): ContextItemView( chat: chat, - contextItem: quotedItem, + contextItems: [quotedItem], contextIcon: "arrowshape.turn.up.left", cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } ) @@ -695,18 +702,17 @@ struct ComposeView: View { case let .editingItem(chatItem: editingItem): ContextItemView( chat: chat, - contextItem: editingItem, + contextItems: [editingItem], contextIcon: "pencil", cancelContextItem: { clearState() } ) Divider() - case let .forwardingItem(chatItem: forwardedItem, _): + case let .forwardingItems(chatItems, _): ContextItemView( chat: chat, - contextItem: forwardedItem, + contextItems: chatItems, contextIcon: "arrowshape.turn.up.forward", - cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }, - showSender: false + cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } ) Divider() } @@ -730,10 +736,11 @@ struct ComposeView: View { } if chat.chatInfo.contact?.nextSendGrpInv ?? false { await sendMemberContactInvitation() - } else if case let .forwardingItem(ci, fromChatInfo) = composeState.contextItem { - sent = await forwardItem(ci, fromChatInfo, ttl) + } else if case let .forwardingItems(chatItems, fromChatInfo) = composeState.contextItem { + // Composed text is send as a reply to the last forwarded item + sent = await forwardItems(chatItems, fromChatInfo, ttl).last if !composeState.message.isEmpty { - sent = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl) + _ = await send(checkLinkPreview(), quoted: sent?.id, live: false, ttl: ttl) } } else if case let .editingItem(ci) = composeState.contextItem { sent = await updateMessage(ci, live: live) @@ -750,27 +757,28 @@ struct ComposeView: View { sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl) case .linkPreview: sent = await send(checkLinkPreview(), quoted: quoted, live: live, ttl: ttl) - case let .mediaPreviews(mediaPreviews: media): - // TODO batch send: batch media previews + case let .mediaPreviews(media): let last = media.count - 1 + var msgs: [ComposedMessage] = [] if last >= 0 { for i in 0.. 0 { + // Sleep to allow `progressByTimeout` update be rendered + try? await Task.sleep(nanoseconds: 100_000000) + } + if let (fileSource, msgContent) = mediaContent(media[i], text: "") { + msgs.append(ComposedMessage(fileSource: fileSource, msgContent: msgContent)) } - _ = try? await Task.sleep(nanoseconds: 100_000000) } - if case (_, .video(_, _, _)) = media[last] { - sent = await sendVideo(media[last], text: msgText, quoted: quoted, live: live, ttl: ttl) - } else { - sent = await sendImage(media[last], text: msgText, quoted: quoted, live: live, ttl: ttl) + if let (fileSource, msgContent) = mediaContent(media[last], text: msgText) { + msgs.append(ComposedMessage(fileSource: fileSource, quotedItemId: quoted, msgContent: msgContent)) } } - if sent == nil { - sent = await send(.text(msgText), quoted: quoted, live: live, ttl: ttl) + if msgs.isEmpty { + msgs = [ComposedMessage(quotedItemId: quoted, msgContent: .text(msgText))] } + sent = await send(msgs, live: live, ttl: ttl).last + case let .voicePreview(recordingFileName, duration): stopPlayback.toggle() let file = voiceCryptoFile(recordingFileName) @@ -792,6 +800,20 @@ struct ComposeView: View { } return sent + func mediaContent(_ media: (String, UploadContent?), text: String) -> (CryptoFile?, MsgContent)? { + let (previewImage, uploadContent) = media + return switch uploadContent { + case let .simpleImage(image): + (saveImage(image), .image(text: text, image: previewImage)) + case let .animatedImage(image): + (saveAnimImage(image), .image(text: text, image: previewImage)) + case let .video(_, url, duration): + (moveTempFileFromURL(url), .video(text: text, image: previewImage, duration: duration)) + case .none: + nil + } + } + func sending() async { await MainActor.run { composeState.inProgress = true } } @@ -855,23 +877,6 @@ struct ComposeView: View { } } - func sendImage(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - let (image, data) = imageData - if let data = data, let savedFile = saveAnyImage(data) { - return await send(.image(text: text, image: image), quoted: quoted, file: savedFile, live: live, ttl: ttl) - } - return nil - } - - func sendVideo(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { - let (image, data) = imageData - if case let .video(_, url, duration) = data, let savedFile = moveTempFileFromURL(url) { - ChatModel.shared.filesToDelete.remove(url) - return await send(.video(text: text, image: image, duration: duration), quoted: quoted, file: savedFile, live: live, ttl: ttl) - } - return nil - } - func voiceCryptoFile(_ fileName: String) -> CryptoFile? { if !privacyEncryptLocalFilesGroupDefault.get() { return CryptoFile.plain(fileName) @@ -888,17 +893,22 @@ struct ComposeView: View { } func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { + await send( + [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)], + live: live, + ttl: ttl + ).first + } + + func send(_ msgs: [ComposedMessage], live: Bool, ttl: Int?) async -> [ChatItem] { if let chatItems = chat.chatInfo.chatType == .local - ? await apiCreateChatItems( - noteFolderId: chat.chatInfo.apiId, - composedMessages: [ComposedMessage(fileSource: file, msgContent: mc)] - ) + ? await apiCreateChatItems(noteFolderId: chat.chatInfo.apiId, composedMessages: msgs) : await apiSendMessages( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, live: live, ttl: ttl, - composedMessages: [ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc)] + composedMessages: msgs ) { await MainActor.run { chatModel.removeLiveDummy(animated: false) @@ -906,33 +916,43 @@ struct ComposeView: View { chatModel.addChatItem(chat.chatInfo, chatItem) } } - // UI only supports sending one item at a time - return chatItems.first + return chatItems } - if let file = file { - removeFile(file.filePath) + for msg in msgs { + if let file = msg.fileSource { + removeFile(file.filePath) + } } - return nil + return [] } - func forwardItem(_ forwardedItem: ChatItem, _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> ChatItem? { + func forwardItems(_ forwardedItems: [ChatItem], _ fromChatInfo: ChatInfo, _ ttl: Int?) async -> [ChatItem] { if let chatItems = await apiForwardChatItems( toChatType: chat.chatInfo.chatType, toChatId: chat.chatInfo.apiId, fromChatType: fromChatInfo.chatType, fromChatId: fromChatInfo.apiId, - itemIds: [forwardedItem.id], + itemIds: forwardedItems.map { $0.id }, ttl: ttl ) { await MainActor.run { for chatItem in chatItems { chatModel.addChatItem(chat.chatInfo, chatItem) } + if forwardedItems.count != chatItems.count { + showAlert( + String.localizedStringWithFormat( + NSLocalizedString("%d messages not forwarded", comment: "alert title"), + forwardedItems.count - chatItems.count + ), + message: NSLocalizedString("Messages were deleted after you selected them.", comment: "alert message") + ) + } } - // TODO batch send: forward multiple messages - return chatItems.first + return chatItems + } else { + return [] } - return nil } func checkLinkPreview() -> MsgContent { @@ -949,14 +969,6 @@ struct ComposeView: View { return .text(msgText) } } - - func saveAnyImage(_ img: UploadContent) -> CryptoFile? { - switch img { - case let .simpleImage(image): return saveImage(image) - case let .animatedImage(image): return saveAnimImage(image) - default: return nil - } - } } private func startVoiceMessageRecording() async { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index 6245bbe21f..8b988f5624 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct ContextItemView: View { @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat - let contextItem: ChatItem + let contextItems: [ChatItem] let contextIcon: String let cancelContextItem: () -> Void var showSender: Bool = true @@ -24,13 +24,22 @@ struct ContextItemView: View { .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) .foregroundColor(theme.colors.secondary) - if showSender, let sender = contextItem.memberDisplayName { - VStack(alignment: .leading, spacing: 4) { - Text(sender).font(.caption).foregroundColor(theme.colors.secondary) - msgContentView(lines: 2) - } + if let singleItem = contextItems.first, contextItems.count == 1 { + if showSender, let sender = singleItem.memberDisplayName { + VStack(alignment: .leading, spacing: 4) { + Text(sender).font(.caption).foregroundColor(theme.colors.secondary) + msgContentView(lines: 2, contextItem: singleItem) + } + } else { + msgContentView(lines: 3, contextItem: singleItem) + } } else { - msgContentView(lines: 3) + Text( + chat.chatInfo.chatType == .local + ? "Saving \(contextItems.count) messages" + : "Forwarding \(contextItems.count) messages" + ) + .italic() } Spacer() Button { @@ -45,23 +54,32 @@ struct ContextItemView: View { .padding(12) .frame(minHeight: 54) .frame(maxWidth: .infinity) - .background(chatItemFrameColor(contextItem, theme)) + .background(background) } - private func msgContentView(lines: Int) -> some View { - contextMsgPreview() + private var background: Color { + contextItems.first + .map { chatItemFrameColor($0, theme) } + ?? Color(uiColor: .tertiarySystemBackground) + } + + private func msgContentView(lines: Int, contextItem: ChatItem) -> some View { + contextMsgPreview(contextItem) .multilineTextAlignment(isRightToLeft(contextItem.text) ? .trailing : .leading) .lineLimit(lines) } - private func contextMsgPreview() -> Text { + private func contextMsgPreview(_ contextItem: ChatItem) -> Text { return attachment() + messageText(contextItem.text, contextItem.formattedText, nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) func attachment() -> Text { + let isFileLoaded = if let fileSource = getLoadedFileSource(contextItem.file) { + FileManager.default.fileExists(atPath: getAppFilePath(fileSource.filePath).path) + } else { false } switch contextItem.content.msgContent { - case .file: return image("doc.fill") + case .file: return isFileLoaded ? image("doc.fill") : Text("") case .image: return image("photo") - case .voice: return image("play.fill") + case .voice: return isFileLoaded ? image("play.fill") : Text("") default: return Text("") } } @@ -75,6 +93,6 @@ struct ContextItemView: View { struct ContextItemView_Previews: PreviewProvider { static var previews: some View { let contextItem: ChatItem = ChatItem.getSample(1, .directSnd, .now, "hello") - return ContextItemView(chat: Chat.sampleData, contextItem: contextItem, contextIcon: "pencil.circle", cancelContextItem: {}) + return ContextItemView(chat: Chat.sampleData, contextItems: [contextItem], contextIcon: "pencil.circle", cancelContextItem: {}) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 9ad6e986bd..50ec8f28c1 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -227,6 +227,7 @@ struct SendMessageView: View { !composeState.editing { if case .noContextItem = composeState.contextItem, !composeState.voicePreview, + !composeState.manyMediaPreviews, let send = sendLiveMessage, let update = updateLiveMessage { Button { diff --git a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift index 87bc73a60e..746c423b8f 100644 --- a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift +++ b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift @@ -32,12 +32,15 @@ struct SelectedItemsBottomToolbar: View { var deleteItems: (Bool) -> Void var moderateItems: () -> Void //var shareItems: () -> Void + var forwardItems: () -> Void @State var deleteEnabled: Bool = false @State var deleteForEveryoneEnabled: Bool = false @State var canModerate: Bool = false @State var moderateEnabled: Bool = false + @State var forwardEnabled: Bool = false + @State var allButtonsDisabled = false var body: some View { @@ -50,6 +53,7 @@ struct SelectedItemsBottomToolbar: View { } label: { Image(systemName: "trash") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) .foregroundColor(!deleteEnabled || allButtonsDisabled ? theme.colors.secondary: .red) } @@ -61,24 +65,24 @@ struct SelectedItemsBottomToolbar: View { } label: { Image(systemName: "flag") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) .foregroundColor(!moderateEnabled || allButtonsDisabled ? theme.colors.secondary : .red) } .disabled(!moderateEnabled || allButtonsDisabled) .opacity(canModerate ? 1 : 0) - Spacer() Button { - //shareItems() + forwardItems() } label: { - Image(systemName: "square.and.arrow.up") + Image(systemName: "arrowshape.turn.up.forward") .resizable() + .scaledToFit() .frame(width: 20, height: 20, alignment: .center) - .foregroundColor(allButtonsDisabled ? theme.colors.secondary : theme.colors.primary) + .foregroundColor(!forwardEnabled || allButtonsDisabled ? theme.colors.secondary : theme.colors.primary) } - .disabled(allButtonsDisabled) - .opacity(0) + .disabled(!forwardEnabled || allButtonsDisabled) } .frame(maxHeight: .infinity) .padding([.leading, .trailing], 12) @@ -106,15 +110,16 @@ struct SelectedItemsBottomToolbar: View { if let selected = selectedItems { let me: Bool let onlyOwnGroupItems: Bool - (deleteEnabled, deleteForEveryoneEnabled, me, onlyOwnGroupItems, selectedChatItems) = chatItems.reduce((true, true, true, true, [])) { (r, ci) in + (deleteEnabled, deleteForEveryoneEnabled, me, onlyOwnGroupItems, forwardEnabled, selectedChatItems) = chatItems.reduce((true, true, true, true, true, [])) { (r, ci) in if selected.contains(ci.id) { - var (de, dee, me, onlyOwnGroupItems, sel) = r + var (de, dee, me, onlyOwnGroupItems, fe, sel) = r de = de && ci.canBeDeletedForSelf dee = dee && ci.meta.deletable && !ci.localNote onlyOwnGroupItems = onlyOwnGroupItems && ci.chatDir == .groupSnd me = me && ci.content.msgContent != nil && ci.memberToModerate(chatInfo) != nil + fe = fe && ci.content.msgContent != nil && ci.meta.itemDeleted == nil && !ci.isLiveDummy sel.insert(ci.id) // we are collecting new selected items here to account for any changes in chat items list - return (de, dee, me, onlyOwnGroupItems, sel) + return (de, dee, me, onlyOwnGroupItems, fe, sel) } else { return r } diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 7b80dd1544..b8de0e4ceb 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -47,8 +47,24 @@ func showAlert( buttonAction() }) if cancelButton { - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel)) + alert.addAction(cancelAlertAction) } topController.present(alert, animated: true) } } + +func showAlert( + _ title: String, + message: String? = nil, + actions: () -> [UIAlertAction] = { [okAlertAction] } +) { + if let topController = getTopViewController() { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + for action in actions() { alert.addAction(action) } + topController.present(alert, animated: true) + } +} + +let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default) + +let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index c210431d12..b0cd62a867 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -49,6 +49,7 @@ public enum ChatCommand { case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64]) case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) + case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64]) case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) case apiGetNtfToken case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode) @@ -204,6 +205,7 @@ public enum ChatCommand { case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)" case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))" + case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))" case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl): let ttlStr = ttl != nil ? "\(ttl!)" : "default" return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)" @@ -359,6 +361,7 @@ public enum ChatCommand { case .apiConnectContactViaAddress: return "apiConnectContactViaAddress" case .apiDeleteMemberChatItem: return "apiDeleteMemberChatItem" case .apiChatItemReaction: return "apiChatItemReaction" + case .apiPlanForwardChatItems: return "apiPlanForwardChatItems" case .apiForwardChatItems: return "apiForwardChatItems" case .apiGetNtfToken: return "apiGetNtfToken" case .apiRegisterToken: return "apiRegisterToken" @@ -601,6 +604,7 @@ public enum ChatResponse: Decodable, Error { case groupEmpty(user: UserRef, groupInfo: GroupInfo) case userContactLinkSubscribed case newChatItems(user: UserRef, chatItems: [AChatItem]) + case forwardPlan(user: UserRef, chatItemIds: [Int64], forwardConfirmation: ForwardConfirmation?) case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) case chatItemNotChanged(user: UserRef, chatItem: AChatItem) @@ -772,6 +776,7 @@ public enum ChatResponse: Decodable, Error { case .groupEmpty: return "groupEmpty" case .userContactLinkSubscribed: return "userContactLinkSubscribed" case .newChatItems: return "newChatItems" + case .forwardPlan: return "forwardPlan" case .chatItemsStatusesUpdated: return "chatItemsStatusesUpdated" case .chatItemUpdated: return "chatItemUpdated" case .chatItemNotChanged: return "chatItemNotChanged" @@ -943,6 +948,7 @@ public enum ChatResponse: Decodable, Error { case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) + case let .forwardPlan(u, chatItemIds, forwardConfirmation): return withUser(u, "items: \(chatItemIds) forwardConfirmation: \(String(describing: forwardConfirmation))") case let .chatItemsStatusesUpdated(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) @@ -1134,7 +1140,7 @@ public enum ChatPagination { public struct ComposedMessage: Encodable { public var fileSource: CryptoFile? var quotedItemId: Int64? - var msgContent: MsgContent + public var msgContent: MsgContent public init(fileSource: CryptoFile? = nil, quotedItemId: Int64? = nil, msgContent: MsgContent) { self.fileSource = fileSource @@ -1595,6 +1601,13 @@ public enum NetworkStatus: Decodable, Equatable { } } +public enum ForwardConfirmation: Decodable, Hashable { + case filesNotAccepted(fileIds: [Int64]) + case filesInProgress(filesCount: Int) + case filesMissing(filesCount: Int) + case filesFailed(filesCount: Int) +} + public struct ConnNetworkStatus: Decodable { public var agentConnId: String public var networkStatus: NetworkStatus From 10ded1530cf9396feddddde7ee3fabf14ca84a89 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:14:50 +0400 Subject: [PATCH 070/704] desktop: cleanup current invitation when closing new chat sheet by pressing on center modal (#4904) --- .../kotlin/chat/simplex/common/views/newchat/NewChatView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 0e7a01ddab..8e6af4080d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -68,7 +68,7 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC * Otherwise, it will be called here AFTER [AddContactLearnMore] is launched and will clear the value too soon. * It will be dropped automatically when connection established or when user goes away from this screen. **/ - if (chatModel.showingInvitation.value != null && ModalManager.start.openModalCount() == 1) { + if (chatModel.showingInvitation.value != null && ModalManager.start.openModalCount() <= 1) { val conn = contactConnection.value if (chatModel.showingInvitation.value?.connChatUsed == false && conn != null) { AlertManager.shared.showAlertDialog( From c5813b348978c06a2a24c4bb8bc377f262cc91fa Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 19 Sep 2024 08:36:54 +0000 Subject: [PATCH 071/704] android, desktop: catch URI creation errors (#4901) * android, desktop: catch URI creation errors * showing alert when pasting an incorrect link * moved from Uri to String while processing SimpleX links --- .../src/main/java/chat/simplex/app/MainActivity.kt | 9 +++------ .../kotlin/chat/simplex/common/model/ChatModel.kt | 2 +- .../common/views/chat/group/GroupMemberInfoView.kt | 3 +-- .../simplex/common/views/chatlist/ChatListView.kt | 4 ++-- .../chat/simplex/common/views/helpers/Utils.kt | 7 +++---- .../chat/simplex/common/views/newchat/ConnectPlan.kt | 12 ++++++------ .../simplex/common/views/newchat/NewChatSheet.kt | 2 +- .../chat/simplex/common/views/newchat/NewChatView.kt | 2 +- .../simplex/common/views/call/CallView.desktop.kt | 2 +- 9 files changed, 19 insertions(+), 24 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index c63b6cb497..f29c0c3387 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -150,12 +150,9 @@ fun processIntent(intent: Intent?) { "android.intent.action.VIEW" -> { val uri = intent.data if (uri != null) { - val transformedUri = uri.toURIOrNull() - if (transformedUri != null) { - chatModel.appOpenUrl.value = null to transformedUri - } else { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_parsing_uri_title), generalGetString(MR.strings.error_parsing_uri_desc)) - } + chatModel.appOpenUrl.value = null to uri.toString() + } else { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_parsing_uri_title), generalGetString(MR.strings.error_parsing_uri_desc)) } } } 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 5671322d17..ac3397ce97 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 @@ -82,7 +82,7 @@ object ChatModel { val desktopOnboardingRandomPassword = mutableStateOf(false) // set when app is opened via contact or invitation URI (rhId, uri) - val appOpenUrl = mutableStateOf?>(null) + val appOpenUrl = mutableStateOf?>(null) // Needed to check for bottom nav bar and to apply or not navigation bar color on Android val newChatSheetVisible = mutableStateOf(false) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index f814fb2eb8..9981d70a52 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -665,9 +665,8 @@ private fun updateMemberRoleDialog( fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) { try { - val uri = URI(connReqUri) withBGApi { - planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) + planAndConnect(rhId, connReqUri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 54c67674ad..50949b0b16 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -478,7 +478,7 @@ private fun ToggleFilterEnabledButton() { @Composable expect fun ActiveCallInteractiveArea(call: Call) -fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) { +fun connectIfOpenedViaUri(rhId: Long?, uri: String, chatModel: ChatModel) { Log.d(TAG, "connectIfOpenedViaUri: opened via link") if (chatModel.currentUser.value == null) { chatModel.appOpenUrl.value = rhId to uri @@ -566,7 +566,7 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState< withBGApi { planAndConnect( chatModel.remoteHostId(), - URI.create(link), + link, incognito = null, filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, filterKnownGroup = { searchChatFilteredBySimplexLink.value = it.id }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 7b504116cc..39611361e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -480,12 +480,11 @@ inline fun serializableSaver(): Saver = Saver( ) fun UriHandler.openVerifiedSimplexUri(uri: String) { - val URI = try { URI.create(uri) } catch (e: Exception) { null } - if (URI != null) { - connectIfOpenedViaUri(chatModel.remoteHostId(), URI, ChatModel) - } + connectIfOpenedViaUri(chatModel.remoteHostId(), uri, ChatModel) } +fun uriCreateOrNull(uri: String) = try { URI.create(uri) } catch (e: Exception) { null } + fun UriHandler.openUriCatching(uri: String) { try { openUri(uri) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index e49fbcf1e6..6c18e47df3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -20,7 +20,7 @@ enum class ConnectionLinkType { suspend fun planAndConnect( rhId: Long?, - uri: URI, + uri: String, incognito: Boolean?, close: (() -> Unit)?, cleanup: (() -> Unit)? = null, @@ -29,7 +29,7 @@ suspend fun planAndConnect( ) { val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri.toString()) if (connectionPlan != null) { - val link = strHasSingleSimplexLink(uri.toString().trim()) + val link = strHasSingleSimplexLink(uri.trim()) val linkText = if (link?.format is Format.SimplexLink) "

${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}" else @@ -323,13 +323,13 @@ suspend fun planAndConnect( suspend fun connectViaUri( chatModel: ChatModel, rhId: Long?, - uri: URI, + uri: String, incognito: Boolean, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, cleanup: (() -> Unit)?, ) { - val pcc = chatModel.controller.apiConnect(rhId, incognito, uri.toString()) + val pcc = chatModel.controller.apiConnect(rhId, incognito, uri) val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION if (pcc != null) { withChats { @@ -361,7 +361,7 @@ fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType fun askCurrentOrIncognitoProfileAlert( chatModel: ChatModel, rhId: Long?, - uri: URI, + uri: String, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, title: String, @@ -417,7 +417,7 @@ fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, co fun ownGroupLinkConfirmConnect( chatModel: ChatModel, rhId: Long?, - uri: URI, + uri: String, linkText: String, incognito: Boolean?, connectionPlan: ConnectionPlan?, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 2a6c8838e4..1a3ea10806 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -482,7 +482,7 @@ private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState< withBGApi { planAndConnect( chatModel.remoteHostId(), - URI.create(link), + link, incognito = null, filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id }, close = close, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 8e6af4080d..3ac8cdee64 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -655,7 +655,7 @@ private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boole private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanup: (() -> Unit)? = null) { planAndConnect( rhId, - URI.create(link), + link, close = close, cleanup = cleanup, incognito = null diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt index d6331616cc..8d40ab4c0a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt @@ -204,7 +204,7 @@ fun startServer(onResponse: (WVAPIMessage) -> Unit): NanoWSD { return when { session.headers["upgrade"] == "websocket" -> super.handle(session) session.uri.contains("/simplex/call/") -> resourcesToResponse("/desktop/call.html") - else -> resourcesToResponse(URI.create(session.uri).path) + else -> resourcesToResponse(uriCreateOrNull(session.uri)?.path ?: return newFixedLengthResponse("Error parsing URL")) } } } From f7cb6f279640654f6b21186490c0880c7e6ca74f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 19 Sep 2024 14:10:07 +0100 Subject: [PATCH 072/704] Revert "android, desktop: compose 1.6.11 (#4902)" This reverts commit acc9be1a5b6c9cd617b7ff3c2ee8b35bae51fd10. --- apps/multiplatform/gradle.properties | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 11d7b5e7fb..6123ce156f 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -21,6 +21,8 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 @@ -32,4 +34,4 @@ desktop.version_code=66 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 -compose.version=1.6.11 +compose.version=1.6.1 From d69e222b7b27351ad5f5f1d6355507c6b642996b Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 19 Sep 2024 15:39:14 +0100 Subject: [PATCH 073/704] android, desktop: bulk forward (#4868) --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/ChatModel.kt | 8 + .../chat/simplex/common/model/SimpleXAPI.kt | 202 +++++++++++++----- .../simplex/common/views/chat/ChatView.kt | 166 +++++++++++++- .../simplex/common/views/chat/ComposeView.kt | 70 +++--- .../common/views/chat/ContextItemView.kt | 66 +++--- .../views/chat/SelectableChatItemToolbars.kt | 29 ++- .../common/views/chatlist/ShareListView.kt | 16 +- .../simplex/common/views/helpers/Enums.kt | 5 +- .../commonMain/resources/MR/base/strings.xml | 16 ++ 9 files changed, 450 insertions(+), 128 deletions(-) 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 ac3397ce97..4fcda25855 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 @@ -1404,6 +1404,14 @@ class Group ( var members: List ) +@Serializable +sealed class ForwardConfirmation { + @Serializable @SerialName("filesNotAccepted") data class FilesNotAccepted(val fileIds: List) : ForwardConfirmation() + @Serializable @SerialName("filesInProgress") data class FilesInProgress(val filesCount: Int) : ForwardConfirmation() + @Serializable @SerialName("filesMissing") data class FilesMissing(val filesCount: Int) : ForwardConfirmation() + @Serializable @SerialName("filesFailed") data class FilesFailed(val filesCount: Int) : ForwardConfirmation() +} + @Serializable data class GroupInfo ( val groupId: Long, 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 906ca826ca..7bf97a6e37 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 @@ -1,18 +1,19 @@ package chat.simplex.common.model import SectionItemView -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.* +import androidx.compose.material.* import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatModel.changingActiveUserMutex @@ -905,7 +906,15 @@ object ChatController { return processSendMessageCmd(rh, cmd)?.map { it.chatItem } } - + suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, chatItemIds: List): CR.ForwardPlan? { + return when (val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds))) { + is CR.ForwardPlan -> r + else -> { + apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r) + null + } + } + } suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, mc: MsgContent, live: Boolean = false): AChatItem? { val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, mc, live)) @@ -1541,50 +1550,132 @@ object ChatController { } } - suspend fun apiReceiveFile(rh: Long?, fileId: Long, userApprovedRelays: Boolean, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? { - // -1 here is to override default behavior of providing current remote host id because file can be asked by local device while remote is connected - val r = sendCmd(rh, CC.ReceiveFile(fileId, userApprovedRelays = userApprovedRelays, encrypt = encrypted, inline = inline)) - return when (r) { - is CR.RcvFileAccepted -> r.chatItem - is CR.RcvFileAcceptedSndCancelled -> { - Log.d(TAG, "apiReceiveFile error: sender cancelled file transfer") - if (!auto) { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.cannot_receive_file), - generalGetString(MR.strings.sender_cancelled_file_transfer) - ) - } - null - } + suspend fun receiveFiles(rhId: Long?, user: UserLike, fileIds: List, userApprovedRelays: Boolean = false, auto: Boolean = false) { + val fileIdsToApprove = mutableListOf() + val srvsToApprove = mutableSetOf() + val otherFileErrs = mutableListOf() - else -> { - if (!(networkErrorAlert(r))) { - val maybeChatError = chatError(r) - if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { - Log.d(TAG, "apiReceiveFile ignoring FileCancelled or FileAlreadyReceiving error") - } else if (maybeChatError is ChatErrorType.FileNotApproved) { - Log.d(TAG, "apiReceiveFile FileNotApproved error") - if (!auto) { - val srvs = maybeChatError.unknownServers.map{ serverHostname(it) } - AlertManager.shared.showAlertDialog( - title = generalGetString(MR.strings.file_not_approved_title), - text = generalGetString(MR.strings.file_not_approved_descr).format(srvs.sorted().joinToString(separator = ", ")), - confirmText = generalGetString(MR.strings.download_file), - onConfirm = { - val user = chatModel.currentUser.value - if (user != null) { - withBGApi { chatModel.controller.receiveFile(rh, user, fileId, userApprovedRelays = true) } - } - }, - ) - } - } else if (!auto) { - apiErrorAlert("apiReceiveFile", generalGetString(MR.strings.error_receiving_file), r) - } + for (fileId in fileIds) { + val r = sendCmd( + rhId, CC.ReceiveFile( + fileId, + userApprovedRelays = userApprovedRelays || !appPrefs.privacyAskToApproveRelays.get(), + encrypt = appPrefs.privacyEncryptLocalFiles.get(), + inline = null + ) + ) + if (r is CR.RcvFileAccepted) { + chatItemSimpleUpdate(rhId, user, r.chatItem) + } else { + val maybeChatError = chatError(r) + if (maybeChatError is ChatErrorType.FileNotApproved) { + fileIdsToApprove.add(maybeChatError.fileId) + srvsToApprove.addAll(maybeChatError.unknownServers.map { serverHostname(it) }) + } else { + otherFileErrs.add(r) } - null } } + + if (!auto) { + // If there are not approved files, alert is shown the same way both in case of singular and plural files reception + if (fileIdsToApprove.isNotEmpty()) { + showFilesToApproveAlert( + srvsToApprove = srvsToApprove, + otherFileErrs = otherFileErrs, + approveFiles = { + withBGApi { + receiveFiles( + rhId = rhId, + user = user, + fileIds = fileIdsToApprove, + userApprovedRelays = true + ) + } + } + ) + } else if (otherFileErrs.size == 1) { // If there is a single other error, we differentiate on it + when (val errCR = otherFileErrs.first()) { + is CR.RcvFileAcceptedSndCancelled -> { + Log.d(TAG, "receiveFiles error: sender cancelled file transfer") + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.cannot_receive_file), + generalGetString(MR.strings.sender_cancelled_file_transfer) + ) + } + else -> { + val maybeChatError = chatError(errCR) + if (maybeChatError is ChatErrorType.FileCancelled || maybeChatError is ChatErrorType.FileAlreadyReceiving) { + Log.d(TAG, "receiveFiles ignoring FileCancelled or FileAlreadyReceiving error") + } else { + apiErrorAlert("receiveFiles", generalGetString(MR.strings.error_receiving_file), errCR) + } + } + } + } else if (otherFileErrs.size > 1) { // If there are multiple other errors, we show general alert + val errsStr = otherFileErrs.map { json.encodeToString(it) }.joinToString(separator = "\n") + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.error_receiving_file), + text = String.format(generalGetString(MR.strings.n_file_errors), otherFileErrs.size, errsStr), + shareText = true + ) + } + } + } + + private fun showFilesToApproveAlert( + srvsToApprove: Set, + otherFileErrs: List, + approveFiles: (() -> Unit) + ) { + val srvsToApproveStr = srvsToApprove.sorted().joinToString(separator = ", ") + val alertText = + generalGetString(MR.strings.file_not_approved_descr).format(srvsToApproveStr) + + (if (otherFileErrs.isNotEmpty()) "\n" + generalGetString(MR.strings.n_other_file_errors).format(otherFileErrs.size) else "") + + AlertManager.shared.showAlertDialogButtonsColumn(generalGetString(MR.strings.file_not_approved_title), alertText, belowTextContent = { + if (otherFileErrs.isNotEmpty()) { + val clipboard = LocalClipboardManager.current + SimpleButtonFrame(click = { + clipboard.setText(AnnotatedString(otherFileErrs.map { json.encodeToString(it) }.joinToString(separator = "\n"))) + }) { + Icon( + painterResource(MR.images.ic_content_copy), + contentDescription = null, + tint = MaterialTheme.colors.primary, + modifier = Modifier.padding(end = 8.dp) + ) + Text(generalGetString(MR.strings.copy_error), color = MaterialTheme.colors.primary) + } + } + }) { + Row( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), + horizontalArrangement = Arrangement.SpaceBetween + ) { + val focusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + // Wait before focusing to prevent auto-confirming if a user used Enter key on hardware keyboard + delay(200) + focusRequester.requestFocus() + } + TextButton(onClick = AlertManager.shared::hideAlert) { Text(generalGetString(MR.strings.cancel_verb)) } + TextButton(onClick = { + approveFiles.invoke() + AlertManager.shared.hideAlert() + }, Modifier.focusRequester(focusRequester)) { Text(generalGetString(MR.strings.download_file)) } + } + } + } + + suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, userApprovedRelays: Boolean = false, auto: Boolean = false) { + receiveFiles( + rhId = rhId, + user = user, + fileIds = listOf(fileId), + userApprovedRelays = userApprovedRelays, + auto = auto + ) } suspend fun cancelFile(rh: Long?, user: User, fileId: Long) { @@ -2689,19 +2780,6 @@ object ChatController { } } - suspend fun receiveFile(rhId: Long?, user: UserLike, fileId: Long, userApprovedRelays: Boolean = false, auto: Boolean = false) { - val chatItem = apiReceiveFile( - rhId, - fileId, - userApprovedRelays = userApprovedRelays || !appPrefs.privacyAskToApproveRelays.get(), - encrypted = appPrefs.privacyEncryptLocalFiles.get(), - auto = auto - ) - if (chatItem != null) { - chatItemSimpleUpdate(rhId, user, chatItem) - } - } - suspend fun leaveGroup(rh: Long?, groupId: Long) { val groupInfo = apiLeaveGroup(rh, groupId) if (groupInfo != null) { @@ -2914,6 +2992,7 @@ sealed class CC { class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC() class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC() class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC() + class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List): CC() class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC() class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC() class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC() @@ -3072,6 +3151,9 @@ sealed class CC { val ttlStr = if (ttl != null) "$ttl" else "default" "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}" } + is ApiPlanForwardChatItems -> { + "/_forward plan ${chatRef(fromChatType, fromChatId)} ${chatItemIds.joinToString(",")}" + } is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}" is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}" is ApiJoinGroup -> "/_join #$groupId" @@ -3216,6 +3298,7 @@ sealed class CC { is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem" is ApiChatItemReaction -> "apiChatItemReaction" is ApiForwardChatItems -> "apiForwardChatItems" + is ApiPlanForwardChatItems -> "apiPlanForwardChatItems" is ApiNewGroup -> "apiNewGroup" is ApiAddMember -> "apiAddMember" is ApiJoinGroup -> "apiJoinGroup" @@ -4878,6 +4961,7 @@ sealed class CR { @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR() @Serializable @SerialName("chatItemsDeleted") class ChatItemsDeleted(val user: UserRef, val chatItemDeletions: List, val byUser: Boolean): CR() + @Serializable @SerialName("forwardPlan") class ForwardPlan(val user: UserRef, val itemsCount: Int, val chatItemIds: List, val forwardConfirmation: ForwardConfirmation? = null): CR() // group events @Serializable @SerialName("groupCreated") class GroupCreated(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("sentGroupInvitation") class SentGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val member: GroupMember): CR() @@ -5055,6 +5139,7 @@ sealed class CR { is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" is ChatItemsDeleted -> "chatItemsDeleted" + is ForwardPlan -> "forwardPlan" is GroupCreated -> "groupCreated" is SentGroupInvitation -> "sentGroupInvitation" is UserAcceptedGroupSent -> "userAcceptedGroupSent" @@ -5224,6 +5309,7 @@ sealed class CR { is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") is ChatItemsDeleted -> withUser(user, "${chatItemDeletions.map { (deletedChatItem, toChatItem) -> "deletedChatItem: ${json.encodeToString(deletedChatItem)}\ntoChatItem: ${json.encodeToString(toChatItem)}" }} \nbyUser: $byUser") + is ForwardPlan -> withUser(user, "itemsCount: $itemsCount\nchatItemIds: ${json.encodeToString(chatItemIds)}\nforwardConfirmation: ${json.encodeToString(forwardConfirmation)}") is GroupCreated -> withUser(user, json.encodeToString(groupInfo)) is SentGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmember: $member") is UserAcceptedGroupSent -> json.encodeToString(groupInfo) 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 2bef0bdca7..835f884f98 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 @@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.* +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs @@ -172,7 +173,30 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - }) } } - } + }, + forwardItems = { + val itemIds = selectedChatItems.value + + if (itemIds != null) { + withBGApi { + val chatItemIds = itemIds.toList() + val forwardPlan = controller.apiPlanForwardChatItems( + rh = chatRh, + fromChatType = chatInfo.chatType, + fromChatId = chatInfo.apiId, + chatItemIds = chatItemIds + ) + + if (forwardPlan != null) { + if (forwardPlan.chatItemIds.count() < chatItemIds.count() || forwardPlan.forwardConfirmation != null) { + handleForwardConfirmation(chatRh, forwardPlan, chatInfo) + } else { + forwardContent(forwardPlan.chatItemIds, chatInfo) + } + } + } + } + }, ) } }, @@ -347,9 +371,9 @@ fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) - openDirectChat(chatRh, contactId, chatModel) } }, - forwardItem = { cItem, cInfo -> + forwardItem = { cInfo, cItem -> chatModel.chatId.value = null - chatModel.sharedContent.value = SharedContent.Forward(cInfo, cItem) + chatModel.sharedContent.value = SharedContent.Forward(listOf(cItem), cInfo) }, updateContactStats = { contact -> withBGApi { @@ -1416,6 +1440,65 @@ private fun TopEndFloatingButton( } } +@Composable +private fun DownloadFilesButton( + forwardConfirmation: ForwardConfirmation.FilesNotAccepted, + rhId: Long?, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding +) { + val user = chatModel.currentUser.value + + if (user != null) { + TextButton( + contentPadding = contentPadding, + modifier = modifier, + onClick = { + AlertManager.shared.hideAlert() + + withBGApi { + controller.receiveFiles( + rhId = rhId, + fileIds = forwardConfirmation.fileIds, + user = user + ) + } + } + ) { + Text(stringResource(MR.strings.forward_files_not_accepted_receive_files), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } +} + +@Composable +private fun ForwardButton( + forwardPlan: CR.ForwardPlan, + chatInfo: ChatInfo, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding +) { + TextButton( + onClick = { + forwardContent(forwardPlan.chatItemIds, chatInfo) + AlertManager.shared.hideAlert() + }, + modifier = modifier, + contentPadding = contentPadding + ) { + Text(stringResource(MR.strings.forward_chat_item), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } +} + +@Composable +private fun ButtonRow(horizontalArrangement: Arrangement.Horizontal, content: @Composable() (RowScope.() -> Unit)) { + Row( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), + horizontalArrangement = horizontalArrangement + ) { + content() + } +} + val chatViewScrollState = MutableStateFlow(false) fun addGroupMembers(groupInfo: GroupInfo, rhId: Long?, view: Any? = null, close: (() -> Unit)? = null) { @@ -1712,6 +1795,83 @@ private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConf override val touchSlop: Float get() = slop } +private fun forwardContent(chatItemsIds: List, chatInfo: ChatInfo) { + chatModel.chatId.value = null + chatModel.sharedContent.value = SharedContent.Forward( + chatModel.chatItems.value.filter { chatItemsIds.contains(it.id) }, + chatInfo + ) +} + +private fun forwardConfirmationAlertDescription(forwardConfirmation: ForwardConfirmation): String { + return when (forwardConfirmation) { + is ForwardConfirmation.FilesNotAccepted -> String.format(generalGetString(MR.strings.forward_files_not_accepted_desc), forwardConfirmation.fileIds.count()) + is ForwardConfirmation.FilesInProgress -> String.format(generalGetString(MR.strings.forward_files_in_progress_desc), forwardConfirmation.filesCount) + is ForwardConfirmation.FilesFailed -> String.format(generalGetString(MR.strings.forward_files_failed_to_receive_desc), forwardConfirmation.filesCount) + is ForwardConfirmation.FilesMissing -> String.format(generalGetString(MR.strings.forward_files_missing_desc), forwardConfirmation.filesCount) + } +} + +private fun handleForwardConfirmation( + rhId: Long?, + forwardPlan: CR.ForwardPlan, + chatInfo: ChatInfo +) { + var alertDescription = if (forwardPlan.forwardConfirmation != null) forwardConfirmationAlertDescription(forwardPlan.forwardConfirmation) else "" + + if (forwardPlan.chatItemIds.isNotEmpty()) { + alertDescription += "\n${generalGetString(MR.strings.forward_alert_forward_messages_without_files)}" + } + + AlertManager.shared.showAlertDialogButtonsColumn( + title = if (forwardPlan.chatItemIds.isNotEmpty()) + String.format(generalGetString(MR.strings.forward_alert_title_messages_to_forward), forwardPlan.chatItemIds.count()) else + generalGetString(MR.strings.forward_alert_title_nothing_to_forward), + text = alertDescription, + buttons = { + if (forwardPlan.chatItemIds.isNotEmpty()) { + when (val confirmation = forwardPlan.forwardConfirmation) { + is ForwardConfirmation.FilesNotAccepted -> { + val fillMaxWidthModifier = Modifier.fillMaxWidth() + val contentPadding = PaddingValues(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) + Column { + ForwardButton(forwardPlan, chatInfo, fillMaxWidthModifier, contentPadding) + DownloadFilesButton(confirmation, rhId, fillMaxWidthModifier, contentPadding) + TextButton(onClick = { AlertManager.shared.hideAlert() }, modifier = fillMaxWidthModifier, contentPadding = contentPadding) { + Text(stringResource(MR.strings.cancel_verb), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + else -> { + ButtonRow(Arrangement.SpaceBetween) { + TextButton(onClick = { AlertManager.shared.hideAlert() }) { + Text(stringResource(MR.strings.cancel_verb), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + ForwardButton(forwardPlan, chatInfo) + } + } + } + } else { + when (val confirmation = forwardPlan.forwardConfirmation) { + is ForwardConfirmation.FilesNotAccepted -> { + ButtonRow(Arrangement.SpaceBetween) { + TextButton(onClick = { AlertManager.shared.hideAlert() }) { + Text(stringResource(MR.strings.cancel_verb), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + DownloadFilesButton(confirmation, rhId) + } + } + else -> ButtonRow(Arrangement.Center) { + TextButton(onClick = { AlertManager.shared.hideAlert() }) { + Text(stringResource(MR.strings.ok), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + } + } + ) +} + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, 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 821a449509..cad18af9bb 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 @@ -13,7 +13,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontStyle import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -49,7 +48,7 @@ sealed class ComposeContextItem { @Serializable object NoContextItem: ComposeContextItem() @Serializable class QuotedItem(val chatItem: ChatItem): ComposeContextItem() @Serializable class EditingItem(val chatItem: ChatItem): ComposeContextItem() - @Serializable class ForwardingItem(val chatItem: ChatItem, val fromChatInfo: ChatInfo): ComposeContextItem() + @Serializable class ForwardingItems(val chatItems: List, val fromChatInfo: ChatInfo): ComposeContextItem() } @Serializable @@ -85,7 +84,7 @@ data class ComposeState( } val forwarding: Boolean get() = when (contextItem) { - is ComposeContextItem.ForwardingItem -> true + is ComposeContextItem.ForwardingItems -> true else -> false } val sendEnabled: () -> Boolean @@ -407,33 +406,41 @@ fun ComposeView( return null } - suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? { + suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): List? { val cInfo = chat.chatInfo val cs = composeState.value - var sent: ChatItem? + var sent: List? val msgText = text ?: cs.message fun sending() { composeState.value = composeState.value.copy(inProgress = true) } - suspend fun forwardItem(rhId: Long?, forwardedItem: ChatItem, fromChatInfo: ChatInfo, ttl: Int?): ChatItem? { + suspend fun forwardItem(rhId: Long?, forwardedItem: List, fromChatInfo: ChatInfo, ttl: Int?): List? { val chatItems = controller.apiForwardChatItems( rh = rhId, toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, - itemIds = listOf(forwardedItem.id), + itemIds = forwardedItem.map { it.id }, ttl = ttl ) + chatItems?.forEach { chatItem -> withChats { addChatItem(rhId, chat.chatInfo, chatItem) } } - // TODO batch send: forward multiple messages - return chatItems?.firstOrNull() + + if (chatItems != null && chatItems.count() < forwardedItem.count()) { + AlertManager.shared.showAlertMsg( + title = String.format(generalGetString(MR.strings.forward_files_messages_deleted_after_selection_title), forwardedItem.count() - chatItems.count()), + text = generalGetString(MR.strings.forward_files_messages_deleted_after_selection_desc) + ) + } + + return chatItems } fun checkLinkPreview(): MsgContent { @@ -506,16 +513,25 @@ fun ComposeView( if (chat.nextSendGrpInv) { sendMemberContactInvitation() sent = null - } else if (cs.contextItem is ComposeContextItem.ForwardingItem) { - sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItem, cs.contextItem.fromChatInfo, ttl = ttl) + } else if (cs.contextItem is ComposeContextItem.ForwardingItems) { + sent = forwardItem(chat.remoteHostId, cs.contextItem.chatItems, cs.contextItem.fromChatInfo, ttl = ttl) if (cs.message.isNotEmpty()) { - sent = send(chat, checkLinkPreview(), quoted = sent?.id, live = false, ttl = ttl) + sent?.mapIndexed { index, message -> + if (index == sent!!.lastIndex) { + send(chat, checkLinkPreview(), quoted = message.id, live = false, ttl = ttl) + } else { + message + } + } } - } else if (cs.contextItem is ComposeContextItem.EditingItem) { + } + else if (cs.contextItem is ComposeContextItem.EditingItem) { val ei = cs.contextItem.chatItem - sent = updateMessage(ei, chat, live) + val updatedMessage = updateMessage(ei, chat, live) + sent = if (updatedMessage != null) listOf(updatedMessage) else null } else if (liveMessage != null && liveMessage.sent) { - sent = updateMessage(liveMessage.chatItem, chat, live) + val updatedMessage = updateMessage(liveMessage.chatItem, chat, live) + sent = if (updatedMessage != null) listOf(updatedMessage) else null } else { val msgs: ArrayList = ArrayList() val files: ArrayList = ArrayList() @@ -608,21 +624,23 @@ fun ComposeView( localPath = file.filePath ) } - sent = send(chat, content, if (index == 0) quotedItemId else null, file, + val sendResult = send(chat, content, if (index == 0) quotedItemId else null, file, live = if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false, ttl = ttl ) + sent = if (sendResult != null) listOf(sendResult) else null } if (sent == null && (cs.preview is ComposePreview.MediaPreview || cs.preview is ComposePreview.FilePreview || cs.preview is ComposePreview.VoicePreview) ) { - sent = send(chat, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) + val sendResult = send(chat, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) + sent = if (sendResult != null) listOf(sendResult) else null } } val wasForwarding = cs.forwarding - val forwardingFromChatId = (cs.contextItem as? ComposeContextItem.ForwardingItem)?.fromChatInfo?.id + val forwardingFromChatId = (cs.contextItem as? ComposeContextItem.ForwardingItems)?.fromChatInfo?.id clearState(live) val draft = chatModel.draft.value if (wasForwarding && chatModel.draftChatId.value == chat.chatInfo.id && forwardingFromChatId != chat.chatInfo.id && draft != null) { @@ -724,8 +742,8 @@ fun ComposeView( val typedMsg = cs.message if ((cs.sendEnabled() || cs.contextItem is ComposeContextItem.QuotedItem) && (cs.liveMessage == null || !cs.liveMessage.sent)) { val ci = sendMessageAsync(typedMsg, live = true, ttl = null) - if (ci != null) { - composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = typedMsg, sent = true)) + if (!ci.isNullOrEmpty()) { + composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci.last(), typedMsg = typedMsg, sentMsg = typedMsg, sent = true)) } } else if (cs.liveMessage == null) { val cItem = chatModel.addLiveDummy(chat.chatInfo) @@ -745,8 +763,8 @@ fun ComposeView( val sentMsg = liveMessageToSend(liveMessage, typedMsg) if (sentMsg != null) { val ci = sendMessageAsync(sentMsg, live = true, ttl = null) - if (ci != null) { - composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci, typedMsg = typedMsg, sentMsg = sentMsg, sent = true)) + if (!ci.isNullOrEmpty()) { + composeState.value = composeState.value.copy(liveMessage = LiveMessage(ci.last(), typedMsg = typedMsg, sentMsg = sentMsg, sent = true)) } } else if (liveMessage.typedMsg != typedMsg) { composeState.value = composeState.value.copy(liveMessage = liveMessage.copy(typedMsg = typedMsg)) @@ -805,13 +823,13 @@ fun ComposeView( fun contextItemView() { when (val contextItem = composeState.value.contextItem) { ComposeContextItem.NoContextItem -> {} - is ComposeContextItem.QuotedItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_reply)) { + is ComposeContextItem.QuotedItem -> ContextItemView(listOf(contextItem.chatItem), painterResource(MR.images.ic_reply), chatType = chat.chatInfo.chatType) { composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) } - is ComposeContextItem.EditingItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_edit_filled)) { + is ComposeContextItem.EditingItem -> ContextItemView(listOf(contextItem.chatItem), painterResource(MR.images.ic_edit_filled), chatType = chat.chatInfo.chatType) { clearState() } - is ComposeContextItem.ForwardingItem -> ContextItemView(contextItem.chatItem, painterResource(MR.images.ic_forward), showSender = false) { + is ComposeContextItem.ForwardingItems -> ContextItemView(contextItem.chatItems, painterResource(MR.images.ic_forward), showSender = false, chatType = chat.chatInfo.chatType) { composeState.value = composeState.value.copy(contextItem = ComposeContextItem.NoContextItem) } } @@ -834,7 +852,7 @@ fun ComposeView( is SharedContent.Media -> composeState.processPickedMedia(shared.uris, shared.text) is SharedContent.File -> composeState.processPickedFile(shared.uri, shared.text) is SharedContent.Forward -> composeState.value = composeState.value.copy( - contextItem = ComposeContextItem.ForwardingItem(shared.chatItem, shared.fromChatInfo), + contextItem = ComposeContextItem.ForwardingItems(shared.chatItems, shared.fromChatInfo), preview = if (composeState.value.preview is ComposePreview.CLinkPreview) composeState.value.preview else ComposePreview.NoPreview ) null -> {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt index 0c4efa7d0d..5850f0b7ec 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt @@ -13,28 +13,31 @@ import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.runtime.* import androidx.compose.ui.text.* +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.* import chat.simplex.common.model.* +import chat.simplex.common.platform.getLoadedFilePath +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import kotlinx.datetime.Clock @Composable fun ContextItemView( - contextItem: ChatItem, + contextItems: List, contextIcon: Painter, showSender: Boolean = true, - cancelContextItem: () -> Unit + chatType: ChatType, + cancelContextItem: () -> Unit, ) { - val sent = contextItem.chatDir.sent val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage @Composable - fun MessageText(attachment: ImageResource?, lines: Int) { + fun MessageText(contextItem: ChatItem, attachment: ImageResource?, lines: Int) { val inlineContent: Pair Unit, Map>? = if (attachment != null) { remember(contextItem.id) { val inlineContentBuilder: AnnotatedString.Builder.() -> Unit = { @@ -62,19 +65,24 @@ fun ContextItemView( ) } - fun attachment(): ImageResource? = - when (contextItem.content.msgContent) { - is MsgContent.MCFile -> MR.images.ic_draft_filled + fun attachment(contextItem: ChatItem): ImageResource? { + val fileIsLoaded = getLoadedFilePath(contextItem.file) != null + + return when (contextItem.content.msgContent) { + is MsgContent.MCFile -> if (fileIsLoaded) MR.images.ic_draft_filled else null is MsgContent.MCImage -> MR.images.ic_image - is MsgContent.MCVoice -> MR.images.ic_play_arrow_filled + is MsgContent.MCVoice -> if (fileIsLoaded) MR.images.ic_play_arrow_filled else null else -> null } + } @Composable - fun ContextMsgPreview(lines: Int) { - MessageText(remember(contextItem.id) { attachment() }, lines) + fun ContextMsgPreview(contextItem: ChatItem, lines: Int) { + MessageText(contextItem, remember(contextItem.id) { attachment(contextItem) }, lines) } + val sent = contextItems[0].chatDir.sent + Row( Modifier .padding(top = 8.dp) @@ -97,20 +105,27 @@ fun ContextItemView( contentDescription = stringResource(MR.strings.icon_descr_context), tint = MaterialTheme.colors.secondary, ) - val sender = contextItem.memberDisplayName - if (showSender && sender != null) { - Column( - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - sender, - style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) - ) - ContextMsgPreview(lines = 2) + + if (contextItems.count() == 1) { + val contextItem = contextItems[0] + val sender = contextItem.memberDisplayName + + if (showSender && sender != null) { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + sender, + style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) + ) + ContextMsgPreview(contextItem, lines = 2) + } + } else { + ContextMsgPreview(contextItem, lines = 3) } - } else { - ContextMsgPreview(lines = 3) + } else if (contextItems.isNotEmpty()) { + Text(String.format(generalGetString(if (chatType == ChatType.Local) MR.strings.compose_save_messages_n else MR.strings.compose_forward_messages_n), contextItems.count()), fontStyle = FontStyle.Italic) } } IconButton(onClick = cancelContextItem) { @@ -129,8 +144,9 @@ fun ContextItemView( fun PreviewContextItemView() { SimpleXTheme { ContextItemView( - contextItem = ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello"), - contextIcon = painterResource(MR.images.ic_edit_filled) + contextItems = listOf(ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), "hello")), + contextIcon = painterResource(MR.images.ic_edit_filled), + chatType = ChatType.Direct ) {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index 2aebe07306..d12e7ac090 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -51,17 +52,29 @@ fun SelectedItemsBottomToolbar( selectedChatItems: MutableState?>, deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible moderateItems: () -> Unit, -// shareItems: () -> Unit, + forwardItems: () -> Unit, ) { val deleteEnabled = remember { mutableStateOf(false) } val deleteForEveryoneEnabled = remember { mutableStateOf(false) } val canModerate = remember { mutableStateOf(false) } val moderateEnabled = remember { mutableStateOf(false) } + val forwardEnabled = remember { mutableStateOf(false) } val allButtonsDisabled = remember { mutableStateOf(false) } Box { // It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}) - Row(Modifier.matchParentSize().background(MaterialTheme.colors.background), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { + Row( + Modifier + .matchParentSize() + .background(MaterialTheme.colors.background) + .pointerInput(Unit) { + detectGesture { + true + } + }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { IconButton({ deleteItems(deleteForEveryoneEnabled.value) }, enabled = deleteEnabled.value && !allButtonsDisabled.value) { Icon( painterResource(MR.images.ic_delete), @@ -80,18 +93,18 @@ fun SelectedItemsBottomToolbar( ) } - IconButton({ /*shareItems()*/ }, Modifier.alpha(0f), enabled = false/*!allButtonsDisabled.value*/) { + IconButton({ forwardItems() }, enabled = forwardEnabled.value && !allButtonsDisabled.value) { Icon( - painterResource(MR.images.ic_share), + painterResource(MR.images.ic_forward), null, Modifier.size(22.dp), - tint = if (allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + tint = if (!forwardEnabled.value || allButtonsDisabled.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } } } LaunchedEffect(chatInfo, chatItems, selectedChatItems.value) { - recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, allButtonsDisabled) + recheckItems(chatInfo, chatItems, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, allButtonsDisabled) } } @@ -102,6 +115,7 @@ private fun recheckItems(chatInfo: ChatInfo, deleteForEveryoneEnabled: MutableState, canModerate: MutableState, moderateEnabled: MutableState, + forwardEnabled: MutableState, allButtonsDisabled: MutableState ) { val count = selectedChatItems.value?.size ?: 0 @@ -112,6 +126,7 @@ private fun recheckItems(chatInfo: ChatInfo, var rDeleteForEveryoneEnabled = true var rModerateEnabled = true var rOnlyOwnGroupItems = true + var rForwardEnabled = true val rSelectedChatItems = mutableSetOf() for (ci in chatItems) { if (selected.contains(ci.id)) { @@ -119,6 +134,7 @@ private fun recheckItems(chatInfo: ChatInfo, rDeleteForEveryoneEnabled = rDeleteForEveryoneEnabled && ci.meta.deletable && !ci.localNote rOnlyOwnGroupItems = rOnlyOwnGroupItems && ci.chatDir is CIDirection.GroupSnd rModerateEnabled = rModerateEnabled && ci.content.msgContent != null && ci.memberToModerate(chatInfo) != null + rForwardEnabled = rForwardEnabled && ci.content.msgContent != null && ci.meta.itemDeleted == null && !ci.isLiveDummy rSelectedChatItems.add(ci.id) // we are collecting new selected items here to account for any changes in chat items list } } @@ -126,6 +142,7 @@ private fun recheckItems(chatInfo: ChatInfo, deleteEnabled.value = rDeleteEnabled deleteForEveryoneEnabled.value = rDeleteForEveryoneEnabled moderateEnabled.value = rModerateEnabled + forwardEnabled.value = rForwardEnabled selectedChatItems.value = rSelectedChatItems } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index b4d0b05584..769a0b83f6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -58,11 +58,13 @@ fun ShareListView(chatModel: ChatModel, stopped: Boolean) { hasSimplexLink = hasSimplexLink(sharedContent.text) } is SharedContent.Forward -> { - val mc = sharedContent.chatItem.content.msgContent - if (mc != null) { - isMediaOrFileAttachment = mc.isMediaOrFileAttachment - isVoice = mc.isVoice - hasSimplexLink = hasSimplexLink(mc.text) + sharedContent.chatItems.forEach { ci -> + val mc = ci.content.msgContent + if (mc != null) { + isMediaOrFileAttachment = isMediaOrFileAttachment || mc.isMediaOrFileAttachment + isVoice = isVoice || mc.isVoice + hasSimplexLink = hasSimplexLink || hasSimplexLink(mc.text) + } } } null -> {} @@ -175,11 +177,11 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal title = { Row(verticalAlignment = Alignment.CenterVertically) { Text( - when (chatModel.sharedContent.value) { + when (val v = chatModel.sharedContent.value) { is SharedContent.Text -> stringResource(MR.strings.share_message) is SharedContent.Media -> stringResource(MR.strings.share_image) is SharedContent.File -> stringResource(MR.strings.share_file) - is SharedContent.Forward -> stringResource(MR.strings.forward_message) + is SharedContent.Forward -> if (v.chatItems.size > 1) stringResource(MR.strings.forward_multiple) else stringResource(MR.strings.forward_message) null -> stringResource(MR.strings.share_message) }, color = MaterialTheme.colors.onBackground, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt index ee4638445b..30811d5c94 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Enums.kt @@ -2,8 +2,7 @@ package chat.simplex.common.views.helpers import androidx.compose.runtime.saveable.Saver -import chat.simplex.common.model.ChatInfo -import chat.simplex.common.model.ChatItem +import chat.simplex.common.model.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.* import kotlinx.serialization.descriptors.* @@ -15,7 +14,7 @@ sealed class SharedContent { data class Text(val text: String): SharedContent() data class Media(val text: String, val uris: List): SharedContent() data class File(val text: String, val uri: URI): SharedContent() - data class Forward(val chatItem: ChatItem, val fromChatInfo: ChatInfo): SharedContent() + data class Forward(val chatItems: List, val fromChatInfo: ChatInfo): SharedContent() } enum class AnimatedViewState { 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 55cd86e035..08a16075a4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -125,6 +125,7 @@ Destination server version of %1$s is incompatible with forwarding server %2$s. Please try later. Error sending message + Error forwarding messages Error creating message Error loading details Error adding member(s) @@ -133,7 +134,9 @@ Sender cancelled file transfer. Unknown servers! Without Tor or VPN, your IP address will be visible to these XFTP relays:\n%1$s. + %1$d other file error(s). Error receiving file + %1$d file error(s):\n%2$s Error creating address Contact already exists You are already connected to %1$s. @@ -378,12 +381,23 @@ No selected chat Nothing selected Selected %d + Forward %1$s message(s)? + Nothing to forward! + Forward messages without files? + Messages were deleted after you selected them. + %1$d file(s) were not downloaded. + %1$d file(s) are still being downloaded. + %1$d file(s) failed to download. + %1$d file(s) were deleted. + Download + %1$s messages not forwarded Share message… Share media… Share file… Forward message… + Forward messages… Cannot send message Selected chat preferences prohibit this message. @@ -405,6 +419,8 @@ Files and media prohibited! Only group owners can enable files and media. Send direct message to connect + Forwarding %1$s messages + Saving %1$s messages SimpleX links not allowed Files and media not allowed Voice messages not allowed From 5ca27f63e68edb861822183d7e180e1f75b172ae Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:27:14 +0400 Subject: [PATCH 074/704] core: send errors processing (#4910) * core: send errors processing * test --- src/Simplex/Chat.hs | 20 ++++++++++++++++---- tests/ChatTests/Direct.hs | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d5f06a326f..7ca2f4b948 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2945,8 +2945,8 @@ processChatCommand' vr = \case msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers let itemsData = prepareSndItemsData msgs_ cmrs ciFiles_ quotedItems_ when (length itemsData /= length cmrs) $ logError "sendContactContentMessages: cmrs and itemsData length mismatch" - (errs, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live - unless (null errs) $ toView $ CRChatErrors (Just user) errs + r@(_, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live + processSendErrs user r forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> forM_ cis $ \ci -> startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) deleteAt @@ -3010,8 +3010,8 @@ processChatCommand' vr = \case cis_ <- saveSndChatItems user (CDGroupSnd gInfo) itemsData timed_ live when (length itemsData /= length cmrs) $ logError "sendGroupContentMessages: cmrs and cis_ length mismatch" createMemberSndStatuses cis_ msgs_ gsr - let (errs, cis) = partitionEithers cis_ - unless (null errs) $ toView $ CRChatErrors (Just user) errs + let r@(_, cis) = partitionEithers cis_ + processSendErrs user r forM_ (timed_ >>= timedDeleteAt') $ \deleteAt -> forM_ cis $ \ci -> startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) deleteAt @@ -3103,6 +3103,18 @@ processChatCommand' vr = \case | (msg_, (ComposedMessage {msgContent}, itemForwarded), f, q) <- zipWith4 (,,,) msgs_ (L.toList cmrs') (L.toList ciFiles_) (L.toList quotedItems_) ] + processSendErrs :: User -> ([ChatError], [ChatItem c d]) -> CM () + processSendErrs user = \case + -- no errors + ([], _) -> pure () + -- at least one item is successfully created + (errs, _ci : _) -> toView $ CRChatErrors (Just user) errs + -- single error + ([err], []) -> throwError err + -- multiple errors + (errs@(err : _), []) -> do + toView $ CRChatErrors (Just user) errs + throwError err getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect]) getCommandDirectChatItems user ctId itemIds = do ct <- withFastStore $ \db -> getContact db vr user ctId diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 97a9d89200..c47cf975a1 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -2547,7 +2547,7 @@ setupDesynchronizedRatchet tmp alice = do (bob "/tail @alice 1" bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)" - bob `send` "@alice 1" + bob ##> "@alice 1" bob <## "error: command is prohibited, sendMessagesB: send prohibited" (alice Date: Fri, 20 Sep 2024 09:45:30 +0100 Subject: [PATCH 075/704] core: 6.1.0.3 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index ab4c9790ef..4e4ba3a549 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.2 +version: 6.1.0.3 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 51602084f4..bf42c5117e 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.2 +version: 6.1.0.3 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 68e570656dfae448ba6c1ebcd6ce2ddd9575751f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 20 Sep 2024 12:34:38 +0100 Subject: [PATCH 076/704] ui: translations (#4915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ios: update core library * Translated using Weblate (Portuguese) Currently translated at 46.2% (944 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (1787 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (1787 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 99.9% (1799 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Japanese) Currently translated at 88.6% (1809 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Vietnamese) Currently translated at 38.5% (786 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 38.8% (793 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (German) Currently translated at 99.9% (2040 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Japanese) Currently translated at 90.0% (1847 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Vietnamese) Currently translated at 39.4% (810 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 40.0% (827 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Italian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Arabic) Currently translated at 99.3% (2054 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * ios: update core library * Translated using Weblate (Portuguese) Currently translated at 46.2% (944 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (1787 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.2% (1787 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Dutch) Currently translated at 99.9% (1799 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2041 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Japanese) Currently translated at 88.6% (1809 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Vietnamese) Currently translated at 38.5% (786 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 38.8% (793 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (German) Currently translated at 99.9% (2040 of 2041 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Japanese) Currently translated at 90.0% (1847 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Vietnamese) Currently translated at 39.4% (810 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2051 of 2051 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 40.0% (827 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Italian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Arabic) Currently translated at 99.3% (2054 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2067 of 2067 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1800 of 1800 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * process localizations * fix interpolation --------- Co-authored-by: Antonio Oliveira Co-authored-by: summoner001 Co-authored-by: Ghost of Sparta Co-authored-by: Random Co-authored-by: M1K4 Co-authored-by: Bezruchenko Simon Co-authored-by: Miyu Sakatsuki Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: mlanp Co-authored-by: å¤§ēŽ‹å«ęˆ‘ę„å·”å±± Co-authored-by: jonnysemon --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 +- .../bg.xcloc/Localized Contents/bg.xliff | 113 ++++- .../cs.xcloc/Localized Contents/cs.xliff | 113 ++++- .../de.xcloc/Localized Contents/de.xliff | 133 +++++- .../en.xcloc/Localized Contents/en.xliff | 139 +++++- .../es.xcloc/Localized Contents/es.xliff | 113 ++++- .../fi.xcloc/Localized Contents/fi.xliff | 113 ++++- .../fr.xcloc/Localized Contents/fr.xliff | 113 ++++- .../hu.xcloc/Localized Contents/hu.xliff | 437 +++++++++++------- .../it.xcloc/Localized Contents/it.xliff | 133 +++++- .../ja.xcloc/Localized Contents/ja.xliff | 113 ++++- .../nl.xcloc/Localized Contents/nl.xliff | 180 ++++++-- .../pl.xcloc/Localized Contents/pl.xliff | 113 ++++- .../ru.xcloc/Localized Contents/ru.xliff | 113 ++++- .../th.xcloc/Localized Contents/th.xliff | 113 ++++- .../tr.xcloc/Localized Contents/tr.xliff | 113 ++++- .../uk.xcloc/Localized Contents/uk.xliff | 113 ++++- .../Localized Contents/zh-Hans.xliff | 113 ++++- .../SimpleX SE/hu.lproj/Localizable.strings | 4 +- .../SimpleX SE/nl.lproj/Localizable.strings | 2 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 + apps/ios/bg.lproj/Localizable.strings | 11 +- apps/ios/cs.lproj/Localizable.strings | 8 +- apps/ios/de.lproj/Localizable.strings | 75 ++- apps/ios/es.lproj/Localizable.strings | 15 +- apps/ios/fi.lproj/Localizable.strings | 8 +- apps/ios/fr.lproj/Localizable.strings | 15 +- apps/ios/hu.lproj/Localizable.strings | 371 ++++++++------- apps/ios/it.lproj/Localizable.strings | 75 ++- apps/ios/ja.lproj/Localizable.strings | 8 +- apps/ios/nl.lproj/Localizable.strings | 118 +++-- apps/ios/pl.lproj/Localizable.strings | 15 +- apps/ios/ru.lproj/Localizable.strings | 15 +- apps/ios/th.lproj/Localizable.strings | 8 +- apps/ios/tr.lproj/Localizable.strings | 15 +- apps/ios/uk.lproj/Localizable.strings | 15 +- apps/ios/zh-Hans.lproj/Localizable.strings | 17 +- .../commonMain/resources/MR/ar/strings.xml | 33 +- .../commonMain/resources/MR/de/strings.xml | 21 +- .../commonMain/resources/MR/hu/strings.xml | 408 ++++++++-------- .../commonMain/resources/MR/it/strings.xml | 36 ++ .../commonMain/resources/MR/ja/strings.xml | 53 ++- .../commonMain/resources/MR/nl/strings.xml | 77 +-- .../commonMain/resources/MR/pt/strings.xml | 19 + .../commonMain/resources/MR/uk/strings.xml | 21 +- .../commonMain/resources/MR/vi/strings.xml | 48 ++ .../resources/MR/zh-rCN/strings.xml | 27 +- 47 files changed, 3126 insertions(+), 804 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index fab3e10990..ea48d5d7b3 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1111,8 +1111,8 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool showAlert( title: NSLocalizedString("Unknown servers!", comment: "alert title"), message: ( - NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs).", comment: "alert message") + - (otherErrsStr != "" ? "\n\n" + NSLocalizedString("Other file errors:\n\(otherErrsStr)", comment: "alert message") : "") + String.localizedStringWithFormat(NSLocalizedString("Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.", comment: "alert message"), srvs) + + (otherErrsStr != "" ? "\n\n" + String.localizedStringWithFormat(NSLocalizedString("Other file errors:\n%@", comment: "alert message"), otherErrsStr) : "") ), buttonTitle: NSLocalizedString("Download", comment: "alert button"), buttonAction: { @@ -1156,7 +1156,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool await MainActor.run { showAlert( NSLocalizedString("Error receiving file", comment: "alert title"), - message: NSLocalizedString("File errors:\n\(otherErrsStr)", comment: "alert message") + message: String.localizedStringWithFormat(NSLocalizedString("File errors:\n%@", comment: "alert message"), otherErrsStr) ) } } 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 f55c87b1b8..cbd0c2742b 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -161,11 +161,31 @@ %d Гни time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d часа time interval + + %d messages not forwarded + alert title + %d min %d мин. @@ -1194,7 +1214,7 @@ Cannot receive file Š¤Š°Š¹Š»ŃŠŃ‚ не може Га бъГе ŠæŠ¾Š»ŃƒŃ‡ŠµŠ½ - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2350,6 +2370,10 @@ This is your own one-time link! ŠŠµ изпращай ŠøŃŃ‚Š¾Ń€ŠøŃ на нови членове. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address ŠŠµ съзГавай аГрес @@ -2373,7 +2397,8 @@ This is your own one-time link! Download Š˜Š·Ń‚ŠµŠ³Š»Šø - chat item action + alert button + chat item action Download errors @@ -2389,6 +2414,10 @@ This is your own one-time link! Двали файл server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2809,7 +2838,7 @@ This is your own one-time link! Error receiving file Š“Ń€ŠµŃˆŠŗŠ° при ŠæŠ¾Š»ŃƒŃ‡Š°Š²Š°Š½Šµ на файл - No comment provided by engineer. + alert title Error reconnecting server @@ -3035,6 +3064,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3165,11 +3199,23 @@ This is your own one-time link! ŠŸŃ€ŠµŠæŃ€Š°Ń‚Šø chat item action + + Forward %d message(s)? + alert title + Forward and save messages ŠŸŃ€ŠµŠæŃ€Š°Ń‰Š°Š½Šµ Šø запазване на ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŃ No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded ŠŸŃ€ŠµŠæŃ€Š°Ń‚ŠµŠ½Š¾ @@ -3180,6 +3226,10 @@ This is your own one-time link! ŠŸŃ€ŠµŠæŃ€Š°Ń‚ŠµŠ½Š¾ от No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3465,6 +3515,10 @@ Error: %2$@ ICE ŃŃŠŃ€Š²ŃŠŃ€Šø (по еГин на реГ) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Ако не можете Га се срещнете лично, покажете QR коГ във виГеоразговора или споГелете линка. @@ -4146,6 +4200,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Š”ŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŃŃ‚Š°, файловете Šø разговорите са защитени чрез **криптиране от край Го край** с перфектна секретност при препращане, правГопоГобно опровержение Šø Š²ŃŠŠ·ŃŃ‚Š°Š½Š¾Š²ŃŠ²Š°Š½Šµ при взлом. @@ -4435,6 +4493,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Š˜Š·Š²ŠµŃŃ‚ŠøŃ @@ -4467,7 +4529,7 @@ This is your link for group %@! Ok ŠžŠŗ - No comment provided by engineer. + alert button Old database @@ -4655,6 +4717,11 @@ Requires compatible VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING бройка @@ -4690,6 +4757,10 @@ Requires compatible VPN. ŠšŠ¾Š“ŃŠŃ‚ за Š“Š¾ŃŃ‚ŃŠŠæ е заГаГен! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ŠŸŠ°Ń€Š¾Š»Š° за показване @@ -4834,6 +4905,10 @@ Error: %@ Полски интерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Š’ŃŠŠ¶Š¼Š¾Š¶Š½Š¾ е ŠæŃ€ŃŠŃŃ‚Š¾Š²ŠøŃŃ‚ Š¾Ń‚ŠæŠµŃ‡Š°Ń‚ŃŠŠŗ на сертификата в аГреса на ŃŃŠŃ€Š²ŃŠŃ€Š° Га е неправилен @@ -5010,6 +5085,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push ŠøŠ·Š²ŠµŃŃ‚ŠøŃ @@ -5398,6 +5477,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5506,6 +5589,10 @@ Enable in *Network & servers* settings. Запазено ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŠµ message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5698,7 +5785,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. ŠŸŠ¾Š“Š°Ń‚ŠµŠ»ŃŃ‚ отмени ŠæŃ€ŠµŃ…Š²ŃŠŃ€Š»ŃŠ½ŠµŃ‚Š¾ на файла. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6738,7 +6825,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6848,6 +6935,10 @@ To connect, please ask your contact to create another connection link and check Използвай .onion хостове No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Използвай ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ на SimpleX Chat? @@ -6919,6 +7010,10 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Š˜Š·ŠæŠ¾Š»Š·Š²Š°Ń‚ се ŃŃŠŃ€Š²ŃŠŃ€ŠøŃ‚Šµ на SimpleX Chat. @@ -7148,7 +7243,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7547,6 +7642,10 @@ Repeat connection request? Š’Š°ŃˆŠøŃ‚Šµ контакти ще останат ŃŠ²ŃŠŃ€Š·Š°Š½Šø. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Š’Š°ŃˆŠ°Ń‚Š° Ń‚ŠµŠŗŃƒŃ‰Š° чат база Ганни ще бъГе Š˜Š—Š¢Š Š˜Š¢Š Šø Š—ŠŠœŠ•ŠŠ•ŠŠ с импортираната. 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 3b29a1e51f..f53628f35f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -158,11 +158,31 @@ %d dnĆ­ time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d hodin time interval + + %d messages not forwarded + alert title + %d min %d minuty @@ -1153,7 +1173,7 @@ Cannot receive file Nelze přijmout soubor - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2271,6 +2291,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address NevytvÔřet adresu @@ -2293,7 +2317,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2308,6 +2333,10 @@ This is your own one-time link! StĆ”hnout soubor server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2713,7 +2742,7 @@ This is your own one-time link! Error receiving file Chyba při příjmu souboru - No comment provided by engineer. + alert title Error reconnecting server @@ -2932,6 +2961,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3058,10 +3092,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3070,6 +3116,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3348,6 +3398,10 @@ Error: %2$@ Servery ICE (jeden na řÔdek) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Pokud se nemůžete setkat osobně, zobrazte QR kód ve videohovoru nebo sdĆ­lejte odkaz. @@ -4002,6 +4056,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4276,6 +4334,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications OznĆ”menĆ­ @@ -4307,7 +4369,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4487,6 +4549,11 @@ Vyžaduje povolenĆ­ sĆ­tě VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Počet PING @@ -4522,6 +4589,10 @@ Vyžaduje povolenĆ­ sĆ­tě VPN. Heslo nastaveno! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Heslo k zobrazenĆ­ @@ -4658,6 +4729,10 @@ Error: %@ PolskĆ© rozhranĆ­ No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Je možnĆ©, že otisk certifikĆ”tu v adrese serveru je nesprĆ”vný @@ -4831,6 +4906,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications NabĆ­zenĆ” oznĆ”menĆ­ @@ -5208,6 +5287,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5312,6 +5395,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5500,7 +5587,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. OdesĆ­latel zruÅ”il přenos souboru. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6511,7 +6598,7 @@ Před zapnutĆ­m tĆ©to funkce budete vyzvĆ”ni k dokončenĆ­ ověřenĆ­. Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6616,6 +6703,10 @@ Chcete-li se připojit, požÔdejte svÅÆj kontakt o vytvořenĆ­ dalŔího odkazu Použít hostitele .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Používat servery SimpleX Chat? @@ -6684,6 +6775,10 @@ Chcete-li se připojit, požÔdejte svÅÆj kontakt o vytvořenĆ­ dalŔího odkazu User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Používat servery SimpleX Chat. @@ -6896,7 +6991,7 @@ Chcete-li se připojit, požÔdejte svÅÆj kontakt o vytvořenĆ­ dalŔího odkazu Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7278,6 +7373,10 @@ Repeat connection request? VaÅ”e kontakty zÅÆstanou připojeny. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. VaÅ”e aktuĆ”lnĆ­ chat databĆ”ze bude ODSTRANĚNA a NAHRAZENA importovanou. 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 884c322dae..a126212534 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d Tage time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d Stunden time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1026,6 +1047,7 @@ Auto-accept settings + Einstellungen automatisch akzeptieren alert title @@ -1221,7 +1243,7 @@ Cannot receive file Datei kann nicht empfangen werden - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + Die Chat-PrƤferenzen wurden geƤndert. alert message @@ -1754,6 +1777,7 @@ Das ist Ihr eigener Einmal-Link! Corner + Ecke No comment provided by engineer. @@ -2425,6 +2449,10 @@ Das ist Ihr eigener Einmal-Link! Den Nachrichtenverlauf nicht an neue Mitglieder senden. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Keine Adresse erstellt @@ -2448,7 +2476,8 @@ Das ist Ihr eigener Einmal-Link! Download Herunterladen - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Das ist Ihr eigener Einmal-Link! Datei herunterladen server test step + + Download files + alert action + Downloaded Heruntergeladen @@ -2742,6 +2775,7 @@ Das ist Ihr eigener Einmal-Link! Error changing connection profile + Fehler beim Wechseln des Verbindungs-Profils No comment provided by engineer. @@ -2756,6 +2790,7 @@ Das ist Ihr eigener Einmal-Link! Error changing to incognito! + Fehler beim Wechseln zum Inkognito-Profil! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Das ist Ihr eigener Einmal-Link! Error migrating settings + Fehler beim Migrieren der Einstellungen No comment provided by engineer. @@ -2890,7 +2926,7 @@ Das ist Ihr eigener Einmal-Link! Error receiving file Fehler beim Empfangen der Datei - No comment provided by engineer. + alert title Error reconnecting server @@ -2984,6 +3020,7 @@ Das ist Ihr eigener Einmal-Link! Error switching profile + Fehler beim Wechseln des Profils No comment provided by engineer. @@ -3122,6 +3159,11 @@ Das ist Ihr eigener Einmal-Link! Datei-Fehler No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Datei nicht gefunden - hƶchstwahrscheinlich wurde die Datei gelƶscht oder der Transfer abgebrochen. @@ -3257,11 +3299,23 @@ Das ist Ihr eigener Einmal-Link! Weiterleiten chat item action + + Forward %d message(s)? + alert title + Forward and save messages Nachrichten weiterleiten und speichern No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Weitergeleitet @@ -3272,6 +3326,10 @@ Das ist Ihr eigener Einmal-Link! Weitergeleitet aus No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Weiterleitungsserver %@ konnte sich nicht mit dem Zielserver %@ verbinden. Bitte versuchen Sie es spƤter erneut. @@ -3566,6 +3624,10 @@ Fehler: %2$@ ICE-Server (einer pro Zeile) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Falls Sie sich nicht persƶnlich treffen kƶnnen, zeigen Sie den QR-Code in einem Videoanruf oder teilen Sie den Link. @@ -4213,6 +4275,7 @@ Das ist Ihr Link für die Gruppe %@! Message shape + Nachrichten-Form No comment provided by engineer. @@ -4265,6 +4328,10 @@ Das ist Ihr Link für die Gruppe %@! Gesendete Nachrichten No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. @@ -4560,6 +4627,10 @@ Das ist Ihr Link für die Gruppe %@! Nichts ausgewƤhlt No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Benachrichtigungen @@ -4592,7 +4663,7 @@ Das ist Ihr Link für die Gruppe %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4854,11 @@ Dies erfordert die Aktivierung eines VPNs. Andere %@ Server No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING-ZƤhler @@ -4818,6 +4894,10 @@ Dies erfordert die Aktivierung eines VPNs. Zugangscode eingestellt! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Passwort anzeigen @@ -4967,6 +5047,10 @@ Fehler: %@ Polnische BedienoberflƤche No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig @@ -5154,6 +5238,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Proxy-Server No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-Benachrichtigungen @@ -5377,6 +5465,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Remove archive? + Archiv entfernen? No comment provided by engineer. @@ -5559,6 +5648,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SMP-Server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Dateien sicher empfangen @@ -5647,6 +5740,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Save your profile? + Ihr Profil speichern? alert title @@ -5669,6 +5763,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Gespeicherte Nachricht message info title + + Saving %lld messages + No comment provided by engineer. + Scale Skalieren @@ -5751,6 +5849,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Select chat profile + Chat-Profil auswƤhlen No comment provided by engineer. @@ -5871,7 +5970,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sender cancelled file transfer. Der Absender hat die Dateiübertragung abgebrochen. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6090,6 +6189,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Settings were changed. + Die Einstellungen wurden geƤndert. alert message @@ -6129,6 +6229,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Share profile + Profil teilen No comment provided by engineer. @@ -6298,6 +6399,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Some app settings were not migrated. + Einige App-Einstellungen wurden nicht migriert. No comment provided by engineer. @@ -6477,6 +6579,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Tail + Sprechblase No comment provided by engineer. @@ -6673,6 +6776,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The uploaded database archive will be permanently removed from the servers. + Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt. No comment provided by engineer. @@ -6955,7 +7059,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Unknown servers! Unbekannte Server! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7173,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verwende .onion-Hosts No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Verwenden Sie SimpleX-Chat-Server? @@ -7144,6 +7252,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Benutzer-Auswahl No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Verwendung von SimpleX-Chat-Servern. @@ -7377,7 +7489,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7760,6 +7872,7 @@ Verbindungsanfrage wiederholen? Your chat preferences + Ihre Chat-PrƤferenzen alert title @@ -7769,6 +7882,7 @@ Verbindungsanfrage wiederholen? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Ihre Verbindung wurde auf %@ verschoben. WƤhrend Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf. No comment provided by engineer. @@ -7786,6 +7900,10 @@ Verbindungsanfrage wiederholen? Ihre Kontakte bleiben weiterhin verbunden. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Ihre aktuelle Chat-Datenbank wird GELƖSCHT und durch die Importierte ERSETZT. @@ -7823,6 +7941,7 @@ Verbindungsanfrage wiederholen? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Ihr Profil wurde geƤndert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet. alert message 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 6eb935222f..e98bd7b784 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -162,11 +162,36 @@ %d days time interval + + %d file(s) are still being downloaded. + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d hours time interval + + %d messages not forwarded + %d messages not forwarded + alert title + %d min %d min @@ -1223,7 +1248,7 @@ Cannot receive file Cannot receive file - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2429,6 +2454,11 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Don't create address @@ -2452,7 +2482,8 @@ This is your own one-time link! Download Download - chat item action + alert button + chat item action Download errors @@ -2469,6 +2500,11 @@ This is your own one-time link! Download file server test step + + Download files + Download files + alert action + Downloaded Downloaded @@ -2897,7 +2933,7 @@ This is your own one-time link! Error receiving file Error receiving file - No comment provided by engineer. + alert title Error reconnecting server @@ -3130,6 +3166,13 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. File not found - most likely file was deleted or cancelled. @@ -3265,11 +3308,26 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + Forward %d message(s)? + alert title + Forward and save messages Forward and save messages No comment provided by engineer. + + Forward messages + Forward messages + alert action + + + Forward messages without files? + Forward messages without files? + alert message + Forwarded Forwarded @@ -3280,6 +3338,11 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Forwarding server %@ failed to connect to destination server %@. Please try later. @@ -3574,6 +3637,11 @@ Error: %2$@ ICE servers (one per line) No comment provided by engineer. + + IP address + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. If you can't meet in person, show QR code in a video call, or share the link. @@ -4274,6 +4342,11 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. @@ -4569,6 +4642,11 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + Nothing to forward! + alert title + Notifications Notifications @@ -4601,7 +4679,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4792,6 +4870,13 @@ Requires compatible VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + Other file errors: +%@ + alert message + PING count PING count @@ -4827,6 +4912,11 @@ Requires compatible VPN. Passcode set! No comment provided by engineer. + + Password + Password + No comment provided by engineer. + Password to show Password to show @@ -4976,6 +5066,11 @@ Error: %@ Polish interface No comment provided by engineer. + + Port + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Possibly, certificate fingerprint in server address is incorrect @@ -5163,6 +5258,11 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + Proxy requires password + No comment provided by engineer. + Push notifications Push notifications @@ -5569,6 +5669,11 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + SOCKS proxy + No comment provided by engineer. + Safely receive files Safely receive files @@ -5680,6 +5785,11 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + Saving %lld messages + No comment provided by engineer. + Scale Scale @@ -5883,7 +5993,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Sender cancelled file transfer. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6972,7 +7082,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7086,6 +7196,11 @@ To connect, please ask your contact to create another connection link and check Use .onion hosts No comment provided by engineer. + + Use SOCKS proxy + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Use SimpleX Chat servers? @@ -7161,6 +7276,11 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + Username + No comment provided by engineer. + Using SimpleX Chat servers. Using SimpleX Chat servers. @@ -7394,7 +7514,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7805,6 +7925,11 @@ Repeat connection request? Your contacts will remain connected. No comment provided by engineer. + + Your credentials may be sent unencrypted. + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Your current chat database will be DELETED and REPLACED with the imported one. 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 a10a1594de..22251b7443 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -161,11 +161,31 @@ %d dĆ­as time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d horas time interval + + %d messages not forwarded + alert title + %d min %d minutos @@ -1221,7 +1241,7 @@ Cannot receive file No se puede recibir el archivo - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! No se envĆ­a el historial a los miembros nuevos. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address No crear dirección SimpleX @@ -2448,7 +2472,8 @@ This is your own one-time link! Download Descargar - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! Descargar archivo server test step + + Download files + alert action + Downloaded Descargado @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file Error al recibir archivo - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! Error de archivo No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Archivo no encontrado, probablemente haya sido borrado o cancelado. @@ -3257,11 +3291,23 @@ This is your own one-time link! Reenviar chat item action + + Forward %d message(s)? + alert title + Forward and save messages Reenviar y guardar mensajes No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Reenviado @@ -3272,6 +3318,10 @@ This is your own one-time link! Reenviado por No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. El servidor de reenvĆ­o %@ no ha podido conectarse al servidor de destino %@. Por favor, intentalo mĆ”s tarde. @@ -3566,6 +3616,10 @@ Error: %2$@ Servidores ICE (uno por lĆ­nea) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace. @@ -4265,6 +4319,10 @@ This is your link for group %@! Mensajes enviados No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Los mensajes, archivos y llamadas estĆ”n protegidos mediante **cifrado de extremo a extremo** con secreto perfecto hacĆ­a adelante, repudio y recuperación tras ataque. @@ -4560,6 +4618,10 @@ This is your link for group %@! Nada seleccionado No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Notificaciones @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requiere activación de la VPN. Otros servidores %@ No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Contador PING @@ -4818,6 +4885,10 @@ Requiere activación de la VPN. Ā”Código de acceso guardado! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ContraseƱa para hacerlo visible @@ -4967,6 +5038,10 @@ Error: %@ Interfaz en polaco No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta @@ -5154,6 +5229,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Servidores con proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Notificaciones automĆ”ticas @@ -5559,6 +5638,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Servidor SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Recibe archivos de forma segura @@ -5669,6 +5752,10 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Mensaje guardado message info title + + Saving %lld messages + No comment provided by engineer. + Scale Escala @@ -5871,7 +5958,7 @@ ActĆ­valo en ajustes de *Servidores y Redes*. Sender cancelled file transfer. El remitente ha cancelado la transferencia de archivos. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ Se te pedirĆ” que completes la autenticación antes de activar esta función.
Unknown servers! Ā”Servidores desconocidos! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usar hosts .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? ĀæUsar servidores SimpleX Chat? @@ -7144,6 +7235,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Selección de usuarios No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Usar servidores SimpleX Chat. @@ -7377,7 +7472,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Sin Tor o VPN, tu dirección IP serĆ” visible para estos servidores XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? Tus contactos permanecerĆ”n conectados. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. La base de datos actual serĆ” ELIMINADA y SUSTITUIDA por la importada. 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 cf2ea3c36d..3035f3e8f7 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -156,11 +156,31 @@ %d pƤivƤƤ time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d tuntia time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1146,7 +1166,7 @@ Cannot receive file Tiedostoa ei voi vastaanottaa - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2264,6 +2284,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address ƄlƤ luo osoitetta @@ -2286,7 +2310,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2301,6 +2326,10 @@ This is your own one-time link! Lataa tiedosto server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2704,7 +2733,7 @@ This is your own one-time link! Error receiving file Virhe tiedoston vastaanottamisessa - No comment provided by engineer. + alert title Error reconnecting server @@ -2922,6 +2951,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3048,10 +3082,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3060,6 +3106,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3338,6 +3388,10 @@ Error: %2$@ ICE-palvelimet (yksi per rivi) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Jos et voi tavata henkilƶkohtaisesti, nƤytƤ QR-koodi videopuhelussa tai jaa linkki. @@ -3992,6 +4046,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4265,6 +4323,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Ilmoitukset @@ -4296,7 +4358,7 @@ This is your link for group %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4475,6 +4537,11 @@ EdellyttƤƤ VPN:n sallimista. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING-mƤƤrƤ @@ -4510,6 +4577,10 @@ EdellyttƤƤ VPN:n sallimista. PƤƤsykoodi asetettu! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Salasana nƤytettƤvƤksi @@ -4646,6 +4717,10 @@ Error: %@ Puolalainen kƤyttƶliittymƤ No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Palvelimen osoitteen varmenteen sormenjƤlki on mahdollisesti virheellinen @@ -4819,6 +4894,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-ilmoitukset @@ -5196,6 +5275,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5300,6 +5383,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5487,7 +5574,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. LƤhettƤjƤ peruutti tiedoston siirron. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6496,7 +6583,7 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tƤmƤ ominaisuus ote Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6601,6 +6688,10 @@ Jos haluat muodostaa yhteyden, pyydƤ kontaktiasi luomaan toinen yhteyslinkki ja KƤytƤ .onion-isƤntiƤ No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? KƤytƤ SimpleX Chat palvelimia? @@ -6669,6 +6760,10 @@ Jos haluat muodostaa yhteyden, pyydƤ kontaktiasi luomaan toinen yhteyslinkki ja User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. KƤyttƤƤ SimpleX Chat -palvelimia. @@ -6881,7 +6976,7 @@ Jos haluat muodostaa yhteyden, pyydƤ kontaktiasi luomaan toinen yhteyslinkki ja Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7263,6 +7358,10 @@ Repeat connection request? Kontaktisi pysyvƤt yhdistettyinƤ. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Nykyinen keskustelut-tietokantasi poistetaan ja korvataan tuodulla tietokannalla. 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 547d0f3674..2d86fbe760 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -161,11 +161,31 @@ %d jours time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d heures time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1221,7 +1241,7 @@ Cannot receive file Impossible de recevoir le fichier - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ Il s'agit de votre propre lien unique ! Ne pas envoyer d'historique aux nouveaux membres. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Ne pas crĆ©er d'adresse @@ -2448,7 +2472,8 @@ Il s'agit de votre propre lien unique ! Download TĆ©lĆ©charger - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ Il s'agit de votre propre lien unique ! TĆ©lĆ©charger le fichier server test step + + Download files + alert action + Downloaded TĆ©lĆ©chargĆ© @@ -2890,7 +2919,7 @@ Il s'agit de votre propre lien unique ! Error receiving file Erreur lors de la rĆ©ception du fichier - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ Il s'agit de votre propre lien unique ! Erreur de fichier No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Fichier introuvable - le fichier a probablement Ć©tĆ© supprimĆ© ou annulĆ©. @@ -3257,11 +3291,23 @@ Il s'agit de votre propre lien unique ! TransfĆ©rer chat item action + + Forward %d message(s)? + alert title + Forward and save messages TransfĆ©rer et sauvegarder des messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded TransfĆ©rĆ© @@ -3272,6 +3318,10 @@ Il s'agit de votre propre lien unique ! TransfĆ©rĆ© depuis No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Le serveur de redirection %@ n'a pas rĆ©ussi Ć  se connecter au serveur de destination %@. Veuillez rĆ©essayer plus tard. @@ -3566,6 +3616,10 @@ Erreur : %2$@ Serveurs ICE (un par ligne) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Si vous ne pouvez pas vous rencontrer en personne, montrez le code QR lors d'un appel vidĆ©o ou partagez le lien. @@ -4265,6 +4319,10 @@ Voici votre lien pour le groupe %@ ! Messages envoyĆ©s No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Les messages, fichiers et appels sont protĆ©gĆ©s par un chiffrement **de bout en bout** avec une confidentialitĆ© persistante, une rĆ©pudiation et une rĆ©cupĆ©ration en cas d'effraction. @@ -4560,6 +4618,10 @@ Voici votre lien pour le groupe %@ ! Aucune sĆ©lection No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Notifications @@ -4592,7 +4654,7 @@ Voici votre lien pour le groupe %@ ! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ NĆ©cessite l'activation d'un VPN. Autres serveurs %@ No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Nombre de PING @@ -4818,6 +4885,10 @@ NĆ©cessite l'activation d'un VPN. Code d'accĆØs dĆ©fini ! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Mot de passe Ć  entrer @@ -4967,6 +5038,10 @@ Erreur : %@ Interface en polonais No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte @@ -5154,6 +5229,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Serveurs routĆ©s via des proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Notifications push @@ -5559,6 +5638,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Serveur SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files RĆ©ception de fichiers en toute sĆ©curitĆ© @@ -5669,6 +5752,10 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Message enregistrĆ© message info title + + Saving %lld messages + No comment provided by engineer. + Scale Ɖchelle @@ -5871,7 +5958,7 @@ Activez-le dans les paramĆØtres *RĆ©seau et serveurs*. Sender cancelled file transfer. L'expĆ©diteur a annulĆ© le transfert de fichiers. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ Vous serez invitĆ© Ć  confirmer l'authentification avant que cette fonction ne s Unknown servers! Serveurs inconnus ! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ Pour vous connecter, veuillez demander Ć  votre contact de crĆ©er un autre lien Utiliser les hĆ“tes .onions No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Utiliser les serveurs SimpleX Chat ? @@ -7144,6 +7235,10 @@ Pour vous connecter, veuillez demander Ć  votre contact de crĆ©er un autre lien SĆ©lection de l'utilisateur No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Vous utilisez les serveurs SimpleX. @@ -7377,7 +7472,7 @@ Pour vous connecter, veuillez demander Ć  votre contact de crĆ©er un autre lien Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ RĆ©pĆ©ter la demande de connexion ? Vos contacts resteront connectĆ©s. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Votre base de donnĆ©es de chat actuelle va ĆŖtre SUPPRIMEE et REMPLACEE par celle importĆ©e. 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 063dd3d14a..2f79096b14 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d nap time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d óra time interval + + %d messages not forwarded + alert title + %d min %d perc @@ -328,12 +349,12 @@ **Add contact**: to create a new invitation link, or connect via a link you received. - **Ismerős hozzĆ”adĆ”sa**: Ćŗj meghĆ­vó hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz. + **Ismerős hozzĆ”adĆ”sa**: Ćŗj meghĆ­vó-hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz. No comment provided by engineer. **Add new contact**: to create your one-time QR Code or link for your contact. - **Új ismerős hozzĆ”adĆ”sa**: egyszer hasznĆ”latos QR-kód vagy hivatkozĆ”s lĆ©trehozĆ”sa az ismerőse szĆ”mĆ”ra. + **Új ismerős hozzĆ”adĆ”sa**: egyszer hasznĆ”lható QR-kód vagy hivatkozĆ”s lĆ©trehozĆ”sa az ismerőse szĆ”mĆ”ra. No comment provided by engineer. @@ -343,7 +364,7 @@ **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. - **PrivĆ”tabb**: 20 percenkĆ©nt ellenőrzi az Ćŗj üzeneteket. Az eszkƶztoken megosztĆ”sra kerül a SimpleX Chat kiszolgĆ”lóval, de az nem, hogy hĆ”ny ismerőse vagy üzenete van. + **PrivĆ”tabb**: 20 percenkĆ©nt ellenőrzi az Ćŗj üzeneteket. Az eszkƶztoken megosztĆ”sra kerül a SimpleX Chat-kiszolgĆ”lóval, de az nem, hogy hĆ”ny ismerőse vagy üzenete van. No comment provided by engineer. @@ -353,12 +374,12 @@ **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. - **MegjegyzĆ©s**: ha kĆ©t eszkƶzƶn is ugyanazt az adatbĆ”zist hasznĆ”lja, akkor biztonsĆ”gi vĆ©delemkĆ©nt megszakĆ­tja az ismerőseitől Ć©rkező üzenetek visszafejtĆ©sĆ©t. + **Figyelem:** ha kĆ©t eszkƶzƶn is ugyanazt az adatbĆ”zist hasznĆ”lja, akkor biztonsĆ”gi vĆ©delemkĆ©nt megszakĆ­tja az ismerőseitől Ć©rkező üzenetek visszafejtĆ©sĆ©t. No comment provided by engineer. **Please note**: you will NOT be able to recover or change passphrase if you lose it. - **Figyelem**: NEM tudja visszaĆ”llĆ­tani vagy megvĆ”ltoztatni jelmondatĆ”t, ha elveszĆ­ti azt. + **Figyelem:** NEM tudja visszaĆ”llĆ­tani vagy megvĆ”ltoztatni jelmondatĆ”t, ha elveszĆ­ti azt. No comment provided by engineer. @@ -373,7 +394,7 @@ **Warning**: the archive will be removed. - **Figyelem**: az archĆ­vum tƶrlĆ©sre kerül. + **Figyelem**: az archĆ­vum eltĆ”volĆ­tĆ”sra kerül. No comment provided by engineer. @@ -522,8 +543,8 @@ A separate TCP connection will be used **for each contact and group member**. **Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. - A rendszer külƶn TCP-kapcsolatot fog hasznĆ”lni **minden ismerőshƶz Ć©s csoporttaghoz**. -**Figyelem**: sok kapcsolódĆ”s esetĆ©n, az akkumulĆ”tor- Ć©s adatforgalom fogyasztĆ”s jelentősen megnőhet, Ć©s egyes kapcsolatok meghiĆŗsulhatnak. + **Minden egyes kapcsolathoz Ć©s csoporttaghoz** külƶn TCP-kapcsolat lesz hasznĆ”lva. +**Figyelem:** ha sok kapcsolata van, az akkumulĆ”tor-hasznĆ”lat Ć©s az adatforgalom jelentősen megnƶvekedhet, Ć©s nĆ©hĆ”ny kapcsolódĆ”si kĆ­sĆ©rlet sikertelen lehet. No comment provided by engineer. @@ -543,17 +564,17 @@ About SimpleX - A SimpleX-ről + A SimpleXről No comment provided by engineer. About SimpleX Chat - A SimpleX Chat-ről + A SimpleX Chatről No comment provided by engineer. About SimpleX address - A SimpleX cĆ­mről + A SimpleX-cĆ­mről No comment provided by engineer. @@ -570,12 +591,12 @@ Accept connection request? - IsmerőskĆ©relem elfogadĆ”sa? + KapcsolatkĆ©rĆ©s elfogadĆ”sa? No comment provided by engineer. Accept contact request from %@? - Elfogadja %@ kapcsolat kĆ©rĆ©sĆ©t? + Elfogadja %@ kapcsolatkĆ©rĆ©sĆ©t? notification body @@ -636,7 +657,7 @@ Add welcome message - Üdvƶzlő üzenet hozzĆ”adĆ”sa + Üdvƶzlőüzenet hozzĆ”adĆ”sa No comment provided by engineer. @@ -751,7 +772,7 @@ Allow calls only if your contact allows them. - A hĆ­vĆ”sok kezdemĆ©nyezĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + A hĆ­vĆ”sok kezdemĆ©nyezĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. No comment provided by engineer. @@ -761,7 +782,7 @@ Allow disappearing messages only if your contact allows it to you. - Az eltűnő üzenetek küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi az ƶn szĆ”mĆ”ra. + Az eltűnő üzenetek küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi az ƶn szĆ”mĆ”ra. No comment provided by engineer. @@ -771,12 +792,12 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) - Az üzenetek vĆ©gleges tƶrlĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra) + Az üzenetek vĆ©gleges tƶrlĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra) No comment provided by engineer. Allow message reactions only if your contact allows them. - Az üzenetreakciók küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + Az üzenetreakciók küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. No comment provided by engineer. @@ -806,12 +827,12 @@ Allow to send SimpleX links. - A SimpleX hivatkozĆ”sok küldĆ©se engedĆ©lyezve van. + A SimpleX-hivatkozĆ”sok küldĆ©se engedĆ©lyezve van. No comment provided by engineer. Allow to send files and media. - FĆ”jlok Ć©s mĆ©diatartalom küldĆ©sĆ©nek engedĆ©lyezĆ©se. + FĆ”jlok Ć©s mĆ©diatartalmak küldĆ©sĆ©nek engedĆ©lyezĆ©se. No comment provided by engineer. @@ -821,7 +842,7 @@ Allow voice messages only if your contact allows them. - A hangüzenetek küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + A hangüzenetek küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. No comment provided by engineer. @@ -1016,7 +1037,7 @@ Auto-accept contact requests - KapcsolódĆ”si kĆ©relmek automatikus elfogadĆ”sa + KapcsolatkĆ©rĆ©sek automatikus elfogadĆ”sa No comment provided by engineer. @@ -1026,6 +1047,7 @@ Auto-accept settings + BeĆ”llĆ­tĆ”sok automatikus elfogadĆ”sa alert title @@ -1221,7 +1243,7 @@ Cannot receive file Nem lehet fogadni a fĆ”jlt - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + A csevegĆ©si beĆ”llĆ­tĆ”sok megvĆ”ltoztak. alert message @@ -1547,14 +1570,14 @@ Connect to yourself? This is your own SimpleX address! KapcsolódĆ”s sajĆ”t magĆ”hoz? -Ez az ƶn SimpleX cĆ­me! +Ez az ƶn SimpleX-cĆ­me! No comment provided by engineer. Connect to yourself? This is your own one-time link! KapcsolódĆ”s sajĆ”t magĆ”hoz? -Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! +Ez az ƶn egyszer hasznĆ”lható hivatkozĆ”sa! No comment provided by engineer. @@ -1569,7 +1592,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Connect via one-time link - KapcsolódĆ”s egyszer hasznĆ”latos hivatkozĆ”son keresztül + KapcsolódĆ”s egyszer hasznĆ”lható hivatkozĆ”son keresztül No comment provided by engineer. @@ -1609,7 +1632,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Connecting to server… (error: %@) - KapcsolódĆ”s a kiszolgĆ”lóhoz... (hiba: %@) + KapcsolódĆ”s a kiszolgĆ”lóhoz… (hiba: %@) No comment provided by engineer. @@ -1649,7 +1672,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Connection request sent! - KapcsolódĆ”si kĆ©rĆ©s elküldve! + KapcsolatkĆ©rĆ©s elküldve! No comment provided by engineer. @@ -1754,6 +1777,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Corner + Sarok No comment provided by engineer. @@ -1768,7 +1792,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Create SimpleX address - SimpleX cĆ­m lĆ©trehozĆ”sa + SimpleX-cĆ­m lĆ©trehozĆ”sa No comment provided by engineer. @@ -2237,7 +2261,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Delivery receipts! - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek! + KĆ©zbesĆ­tĆ©si jelentĆ©sek! No comment provided by engineer. @@ -2307,12 +2331,12 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Device authentication is disabled. Turning off SimpleX Lock. - A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r ki van kapcsolva. + A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX-zĆ”r ki van kapcsolva. No comment provided by engineer. Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication. - A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be, miutĆ”n beĆ”llĆ­totta a kĆ©pernyőzĆ”rat az eszkƶzĆ©n. + A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX-zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be, miutĆ”n beĆ”llĆ­totta a kĆ©pernyőzĆ”rat az eszkƶzĆ©n. No comment provided by engineer. @@ -2337,7 +2361,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Disable SimpleX Lock - SimpleX zĆ”r kikapcsolĆ”sa + SimpleX-zĆ”r kikapcsolĆ”sa authentication reason @@ -2425,6 +2449,10 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Az előzmĆ©nyek ne kerüljenek elküldĆ©sre az Ćŗj tagok szĆ”mĆ”ra. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Ne hozzon lĆ©tre cĆ­met @@ -2448,7 +2476,8 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Download LetƶltĆ©s - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! FĆ”jl letƶltĆ©se server test step + + Download files + alert action + Downloaded Letƶltve @@ -2517,7 +2550,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Enable SimpleX Lock - SimpleX zĆ”r bekapcsolĆ”sa + SimpleX-zĆ”r bekapcsolĆ”sa authentication reason @@ -2602,7 +2635,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Encrypt stored files & media - TĆ”rolt fĆ”jlok Ć©s mĆ©diatartalmak titkosĆ­tĆ”sa + A tĆ”rolt fĆ”jlok- Ć©s a mĆ©diatartalmak titkosĆ­tĆ”sa No comment provided by engineer. @@ -2662,7 +2695,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Enter correct passphrase. - Helyes jelmondat bevitele. + Adja meg a helyes jelmondatot. No comment provided by engineer. @@ -2697,12 +2730,12 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Enter welcome message… - Üdvƶzlő üzenet megadĆ”sa… + Üdvƶzlőüzenet megadĆ”sa… placeholder Enter welcome message… (optional) - Üdvƶzlő üzenet megadĆ”sa… (opcionĆ”lis) + Üdvƶzlőüzenet megadĆ”sa… (opcionĆ”lis) placeholder @@ -2722,7 +2755,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error accepting contact request - Hiba tƶrtĆ©nt a kapcsolatfelvĆ©teli kĆ©relem elfogadĆ”sakor + Hiba tƶrtĆ©nt a kapcsolatkĆ©rĆ©s elfogadĆ”sakor No comment provided by engineer. @@ -2742,6 +2775,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error changing connection profile + Hiba a kapcsolati profilra való vĆ”ltĆ”s kƶzben No comment provided by engineer. @@ -2756,6 +2790,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error changing to incognito! + Hiba az inkognitó-profilra való vĆ”ltĆ”s kƶzben! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error migrating settings + Hiba a beallĆ­tĆ”sok Ć”tkƶltƶztetĆ©se kƶzben No comment provided by engineer. @@ -2890,7 +2926,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error receiving file Hiba a fĆ”jl fogadĆ”sakor - No comment provided by engineer. + alert title Error reconnecting server @@ -2969,7 +3005,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error setting delivery receipts! - Hiba tƶrtĆ©nt a kĆ©zbesĆ­tĆ©si igazolĆ”sok beĆ”llĆ­tĆ”sakor! + Hiba tƶrtĆ©nt a kĆ©zbesĆ­tĆ©si jelentĆ©sek beĆ”llĆ­tĆ”sakor! No comment provided by engineer. @@ -2984,6 +3020,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Error switching profile + Hiba a profilvĆ”ltĆ”s kƶzben No comment provided by engineer. @@ -3109,7 +3146,7 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Faster joining and more reliable messages. - Gyorsabb csatlakozĆ”s Ć©s megbĆ­zhatóbb üzenet kĆ©zbesĆ­tĆ©s. + Gyorsabb csatlakozĆ”s Ć©s megbĆ­zhatóbb üzenetkĆ©zbesĆ­tĆ©s. No comment provided by engineer. @@ -3122,6 +3159,11 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! FĆ”jlhiba No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. A fĆ”jl nem talĆ”lható - valószĆ­nűleg a fĆ”jlt tƶrƶltĆ©k vagy visszavontĆ”k. @@ -3169,27 +3211,27 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! Files & media - FĆ”jlok Ć©s mĆ©dia + FĆ”jlok Ć©s mĆ©diatartalmak No comment provided by engineer. Files and media - FĆ”jlok Ć©s mĆ©diatartalom + FĆ”jlok Ć©s mĆ©diatartalmak chat feature Files and media are prohibited in this group. - A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva ebben a csoportban. + A fĆ”jlok- Ć©s a mĆ©diatartalmak le vannak tiltva ebben a csoportban. No comment provided by engineer. Files and media not allowed - FĆ”jlok Ć©s mĆ©dia tartalom küldĆ©se le van tiltva + A fĆ”jlok- Ć©s mĆ©diatartalmak nincsenek engedĆ©lyezve No comment provided by engineer. Files and media prohibited! - A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva! + A fĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©se le van tiltva! No comment provided by engineer. @@ -3257,11 +3299,23 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! TovĆ”bbĆ­tĆ”s chat item action + + Forward %d message(s)? + alert title + Forward and save messages Üzenetek tovĆ”bbĆ­tĆ”sa Ć©s mentĆ©se No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded TovĆ”bbĆ­tott @@ -3272,6 +3326,10 @@ Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! TovĆ”bbĆ­tva innen: No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. A(z) %@ tovĆ”bbĆ­tó kiszolgĆ”ló nem tudott csatlakozni a(z) %@ cĆ©lkiszolgĆ”lóhoz. PróbĆ”lja meg kĆ©sőbb. @@ -3323,7 +3381,7 @@ Hiba: %2$@ Fully decentralized – visible only to members. - Teljesen decentralizĆ”lt - kizĆ”rólag tagok szĆ”mĆ”ra lĆ”tható. + Teljesen decentralizĆ”lt - csak a tagok szĆ”mĆ”ra lĆ”tható. No comment provided by engineer. @@ -3333,7 +3391,7 @@ Hiba: %2$@ Further reduced battery usage - TovĆ”bb csƶkkentett akkumulĆ”tor hasznĆ”lat + TovĆ”bb csƶkkentett akkumulĆ”tor-hasznĆ”lat No comment provided by engineer. @@ -3383,17 +3441,17 @@ Hiba: %2$@ Group invitation - Csoportos meghĆ­vó + CsoportmeghĆ­vó No comment provided by engineer. Group invitation expired - A csoport meghĆ­vó lejĆ”rt + A csoportmeghĆ­vó lejĆ”rt No comment provided by engineer. Group invitation is no longer valid, it was removed by sender. - A csoport meghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője tƶrƶlte. + A csoportmeghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője eltĆ”volĆ­totta. No comment provided by engineer. @@ -3418,7 +3476,7 @@ Hiba: %2$@ Group members can send SimpleX links. - A csoport tagjai küldhetnek SimpleX hivatkozĆ”sokat. + A csoport tagjai küldhetnek SimpleX-hivatkozĆ”sokat. No comment provided by engineer. @@ -3453,7 +3511,7 @@ Hiba: %2$@ Group preferences - Csoport beĆ”llĆ­tĆ”sok + CsoportbeĆ”llĆ­tĆ”sok No comment provided by engineer. @@ -3468,7 +3526,7 @@ Hiba: %2$@ Group welcome message - Csoport üdvƶzlő üzenete + A csoport üdvƶzlőüzenete No comment provided by engineer. @@ -3566,6 +3624,10 @@ Hiba: %2$@ ICE-kiszolgĆ”lók (soronkĆ©nt egy) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Ha nem tud szemĆ©lyesen talĆ”lkozni, mutassa meg a QR-kódot egy videohĆ­vĆ”s kƶzben, vagy ossza meg a hivatkozĆ”st. @@ -3573,7 +3635,7 @@ Hiba: %2$@ If you enter this passcode when opening the app, all app data will be irreversibly removed! - Ha az alkalmazĆ”s megnyitĆ”sakor megadja ezt a jelkódot, az ƶsszes alkalmazĆ”sadat vĆ©glegesen tƶrlődik! + Ha az alkalmazĆ”s megnyitĆ”sakor megadja ezt a jelkódot, az ƶsszes alkalmazĆ”sadat vĆ©glegesen eltĆ”volĆ­tĆ”sra kerül! No comment provided by engineer. @@ -3643,7 +3705,7 @@ Hiba: %2$@ Improved message delivery - TovĆ”bbfejlesztett üzenetküldĆ©s + TovĆ”bbfejlesztett üzenetkĆ©zbesĆ­tĆ©s No comment provided by engineer. @@ -3683,7 +3745,7 @@ Hiba: %2$@ Incognito mode - Inkognitó mód + Inkognitómód No comment provided by engineer. @@ -4073,7 +4135,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Győződjƶn meg arról, hogy a %@ kiszolgĆ”lócĆ­mek megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva (%@). + Győződjƶn meg arról, hogy a(z) %@ kiszolgĆ”lócĆ­mek megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva (%@). No comment provided by engineer. @@ -4158,12 +4220,12 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Message delivery receipts! - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek! + ÜzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek! No comment provided by engineer. Message delivery warning - Üzenet kĆ©zbesĆ­tĆ©si figyelmeztetĆ©s + ÜzenetkĆ©zbesĆ­tĆ©si figyelmeztetĆ©s item status text @@ -4203,7 +4265,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Message reception - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©s + ÜzenetjelentĆ©s No comment provided by engineer. @@ -4213,6 +4275,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Message shape + ÜzenetbuborĆ©k formĆ”ja No comment provided by engineer. @@ -4265,6 +4328,10 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Elküldƶtt üzenetek No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Az üzeneteket, fĆ”jlokat Ć©s hĆ­vĆ”sokat **vĆ©gpontok kƶzƶtti titkosĆ­tĆ”ssal**, sĆ©rülĆ©s utĆ”ni titkossĆ”g-vĆ©delemmel Ć©s -helyreĆ”llĆ­tĆ”ssal, tovĆ”bbĆ” visszautasĆ­tĆ”ssal vĆ©di. @@ -4322,7 +4389,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). - Sikertelen Ć”tkƶltƶztetĆ©s. Koppintson a **KihagyĆ”s** lehetősĆ©gre az aktuĆ”lis adatbĆ”zis hasznĆ”latĆ”nak folytatĆ”sĆ”hoz. Jelentse a problĆ©mĆ”t az alkalmazĆ”s fejlesztőinek csevegĆ©sben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). + Sikertelen Ć”tkƶltƶztetĆ©s. Koppintson a **KihagyĆ”s** lehetősĆ©gre a jelenlegi adatbĆ”zis hasznĆ”latĆ”nak folytatĆ”sĆ”hoz. Jelentse a problĆ©mĆ”t az alkalmazĆ”s fejlesztőinek csevegĆ©sben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). No comment provided by engineer. @@ -4432,7 +4499,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! New contact request - Új ismerőskĆ©relem + Új kapcsolatkĆ©rĆ©s notification @@ -4560,6 +4627,10 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Nincs kivĆ”lasztva semmi No comment provided by engineer. + + Nothing to forward! + alert title + Notifications ƉrtesĆ­tĆ©sek @@ -4592,7 +4663,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! Ok Rendben - No comment provided by engineer. + alert button Old database @@ -4606,7 +4677,7 @@ Ez az ƶn hivatkozĆ”sa a(z) %@ csoporthoz! One-time invitation link - Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s + Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s No comment provided by engineer. @@ -4735,7 +4806,7 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. Open server settings - KiszolgĆ”ló beĆ”llĆ­tĆ”sainak megnyitĆ”sa + KiszolgĆ”ló-beĆ”llĆ­tĆ”sok megnyitĆ”sa No comment provided by engineer. @@ -4783,6 +4854,11 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. TovĆ”bbi %@ kiszolgĆ”lók No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING szĆ”mlĆ”ló @@ -4818,6 +4894,10 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. A jelkód beĆ”llĆ­tva! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Jelszó megjelenĆ­tĆ©se @@ -4825,7 +4905,7 @@ VPN engedĆ©lyezĆ©se szüksĆ©ges. Past member %@ - %@ (mĆ”r nem tag) + (mĆ”r nem tag) %@ past/unknown group member @@ -4929,7 +5009,7 @@ Hiba: %@ Please enter correct current passphrase. - Adja meg a helyes aktuĆ”lis jelmondatĆ”t. + Adja meg a helyes, jelenlegi jelmondatĆ”t. No comment provided by engineer. @@ -4967,6 +5047,10 @@ Hiba: %@ Lengyel kezelőfelület No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect LehetsĆ©ges, hogy a kiszolgĆ”ló cĆ­mĆ©ben szereplő tanĆŗsĆ­tvĆ”ny-ujjlenyomat helytelen @@ -5009,7 +5093,7 @@ Hiba: %@ Private filenames - PrivĆ”t fĆ”jl nevek + PrivĆ”t fĆ”jlnevek No comment provided by engineer. @@ -5089,7 +5173,7 @@ Hiba: %@ Prohibit sending SimpleX links. - A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva. + A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva. No comment provided by engineer. @@ -5104,7 +5188,7 @@ Hiba: %@ Prohibit sending files and media. - FĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©s letiltĆ”sa. + FĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©sĆ©nek letiltĆ”sa. No comment provided by engineer. @@ -5154,6 +5238,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Proxyzott kiszolgĆ”lók No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push Ć©rtesĆ­tĆ©sek @@ -5221,7 +5309,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Receipts are disabled - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©s letiltva + A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva No comment provided by engineer. @@ -5241,7 +5329,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Received file event - Fogadott fĆ”jl esemĆ©ny + Fogadott fĆ”jlesemĆ©ny notification @@ -5301,7 +5389,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reconnect all connected servers to force message delivery. It uses additional traffic. - ÚjrakapcsolódĆ”s az ƶsszes kiszolgĆ”lóhoz az üzenetek kĆ©zbesĆ­tĆ©sĆ©nek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi forgalmat hasznĆ”l. + Az ƶsszes kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. No comment provided by engineer. @@ -5316,7 +5404,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reconnect server to force message delivery. It uses additional traffic. - A kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenet kĆ©zbesĆ­tĆ©sĆ©nek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. + A kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. No comment provided by engineer. @@ -5341,7 +5429,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reduced battery usage - Csƶkkentett akkumulĆ”torhasznĆ”lat + Csƶkkentett akkumulĆ”tor-hasznĆ”lat No comment provided by engineer. @@ -5357,7 +5445,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Reject contact request - KapcsolatfelvĆ©teli kĆ©relem elutasĆ­tĆ”sa + KapcsolatkĆ©rĆ©s elutasĆ­tĆ”sa No comment provided by engineer. @@ -5377,6 +5465,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Remove archive? + ArchĆ­vum eltĆ”volĆ­tĆ”sa? No comment provided by engineer. @@ -5416,7 +5505,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Repeat connection request? - KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? + KapcsolatkĆ©rĆ©s megismĆ©tlĆ©se? No comment provided by engineer. @@ -5431,7 +5520,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Repeat join request? - CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? + CsatlakozĆ”skĆ©rĆ©s megismĆ©tlĆ©se? No comment provided by engineer. @@ -5559,6 +5648,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. SMP-kiszolgĆ”ló No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files FĆ”jlok biztonsĆ”gos fogadĆ”sa @@ -5607,12 +5700,12 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Save group profile - Csoportprofil elmentĆ©se + Csoportprofil mentĆ©se No comment provided by engineer. Save passphrase and open chat - Jelmondat elmentĆ©se Ć©s csevegĆ©s megnyitĆ”sa + Jelmondat mentĆ©se Ć©s a csevegĆ©s megnyitĆ”sa No comment provided by engineer. @@ -5642,11 +5735,12 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Save welcome message? - Üdvƶzlőszƶveg mentĆ©se? + Üdvƶzlőüzenet mentĆ©se? No comment provided by engineer. Save your profile? + Profil mentĆ©se? alert title @@ -5669,6 +5763,10 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Mentett üzenet message info title + + Saving %lld messages + No comment provided by engineer. + Scale MĆ©retezĆ©s @@ -5711,12 +5809,12 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Search bar accepts invitation links. - A keresősĆ”v elfogadja a meghĆ­vó hivatkozĆ”sokat. + A keresősĆ”v elfogadja a meghĆ­vó-hivatkozĆ”sokat. No comment provided by engineer. Search or paste SimpleX link - KeresĆ©s, vagy SimpleX hivatkozĆ”s beillesztĆ©se + KeresĆ©s, vagy SimpleX-hivatkozĆ”s beillesztĆ©se No comment provided by engineer. @@ -5746,16 +5844,17 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Select - VĆ”lasztĆ”s + KijelƶlĆ©s chat item action Select chat profile + CsevegĆ©si profil kivĆ”lasztĆ”sa No comment provided by engineer. Selected %lld - %lld kivĆ”lasztva + %lld kijelƶlve No comment provided by engineer. @@ -5855,7 +5954,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Send receipts - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek + KĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se No comment provided by engineer. @@ -5871,11 +5970,11 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Sender cancelled file transfer. A fĆ”jl küldője visszavonta az Ć”tvitelt. - No comment provided by engineer. + alert message Sender may have deleted the connection request. - A küldő tƶrƶlhette a kapcsolódĆ”si kĆ©relmet. + A küldő tƶrƶlhette a kapcsolatkĆ©rĆ©st. No comment provided by engineer. @@ -5895,22 +5994,22 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Sending receipts is disabled for %lld contacts - A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se le van tiltva %lld ismerősnĆ©l + A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %lld ismerősnĆ©l No comment provided by engineer. Sending receipts is disabled for %lld groups - A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se le van tiltva %lld csoportban + A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %lld csoportban No comment provided by engineer. Sending receipts is enabled for %lld contacts - A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezve van %lld ismerős szĆ”mĆ”ra + A kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %lld ismerősnĆ©l No comment provided by engineer. Sending receipts is enabled for %lld groups - A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezve van %lld csoportban + A kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %lld csoportban No comment provided by engineer. @@ -5935,7 +6034,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Sent file event - Elküldƶtt fĆ”jl esemĆ©ny + Elküldƶtt fĆ”jlesemĆ©ny notification @@ -6090,6 +6189,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Settings were changed. + A beĆ”llĆ­tĆ”sok megvĆ”ltoztak. alert message @@ -6104,7 +6204,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Share 1-time link - Egyszer hasznĆ”latos hivatkozĆ”s megosztĆ”sa + Egyszer hasznĆ”lható hivatkozĆ”s megosztĆ”sa No comment provided by engineer. @@ -6129,11 +6229,12 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Share profile + Profil megosztĆ”sa No comment provided by engineer. Share this 1-time invite link - Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s megosztĆ”sa + Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s megosztĆ”sa No comment provided by engineer. @@ -6198,7 +6299,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. SimpleX Address - SimpleX cĆ­m + SimpleX-cĆ­m No comment provided by engineer. @@ -6208,7 +6309,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. SimpleX Lock - SimpleX zĆ”r + SimpleX-zĆ”r No comment provided by engineer. @@ -6218,17 +6319,17 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. SimpleX Lock not enabled! - A SimpleX zĆ”r nincs bekapcsolva! + A SimpleX-zĆ”r nincs bekapcsolva! No comment provided by engineer. SimpleX Lock turned on - SimpleX zĆ”r bekapcsolva + SimpleX-zĆ”r bekapcsolva No comment provided by engineer. SimpleX address - SimpleX cĆ­m + SimpleX-cĆ­m No comment provided by engineer. @@ -6248,22 +6349,22 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. SimpleX links - SimpleX hivatkozĆ”sok + SimpleX-hivatkozĆ”sok chat feature SimpleX links are prohibited in this group. - A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban. + A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban. No comment provided by engineer. SimpleX links not allowed - A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva + A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva No comment provided by engineer. SimpleX one-time invitation - SimpleX egyszer hasznĆ”latos meghĆ­vó + Egyszer hasznĆ”lható SimpleX-meghĆ­vó simplex link type @@ -6298,6 +6399,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Some app settings were not migrated. + Egyes alkalmazĆ”sbeĆ”llĆ­tĆ”sok nem lettek Ć”tkƶltƶztetve. No comment provided by engineer. @@ -6387,12 +6489,12 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Stop receiving file? - FĆ”jl fogadĆ”s megĆ”llĆ­tĆ”sa? + FĆ”jlfogadĆ”s megĆ”llĆ­tĆ”sa? No comment provided by engineer. Stop sending file? - FĆ”jl küldĆ©s megĆ”llĆ­tĆ”sa? + FĆ”jlküldĆ©s megĆ”llĆ­tĆ”sa? No comment provided by engineer. @@ -6477,6 +6579,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Tail + Farok No comment provided by engineer. @@ -6521,7 +6624,7 @@ EngedĆ©lyezze a BeĆ”llĆ­tĆ”sok / HĆ”lózat Ć©s kiszolgĆ”lók menüben. Temporary file error - Ideiglenes fĆ”jlhiba + IdeiglenesfĆ”jl-hiba No comment provided by engineer. @@ -6573,7 +6676,7 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. The app can notify you when you receive messages or contact requests - please open settings to enable. - Az alkalmazĆ”s Ć©rtesĆ­teni fogja, amikor üzeneteket vagy kapcsolatfelvĆ©teli kĆ©rĆ©seket kap – beĆ”llĆ­tĆ”sok megnyitĆ”sa az engedĆ©lyezĆ©shez. + Az alkalmazĆ”s Ć©rtesĆ­teni fogja, amikor üzeneteket vagy kapcsolatkĆ©rĆ©seket kap – beĆ”llĆ­tĆ”sok megnyitĆ”sa az engedĆ©lyezĆ©shez. No comment provided by engineer. @@ -6668,11 +6771,12 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. The text you pasted is not a SimpleX link. - A beillesztett szƶveg nem egy SimpleX hivatkozĆ”s. + A beillesztett szƶveg nem egy SimpleX-hivatkozĆ”s. No comment provided by engineer. The uploaded database archive will be permanently removed from the servers. + A feltƶltƶtt adatbĆ”zis-archĆ­vum vĆ©glegesen eltĆ”volĆ­tĆ”sra kerül a kiszolgĆ”lókról. No comment provided by engineer. @@ -6692,7 +6796,7 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalommal együtt tƶrlĆ©sre kerülnek. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak. + Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalmakkal együtt tƶrlĆ©sre kerül. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak. No comment provided by engineer. @@ -6737,12 +6841,12 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. This is your own SimpleX address! - Ez az ƶn SimpleX cĆ­me! + Ez az ƶn SimpleX-cĆ­me! No comment provided by engineer. This is your own one-time link! - Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! + Ez az ƶn egyszer hasznĆ”lható hivatkozĆ”sa! No comment provided by engineer. @@ -6798,7 +6902,7 @@ Ez valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. - A biztonsĆ”ga Ć©rdekĆ©ben kapcsolja be a SimpleX zĆ”r funkciót. + A biztonsĆ”ga Ć©rdekĆ©ben kapcsolja be a SimpleX-zĆ”r funkciót. A funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”llĆ­tĆ”sĆ”ra az eszkƶzĆ©n. No comment provided by engineer. @@ -6829,7 +6933,7 @@ A funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”ll Toggle incognito when connecting. - Inkognitó mód kapcsolódĆ”skor. + Inkognitómód kapcsolódĆ”skor. No comment provided by engineer. @@ -6955,7 +7059,7 @@ A funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”ll Unknown servers! Ismeretlen kiszolgĆ”lók! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,9 +7173,13 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc Tor .onion kiszolgĆ”lók hasznĆ”lata No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? - SimpleX Chat kiszolgĆ”lók hasznĆ”lata? + SimpleX Chat-kiszolgĆ”lók hasznĆ”lata? No comment provided by engineer. @@ -7144,9 +7252,13 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc FelhasznĆ”ló kivĆ”lasztĆ”sa No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. - SimpleX Chat kiszolgĆ”lók hasznĆ”latban. + SimpleX Chat-kiszolgĆ”lók hasznĆ”latban. No comment provided by engineer. @@ -7256,22 +7368,22 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc Waiting for desktop... - VĆ”rakozĆ”s az asztali kliensre... + VĆ”rakozĆ”s az asztali kliensre… No comment provided by engineer. Waiting for file - FĆ”jlra vĆ”rakozĆ”s + VĆ”rakozĆ”s a fĆ”jlra No comment provided by engineer. Waiting for image - KĆ©pre vĆ”rakozĆ”s + VĆ”rakozĆ”s a kĆ©pre No comment provided by engineer. Waiting for video - Videóra vĆ”rakozĆ”s + VĆ”rakozĆ”s a videóra No comment provided by engineer. @@ -7306,12 +7418,12 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc Welcome message - Üdvƶzlő üzenet + Üdvƶzlőüzenet No comment provided by engineer. Welcome message is too long - Az üdvƶzlő üzenet tĆŗl hosszĆŗ + Az üdvƶzlőüzenet tĆŗl hosszĆŗ No comment provided by engineer. @@ -7331,12 +7443,12 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc When people request to connect, you can accept or reject it. - Amikor az emberek kapcsolódĆ”st kĆ©relmeznek, ƶn elfogadhatja vagy elutasĆ­thatja azokat. + Amikor az emberek kapcsolatot kĆ©rnek, ƶn elfogadhatja vagy elutasĆ­thatja azokat. No comment provided by engineer. When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. - Inkognitóprofil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott. + Inkognitó-profil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott. No comment provided by engineer. @@ -7356,17 +7468,17 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc With encrypted files and media. - TitkosĆ­tott fĆ”jlokkal Ć©s mĆ©diatartalommal. + TitkosĆ­tott fĆ”jlokkal Ć©s mĆ©diatartalmakkal. No comment provided by engineer. With optional welcome message. - OpcionĆ”lis üdvƶzlő üzenettel. + OpcionĆ”lis üdvƶzlőüzenettel. No comment provided by engineer. With reduced battery usage. - Csƶkkentett akkumulĆ”torhasznĆ”lattal. + Csƶkkentett akkumulĆ”tor-hasznĆ”lattal. No comment provided by engineer. @@ -7376,8 +7488,8 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - Tor vagy VPN nĆ©lkül az IP-cĆ­me lĆ”tható lesz ezen XFTP Ć”tjĆ”tszók szĆ”mĆ”ra: %@. - No comment provided by engineer. + A megosztĆ”st az AdatvĆ©delem Ć©s biztonsĆ”g / SimpleX zĆ”r menüben engedĆ©lyezheti. + alert message Wrong database passphrase @@ -7441,7 +7553,7 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc You are already connecting via this one-time link! - A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”latos hivatkozĆ”son keresztül! + A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”lható hivatkozĆ”son keresztül! No comment provided by engineer. @@ -7451,7 +7563,7 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc You are already joining the group %@. - A csatlakozĆ”s mĆ”r folyamatban van a(z) %@ csoporthoz. + A csatlakozĆ”s mĆ”r folyamatban van a(z) %@ nevű csoporthoz. No comment provided by engineer. @@ -7468,7 +7580,7 @@ A kapcsolódĆ”shoz kĆ©rje meg az ismerősĆ©t, hogy hozzon lĆ©tre egy mĆ”sik kapc You are already joining the group! Repeat join request? CsatlakozĆ”s folyamatban! -CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? +CsatlakozĆ”skĆ©rĆ©s megismĆ©tlĆ©se? No comment provided by engineer. @@ -7568,7 +7680,7 @@ CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You can turn on SimpleX Lock via Settings. - A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be. + A SimpleX-zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be. No comment provided by engineer. @@ -7578,7 +7690,7 @@ CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You can view invitation link again in connection details. - A meghĆ­vó hivatkozĆ”st Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l. + A meghĆ­vó-hivatkozĆ”st Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l. No comment provided by engineer. @@ -7598,14 +7710,14 @@ CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You have already requested connection via this address! - MĆ”r kĆ©rt egy kapcsolódĆ”si kĆ©relmet ezen a cĆ­men keresztül! + MĆ”r küldƶtt egy kapcsolatkĆ©rĆ©st ezen a cĆ­men keresztül! No comment provided by engineer. You have already requested connection! Repeat connection request? - MĆ”r kĆ©rt egy kapcsolódĆ”si kĆ©relmet! -KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? + MĆ”r küldƶtt egy kapcsolódĆ”si kĆ©relmet! +KapcsolatkĆ©rĆ©s megismĆ©tlĆ©se? No comment provided by engineer. @@ -7655,12 +7767,12 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You rejected group invitation - Csoport meghĆ­vó elutasĆ­tva + CsoportmeghĆ­vó elutasĆ­tva No comment provided by engineer. You sent group invitation - Csoport meghĆ­vó elküldve + CsoportmeghĆ­vó elküldve No comment provided by engineer. @@ -7675,7 +7787,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? You will be connected when your connection request is accepted, please wait or check later! - Akkor lesz kapcsolódva, ha a kapcsolódĆ”si kĆ©relme elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! + Akkor lesz kapcsolódva, ha a kapcsolatkĆ©rĆ©se elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! No comment provided by engineer. @@ -7735,7 +7847,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? Your SimpleX address - Profil SimpleX cĆ­me + Profil SimpleX-cĆ­me No comment provided by engineer. @@ -7760,6 +7872,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? Your chat preferences + CsevegĆ©si beĆ”llĆ­tĆ”sok alert title @@ -7769,6 +7882,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + A kapcsolata Ć”t lett helyezve ide: %@, de egy vĆ”ratlan hiba tƶrtĆ©nt a profilra való Ć”tirĆ”nyĆ­tĆ”s kƶzben. No comment provided by engineer. @@ -7786,6 +7900,10 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? Az ismerősei tovĆ”bbra is kapcsolódva maradnak. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. A jelenlegi csevegĆ©si adatbĆ”zis TƖRLŐDNI FOG, Ć©s a HELYƉRE az importĆ”lt adatbĆ”zis kerül. @@ -7818,11 +7936,12 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. - Profilja az eszkƶzƶn van tĆ”rolva, Ć©s csak az ismerősƶkkel kerül megosztĆ”sra. A SimpleX kiszolgĆ”lók nem lĆ”tjhatjĆ”k profiljĆ”t. + Profilja az eszkƶzƶn van tĆ”rolva Ć©s csak az ismerőseivel kerül megosztĆ”sra. A SimpleX-kiszolgĆ”lók nem lĆ”thatjĆ”k a profiljĆ”t. No comment provided by engineer. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + A profilja megvĆ”ltozott. Ha elmenti, a frissĆ­tett profil elküldĆ©sre kerül az ƶsszes ismerősĆ©nek. alert message @@ -7897,7 +8016,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? agreeing encryption for %@… - titkosĆ­tĆ”s jóvĆ”hagyĆ”sa %@ szĆ”mĆ”ra… + titkosĆ­tĆ”s elfogadĆ”sa %@ szĆ”mĆ”ra… chat item text @@ -8027,7 +8146,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? connect to SimpleX Chat developers. - KapcsolódĆ”s a SimpleX Chat fejlesztőkhƶz. + kapcsolódĆ”s a SimpleX Chat fejlesztőkhƶz. No comment provided by engineer. @@ -8062,7 +8181,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? connecting (introduction invitation) - kapcsolódĆ”s (bemutatkozó meghĆ­vó) + kapcsolódĆ”s (bemutatkozó-meghĆ­vó) No comment provided by engineer. @@ -8202,7 +8321,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? encryption agreed - titkosĆ­tĆ”s egyeztetve + titkosĆ­tĆ”s elfogadva chat item text @@ -8312,7 +8431,7 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? incognito via one-time link - inkognitó egy egyszer hasznĆ”latos hivatkozĆ”son keresztül + inkognitó egy egyszer hasznĆ”lható hivatkozĆ”son keresztül chat list item description @@ -8549,17 +8668,17 @@ KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? removed contact address - tƶrƶlt kapcsolattartĆ”si cĆ­m + eltĆ”volĆ­totta a kapcsolattartĆ”si cĆ­met profile update event chat item removed profile picture - tƶrƶlte a profilkĆ©pĆ©t + eltĆ”volĆ­totta a profilkĆ©pĆ©t profile update event chat item removed you - eltĆ”volĆ­tottak + eltĆ”volĆ­totta ƶnt rcv group event chat item @@ -8703,7 +8822,7 @@ utoljĆ”ra fogadott üzenet: %2$@ via one-time link - egy egyszer hasznĆ”latos hivatkozĆ”son keresztül + egyszer hasznĆ”lható hivatkozĆ”son keresztül chat list item description @@ -8803,12 +8922,12 @@ utoljĆ”ra fogadott üzenet: %2$@ you shared one-time link - egyszer hasznĆ”latos hivatkozĆ”st osztott meg + egyszer hasznĆ”lható hivatkozĆ”st osztott meg chat list item description you shared one-time link incognito - egyszer hasznĆ”latos hivatkozĆ”st osztott meg inkognitóban + egyszer hasznĆ”lható hivatkozĆ”st osztott meg inkognitóban chat list item description @@ -8946,7 +9065,7 @@ utoljĆ”ra fogadott üzenet: %2$@ Currently maximum supported file size is %@. - Jelenleg a maximĆ”lis tĆ”mogatott fĆ”jlmĆ©ret %@. + Jelenleg a maximĆ”lisan tĆ”mogatott fĆ”jlmĆ©ret: %@. No comment provided by engineer. @@ -9096,7 +9215,7 @@ utoljĆ”ra fogadott üzenet: %2$@ You can allow sharing in Privacy & Security / SimpleX Lock settings. - A megosztĆ”st az AdatvĆ©delem Ć©s biztonsĆ”g / SimpleX zĆ”r menüben engedĆ©lyezheti. + A megosztĆ”st az AdatvĆ©delem Ć©s biztonsĆ”g / SimpleX-zĆ”r menüben engedĆ©lyezheti. No comment provided by engineer. 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 a5e013fec2..38e3fee0de 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d giorni time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ore time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1026,6 +1047,7 @@ Auto-accept settings + Accetta automaticamente le impostazioni alert title @@ -1221,7 +1243,7 @@ Cannot receive file Impossibile ricevere il file - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + Le preferenze della chat sono state cambiate. alert message @@ -1754,6 +1777,7 @@ Questo ĆØ il tuo link una tantum! Corner + Angolo No comment provided by engineer. @@ -2425,6 +2449,10 @@ Questo ĆØ il tuo link una tantum! Non inviare la cronologia ai nuovi membri. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Non creare un indirizzo @@ -2448,7 +2476,8 @@ Questo ĆØ il tuo link una tantum! Download Scarica - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Questo ĆØ il tuo link una tantum! Scarica file server test step + + Download files + alert action + Downloaded Scaricato @@ -2742,6 +2775,7 @@ Questo ĆØ il tuo link una tantum! Error changing connection profile + Errore nel cambio di profilo di connessione No comment provided by engineer. @@ -2756,6 +2790,7 @@ Questo ĆØ il tuo link una tantum! Error changing to incognito! + Errore nel passaggio a incognito! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Questo ĆØ il tuo link una tantum! Error migrating settings + Errore nella migrazione delle impostazioni No comment provided by engineer. @@ -2890,7 +2926,7 @@ Questo ĆØ il tuo link una tantum! Error receiving file Errore nella ricezione del file - No comment provided by engineer. + alert title Error reconnecting server @@ -2984,6 +3020,7 @@ Questo ĆØ il tuo link una tantum! Error switching profile + Errore nel cambio di profilo No comment provided by engineer. @@ -3122,6 +3159,11 @@ Questo ĆØ il tuo link una tantum! Errore del file No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. File non trovato - probabilmente ĆØ stato eliminato o annullato. @@ -3257,11 +3299,23 @@ Questo ĆØ il tuo link una tantum! Inoltra chat item action + + Forward %d message(s)? + alert title + Forward and save messages Inoltra e salva i messaggi No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Inoltrato @@ -3272,6 +3326,10 @@ Questo ĆØ il tuo link una tantum! Inoltrato da No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Il server di inoltro %@ non ĆØ riuscito a connettersi al server di destinazione %@. Riprova più tardi. @@ -3566,6 +3624,10 @@ Errore: %2$@ Server ICE (uno per riga) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Se non potete incontrarvi di persona, mostra il codice QR in una videochiamata o condividi il link. @@ -4213,6 +4275,7 @@ Questo ĆØ il tuo link per il gruppo %@! Message shape + Forma del messaggio No comment provided by engineer. @@ -4265,6 +4328,10 @@ Questo ĆØ il tuo link per il gruppo %@! Messaggi inviati No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione. @@ -4560,6 +4627,10 @@ Questo ĆØ il tuo link per il gruppo %@! Nessuna selezione No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Notifiche @@ -4592,7 +4663,7 @@ Questo ĆØ il tuo link per il gruppo %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4854,11 @@ Richiede l'attivazione della VPN. Altri %@ server No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Conteggio PING @@ -4818,6 +4894,10 @@ Richiede l'attivazione della VPN. Codice di accesso impostato! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Password per mostrare @@ -4967,6 +5047,10 @@ Errore: %@ Interfaccia polacca No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Probabilmente l'impronta del certificato nell'indirizzo del server ĆØ sbagliata @@ -5154,6 +5238,10 @@ Attivalo nelle impostazioni *Rete e server*. Server via proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Notifiche push @@ -5377,6 +5465,7 @@ Attivalo nelle impostazioni *Rete e server*. Remove archive? + Rimuovere l'archivio? No comment provided by engineer. @@ -5559,6 +5648,10 @@ Attivalo nelle impostazioni *Rete e server*. Server SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Ricevi i file in sicurezza @@ -5647,6 +5740,7 @@ Attivalo nelle impostazioni *Rete e server*. Save your profile? + Salvare il profilo? alert title @@ -5669,6 +5763,10 @@ Attivalo nelle impostazioni *Rete e server*. Messaggio salvato message info title + + Saving %lld messages + No comment provided by engineer. + Scale Scala @@ -5751,6 +5849,7 @@ Attivalo nelle impostazioni *Rete e server*. Select chat profile + Seleziona il profilo di chat No comment provided by engineer. @@ -5871,7 +5970,7 @@ Attivalo nelle impostazioni *Rete e server*. Sender cancelled file transfer. Il mittente ha annullato il trasferimento del file. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6090,6 +6189,7 @@ Attivalo nelle impostazioni *Rete e server*. Settings were changed. + Le impostazioni sono state cambiate. alert message @@ -6129,6 +6229,7 @@ Attivalo nelle impostazioni *Rete e server*. Share profile + Condividi il profilo No comment provided by engineer. @@ -6298,6 +6399,7 @@ Attivalo nelle impostazioni *Rete e server*. Some app settings were not migrated. + Alcune impostazioni dell'app non sono state migrate. No comment provided by engineer. @@ -6477,6 +6579,7 @@ Attivalo nelle impostazioni *Rete e server*. Tail + Coda No comment provided by engineer. @@ -6673,6 +6776,7 @@ Può accadere a causa di qualche bug o quando la connessione ĆØ compromessa. The uploaded database archive will be permanently removed from the servers. + L'archivio del database caricato verrĆ  rimosso definitivamente dai server. No comment provided by engineer. @@ -6955,7 +7059,7 @@ Ti verrĆ  chiesto di completare l'autenticazione prima di attivare questa funzio Unknown servers! Server sconosciuti! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7173,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa gli host .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Usare i server di SimpleX Chat? @@ -7144,6 +7252,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Selezione utente No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Utilizzo dei server SimpleX Chat. @@ -7377,7 +7489,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Senza Tor o VPN, il tuo indirizzo IP sarĆ  visibile a questi relay XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7760,6 +7872,7 @@ Ripetere la richiesta di connessione? Your chat preferences + Le tue preferenze della chat alert title @@ -7769,6 +7882,7 @@ Ripetere la richiesta di connessione? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + La tua connessione ĆØ stata spostata a %@, ma si ĆØ verificato un errore imprevisto durante il reindirizzamento al profilo. No comment provided by engineer. @@ -7786,6 +7900,10 @@ Ripetere la richiesta di connessione? I tuoi contatti resteranno connessi. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Il tuo attuale database della chat verrĆ  ELIMINATO e SOSTITUITO con quello importato. @@ -7823,6 +7941,7 @@ Ripetere la richiesta di connessione? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Il tuo profilo ĆØ stato cambiato. Se lo salvi, il profilo aggiornato verrĆ  inviato a tutti i tuoi contatti. alert message 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 3edfb59c57..ded20013bb 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -161,11 +161,31 @@ %d ę—„ time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d Ꙃ time interval + + %d messages not forwarded + alert title + %d min %d 分 @@ -1170,7 +1190,7 @@ Cannot receive file ćƒ•ć‚”ć‚¤ćƒ«å—äæ”ćŒć§ćć¾ć›ć‚“ - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2288,6 +2308,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address ć‚¢ćƒ‰ćƒ¬ć‚¹ć‚’ä½œęˆć—ćŖć„ć§ćć ć•ć„ @@ -2310,7 +2334,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2325,6 +2350,10 @@ This is your own one-time link! ćƒ•ć‚”ć‚¤ćƒ«ć‚’ćƒ€ć‚¦ćƒ³ćƒ­ćƒ¼ćƒ‰ server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2729,7 +2758,7 @@ This is your own one-time link! Error receiving file ćƒ•ć‚”ć‚¤ćƒ«å—äæ”ć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿ - No comment provided by engineer. + alert title Error reconnecting server @@ -2947,6 +2976,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3073,10 +3107,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3085,6 +3131,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3363,6 +3413,10 @@ Error: %2$@ ICEć‚µćƒ¼ćƒ (1蔌に1ć‚µćƒ¼ćƒ) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. ē›“ęŽ„ä¼šćˆćŖć„å “åˆćÆć€ćƒ“ćƒ‡ć‚Ŗé€šč©±ć§ QR ć‚³ćƒ¼ćƒ‰ć‚’č”Øē¤ŗć™ć‚‹ć‹ć€ćƒŖćƒ³ć‚Æć‚’å…±ęœ‰ć—ć¦ćć ć•ć„ć€‚ @@ -4016,6 +4070,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4290,6 +4348,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications é€šēŸ„ @@ -4321,7 +4383,7 @@ This is your link for group %@! Ok OK - No comment provided by engineer. + alert button Old database @@ -4501,6 +4563,11 @@ VPN ć‚’ęœ‰åŠ¹ć«ć™ć‚‹åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PINGå›žę•° @@ -4536,6 +4603,10 @@ VPN ć‚’ęœ‰åŠ¹ć«ć™ć‚‹åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ ćƒ‘ć‚¹ć‚³ćƒ¼ćƒ‰ć‚’čØ­å®šć—ć¾ć—ćŸļ¼ No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ćƒ‘ć‚¹ćƒÆćƒ¼ćƒ‰ć‚’č”Øē¤ŗć™ć‚‹ @@ -4672,6 +4743,10 @@ Error: %@ ćƒćƒ¼ćƒ©ćƒ³ćƒ‰čŖžUI No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect ć‚µćƒ¼ćƒć‚¢ćƒ‰ćƒ¬ć‚¹ć®čØ¼ę˜ŽčØ¼IDćŒę­£ć—ććŖć„ć‹ć‚‚ć—ć‚Œć¾ć›ć‚“ @@ -4845,6 +4920,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications ćƒ—ćƒƒć‚·ćƒ„é€šēŸ„ @@ -5221,6 +5300,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5325,6 +5408,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5511,7 +5598,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. é€äæ”č€…ćŒćƒ•ć‚”ć‚¤ćƒ«č»¢é€ć‚’ć‚­ćƒ£ćƒ³ć‚»ćƒ«ć—ć¾ć—ćŸć€‚ - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6514,7 +6601,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6619,6 +6706,10 @@ To connect, please ask your contact to create another connection link and check .onionćƒ›ć‚¹ćƒˆć‚’ä½æć† No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX チャット ć‚µćƒ¼ćƒćƒ¼ć‚’ä½æē”Øć—ć¾ć™ć‹? @@ -6687,6 +6778,10 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX チャット ć‚µćƒ¼ćƒćƒ¼ć‚’ä½æē”Øć™ć‚‹ć€‚ @@ -6899,7 +6994,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7281,6 +7376,10 @@ Repeat connection request? é€£ēµ”å…ˆćÆęŽ„ē¶šć•ć‚ŒćŸć¾ć¾ć«ćŖć‚Šć¾ć™ć€‚ No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. ē¾åœØć®ćƒćƒ£ćƒƒćƒˆ ćƒ‡ćƒ¼ć‚æćƒ™ćƒ¼ć‚¹ćÆå‰Šé™¤ć•ć‚Œć€ć‚¤ćƒ³ćƒćƒ¼ćƒˆć•ć‚ŒćŸćƒ‡ćƒ¼ć‚æćƒ™ćƒ¼ć‚¹ć«ē½®ćę›ćˆć‚‰ć‚Œć¾ć™ć€‚ 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 61fdcc1258..f61ce8924f 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -139,6 +139,7 @@ %1$@, %2$@ + %1$@, %2$@ format for date separator in chat @@ -161,11 +162,31 @@ %d dagen time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d uren time interval + + %d messages not forwarded + alert title + %d min %d min @@ -516,7 +537,7 @@ A separate TCP connection will be used **for each chat profile you have in the app**. - Er wordt een aparte TCP-verbinding gebruikt **voor elk chat profiel dat je in de app hebt**. + Er wordt een aparte TCP-verbinding gebruikt **voor elk chatprofiel dat je in de app hebt**. No comment provided by engineer. @@ -1026,6 +1047,7 @@ Auto-accept settings + Instellingen automatisch accepteren alert title @@ -1150,7 +1172,7 @@ By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). - Via chat profiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). + Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. @@ -1221,7 +1243,7 @@ Cannot receive file Kan bestand niet ontvangen - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -1351,6 +1373,7 @@ Chat preferences were changed. + Chatvoorkeuren zijn gewijzigd. alert message @@ -1360,7 +1383,7 @@ Chats - Gesprekken + Chats No comment provided by engineer. @@ -1754,6 +1777,7 @@ Dit is uw eigen eenmalige link! Corner + Hoek No comment provided by engineer. @@ -1967,7 +1991,7 @@ Dit is uw eigen eenmalige link! Database passphrase is required to open chat. - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. No comment provided by engineer. @@ -2062,12 +2086,12 @@ Dit is uw eigen eenmalige link! Delete chat profile - Chat profiel verwijderen + Chatprofiel verwijderen No comment provided by engineer. Delete chat profile? - Chat profiel verwijderen? + Chatprofiel verwijderen? No comment provided by engineer. @@ -2107,7 +2131,7 @@ Dit is uw eigen eenmalige link! Delete files for all chat profiles - Verwijder bestanden voor alle chat profielen + Verwijder bestanden voor alle chatprofielen No comment provided by engineer. @@ -2425,6 +2449,10 @@ Dit is uw eigen eenmalige link! Stuur geen geschiedenis naar nieuwe leden. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Maak geen adres aan @@ -2448,7 +2476,8 @@ Dit is uw eigen eenmalige link! Download Downloaden - chat item action + alert button + chat item action Download errors @@ -2465,6 +2494,10 @@ Dit is uw eigen eenmalige link! Download bestand server test step + + Download files + alert action + Downloaded Gedownload @@ -2742,6 +2775,7 @@ Dit is uw eigen eenmalige link! Error changing connection profile + Fout bij wijzigen van verbindingsprofiel No comment provided by engineer. @@ -2756,6 +2790,7 @@ Dit is uw eigen eenmalige link! Error changing to incognito! + Fout bij het overschakelen naar incognito! No comment provided by engineer. @@ -2880,6 +2915,7 @@ Dit is uw eigen eenmalige link! Error migrating settings + Fout bij migreren van instellingen No comment provided by engineer. @@ -2890,7 +2926,7 @@ Dit is uw eigen eenmalige link! Error receiving file Fout bij ontvangen van bestand - No comment provided by engineer. + alert title Error reconnecting server @@ -2984,6 +3020,7 @@ Dit is uw eigen eenmalige link! Error switching profile + Fout bij wisselen van profiel No comment provided by engineer. @@ -3122,6 +3159,11 @@ Dit is uw eigen eenmalige link! Bestandsfout No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Bestand niet gevonden - hoogstwaarschijnlijk is het bestand verwijderd of geannuleerd. @@ -3214,7 +3256,7 @@ Dit is uw eigen eenmalige link! Find chats faster - Vind gesprekken sneller + Vind chats sneller No comment provided by engineer. @@ -3257,11 +3299,23 @@ Dit is uw eigen eenmalige link! Doorsturen chat item action + + Forward %d message(s)? + alert title + Forward and save messages Berichten doorsturen en opslaan No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Doorgestuurd @@ -3272,6 +3326,10 @@ Dit is uw eigen eenmalige link! Doorgestuurd vanuit No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. De doorstuurserver %@ kon geen verbinding maken met de bestemmingsserver %@. Probeer het later opnieuw. @@ -3493,7 +3551,7 @@ Fout: %2$@ Hidden chat profiles - Verborgen chat profielen + Verborgen chatprofielen No comment provided by engineer. @@ -3566,6 +3624,10 @@ Fout: %2$@ ICE servers (ƩƩn per lijn) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Als je elkaar niet persoonlijk kunt ontmoeten, laat dan de QR-code zien in een videogesprek of deel de link. @@ -3845,7 +3907,7 @@ Fout: %2$@ It allows having many anonymous connections without any shared data between them in a single chat profile. - Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel. + Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel. No comment provided by engineer. @@ -4213,6 +4275,7 @@ Dit is jouw link voor groep %@! Message shape + Berichtvorm No comment provided by engineer. @@ -4265,6 +4328,10 @@ Dit is jouw link voor groep %@! Berichten verzonden No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. @@ -4367,7 +4434,7 @@ Dit is jouw link voor groep %@! Multiple chat profiles - Meerdere chat profielen + Meerdere chatprofielen No comment provided by engineer. @@ -4517,7 +4584,7 @@ Dit is jouw link voor groep %@! No filtered chats - Geen gefilterde gesprekken + Geen gefilterde chats No comment provided by engineer. @@ -4560,6 +4627,10 @@ Dit is jouw link voor groep %@! Niets geselecteerd No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Meldingen @@ -4592,7 +4663,7 @@ Dit is jouw link voor groep %@! Ok OK - No comment provided by engineer. + alert button Old database @@ -4783,6 +4854,11 @@ Vereist het inschakelen van VPN. Andere %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING count @@ -4818,6 +4894,10 @@ Vereist het inschakelen van VPN. Toegangscode ingesteld! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Wachtwoord om weer te geven @@ -4875,7 +4955,7 @@ Vereist het inschakelen van VPN. Play from the chat list. - Afspelen via de gesprekken lijst. + Afspelen via de chat lijst. No comment provided by engineer. @@ -4954,7 +5034,7 @@ Fout: %@ Please store passphrase securely, you will NOT be able to access chat if you lose it. - Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken. + Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats. No comment provided by engineer. @@ -4967,6 +5047,10 @@ Fout: %@ Poolse interface No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Mogelijk is de certificaat vingerafdruk in het server adres onjuist @@ -5131,7 +5215,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Protect your chat profiles with a password! - Bescherm je chat profielen met een wachtwoord! + Bescherm je chatprofielen met een wachtwoord! No comment provided by engineer. @@ -5154,6 +5238,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push meldingen @@ -5377,6 +5465,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Remove archive? + Archief verwijderen? No comment provided by engineer. @@ -5491,7 +5580,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Restart the app to create a new chat profile - Start de app opnieuw om een nieuw chat profiel aan te maken + Start de app opnieuw om een nieuw chatprofiel aan te maken No comment provided by engineer. @@ -5559,6 +5648,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Veilig bestanden ontvangen @@ -5612,7 +5705,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save passphrase and open chat - Bewaar het wachtwoord en open je gesprekken + Bewaar het wachtwoord en open je chats No comment provided by engineer. @@ -5632,7 +5725,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save servers - Bewaar servers + Servers opslaan No comment provided by engineer. @@ -5647,6 +5740,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Save your profile? + Uw profiel opslaan? alert title @@ -5669,6 +5763,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Opgeslagen bericht message info title + + Saving %lld messages + No comment provided by engineer. + Scale Schaal @@ -5751,6 +5849,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Select chat profile + Selecteer chatprofiel No comment provided by engineer. @@ -5871,7 +5970,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Sender cancelled file transfer. Afzender heeft bestandsoverdracht geannuleerd. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6090,6 +6189,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Settings were changed. + Instellingen zijn gewijzigd. alert message @@ -6129,6 +6229,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Share profile + Profiel delen No comment provided by engineer. @@ -6298,6 +6399,7 @@ Schakel dit in in *Netwerk en servers*-instellingen. Some app settings were not migrated. + Sommige app-instellingen zijn niet gemigreerd. No comment provided by engineer. @@ -6663,7 +6765,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The servers for new connections of your current chat profile **%@**. - De servers voor nieuwe verbindingen van uw huidige chat profiel **%@**. + De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**. No comment provided by engineer. @@ -6673,6 +6775,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. The uploaded database archive will be permanently removed from the servers. + Het geüploade databasearchief wordt permanent van de servers verwijderd. No comment provided by engineer. @@ -6752,7 +6855,7 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. This setting applies to messages in your current chat profile **%@**. - Deze instelling is van toepassing op berichten in je huidige chat profiel **%@**. + Deze instelling is van toepassing op berichten in je huidige chatprofiel **%@**. No comment provided by engineer. @@ -6809,7 +6912,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. - Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chat profielen**. + Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**. No comment provided by engineer. @@ -6924,7 +7027,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Unhide chat profile - Chat profiel zichtbaar maken + Chatprofiel zichtbaar maken No comment provided by engineer. @@ -6955,7 +7058,7 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Unknown servers! Onbekende servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7172,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik .onion-hosts No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX Chat servers gebruiken? @@ -7144,6 +7251,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruikersselectie No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX Chat servers gebruiken. @@ -7377,7 +7488,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7426,7 +7537,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak You already have a chat profile with the same display name. Please choose another name. - Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam. + Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam. No comment provided by engineer. @@ -7760,6 +7871,7 @@ Verbindingsverzoek herhalen? Your chat preferences + Uw chat voorkeuren alert title @@ -7769,6 +7881,7 @@ Verbindingsverzoek herhalen? Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile. + Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel. No comment provided by engineer. @@ -7786,6 +7899,10 @@ Verbindingsverzoek herhalen? Uw contacten blijven verbonden. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Uw huidige chat database wordt VERWIJDERD en VERVANGEN door de geĆÆmporteerde. @@ -7823,6 +7940,7 @@ Verbindingsverzoek herhalen? Your profile was changed. If you save it, the updated profile will be sent to all your contacts. + Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. alert message @@ -8971,7 +9089,7 @@ laatst ontvangen bericht: %2$@ Database passphrase is required to open chat. - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. No comment provided by engineer. 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 87f19d69a3..63239de085 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -161,11 +161,31 @@ %d dni time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d godzin time interval + + %d messages not forwarded + alert title + %d min %d min @@ -1221,7 +1241,7 @@ Cannot receive file Nie można odebrać pliku - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ To jest twój jednorazowy link! Nie wysyłaj historii do nowych członków. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Nie twórz adresu @@ -2448,7 +2472,8 @@ To jest twój jednorazowy link! Download Pobierz - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ To jest twój jednorazowy link! Pobierz plik server test step + + Download files + alert action + Downloaded Pobrane @@ -2890,7 +2919,7 @@ To jest twój jednorazowy link! Error receiving file Błąd odbioru pliku - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ To jest twój jednorazowy link! Błąd pliku No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Nie odnaleziono pliku - najprawdopodobniej plik został usunięty lub anulowany. @@ -3257,11 +3291,23 @@ To jest twój jednorazowy link! Przekaż dalej chat item action + + Forward %d message(s)? + alert title + Forward and save messages Przesyłaj dalej i zapisuj wiadomości No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded Przekazane dalej @@ -3272,6 +3318,10 @@ To jest twój jednorazowy link! Przekazane dalej od No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Serwer przekazujący %@ nie mógł połączyć się z serwerem docelowym %@. Spróbuj ponownie później. @@ -3566,6 +3616,10 @@ Błąd: %2$@ Serwery ICE (po jednym na linię) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Jeśli nie możesz spotkać się osobiście, pokaż kod QR w rozmowie wideo lub udostępnij link. @@ -4265,6 +4319,10 @@ To jest twój link do grupy %@! Wysłane wiadomości No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. @@ -4560,6 +4618,10 @@ To jest twój link do grupy %@! Nic nie jest zaznaczone No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Powiadomienia @@ -4592,7 +4654,7 @@ To jest twój link do grupy %@! Ok Ok - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Wymaga włączenia VPN. Inne %@ serwery No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count Liczba PINGƓW @@ -4818,6 +4885,10 @@ Wymaga włączenia VPN. Pin ustawiony! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Hasło do wyświetlenia @@ -4967,6 +5038,10 @@ Błąd: %@ Polski interfejs No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy @@ -5154,6 +5229,10 @@ Włącz w ustawianiach *Sieć i serwery* . Serwery trasowane przez proxy No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Powiadomienia push @@ -5559,6 +5638,10 @@ Włącz w ustawianiach *Sieć i serwery* . Serwer SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Bezpiecznie otrzymuj pliki @@ -5669,6 +5752,10 @@ Włącz w ustawianiach *Sieć i serwery* . Zachowano wiadomość message info title + + Saving %lld messages + No comment provided by engineer. + Scale Skaluj @@ -5871,7 +5958,7 @@ Włącz w ustawianiach *Sieć i serwery* . Sender cancelled file transfer. Nadawca anulował transfer pliku. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania. Unknown servers! Nieznane serwery! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj hostów .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Użyć serwerów SimpleX Chat? @@ -7144,6 +7235,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wybór użytkownika No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Używanie serwerów SimpleX Chat. @@ -7377,7 +7472,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaÅŗników XFTP: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Powtórzyć prośbę połączenia? Twoje kontakty pozostaną połączone. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Twoja obecna baza danych czatu zostanie usunięta i zastąpiona zaimportowaną. 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 f2e278c9a4..73b6459780 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -161,11 +161,31 @@ %d Гней time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ч. time interval + + %d messages not forwarded + alert title + %d min %d мин @@ -1221,7 +1241,7 @@ Cannot receive file ŠŠµŠ²Š¾Š·Š¼Š¾Š¶Š½Š¾ ŠæŠ¾Š»ŃƒŃ‡ŠøŃ‚ŃŒ файл - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! ŠŠµ Š¾Ń‚ŠæŃ€Š°Š²Š»ŃŃ‚ŃŒ ŠøŃŃ‚Š¾Ń€ŠøŃŽ новым членам. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address ŠŠµ ŃŠ¾Š·Š“Š°Š²Š°Ń‚ŃŒ аГрес @@ -2448,7 +2472,8 @@ This is your own one-time link! Download Š—Š°Š³Ń€ŃƒŠ·ŠøŃ‚ŃŒ - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! Š—Š°Š³Ń€ŃƒŠ·ŠŗŠ° файла server test step + + Download files + alert action + Downloaded ŠŸŃ€ŠøŠ½ŃŃ‚Š¾ @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file ŠžŃˆŠøŠ±ŠŗŠ° при ŠæŠ¾Š»ŃƒŃ‡ŠµŠ½ŠøŠø файла - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! ŠžŃˆŠøŠ±ŠŗŠ° файла No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Файл не найГен - скорее всего, файл был уГален или отменен. @@ -3257,11 +3291,23 @@ This is your own one-time link! ŠŸŠµŃ€ŠµŃŠ»Š°Ń‚ŃŒ chat item action + + Forward %d message(s)? + alert title + Forward and save messages ŠŸŠµŃ€ŠµŃŠ»Š°Ń‚ŃŒ Šø ŃŠ¾Ń…Ń€Š°Š½ŠøŃ‚ŃŒ сообщение No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded ŠŸŠµŃ€ŠµŃŠ»Š°Š½Š¾ @@ -3272,6 +3318,10 @@ This is your own one-time link! ŠŸŠµŃ€ŠµŃŠ»Š°Š½Š¾ ŠøŠ· No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. ŠŸŠµŃ€ŠµŃŃ‹Š»Š°ŃŽŃ‰ŠøŠ¹ сервер %@ не смог ŠæŠ¾Š“ŠŗŠ»ŃŽŃ‡ŠøŃ‚ŃŒŃŃ Šŗ ŃŠµŃ€Š²ŠµŃ€Ńƒ Š½Š°Š·Š½Š°Ń‡ŠµŠ½ŠøŃ %@. ŠŸŠ¾ŠæŃ€Š¾Š±ŃƒŠ¹Ń‚Šµ позже. @@ -3566,6 +3616,10 @@ Error: %2$@ ICE серверы (оГин на строке) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Если Š’Ń‹ не можете Š²ŃŃ‚Ń€ŠµŃ‚ŠøŃ‚ŃŒŃŃ лично, покажите QR-коГ во Š²Ń€ŠµŠ¼Ń виГеозвонка или ŠæŠ¾Š“ŠµŠ»ŠøŃ‚ŠµŃŃŒ ссылкой. @@ -4265,6 +4319,10 @@ This is your link for group %@! Дообщений отправлено No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Š”Š¾Š¾Š±Ń‰ŠµŠ½ŠøŃ, файлы Šø звонки защищены **end-to-end ŃˆŠøŃ„Ń€Š¾Š²Š°Š½ŠøŠµŠ¼** с ŠæŃ€ŃŠ¼Š¾Š¹ ŃŠµŠŗŃ€ŠµŃ‚Š½Š¾ŃŃ‚ŃŒŃŽ (PFS), правГопоГобным отрицанием Šø восстановлением от взлома. @@ -4560,6 +4618,10 @@ This is your link for group %@! ŠŠøŃ‡ŠµŠ³Š¾ не выбрано No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Š£Š²ŠµŠ“Š¾Š¼Š»ŠµŠ½ŠøŃ @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok ŠžŠŗ - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requires compatible VPN. Š”Ń€ŃƒŠ³ŠøŠµ %@ серверы No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count ŠšŠ¾Š»ŠøŃ‡ŠµŃŃ‚Š²Š¾ PING @@ -4818,6 +4885,10 @@ Requires compatible VPN. КоГ Š“Š¾ŃŃ‚ŃƒŠæŠ° ŃƒŃŃ‚Š°Š½Š¾Š²Š»ŠµŠ½! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ŠŸŠ°Ń€Š¾Š»ŃŒ чтобы Ń€Š°ŃŠŗŃ€Ń‹Ń‚ŃŒ @@ -4967,6 +5038,10 @@ Error: %@ Польский интерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Возможно, хэш сертификата в аГресе сервера неверный @@ -5154,6 +5229,10 @@ Enable in *Network & servers* settings. ŠŸŃ€Š¾ŠŗŃŠøŃ€Š¾Š²Š°Š½Š½Ń‹Šµ серверы No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Доставка увеГомлений @@ -5559,6 +5638,10 @@ Enable in *Network & servers* settings. SMP сервер No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files ŠŸŠ¾Š»ŃƒŃ‡Š°Š¹Ń‚Šµ файлы безопасно @@ -5669,6 +5752,10 @@ Enable in *Network & servers* settings. Дохраненное сообщение message info title + + Saving %lld messages + No comment provided by engineer. + Scale ŠœŠ°ŃŃˆŃ‚Š°Š± @@ -5871,7 +5958,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. ŠžŃ‚ŠæŃ€Š°Š²ŠøŃ‚ŠµŠ»ŃŒ отменил ŠæŠµŃ€ŠµŠ“Š°Ń‡Ńƒ файла. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! ŠŠµŠøŠ·Š²ŠµŃŃ‚Š½Ń‹Šµ серверы! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ To connect, please ask your contact to create another connection link and check Š˜ŃŠæŠ¾Š»ŃŒŠ·Š¾Š²Š°Ń‚ŃŒ .onion хосты No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Š˜ŃŠæŠ¾Š»ŃŒŠ·Š¾Š²Š°Ń‚ŃŒ серверы преГосталенные SimpleX Chat? @@ -7144,6 +7235,10 @@ To connect, please ask your contact to create another connection link and check Выбор ŠæŠ¾Š»ŃŒŠ·Š¾Š²Š°Ń‚ŠµŠ»Ń No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Š˜ŃŠæŠ¾Š»ŃŒŠ·ŃƒŃŽŃ‚ŃŃ серверы, преГоставленные SimpleX Chat. @@ -7377,7 +7472,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Без Тора или Š’ŠŸŠ, Š’Š°Ńˆ IP аГрес Š±ŃƒŠ“ŠµŃ‚ Š“Š¾ŃŃ‚ŃƒŠæŠµŠ½ ŃŃ‚ŠøŠ¼ серверам файлов: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? Š’Š°ŃˆŠø контакты ŃŠ¾Ń…Ń€Š°Š½ŃŃ‚ŃŃ. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Š¢ŠµŠŗŃƒŃ‰ŠøŠµ Ганные Š’Š°ŃˆŠµŠ³Š¾ чата Š±ŃƒŠ“ет Š£Š”ŠŠ›Š•ŠŠ« Šø Š—ŠŠœŠ•ŠŠ•ŠŠ« импортированными. 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 870c01af8f..195ded259c 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -151,11 +151,31 @@ %d วัน time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d ąøŠąø±ą¹ˆąø§ą¹‚ąø”ąø‡ time interval + + %d messages not forwarded + alert title + %d min %d นาที @@ -1138,7 +1158,7 @@ Cannot receive file ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąø£ąø±ąøšą¹„ąøŸąø„ą¹Œą¹„ąø”ą¹‰ - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2251,6 +2271,10 @@ This is your own one-time link! Do not send history to new members. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address ąø­ąø¢ą¹ˆąø²ąøŖąø£ą¹‰ąø²ąø‡ąø—ąøµą¹ˆąø­ąø¢ąø¹ą¹ˆ @@ -2273,7 +2297,8 @@ This is your own one-time link! Download - chat item action + alert button + chat item action Download errors @@ -2288,6 +2313,10 @@ This is your own one-time link! ąø”ąø²ąø§ąø™ą¹Œą¹‚ąø«ąø„ąø”ą¹„ąøŸąø„ą¹Œ server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2689,7 +2718,7 @@ This is your own one-time link! Error receiving file ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ąø£ąø±ąøšą¹„ąøŸąø„ą¹Œ - No comment provided by engineer. + alert title Error reconnecting server @@ -2907,6 +2936,11 @@ This is your own one-time link! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3033,10 +3067,22 @@ This is your own one-time link! Forward chat item action + + Forward %d message(s)? + alert title + Forward and save messages No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded No comment provided by engineer. @@ -3045,6 +3091,10 @@ This is your own one-time link! Forwarded from No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3323,6 +3373,10 @@ Error: %2$@ ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ ICE (ąø«ąø™ąø¶ą¹ˆąø‡ą¹€ąø„ąø£ąø·ą¹ˆąø­ąø‡ąø•ą¹ˆąø­ąøŖąø²ąø¢) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. ąø«ąø²ąøąø„ąøøąø“ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąøžąøšąøąø±ąø™ą¹ƒąø™ąøŠąøµąø§ąø“ąø•ąøˆąø£ąø“ąø‡ą¹„ąø”ą¹‰ ą¹ƒąø«ą¹‰ą¹ąøŖąø”ąø‡ąø„ąø“ąø§ąø­ąø²ąø£ą¹Œą¹‚ąø„ą¹‰ąø”ą¹ƒąø™ąø§ąø“ąø”ąøµą¹‚ąø­ąø„ąø­ąø„ ąø«ąø£ąø·ąø­ą¹ąøŠąø£ą¹Œąø„ąø“ąø‡ąøą¹Œ @@ -3975,6 +4029,10 @@ This is your link for group %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. @@ -4246,6 +4304,10 @@ This is your link for group %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications ąøąø²ąø£ą¹ąøˆą¹‰ąø‡ą¹€ąø•ąø·ąø­ąø™ @@ -4277,7 +4339,7 @@ This is your link for group %@! Ok ตกคง - No comment provided by engineer. + alert button Old database @@ -4454,6 +4516,11 @@ Requires compatible VPN. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count ąøˆą¹ąø²ąø™ąø§ąø™ PING @@ -4489,6 +4556,10 @@ Requires compatible VPN. ąø•ąø±ą¹‰ąø‡ąø£ąø«ąø±ąøŖąøœą¹ˆąø²ąø™ą¹€ąø£ąøµąø¢ąøšąø£ą¹‰ąø­ąø¢ą¹ąø„ą¹‰ąø§! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ąø£ąø«ąø±ąøŖąøœą¹ˆąø²ąø™ąø—ąøµą¹ˆąøˆąø°ą¹ąøŖąø”ąø‡ @@ -4625,6 +4696,10 @@ Error: %@ ąø­ąø“ąø™ą¹€ąø•ąø­ąø£ą¹Œą¹€ąøŸąø‹ąø ąø²ąø©ąø²ą¹‚ąø›ą¹ąø„ąø™ąø”ą¹Œ No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect ąø­ąø²ąøˆą¹€ąø›ą¹‡ąø™ą¹„ąø›ą¹„ąø”ą¹‰ąø§ą¹ˆąø²ąø„ąø²ąø¢ąø™ąø“ą¹‰ąø§ąø”ąø·ąø­ąø‚ąø­ąø‡ certificate ą¹ƒąø™ąø—ąøµą¹ˆąø­ąø¢ąø¹ą¹ˆą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œą¹„ąø”ą¹ˆąø–ąø¹ąøąø•ą¹‰ąø­ąø‡ @@ -4798,6 +4873,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications ąøąø²ąø£ą¹ąøˆą¹‰ąø‡ą¹€ąø•ąø·ąø­ąø™ą¹ąøšąøšąø—ąø±ąø™ąø—ąøµ @@ -5173,6 +5252,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -5277,6 +5360,10 @@ Enable in *Network & servers* settings. Saved message message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5464,7 +5551,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. ąøœąø¹ą¹‰ąøŖą¹ˆąø‡ąø¢ąøą¹€ąø„ąø“ąøąøąø²ąø£ą¹‚ąø­ąø™ą¹„ąøŸąø„ą¹Œ - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6468,7 +6555,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6573,6 +6660,10 @@ To connect, please ask your contact to create another connection link and check ą¹ƒąøŠą¹‰ą¹‚ąø®ąøŖąø•ą¹Œ .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? ą¹ƒąøŠą¹‰ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ SimpleX Chat ไหด? @@ -6639,6 +6730,10 @@ To connect, please ask your contact to create another connection link and check User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. ąøąø³ąø„ąø±ąø‡ą¹ƒąøŠą¹‰ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ SimpleX Chat อยู่ @@ -6851,7 +6946,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7232,6 +7327,10 @@ Repeat connection request? ąøœąø¹ą¹‰ąø•ąø“ąø”ąø•ą¹ˆąø­ąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø¢ąø±ąø‡ąø„ąø‡ą¹€ąøŠąø·ą¹ˆąø­ąø”ąø•ą¹ˆąø­ąø­ąø¢ąø¹ą¹ˆ No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. ąøąø²ąø™ąø‚ą¹‰ąø­ąø”ąø¹ąø„ą¹ąøŠąø—ąø›ąø±ąøˆąøˆąøøąøšąø±ąø™ąø‚ąø­ąø‡ąø„ąøøąø“ąøˆąø°ąø–ąø¹ąøąø„ąøšą¹ąø„ąø°ą¹ąø—ąø™ąø—ąøµą¹ˆąø”ą¹‰ąø§ąø¢ąøąø²ąø™ąø‚ą¹‰ąø­ąø”ąø¹ąø„ąø—ąøµą¹ˆąø™ąø³ą¹€ąø‚ą¹‰ąø² 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 3e1199666f..e65fab3814 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -161,11 +161,31 @@ %d gün time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d saat time interval + + %d messages not forwarded + alert title + %d min %d dakika @@ -1196,7 +1216,7 @@ Cannot receive file Dosya alınamıyor - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2358,6 +2378,10 @@ Bu senin kendi tek kullanımlık bağlantın! Yeni üyelere geƧmişi gƶnderme. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address Adres oluşturma @@ -2381,7 +2405,8 @@ Bu senin kendi tek kullanımlık bağlantın! Download İndir - chat item action + alert button + chat item action Download errors @@ -2397,6 +2422,10 @@ Bu senin kendi tek kullanımlık bağlantın! Dosya indir server test step + + Download files + alert action + Downloaded No comment provided by engineer. @@ -2817,7 +2846,7 @@ Bu senin kendi tek kullanımlık bağlantın! Error receiving file Dosya alınırken sorun oluştu - No comment provided by engineer. + alert title Error reconnecting server @@ -3043,6 +3072,11 @@ Bu senin kendi tek kullanımlık bağlantın! File error No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. file error text @@ -3174,11 +3208,23 @@ Bu senin kendi tek kullanımlık bağlantın! İlet chat item action + + Forward %d message(s)? + alert title + Forward and save messages Mesajları ilet ve kaydet No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded İletildi @@ -3189,6 +3235,10 @@ Bu senin kendi tek kullanımlık bağlantın! Şuradan iletildi No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. No comment provided by engineer. @@ -3478,6 +3528,10 @@ Hata: %2$@ ICE sunucuları (her satıra bir tane) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Eğer onunla buluşamıyorsan gƶrüntülü aramada QR kod gƶster veya bağlantığı paylaş. @@ -4161,6 +4215,10 @@ Bu senin grup iƧin bağlantın %@! Messages sent No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. Mesajlar, dosyalar ve aramalar **uƧtan uca şifreleme** ile mükemmel ileri gizlilik, inkar ve izinsiz giriş kurtarma ile korunur. @@ -4451,6 +4509,10 @@ Bu senin grup iƧin bağlantın %@! Nothing selected No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Bildirimler @@ -4483,7 +4545,7 @@ Bu senin grup iƧin bağlantın %@! Ok Tamam - No comment provided by engineer. + alert button Old database @@ -4671,6 +4733,11 @@ VPN'nin etkinleştirilmesi gerekir. Other %@ servers No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING sayısı @@ -4706,6 +4773,10 @@ VPN'nin etkinleştirilmesi gerekir. Şifre ayarlandı! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show Gƶsterilecek şifre @@ -4850,6 +4921,10 @@ Hata: %@ LehƧe arayüz No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil @@ -5032,6 +5107,10 @@ Enable in *Network & servers* settings. Proxied servers No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Anında bildirimler @@ -5420,6 +5499,10 @@ Enable in *Network & servers* settings. SMP server No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Dosyaları güvenle alın @@ -5529,6 +5612,10 @@ Enable in *Network & servers* settings. Kaydedilmiş mesaj message info title + + Saving %lld messages + No comment provided by engineer. + Scale No comment provided by engineer. @@ -5723,7 +5810,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. Gƶnderici dosya gƶnderimini iptal etti. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6770,7 +6857,7 @@ Bu ƶzellik etkinleştirilmeden ƶnce kimlik doğrulamayı tamamlamanız istenec Unknown servers! Bilinmeyen sunucular! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6880,6 +6967,10 @@ Bağlanmak iƧin lütfen kişinizden başka bir bağlantı oluşturmasını iste .onion ana bilgisayarlarını kullan No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? SimpleX Chat sunucuları kullanılsın mı? @@ -6953,6 +7044,10 @@ Bağlanmak iƧin lütfen kişinizden başka bir bağlantı oluşturmasını iste User selection No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. SimpleX Chat sunucuları kullanılıyor. @@ -7184,7 +7279,7 @@ Bağlanmak iƧin lütfen kişinizden başka bir bağlantı oluşturmasını iste Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından gƶrülebilir: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7584,6 +7679,10 @@ Bağlantı isteği tekrarlansın mı? Kişileriniz bağlı kalacaktır. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Mevcut sohbet veritabanınız SİLİNECEK ve iƧe aktarılan veritabanıyla DEĞİŞTİRİLECEKTİR. 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 d371b29109..b2dcccd08d 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -161,11 +161,31 @@ %d Гнів time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d гоГин time interval + + %d messages not forwarded + alert title + %d min %d хв @@ -1221,7 +1241,7 @@ Cannot receive file ŠŠµ Š²Š“Š°Ń”Ń‚ŃŒŃŃ отримати файл - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! ŠŠµ наГсилайте Ń–ŃŃ‚Š¾Ń€Ń–ŃŽ новим ŠŗŠ¾Ń€ŠøŃŃ‚ŃƒŠ²Š°Ń‡Š°Š¼. No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address ŠŠµ ŃŃ‚Š²Š¾Ń€ŃŽŠ²Š°Ń‚Šø Š°Š“Ń€ŠµŃŃƒ @@ -2448,7 +2472,8 @@ This is your own one-time link! Download Завантажити - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! Завантажити файл server test step + + Download files + alert action + Downloaded Завантажено @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file Помилка Š¾Ń‚Ń€ŠøŠ¼Š°Š½Š½Ń Ń„Š°Š¹Š»Ńƒ - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! Помилка Ń„Š°Š¹Š»Ńƒ No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. Файл не знайГено - Š½Š°Š¹Ń–Š¼Š¾Š²Ń–Ń€Š½Ń–ŃˆŠµ, файл було виГалено або скасовано. @@ -3257,11 +3291,23 @@ This is your own one-time link! ŠŸŠµŃ€ŠµŃŠøŠ»Š°Š½Š½Ń chat item action + + Forward %d message(s)? + alert title + Forward and save messages ŠŸŠµŃ€ŠµŃŠøŠ»Š°Š½Š½Ń та Š·Š±ŠµŃ€ŠµŠ¶ŠµŠ½Š½Ń ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½ŃŒ No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded ŠŸŠµŃ€ŠµŃŠ»Š°Š½Š¾ @@ -3272,6 +3318,10 @@ This is your own one-time link! ŠŸŠµŃ€ŠµŃŠ»Š°Š½Š¾ Š· No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. Š”ŠµŃ€Š²ŠµŃ€Ńƒ переаГресації %@ не Š²Š“Š°Š»Š¾ŃŃ Š·'Ń”Š“Š½Š°Ń‚ŠøŃŃ Š· сервером ŠæŃ€ŠøŠ·Š½Š°Ń‡ŠµŠ½Š½Ń %@. Š”ŠæŃ€Š¾Š±ŃƒŠ¹Ń‚Šµ ŠæŃ–Š·Š½Ń–ŃˆŠµ. @@ -3566,6 +3616,10 @@ Error: %2$@ Дервери ICE (по оГному на Š»Ń–Š½Ń–ŃŽ) No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. Якщо ви не можете Š·ŃƒŃŃ‚Ń€Ń–Ń‚ŠøŃŃ особисто, ŠæŠ¾ŠŗŠ°Š¶Ń–Ń‚ŃŒ QR-коГ у Š²Ń–Š“ŠµŠ¾Š“Š·Š²Ń–Š½ŠŗŃƒ або ŠæŠ¾Š“Ń–Š»Ń–Ń‚ŃŒŃŃ ŠæŠ¾ŃŠøŠ»Š°Š½Š½ŃŠ¼. @@ -4265,6 +4319,10 @@ This is your link for group %@! ŠŠ°Š“Ń–ŃŠ»Š°Š½Ń– ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. ŠŸŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń, файли та Гзвінки захищені **наскрізним ŃˆŠøŃ„Ń€ŃƒŠ²Š°Š½Š½ŃŠ¼** Š· Ń–Š“ŠµŠ°Š»ŃŒŠ½Š¾ŃŽ ŃŠµŠŗŃ€ŠµŃ‚Š½Ń–ŃŃ‚ŃŽ переаГресації, Š²Ń–Š“Š¼Š¾Š²Š¾ŃŽ та Š²Ń–Š“Š½Š¾Š²Š»ŠµŠ½Š½ŃŠ¼ ŠæŃ–ŃŠ»Ń злому. @@ -4560,6 +4618,10 @@ This is your link for group %@! ŠŃ–Ń‡Š¾Š³Š¾ не вибрано No comment provided by engineer. + + Nothing to forward! + alert title + Notifications Š”ŠæŠ¾Š²Ń–Ń‰ŠµŠ½Š½Ń @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok ГаразГ - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requires compatible VPN. Š†Š½ŃˆŃ– сервери %@ No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count ŠšŃ–Š»ŃŒŠŗŃ–ŃŃ‚ŃŒ PING @@ -4818,6 +4885,10 @@ Requires compatible VPN. ŠŸŠ°Ń€Š¾Š»ŃŒ встановлено! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ŠŸŠ¾ŠŗŠ°Š·Š°Ń‚Šø ŠæŠ°Ń€Š¾Š»ŃŒ @@ -4967,6 +5038,10 @@ Error: %@ Польський інтерфейс No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect Можливо, в аГресі сервера Š½ŠµŠæŃ€Š°Š²ŠøŠ»ŃŒŠ½Š¾ вказано віГбиток сертифіката @@ -5154,6 +5229,10 @@ Enable in *Network & servers* settings. ŠŸŃ€Š¾ŠŗŃŃ–-сервери No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications Push-ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń @@ -5559,6 +5638,10 @@ Enable in *Network & servers* settings. Дервер SMP No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files Безпечне Š¾Ń‚Ń€ŠøŠ¼Š°Š½Š½Ń файлів @@ -5669,6 +5752,10 @@ Enable in *Network & servers* settings. Збережене ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń message info title + + Saving %lld messages + No comment provided by engineer. + Scale ŠœŠ°ŃŃˆŃ‚Š°Š± @@ -5871,7 +5958,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. ВіГправник скасував ŠæŠµŃ€ŠµŠ“Š°Ń‡Ńƒ Ń„Š°Š¹Š»Ńƒ. - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! ŠŠµŠ²Ń–Š“Š¾Š¼Ń– сервери! - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ To connect, please ask your contact to create another connection link and check Š’ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š¾Š²ŃƒŠ¹Ń‚Šµ хости .onion No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? Š’ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š¾Š²ŃƒŠ²Š°Ń‚Šø сервери SimpleX Chat? @@ -7144,6 +7235,10 @@ To connect, please ask your contact to create another connection link and check Вибір ŠŗŠ¾Ń€ŠøŃŃ‚ŃƒŠ²Š°Ń‡Š° No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. Š’ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š°Š½Š½Ń серверів SimpleX Chat. @@ -7377,7 +7472,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. Без Tor або VPN ваша IP-аГреса буГе Š²ŠøŠ“ŠøŠ¼Š¾ŃŽ Š“Š»Ń цих XFTP-Ń€ŠµŃ‚Ń€Š°Š½ŃŠ»ŃŃ‚Š¾Ń€Ń–Š²: %@. - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? Š’Š°ŃˆŃ– контакти Š·Š°Š»ŠøŃˆŠ°Ń‚ŃŒŃŃ на зв'ŃŠ·ŠŗŃƒ. No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. Š’Š°ŃˆŠ° поточна база Ганих Ń‡Š°Ń‚Ńƒ буГе Š’Š˜Š”ŠŠ›Š•ŠŠ і Š—ŠŠœŠ†ŠŠ•ŠŠ Ń–Š¼ŠæŠ¾Ń€Ń‚Š¾Š²Š°Š½Š¾ŃŽ. 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 b7abefd465..4123cda02c 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 @@ -161,11 +161,31 @@ %d 天 time interval + + %d file(s) are still being downloaded. + forward confirmation reason + + + %d file(s) failed to download. + forward confirmation reason + + + %d file(s) were deleted. + forward confirmation reason + + + %d file(s) were not downloaded. + forward confirmation reason + %d hours %d å°ę—¶ time interval + + %d messages not forwarded + alert title + %d min %d 分钟 @@ -1221,7 +1241,7 @@ Cannot receive file ę— ę³•ęŽ„ę”¶ę–‡ä»¶ - No comment provided by engineer. + alert title Capacity exceeded - recipient did not receive previously sent messages. @@ -2425,6 +2445,10 @@ This is your own one-time link! äøē»™ę–°ęˆå‘˜å‘é€åŽ†å²ę¶ˆęÆć€‚ No comment provided by engineer. + + Do not use credentials with proxy. + No comment provided by engineer. + Don't create address äøåˆ›å»ŗåœ°å€ @@ -2448,7 +2472,8 @@ This is your own one-time link! Download äø‹č½½ - chat item action + alert button + chat item action Download errors @@ -2465,6 +2490,10 @@ This is your own one-time link! 下载文件 server test step + + Download files + alert action + Downloaded 已下载 @@ -2890,7 +2919,7 @@ This is your own one-time link! Error receiving file ęŽ„ę”¶ę–‡ä»¶é”™čÆÆ - No comment provided by engineer. + alert title Error reconnecting server @@ -3122,6 +3151,11 @@ This is your own one-time link! 文件错误 No comment provided by engineer. + + File errors: +%@ + alert message + File not found - most likely file was deleted or cancelled. ę‰¾äøåˆ°ę–‡ä»¶ - å¾ˆåÆčƒ½ę–‡ä»¶å·²č¢«åˆ é™¤ęˆ–å–ę¶ˆć€‚ @@ -3257,11 +3291,23 @@ This is your own one-time link! č½¬å‘ chat item action + + Forward %d message(s)? + alert title + Forward and save messages č½¬å‘å¹¶äæå­˜ę¶ˆęÆ No comment provided by engineer. + + Forward messages + alert action + + + Forward messages without files? + alert message + Forwarded å·²č½¬å‘ @@ -3272,6 +3318,10 @@ This is your own one-time link! č½¬å‘č‡Ŗ No comment provided by engineer. + + Forwarding %lld messages + No comment provided by engineer. + Forwarding server %@ failed to connect to destination server %@. Please try later. č½¬å‘ęœåŠ”å™Ø %@ ę— ę³•čæžęŽ„åˆ°ē›®ę ‡ęœåŠ”å™Ø %@ć€‚čÆ·ēØåŽå°čÆ•ć€‚ @@ -3566,6 +3616,10 @@ Error: %2$@ ICE ęœåŠ”å™Øļ¼ˆęÆč”Œäø€äøŖļ¼‰ No comment provided by engineer. + + IP address + No comment provided by engineer. + If you can't meet in person, show QR code in a video call, or share the link. å¦‚ęžœę‚Øäøčƒ½äŗ²č‡Ŗč§é¢ļ¼ŒåÆä»„åœØč§†é¢‘é€ščÆäø­å±•ē¤ŗäŗŒē»“ē ļ¼Œęˆ–åˆ†äŗ«é“¾ęŽ„ć€‚ @@ -4265,6 +4319,10 @@ This is your link for group %@! å·²å‘é€ēš„ę¶ˆęÆ No comment provided by engineer. + + Messages were deleted after you selected them. + alert message + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. ę¶ˆęÆć€ę–‡ä»¶å’Œé€ščÆå—åˆ° **ē«Æåˆ°ē«ÆåŠ åÆ†** ēš„äæęŠ¤ļ¼Œå…·ęœ‰å®Œå…Øę­£å‘äæåÆ†ć€å¦č®¤å’Œé—Æå…„ę¢å¤ć€‚ @@ -4560,6 +4618,10 @@ This is your link for group %@! ęœŖé€‰äø­ä»»ä½•å†…å®¹ No comment provided by engineer. + + Nothing to forward! + alert title + Notifications é€šēŸ„ @@ -4592,7 +4654,7 @@ This is your link for group %@! Ok å„½ēš„ - No comment provided by engineer. + alert button Old database @@ -4783,6 +4845,11 @@ Requires compatible VPN. 其他 %@ ęœåŠ”å™Ø No comment provided by engineer. + + Other file errors: +%@ + alert message + PING count PING ꬔꕰ @@ -4818,6 +4885,10 @@ Requires compatible VPN. 密码已设置! No comment provided by engineer. + + Password + No comment provided by engineer. + Password to show ę˜¾ē¤ŗåÆ†ē  @@ -4967,6 +5038,10 @@ Error: %@ ę³¢å…°čÆ­ē•Œé¢ No comment provided by engineer. + + Port + No comment provided by engineer. + Possibly, certificate fingerprint in server address is incorrect ęœåŠ”å™Øåœ°å€äø­ēš„čÆä¹¦ęŒ‡ēŗ¹åÆčƒ½äøę­£ē”® @@ -5154,6 +5229,10 @@ Enable in *Network & servers* settings. ä»£ē†ęœåŠ”å™Ø No comment provided by engineer. + + Proxy requires password + No comment provided by engineer. + Push notifications ęŽØé€é€šēŸ„ @@ -5559,6 +5638,10 @@ Enable in *Network & servers* settings. SMP ęœåŠ”å™Ø No comment provided by engineer. + + SOCKS proxy + No comment provided by engineer. + Safely receive files å®‰å…ØęŽ„ę”¶ę–‡ä»¶ @@ -5669,6 +5752,10 @@ Enable in *Network & servers* settings. å·²äæå­˜ēš„ę¶ˆęÆ message info title + + Saving %lld messages + No comment provided by engineer. + Scale 规樔 @@ -5871,7 +5958,7 @@ Enable in *Network & servers* settings. Sender cancelled file transfer. å‘é€äŗŗå·²å–ę¶ˆę–‡ä»¶ä¼ č¾“ć€‚ - No comment provided by engineer. + alert message Sender may have deleted the connection request. @@ -6955,7 +7042,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown servers! ęœŖēŸ„ęœåŠ”å™Øļ¼ - No comment provided by engineer. + alert title Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -7069,6 +7156,10 @@ To connect, please ask your contact to create another connection link and check 使用 .onion 主机 No comment provided by engineer. + + Use SOCKS proxy + No comment provided by engineer. + Use SimpleX Chat servers? 使用 SimpleX Chat ęœåŠ”å™Øļ¼Ÿ @@ -7144,6 +7235,10 @@ To connect, please ask your contact to create another connection link and check ē”Øęˆ·é€‰ę‹© No comment provided by engineer. + + Username + No comment provided by engineer. + Using SimpleX Chat servers. 使用 SimpleX Chat ęœåŠ”å™Øć€‚ @@ -7377,7 +7472,7 @@ To connect, please ask your contact to create another connection link and check Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ä»„äø‹ XFTP äø­ē»§åÆč§ļ¼š%@怂 - No comment provided by engineer. + alert message Wrong database passphrase @@ -7786,6 +7881,10 @@ Repeat connection request? äøŽę‚Øēš„č”ē³»äŗŗäæęŒčæžęŽ„ć€‚ No comment provided by engineer. + + Your credentials may be sent unencrypted. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. ę‚Øå½“å‰ēš„čŠå¤©ę•°ę®åŗ“å°†č¢«åˆ é™¤å¹¶ę›æę¢äøŗåÆ¼å…„ēš„ę•°ę®åŗ“ć€‚ diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings index 8ce4317a9b..13638c4186 100644 --- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings @@ -17,7 +17,7 @@ "Comment" = "HozzĆ”szólĆ”s"; /* No comment provided by engineer. */ -"Currently maximum supported file size is %@." = "Jelenleg a maximĆ”lis tĆ”mogatott fĆ”jlmĆ©ret %@."; +"Currently maximum supported file size is %@." = "Jelenleg a maximĆ”lisan tĆ”mogatott fĆ”jlmĆ©ret: %@."; /* No comment provided by engineer. */ "Database downgrade required" = "AdatbĆ”zis visszafejlesztĆ©se szüksĆ©ges"; @@ -107,5 +107,5 @@ "Wrong database passphrase" = "HibĆ”s adatbĆ”zis jelmondat"; /* No comment provided by engineer. */ -"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztĆ”st az AdatvĆ©delem Ć©s biztonsĆ”g / SimpleX zĆ”r menüben engedĆ©lyezheti."; +"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztĆ”st az AdatvĆ©delem Ć©s biztonsĆ”g / SimpleX-zĆ”r menüben engedĆ©lyezheti."; diff --git a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings index 8412c88ea6..e5d2487b54 100644 --- a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings @@ -32,7 +32,7 @@ "Database passphrase is different from saved in the keychain." = "Het wachtwoord van de database verschilt van het wachtwoord die in de keychain is opgeslagen."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je gesprekken te openen."; +"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je chats te openen."; /* No comment provided by engineer. */ "Database upgrade required" = "Database upgrade vereist"; diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b0b838eef8..9b96b3dee6 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -2117,6 +2117,10 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Libraries", + ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2168,6 +2172,10 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Libraries", + ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index b044c8bc1e..1468292b1e 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -709,7 +709,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "ŠŃŠ¼Š° Š“Š¾ŃŃ‚ŃŠŠæ Го Keychain за запазване на паролата за базата Ганни"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Š¤Š°Š¹Š»ŃŠŃ‚ не може Га бъГе ŠæŠ¾Š»ŃƒŃ‡ŠµŠ½"; /* No comment provided by engineer. */ @@ -1395,7 +1395,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Понижи Š²ŠµŃ€ŃŠøŃŃ‚а Šø отвори чата"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Š˜Š·Ń‚ŠµŠ³Š»Šø"; /* No comment provided by engineer. */ @@ -1683,7 +1684,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Š“Ń€ŠµŃˆŠŗŠ° при Š¾Ń‚Š²Š°Ń€ŃŠ½Šµ на чата"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Š“Ń€ŠµŃˆŠŗŠ° при ŠæŠ¾Š»ŃƒŃ‡Š°Š²Š°Š½Šµ на файл"; /* No comment provided by engineer. */ @@ -2685,7 +2686,7 @@ /* feature offered item */ "offered %@: %@" = "преГлага %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "ŠžŠŗ"; /* No comment provided by engineer. */ @@ -3365,7 +3366,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Š˜Š·ŠæŃ€Š°Ń‰Š°Š½Šµ Го послеГните 100 ŃŃŠŠ¾Š±Ń‰ŠµŠ½ŠøŃ на нови членове."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "ŠŸŠ¾Š“Š°Ń‚ŠµŠ»ŃŃ‚ отмени ŠæŃ€ŠµŃ…Š²ŃŠŃ€Š»ŃŠ½ŠµŃ‚Š¾ на файла."; /* No comment provided by engineer. */ diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index f46da2d120..e6d2018699 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -571,7 +571,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Nelze zĆ­skat přístup ke klƭčence pro uloženĆ­ hesla databĆ”ze"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nelze přijmout soubor"; /* No comment provided by engineer. */ @@ -1380,7 +1380,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "Chyba načƭtĆ”nĆ­ %@ serverÅÆ"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Chyba při příjmu souboru"; /* No comment provided by engineer. */ @@ -2187,7 +2187,7 @@ /* feature offered item */ "offered %@: %@" = "nabĆ­dl %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -2735,7 +2735,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Odeslat je z galerie nebo vlastnĆ­ klĆ”vesnice."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "OdesĆ­latel zruÅ”il přenos souboru."; /* No comment provided by engineer. */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index b69b9fa370..93e80091f7 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ und %lld Mitglieder"; @@ -649,6 +652,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Bilder automatisch akzeptieren"; +/* alert title */ +"Auto-accept settings" = "Einstellungen automatisch akzeptieren"; + /* No comment provided by engineer. */ "Back" = "Zurück"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Die Nachricht kann nicht weitergeleitet werden"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Datei kann nicht empfangen werden"; /* snd error text */ @@ -890,6 +896,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Chat-PrƤferenzen"; +/* alert message */ +"Chat preferences were changed." = "Die Chat-PrƤferenzen wurden geƤndert."; + /* No comment provided by engineer. */ "Chat theme" = "Chat-Design"; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Core Version: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Ecke"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Richtiger Name für %@?"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Datenbank herabstufen und den Chat ƶffnen"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Herunterladen"; /* No comment provided by engineer. */ @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der EmpfƤngeradresse"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Fehler beim Wechseln des Verbindungs-Profils"; + /* No comment provided by engineer. */ "Error changing role" = "Fehler beim Ƅndern der Rolle"; /* No comment provided by engineer. */ "Error changing setting" = "Fehler beim Ƅndern der Einstellung"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Fehler beim Wechseln zum Inkognito-Profil!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Fehler beim Verbinden mit dem Weiterleitungsserver %@. Bitte versuchen Sie es spƤter erneut."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Fehler beim Laden von %@ Servern"; /* No comment provided by engineer. */ -"Error opening chat" = "Fehler beim Ɩffnen des Chats"; +"Error migrating settings" = "Fehler beim Migrieren der Einstellungen"; /* No comment provided by engineer. */ +"Error opening chat" = "Fehler beim Ɩffnen des Chats"; + +/* alert title */ "Error receiving file" = "Fehler beim Empfangen der Datei"; /* No comment provided by engineer. */ @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Fehler beim Beenden des Chats"; +/* No comment provided by engineer. */ +"Error switching profile" = "Fehler beim Wechseln des Profils"; + /* No comment provided by engineer. */ "Error switching profile!" = "Fehler beim Umschalten des Profils!"; @@ -2815,6 +2840,9 @@ /* No comment provided by engineer. */ "Message servers" = "Nachrichten-Server"; +/* No comment provided by engineer. */ +"Message shape" = "Nachrichten-Form"; + /* No comment provided by engineer. */ "Message source remains private." = "Die Nachrichtenquelle bleibt privat."; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "angeboten %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Entfernen"; +/* No comment provided by engineer. */ +"Remove archive?" = "Archiv entfernen?"; + /* No comment provided by engineer. */ "Remove image" = "Bild entfernen"; @@ -3752,6 +3783,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Begrüßungsmeldung speichern?"; +/* alert title */ +"Save your profile?" = "Ihr Profil speichern?"; + /* No comment provided by engineer. */ "saved" = "abgespeichert"; @@ -3833,6 +3867,9 @@ /* chat item action */ "Select" = "AuswƤhlen"; +/* No comment provided by engineer. */ +"Select chat profile" = "Chat-Profil auswƤhlen"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld ausgewƤhlt"; @@ -3905,7 +3942,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Der Absender hat die Dateiübertragung abgebrochen."; /* No comment provided by engineer. */ @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Einstellungen"; +/* alert message */ +"Settings were changed." = "Die Einstellungen wurden geƤndert."; + /* No comment provided by engineer. */ "Shape profile images" = "Form der Profil-Bilder"; @@ -4067,6 +4107,9 @@ /* No comment provided by engineer. */ "Share link" = "Link teilen"; +/* No comment provided by engineer. */ +"Share profile" = "Profil teilen"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Teilen Sie diesen Einmal-Einladungslink"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Weich"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Einige App-Einstellungen wurden nicht migriert."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Einzelne Datei(en) wurde(n) nicht exportiert:"; @@ -4268,6 +4314,9 @@ /* No comment provided by engineer. */ "System authentication" = "System-Authentifizierung"; +/* No comment provided by engineer. */ +"Tail" = "Sprechblase"; + /* No comment provided by engineer. */ "Take picture" = "Machen Sie ein Foto"; @@ -4397,6 +4446,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt."; + /* No comment provided by engineer. */ "Themes" = "Design"; @@ -4574,7 +4626,7 @@ /* No comment provided by engineer. */ "unknown servers" = "Unbekannte Relais"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Unbekannte Server!"; /* No comment provided by engineer. */ @@ -4880,7 +4932,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für Datei-Server sichtbar sein."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@."; /* No comment provided by engineer. */ @@ -5141,9 +5193,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Ihre Chat-Datenbank ist nicht verschlüsselt. Bitte legen Sie ein Passwort fest, um sie zu schützen."; +/* alert title */ +"Your chat preferences" = "Ihre Chat-PrƤferenzen"; + /* No comment provided by engineer. */ "Your chat profiles" = "Ihre Chat-Profile"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Ihre Verbindung wurde auf %@ verschoben. WƤhrend Sie auf das Profil weitergeleitet wurden trat aber ein unerwarteter Fehler auf."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ihr Kontakt hat eine Datei gesendet, die größer ist als die derzeit unterstützte maximale Größe (%@)."; @@ -5177,6 +5235,9 @@ /* 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."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ihr Profil wurde geƤndert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem GerƤt gespeichert."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index ed5efe14ab..debac7711e 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "No se puede reenviar el mensaje"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "No se puede recibir el archivo"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Degradar y abrir Chat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Descargar"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Error al abrir chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Error al recibir archivo"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "ofrecido %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Se envĆ­an hasta 100 mensajes mĆ”s recientes a los miembros nuevos."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "con servidores desconocidos"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Ā”Servidores desconocidos!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Sin Tor o VPN, tu dirección IP serĆ” visible para los servidores de archivos."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sin Tor o VPN, tu dirección IP serĆ” visible para estos servidores XFTP: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index ee7056709e..2bf17ae40a 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -556,7 +556,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Ei pƤƤsyƤ avainnippuun tietokannan salasanan tallentamiseksi"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Tiedostoa ei voi vastaanottaa"; /* No comment provided by engineer. */ @@ -1356,7 +1356,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "Virhe %@-palvelimien lataamisessa"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Virhe tiedoston vastaanottamisessa"; /* No comment provided by engineer. */ @@ -2160,7 +2160,7 @@ /* feature offered item */ "offered %@: %@" = "tarjottu %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -2699,7 +2699,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "LƤhetƤ ne galleriasta tai mukautetuista nƤppƤimistƶistƤ."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "LƤhettƤjƤ peruutti tiedoston siirron."; /* No comment provided by engineer. */ diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index e1c4c20461..0f2c4dfbc5 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Impossible de transfĆ©rer le message"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Impossible de recevoir le fichier"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "RĆ©trograder et ouvrir le chat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "TĆ©lĆ©charger"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Erreur lors de l'ouverture du chat"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Erreur lors de la rĆ©ception du fichier"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "propose %1$@ : %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Envoi des 100 derniers messages aux nouveaux membres."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "L'expĆ©diteur a annulĆ© le transfert de fichiers."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "relais inconnus"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Serveurs inconnus !"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Sans Tor ou un VPN, votre adresse IP sera visible par les serveurs de fichiers."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 54c0b6bd50..2c8d1ebebd 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -65,10 +65,10 @@ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[CsillagozĆ”s a GitHubon](https://github.com/simplex-chat/simplex-chat)"; /* No comment provided by engineer. */ -"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzĆ”adĆ”sa**: Ćŗj meghĆ­vó hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz."; +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzĆ”adĆ”sa**: Ćŗj meghĆ­vó-hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz."; /* No comment provided by engineer. */ -"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzĆ”adĆ”sa**: egyszer hasznĆ”latos QR-kód vagy hivatkozĆ”s lĆ©trehozĆ”sa az ismerőse szĆ”mĆ”ra."; +"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzĆ”adĆ”sa**: egyszer hasznĆ”lható QR-kód vagy hivatkozĆ”s lĆ©trehozĆ”sa az ismerőse szĆ”mĆ”ra."; /* No comment provided by engineer. */ "**Create group**: to create a new group." = "**Csoport lĆ©trehozĆ”sa**: Ćŗj csoport lĆ©trehozĆ”sĆ”hoz."; @@ -80,16 +80,16 @@ "**e2e encrypted** video call" = "**e2e titkosĆ­tott** videóhĆ­vĆ”s"; /* No comment provided by engineer. */ -"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**PrivĆ”tabb**: 20 percenkĆ©nt ellenőrzi az Ćŗj üzeneteket. Az eszkƶztoken megosztĆ”sra kerül a SimpleX Chat kiszolgĆ”lóval, de az nem, hogy hĆ”ny ismerőse vagy üzenete van."; +"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**PrivĆ”tabb**: 20 percenkĆ©nt ellenőrzi az Ćŗj üzeneteket. Az eszkƶztoken megosztĆ”sra kerül a SimpleX Chat-kiszolgĆ”lóval, de az nem, hogy hĆ”ny ismerőse vagy üzenete van."; /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**LegprivĆ”tabb**: ne hasznĆ”lja a SimpleX Chat Ć©rtesĆ­tĆ©si kiszolgĆ”lót, rendszeresen ellenőrizze az üzeneteket a hĆ”ttĆ©rben (attól függően, hogy milyen gyakran hasznĆ”lja az alkalmazĆ”st)."; /* No comment provided by engineer. */ -"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**MegjegyzĆ©s**: ha kĆ©t eszkƶzƶn is ugyanazt az adatbĆ”zist hasznĆ”lja, akkor biztonsĆ”gi vĆ©delemkĆ©nt megszakĆ­tja az ismerőseitől Ć©rkező üzenetek visszafejtĆ©sĆ©t."; +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Figyelem:** ha kĆ©t eszkƶzƶn is ugyanazt az adatbĆ”zist hasznĆ”lja, akkor biztonsĆ”gi vĆ©delemkĆ©nt megszakĆ­tja az ismerőseitől Ć©rkező üzenetek visszafejtĆ©sĆ©t."; /* No comment provided by engineer. */ -"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Figyelem**: NEM tudja visszaĆ”llĆ­tani vagy megvĆ”ltoztatni jelmondatĆ”t, ha elveszĆ­ti azt."; +"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Figyelem:** NEM tudja visszaĆ”llĆ­tani vagy megvĆ”ltoztatni jelmondatĆ”t, ha elveszĆ­ti azt."; /* No comment provided by engineer. */ "**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Javasolt**: az eszkƶztoken Ć©s az Ć©rtesĆ­tĆ©sek elküldĆ©sre kerülnek a SimpleX Chat Ć©rtesĆ­tĆ©si kiszolgĆ”lóra, kivĆ©ve az üzenet tartalma, mĆ©rete vagy az, hogy kitől szĆ”rmazik."; @@ -98,7 +98,7 @@ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**FigyelmeztetĆ©s**: Az azonnali push-Ć©rtesĆ­tĆ©sekhez a kulcstartóban tĆ”rolt jelmondat megadĆ”sa szüksĆ©ges."; /* No comment provided by engineer. */ -"**Warning**: the archive will be removed." = "**Figyelem**: az archĆ­vum tƶrlĆ©sre kerül."; +"**Warning**: the archive will be removed." = "**Figyelem**: az archĆ­vum eltĆ”volĆ­tĆ”sra kerül."; /* No comment provided by engineer. */ "*bold*" = "\\*fĆ©lkƶvĆ©r*"; @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ kapcsolódni szeretne!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ Ć©s tovĆ”bbi %lld tag"; @@ -311,7 +314,7 @@ "A separate TCP connection will be used **for each chat profile you have in the app**." = "A rendszer külƶn TCP-kapcsolatot fog hasznĆ”lni **az alkalmazĆ”sban talĆ”lható minden csevegĆ©si profilhoz**."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "A rendszer külƶn TCP-kapcsolatot fog hasznĆ”lni **minden ismerőshƶz Ć©s csoporttaghoz**.\n**Figyelem**: sok kapcsolódĆ”s esetĆ©n, az akkumulĆ”tor- Ć©s adatforgalom fogyasztĆ”s jelentősen megnőhet, Ć©s egyes kapcsolatok meghiĆŗsulhatnak."; +"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "**Minden egyes kapcsolathoz Ć©s csoporttaghoz** külƶn TCP-kapcsolat lesz hasznĆ”lva.\n**Figyelem:** ha sok kapcsolata van, az akkumulĆ”tor-hasznĆ”lat Ć©s az adatforgalom jelentősen megnƶvekedhet, Ć©s nĆ©hĆ”ny kapcsolódĆ”si kĆ­sĆ©rlet sikertelen lehet."; /* No comment provided by engineer. */ "Abort" = "MegszakĆ­tĆ”s"; @@ -323,13 +326,13 @@ "Abort changing address?" = "CĆ­mvĆ”ltoztatĆ”s megszakĆ­tĆ”sa??"; /* No comment provided by engineer. */ -"About SimpleX" = "A SimpleX-ről"; +"About SimpleX" = "A SimpleXről"; /* No comment provided by engineer. */ -"About SimpleX address" = "A SimpleX cĆ­mről"; +"About SimpleX address" = "A SimpleX-cĆ­mről"; /* No comment provided by engineer. */ -"About SimpleX Chat" = "A SimpleX Chat-ről"; +"About SimpleX Chat" = "A SimpleX Chatről"; /* No comment provided by engineer. */ "above, then choose:" = "gombra fent, majd vĆ”lassza ki:"; @@ -343,10 +346,10 @@ "Accept" = "ElfogadĆ”s"; /* No comment provided by engineer. */ -"Accept connection request?" = "IsmerőskĆ©relem elfogadĆ”sa?"; +"Accept connection request?" = "KapcsolatkĆ©rĆ©s elfogadĆ”sa?"; /* notification body */ -"Accept contact request from %@?" = "Elfogadja %@ kapcsolat kĆ©rĆ©sĆ©t?"; +"Accept contact request from %@?" = "Elfogadja %@ kapcsolatkĆ©rĆ©sĆ©t?"; /* accept contact request via notification swipe action */ @@ -386,7 +389,7 @@ "Add to another device" = "HozzĆ”adĆ”s egy mĆ”sik eszkƶzhƶz"; /* No comment provided by engineer. */ -"Add welcome message" = "Üdvƶzlő üzenet hozzĆ”adĆ”sa"; +"Add welcome message" = "Üdvƶzlőüzenet hozzĆ”adĆ”sa"; /* No comment provided by engineer. */ "Additional accent" = "TovĆ”bbi kiemelĆ©s"; @@ -422,7 +425,7 @@ "Advanced settings" = "Haladó beĆ”llĆ­tĆ”sok"; /* chat item text */ -"agreeing encryption for %@…" = "titkosĆ­tĆ”s jóvĆ”hagyĆ”sa %@ szĆ”mĆ”ra…"; +"agreeing encryption for %@…" = "titkosĆ­tĆ”s elfogadĆ”sa %@ szĆ”mĆ”ra…"; /* chat item text */ "agreeing encryption…" = "titkosĆ­tĆ”s elfogadĆ”sa…"; @@ -470,22 +473,22 @@ "Allow" = "EngedĆ©lyezĆ©s"; /* No comment provided by engineer. */ -"Allow calls only if your contact allows them." = "A hĆ­vĆ”sok kezdemĆ©nyezĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi."; +"Allow calls only if your contact allows them." = "A hĆ­vĆ”sok kezdemĆ©nyezĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi."; /* No comment provided by engineer. */ "Allow calls?" = "HĆ­vĆ”sok engedĆ©lyezĆ©se?"; /* No comment provided by engineer. */ -"Allow disappearing messages only if your contact allows it to you." = "Az eltűnő üzenetek küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi az ƶn szĆ”mĆ”ra."; +"Allow disappearing messages only if your contact allows it to you." = "Az eltűnő üzenetek küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi az ƶn szĆ”mĆ”ra."; /* No comment provided by engineer. */ "Allow downgrade" = "VisszafejlesztĆ©s engedĆ©lyezĆ©se"; /* No comment provided by engineer. */ -"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek vĆ©gleges tƶrlĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra)"; +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek vĆ©gleges tƶrlĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra)"; /* No comment provided by engineer. */ -"Allow message reactions only if your contact allows them." = "Az üzenetreakciók küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi."; +"Allow message reactions only if your contact allows them." = "Az üzenetreakciók küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi."; /* No comment provided by engineer. */ "Allow message reactions." = "Üzenetreakciók engedĆ©lyezĆ©se."; @@ -503,16 +506,16 @@ "Allow to irreversibly delete sent messages. (24 hours)" = "Elküldƶtt üzenetek vĆ©gleges tƶrlĆ©sĆ©nek engedĆ©lyezĆ©se. (24 óra)"; /* No comment provided by engineer. */ -"Allow to send files and media." = "FĆ”jlok Ć©s mĆ©diatartalom küldĆ©sĆ©nek engedĆ©lyezĆ©se."; +"Allow to send files and media." = "FĆ”jlok Ć©s mĆ©diatartalmak küldĆ©sĆ©nek engedĆ©lyezĆ©se."; /* No comment provided by engineer. */ -"Allow to send SimpleX links." = "A SimpleX hivatkozĆ”sok küldĆ©se engedĆ©lyezve van."; +"Allow to send SimpleX links." = "A SimpleX-hivatkozĆ”sok küldĆ©se engedĆ©lyezve van."; /* No comment provided by engineer. */ "Allow to send voice messages." = "Hangüzenetek küldĆ©sĆ©nek engedĆ©lyezĆ©se."; /* No comment provided by engineer. */ -"Allow voice messages only if your contact allows them." = "A hangüzenetek küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi."; +"Allow voice messages only if your contact allows them." = "A hangüzenetek küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi."; /* No comment provided by engineer. */ "Allow voice messages?" = "Hangüzenetek engedĆ©lyezĆ©se?"; @@ -644,11 +647,14 @@ "Auto-accept" = "Automatikus elfogadĆ”s"; /* No comment provided by engineer. */ -"Auto-accept contact requests" = "KapcsolódĆ”si kĆ©relmek automatikus elfogadĆ”sa"; +"Auto-accept contact requests" = "KapcsolatkĆ©rĆ©sek automatikus elfogadĆ”sa"; /* No comment provided by engineer. */ "Auto-accept images" = "KĆ©pek automatikus elfogadĆ”sa"; +/* alert title */ +"Auto-accept settings" = "BeĆ”llĆ­tĆ”sok automatikus elfogadĆ”sa"; + /* No comment provided by engineer. */ "Back" = "Vissza"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Nem lehet tovĆ”bbĆ­tani az üzenetet"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nem lehet fogadni a fĆ”jlt"; /* snd error text */ @@ -890,6 +896,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "CsevegĆ©si beĆ”llĆ­tĆ”sok"; +/* alert message */ +"Chat preferences were changed." = "A csevegĆ©si beĆ”llĆ­tĆ”sok megvĆ”ltoztak."; + /* No comment provided by engineer. */ "Chat theme" = "CsevegĆ©s tĆ©mĆ”ja"; @@ -1005,7 +1014,7 @@ "Connect to desktop" = "TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel"; /* No comment provided by engineer. */ -"connect to SimpleX Chat developers." = "KapcsolódĆ”s a SimpleX Chat fejlesztőkhƶz."; +"connect to SimpleX Chat developers." = "kapcsolódĆ”s a SimpleX Chat fejlesztőkhƶz."; /* No comment provided by engineer. */ "Connect to your friends faster." = "Kapcsolódjon gyorsabban az ismerőseihez."; @@ -1014,10 +1023,10 @@ "Connect to yourself?" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own one-time link!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az ƶn egyszer hasznĆ”latos hivatkozĆ”sa!"; +"Connect to yourself?\nThis is your own one-time link!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az ƶn egyszer hasznĆ”lható hivatkozĆ”sa!"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own SimpleX address!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az ƶn SimpleX cĆ­me!"; +"Connect to yourself?\nThis is your own SimpleX address!" = "KapcsolódĆ”s sajĆ”t magĆ”hoz?\nEz az ƶn SimpleX-cĆ­me!"; /* No comment provided by engineer. */ "Connect via contact address" = "KapcsolódĆ”s a kapcsolattartĆ”si cĆ­men keresztül"; @@ -1026,7 +1035,7 @@ "Connect via link" = "KapcsolódĆ”s egy hivatkozĆ”son keresztül"; /* No comment provided by engineer. */ -"Connect via one-time link" = "KapcsolódĆ”s egyszer hasznĆ”latos hivatkozĆ”son keresztül"; +"Connect via one-time link" = "KapcsolódĆ”s egyszer hasznĆ”lható hivatkozĆ”son keresztül"; /* No comment provided by engineer. */ "Connect with %@" = "KapcsolódĆ”s ezzel: %@"; @@ -1065,7 +1074,7 @@ "connecting (introduced)" = "kapcsolódĆ”s (bejelentve)"; /* No comment provided by engineer. */ -"connecting (introduction invitation)" = "kapcsolódĆ”s (bemutatkozó meghĆ­vó)"; +"connecting (introduction invitation)" = "kapcsolódĆ”s (bemutatkozó-meghĆ­vó)"; /* call status */ "connecting call" = "kapcsolódĆ”si hĆ­vĆ”s…"; @@ -1074,7 +1083,7 @@ "Connecting server…" = "KapcsolódĆ”s a kiszolgĆ”lóhoz…"; /* No comment provided by engineer. */ -"Connecting server… (error: %@)" = "KapcsolódĆ”s a kiszolgĆ”lóhoz... (hiba: %@)"; +"Connecting server… (error: %@)" = "KapcsolódĆ”s a kiszolgĆ”lóhoz… (hiba: %@)"; /* No comment provided by engineer. */ "Connecting to contact, please wait or check later!" = "KapcsolódĆ”s az ismerőshƶz, vĆ”rjon vagy ellenőrizze kĆ©sőbb!"; @@ -1104,7 +1113,7 @@ "Connection notifications" = "KapcsolódĆ”si Ć©rtesĆ­tĆ©sek"; /* No comment provided by engineer. */ -"Connection request sent!" = "KapcsolódĆ”si kĆ©rĆ©s elküldve!"; +"Connection request sent!" = "KapcsolatkĆ©rĆ©s elküldve!"; /* No comment provided by engineer. */ "Connection terminated" = "Kapcsolat megszakĆ­tva"; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "AlapverziószĆ”m: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Sarok"; + /* No comment provided by engineer. */ "Correct name to %@?" = "NĆ©v javĆ­tĆ”sa erre: %@?"; @@ -1215,7 +1227,7 @@ "Create secret group" = "Titkos csoport lĆ©trehozĆ”sa"; /* No comment provided by engineer. */ -"Create SimpleX address" = "SimpleX cĆ­m lĆ©trehozĆ”sa"; +"Create SimpleX address" = "SimpleX-cĆ­m lĆ©trehozĆ”sa"; /* No comment provided by engineer. */ "Create your profile" = "SajĆ”t profil lĆ©trehozĆ”sa"; @@ -1495,7 +1507,7 @@ "Delivery receipts are disabled!" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek ki vannak kapcsolva!"; /* No comment provided by engineer. */ -"Delivery receipts!" = "Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek!"; +"Delivery receipts!" = "KĆ©zbesĆ­tĆ©si jelentĆ©sek!"; /* No comment provided by engineer. */ "Description" = "LeĆ­rĆ”s"; @@ -1537,10 +1549,10 @@ "Device" = "Eszkƶz"; /* No comment provided by engineer. */ -"Device authentication is disabled. Turning off SimpleX Lock." = "A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r ki van kapcsolva."; +"Device authentication is disabled. Turning off SimpleX Lock." = "A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX-zĆ”r ki van kapcsolva."; /* No comment provided by engineer. */ -"Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication." = "A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be, miutĆ”n beĆ”llĆ­totta a kĆ©pernyőzĆ”rat az eszkƶzĆ©n."; +"Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication." = "A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX-zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be, miutĆ”n beĆ”llĆ­totta a kĆ©pernyőzĆ”rat az eszkƶzĆ©n."; /* No comment provided by engineer. */ "different migration in the app/database: %@ / %@" = "külƶnbƶző Ć”tkƶltƶztetĆ©sek az alkalmazĆ”sban/adatbĆ”zisban: %@ / %@"; @@ -1564,7 +1576,7 @@ "Disable for all" = "LetiltĆ”s mindenki szĆ”mĆ”ra"; /* authentication reason */ -"Disable SimpleX Lock" = "SimpleX zĆ”r kikapcsolĆ”sa"; +"Disable SimpleX Lock" = "SimpleX-zĆ”r kikapcsolĆ”sa"; /* No comment provided by engineer. */ "disabled" = "letiltva"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "VisszafejlesztĆ©s Ć©s a csevegĆ©s megnyitĆ”sa"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "LetƶltĆ©s"; /* No comment provided by engineer. */ @@ -1711,7 +1724,7 @@ "Enable self-destruct passcode" = "ƖnmegsemmisĆ­tő jelkód engedĆ©lyezĆ©se"; /* authentication reason */ -"Enable SimpleX Lock" = "SimpleX zĆ”r bekapcsolĆ”sa"; +"Enable SimpleX Lock" = "SimpleX-zĆ”r bekapcsolĆ”sa"; /* No comment provided by engineer. */ "Enable TCP keep-alive" = "TCP Ć©letben tartĆ”sa"; @@ -1741,7 +1754,7 @@ "Encrypt local files" = "Helyi fĆ”jlok titkosĆ­tĆ”sa"; /* No comment provided by engineer. */ -"Encrypt stored files & media" = "TĆ”rolt fĆ”jlok Ć©s mĆ©diatartalmak titkosĆ­tĆ”sa"; +"Encrypt stored files & media" = "A tĆ”rolt fĆ”jlok- Ć©s a mĆ©diatartalmak titkosĆ­tĆ”sa"; /* No comment provided by engineer. */ "Encrypted database" = "TitkosĆ­tott adatbĆ”zis"; @@ -1768,7 +1781,7 @@ "Encrypted message: unexpected error" = "TitkosĆ­tott üzenet: vĆ”ratlan hiba"; /* chat item text */ -"encryption agreed" = "titkosĆ­tĆ”s egyeztetve"; +"encryption agreed" = "titkosĆ­tĆ”s elfogadva"; /* chat item text */ "encryption agreed for %@" = "titkosĆ­tĆ”s elfogadva %@ szĆ”mĆ”ra"; @@ -1804,7 +1817,7 @@ "ended call %@" = "%@ hĆ­vĆ”sa befejeződƶtt"; /* No comment provided by engineer. */ -"Enter correct passphrase." = "Helyes jelmondat bevitele."; +"Enter correct passphrase." = "Adja meg a helyes jelmondatot."; /* No comment provided by engineer. */ "Enter group name…" = "CsoportnĆ©v megadĆ”sa…"; @@ -1828,10 +1841,10 @@ "Enter this device name…" = "EszkƶznĆ©v megadĆ”sa…"; /* placeholder */ -"Enter welcome message…" = "Üdvƶzlő üzenet megadĆ”sa…"; +"Enter welcome message…" = "Üdvƶzlőüzenet megadĆ”sa…"; /* placeholder */ -"Enter welcome message… (optional)" = "Üdvƶzlő üzenet megadĆ”sa… (opcionĆ”lis)"; +"Enter welcome message… (optional)" = "Üdvƶzlőüzenet megadĆ”sa… (opcionĆ”lis)"; /* No comment provided by engineer. */ "Enter your name…" = "Adjon meg egy nevet…"; @@ -1846,7 +1859,7 @@ "Error aborting address change" = "Hiba a cĆ­m megvĆ”ltoztatĆ”sĆ”nak megszakĆ­tĆ”sakor"; /* No comment provided by engineer. */ -"Error accepting contact request" = "Hiba tƶrtĆ©nt a kapcsolatfelvĆ©teli kĆ©relem elfogadĆ”sakor"; +"Error accepting contact request" = "Hiba tƶrtĆ©nt a kapcsolatkĆ©rĆ©s elfogadĆ”sakor"; /* No comment provided by engineer. */ "Error accessing database file" = "Hiba az adatbĆ”zisfĆ”jl elĆ©rĆ©sekor"; @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Hiba a cĆ­m megvĆ”ltoztatĆ”sakor"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Hiba a kapcsolati profilra való vĆ”ltĆ”s kƶzben"; + /* No comment provided by engineer. */ "Error changing role" = "Hiba a szerepkƶr megvĆ”ltoztatĆ”sakor"; /* No comment provided by engineer. */ "Error changing setting" = "Hiba a beĆ”llĆ­tĆ”s megvĆ”ltoztatĆ”sakor"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Hiba az inkognitó-profilra való vĆ”ltĆ”s kƶzben!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Hiba a(z) %@ tovĆ”bbĆ­tó kiszolgĆ”lóhoz való kapcsolódĆ”skor. PróbĆ”lja meg kĆ©sőbb."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Hiba a %@ kiszolgĆ”lók betƶltĆ©sekor"; /* No comment provided by engineer. */ -"Error opening chat" = "Hiba a csevegĆ©s megnyitĆ”sakor"; +"Error migrating settings" = "Hiba a beallĆ­tĆ”sok Ć”tkƶltƶztetĆ©se kƶzben"; /* No comment provided by engineer. */ +"Error opening chat" = "Hiba a csevegĆ©s megnyitĆ”sakor"; + +/* alert title */ "Error receiving file" = "Hiba a fĆ”jl fogadĆ”sakor"; /* No comment provided by engineer. */ @@ -1987,7 +2009,7 @@ "Error sending message" = "Hiba az üzenet küldĆ©sekor"; /* No comment provided by engineer. */ -"Error setting delivery receipts!" = "Hiba tƶrtĆ©nt a kĆ©zbesĆ­tĆ©si igazolĆ”sok beĆ”llĆ­tĆ”sakor!"; +"Error setting delivery receipts!" = "Hiba tƶrtĆ©nt a kĆ©zbesĆ­tĆ©si jelentĆ©sek beĆ”llĆ­tĆ”sakor!"; /* No comment provided by engineer. */ "Error starting chat" = "Hiba a csevegĆ©s elindĆ­tĆ”sakor"; @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Hiba a csevegĆ©s megĆ”llĆ­tĆ”sakor"; +/* No comment provided by engineer. */ +"Error switching profile" = "Hiba a profilvĆ”ltĆ”s kƶzben"; + /* No comment provided by engineer. */ "Error switching profile!" = "Hiba a profil vĆ”ltĆ”sakor!"; @@ -2075,7 +2100,7 @@ "Fast and no wait until the sender is online!" = "Gyors Ć©s nem kell vĆ”rni, amĆ­g a feladó online lesz!"; /* No comment provided by engineer. */ -"Faster joining and more reliable messages." = "Gyorsabb csatlakozĆ”s Ć©s megbĆ­zhatóbb üzenet kĆ©zbesĆ­tĆ©s."; +"Faster joining and more reliable messages." = "Gyorsabb csatlakozĆ”s Ć©s megbĆ­zhatóbb üzenetkĆ©zbesĆ­tĆ©s."; /* swipe action */ "Favorite" = "Kedvenc"; @@ -2111,19 +2136,19 @@ "Files" = "FĆ”jlok"; /* No comment provided by engineer. */ -"Files & media" = "FĆ”jlok Ć©s mĆ©dia"; +"Files & media" = "FĆ”jlok Ć©s mĆ©diatartalmak"; /* chat feature */ -"Files and media" = "FĆ”jlok Ć©s mĆ©diatartalom"; +"Files and media" = "FĆ”jlok Ć©s mĆ©diatartalmak"; /* No comment provided by engineer. */ -"Files and media are prohibited in this group." = "A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva ebben a csoportban."; +"Files and media are prohibited in this group." = "A fĆ”jlok- Ć©s a mĆ©diatartalmak le vannak tiltva ebben a csoportban."; /* No comment provided by engineer. */ -"Files and media not allowed" = "FĆ”jlok Ć©s mĆ©dia tartalom küldĆ©se le van tiltva"; +"Files and media not allowed" = "A fĆ”jlok- Ć©s mĆ©diatartalmak nincsenek engedĆ©lyezve"; /* No comment provided by engineer. */ -"Files and media prohibited!" = "A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva!"; +"Files and media prohibited!" = "A fĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©se le van tiltva!"; /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Olvasatlan Ć©s kedvenc csevegĆ©sekre való szűrĆ©s."; @@ -2204,13 +2229,13 @@ "Full name (optional)" = "Teljes nĆ©v (opcionĆ”lis)"; /* No comment provided by engineer. */ -"Fully decentralized – visible only to members." = "Teljesen decentralizĆ”lt - kizĆ”rólag tagok szĆ”mĆ”ra lĆ”tható."; +"Fully decentralized – visible only to members." = "Teljesen decentralizĆ”lt - csak a tagok szĆ”mĆ”ra lĆ”tható."; /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "Teljesen Ćŗjra implementĆ”lva - hĆ”ttĆ©rben tƶrtĆ©nő műkƶdĆ©s!"; /* No comment provided by engineer. */ -"Further reduced battery usage" = "TovĆ”bb csƶkkentett akkumulĆ”tor hasznĆ”lat"; +"Further reduced battery usage" = "TovĆ”bb csƶkkentett akkumulĆ”tor-hasznĆ”lat"; /* No comment provided by engineer. */ "GIFs and stickers" = "GIF-ek Ć©s matricĆ”k"; @@ -2243,13 +2268,13 @@ "Group image" = "CsoportkĆ©p"; /* No comment provided by engineer. */ -"Group invitation" = "Csoportos meghĆ­vó"; +"Group invitation" = "CsoportmeghĆ­vó"; /* No comment provided by engineer. */ -"Group invitation expired" = "A csoport meghĆ­vó lejĆ”rt"; +"Group invitation expired" = "A csoportmeghĆ­vó lejĆ”rt"; /* No comment provided by engineer. */ -"Group invitation is no longer valid, it was removed by sender." = "A csoport meghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője tƶrƶlte."; +"Group invitation is no longer valid, it was removed by sender." = "A csoportmeghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője eltĆ”volĆ­totta."; /* No comment provided by engineer. */ "Group link" = "CsoporthivatkozĆ”s"; @@ -2273,7 +2298,7 @@ "Group members can send files and media." = "A csoport tagjai küldhetnek fĆ”jlokat Ć©s mĆ©diatartalmakat."; /* No comment provided by engineer. */ -"Group members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX hivatkozĆ”sokat."; +"Group members can send SimpleX links." = "A csoport tagjai küldhetnek SimpleX-hivatkozĆ”sokat."; /* No comment provided by engineer. */ "Group members can send voice messages." = "A csoport tagjai küldhetnek hangüzeneteket."; @@ -2285,7 +2310,7 @@ "Group moderation" = "Csoport moderĆ”ció"; /* No comment provided by engineer. */ -"Group preferences" = "Csoport beĆ”llĆ­tĆ”sok"; +"Group preferences" = "CsoportbeĆ”llĆ­tĆ”sok"; /* No comment provided by engineer. */ "Group profile" = "Csoportprofil"; @@ -2297,7 +2322,7 @@ "group profile updated" = "csoportprofil frissĆ­tve"; /* No comment provided by engineer. */ -"Group welcome message" = "Csoport üdvƶzlő üzenete"; +"Group welcome message" = "A csoport üdvƶzlőüzenete"; /* No comment provided by engineer. */ "Group will be deleted for all members - this cannot be undone!" = "A csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza!"; @@ -2363,7 +2388,7 @@ "If you can't meet in person, show QR code in a video call, or share the link." = "Ha nem tud szemĆ©lyesen talĆ”lkozni, mutassa meg a QR-kódot egy videohĆ­vĆ”s kƶzben, vagy ossza meg a hivatkozĆ”st."; /* No comment provided by engineer. */ -"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazĆ”s megnyitĆ”sakor megadja ezt a jelkódot, az ƶsszes alkalmazĆ”sadat vĆ©glegesen tƶrlődik!"; +"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazĆ”s megnyitĆ”sakor megadja ezt a jelkódot, az ƶsszes alkalmazĆ”sadat vĆ©glegesen eltĆ”volĆ­tĆ”sra kerül!"; /* No comment provided by engineer. */ "If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazĆ”s megnyitĆ”sakor megadja az ƶnmegsemmisĆ­tő jelkódot:"; @@ -2405,7 +2430,7 @@ "Importing archive" = "ArchĆ­vum importĆ”lĆ”sa"; /* No comment provided by engineer. */ -"Improved message delivery" = "TovĆ”bbfejlesztett üzenetküldĆ©s"; +"Improved message delivery" = "TovĆ”bbfejlesztett üzenetkĆ©zbesĆ­tĆ©s"; /* No comment provided by engineer. */ "Improved privacy and security" = "Fejlesztett adatvĆ©delem Ć©s biztonsĆ”g"; @@ -2432,7 +2457,7 @@ "Incognito groups" = "Inkognitó csoportok"; /* No comment provided by engineer. */ -"Incognito mode" = "Inkognitó mód"; +"Incognito mode" = "Inkognitómód"; /* No comment provided by engineer. */ "Incognito mode protects your privacy by using a new random profile for each contact." = "Az inkognitómód vĆ©di szemĆ©lyes adatait azĆ”ltal, hogy minden ismerőshƶz Ćŗj vĆ©letlenszerű profilt hasznĆ”l."; @@ -2444,7 +2469,7 @@ "incognito via group link" = "inkognitó a csoporthivatkozĆ”son keresztül"; /* chat list item description */ -"incognito via one-time link" = "inkognitó egy egyszer hasznĆ”latos hivatkozĆ”son keresztül"; +"incognito via one-time link" = "inkognitó egy egyszer hasznĆ”lható hivatkozĆ”son keresztül"; /* notification */ "Incoming audio call" = "Bejƶvő hanghĆ­vĆ”s"; @@ -2714,7 +2739,7 @@ "Make profile private!" = "Tegye privĆ”ttĆ” a profiljĆ”t!"; /* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjƶn meg arról, hogy a %@ kiszolgĆ”lócĆ­mek megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva (%@)."; +"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjƶn meg arról, hogy a(z) %@ kiszolgĆ”lócĆ­mek megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva (%@)."; /* No comment provided by engineer. */ "Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjƶn meg arról, hogy a WebRTC ICE-kiszolgĆ”ló cĆ­mei megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva."; @@ -2780,10 +2805,10 @@ "Message delivery error" = "ÜzenetkĆ©zbesĆ­tĆ©si hiba"; /* No comment provided by engineer. */ -"Message delivery receipts!" = "Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek!"; +"Message delivery receipts!" = "ÜzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek!"; /* item status text */ -"Message delivery warning" = "Üzenet kĆ©zbesĆ­tĆ©si figyelmeztetĆ©s"; +"Message delivery warning" = "ÜzenetkĆ©zbesĆ­tĆ©si figyelmeztetĆ©s"; /* No comment provided by engineer. */ "Message draft" = "ÜzenetvĆ”zlat"; @@ -2810,11 +2835,14 @@ "message received" = "üzenet Ć©rkezett"; /* No comment provided by engineer. */ -"Message reception" = "Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©s"; +"Message reception" = "ÜzenetjelentĆ©s"; /* No comment provided by engineer. */ "Message servers" = "ÜzenetkiszolgĆ”lók"; +/* No comment provided by engineer. */ +"Message shape" = "ÜzenetbuborĆ©k formĆ”ja"; + /* No comment provided by engineer. */ "Message source remains private." = "Az üzenet forrĆ”sa titokban marad."; @@ -2879,7 +2907,7 @@ "Migration error:" = "ƁtkƶltƶztetĆ©s hiba:"; /* No comment provided by engineer. */ -"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen Ć”tkƶltƶztetĆ©s. Koppintson a **KihagyĆ”s** lehetősĆ©gre az aktuĆ”lis adatbĆ”zis hasznĆ”latĆ”nak folytatĆ”sĆ”hoz. Jelentse a problĆ©mĆ”t az alkalmazĆ”s fejlesztőinek csevegĆ©sben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat)."; +"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen Ć”tkƶltƶztetĆ©s. Koppintson a **KihagyĆ”s** lehetősĆ©gre a jelenlegi adatbĆ”zis hasznĆ”latĆ”nak folytatĆ”sĆ”hoz. Jelentse a problĆ©mĆ”t az alkalmazĆ”s fejlesztőinek csevegĆ©sben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat)."; /* No comment provided by engineer. */ "Migration is completed" = "Az Ć”tkƶltƶztetĆ©s befejeződƶtt"; @@ -2963,7 +2991,7 @@ "New chat experience šŸŽ‰" = "Új csevegĆ©si Ć©lmĆ©ny šŸŽ‰"; /* notification */ -"New contact request" = "Új ismerőskĆ©relem"; +"New contact request" = "Új kapcsolatkĆ©rĆ©s"; /* notification */ "New contact:" = "Új kapcsolat:"; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "ajĆ”nlotta %1$@: %2$@-kor"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Rendben"; /* No comment provided by engineer. */ @@ -3097,7 +3125,7 @@ "on" = "bekapcsolva"; /* No comment provided by engineer. */ -"One-time invitation link" = "Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s"; +"One-time invitation link" = "Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s"; /* No comment provided by engineer. */ "Onion hosts will be **required** for connection.\nRequires compatible VPN." = "A kapcsolódĆ”shoz Onion kiszolgĆ”lókra lesz szüksĆ©g.\nVPN engedĆ©lyezĆ©se szüksĆ©ges."; @@ -3169,7 +3197,7 @@ "Open migration to another device" = "ƁtkƶltƶztetĆ©s megkezdĆ©se egy mĆ”sik eszkƶzre"; /* No comment provided by engineer. */ -"Open server settings" = "KiszolgĆ”ló beĆ”llĆ­tĆ”sainak megnyitĆ”sa"; +"Open server settings" = "KiszolgĆ”ló-beĆ”llĆ­tĆ”sok megnyitĆ”sa"; /* No comment provided by engineer. */ "Open Settings" = "BeĆ”llĆ­tĆ”sok megnyitĆ”sa"; @@ -3232,7 +3260,7 @@ "Password to show" = "Jelszó megjelenĆ­tĆ©se"; /* past/unknown group member */ -"Past member %@" = "%@ (mĆ”r nem tag)"; +"Past member %@" = "(mĆ”r nem tag) %@"; /* No comment provided by engineer. */ "Paste desktop address" = "SzĆ”mĆ­tógĆ©p cĆ­mĆ©nek beillesztĆ©se"; @@ -3301,7 +3329,7 @@ "Please contact group admin." = "LĆ©pjen kapcsolatba a csoport adminnal."; /* No comment provided by engineer. */ -"Please enter correct current passphrase." = "Adja meg a helyes aktuĆ”lis jelmondatĆ”t."; +"Please enter correct current passphrase." = "Adja meg a helyes, jelenlegi jelmondatĆ”t."; /* No comment provided by engineer. */ "Please enter the previous password after restoring database backup. This action can not be undone." = "Előző jelszó megadĆ”sa az adatbĆ”zis biztonsĆ”gi mentĆ©sĆ©nek visszaĆ”llĆ­tĆ”sa utĆ”n. Ez a művelet nem vonható vissza."; @@ -3349,7 +3377,7 @@ "Privacy redefined" = "AdatvĆ©delem ĆŗjraĆ©rtelmezve"; /* No comment provided by engineer. */ -"Private filenames" = "PrivĆ”t fĆ”jl nevek"; +"Private filenames" = "PrivĆ”t fĆ”jlnevek"; /* No comment provided by engineer. */ "Private message routing" = "PrivĆ”t üzenet ĆŗtvĆ”lasztĆ”s"; @@ -3403,10 +3431,10 @@ "Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldĆ©se le van tiltva."; /* No comment provided by engineer. */ -"Prohibit sending files and media." = "FĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©s letiltĆ”sa."; +"Prohibit sending files and media." = "FĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©sĆ©nek letiltĆ”sa."; /* No comment provided by engineer. */ -"Prohibit sending SimpleX links." = "A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva."; +"Prohibit sending SimpleX links." = "A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva."; /* No comment provided by engineer. */ "Prohibit sending voice messages." = "A hangüzenetek küldĆ©se le van tiltva."; @@ -3478,7 +3506,7 @@ "Read more in our GitHub repository." = "TovĆ”bbi informĆ”ció a GitHub tĆ”rolónkban."; /* No comment provided by engineer. */ -"Receipts are disabled" = "Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©s letiltva"; +"Receipts are disabled" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva"; /* No comment provided by engineer. */ "Receive errors" = "ÜzenetfogadĆ”si hibĆ”k"; @@ -3496,7 +3524,7 @@ "received confirmation…" = "visszaigazolĆ”s fogadĆ”sa…"; /* notification */ -"Received file event" = "Fogadott fĆ”jl esemĆ©ny"; +"Received file event" = "Fogadott fĆ”jlesemĆ©ny"; /* message info title */ "Received message" = "Fogadott üzenet"; @@ -3532,7 +3560,7 @@ "Reconnect" = "ÚjrakapcsolĆ”s"; /* No comment provided by engineer. */ -"Reconnect all connected servers to force message delivery. It uses additional traffic." = "ÚjrakapcsolódĆ”s az ƶsszes kiszolgĆ”lóhoz az üzenetek kĆ©zbesĆ­tĆ©sĆ©nek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi forgalmat hasznĆ”l."; +"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Az ƶsszes kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l."; /* No comment provided by engineer. */ "Reconnect all servers" = "ÚjrakapcsolódĆ”s minden kiszolgĆ”lóhoz"; @@ -3541,7 +3569,7 @@ "Reconnect all servers?" = "ÚjrakapcsolódĆ”s minden kiszolgĆ”lóhoz?"; /* No comment provided by engineer. */ -"Reconnect server to force message delivery. It uses additional traffic." = "A kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenet kĆ©zbesĆ­tĆ©sĆ©nek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l."; +"Reconnect server to force message delivery. It uses additional traffic." = "A kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l."; /* No comment provided by engineer. */ "Reconnect server?" = "ÚjrakapcsolódĆ”s a kiszolgĆ”lóhoz?"; @@ -3556,7 +3584,7 @@ "Record updated at: %@" = "A bejegyzĆ©s frissĆ­tve: %@"; /* No comment provided by engineer. */ -"Reduced battery usage" = "Csƶkkentett akkumulĆ”torhasznĆ”lat"; +"Reduced battery usage" = "Csƶkkentett akkumulĆ”tor-hasznĆ”lat"; /* reject incoming call via notification swipe action */ @@ -3566,7 +3594,7 @@ "Reject (sender NOT notified)" = "ElutasĆ­tĆ”s (a feladó NEM kap Ć©rtesĆ­tĆ©st)"; /* No comment provided by engineer. */ -"Reject contact request" = "KapcsolatfelvĆ©teli kĆ©relem elutasĆ­tĆ”sa"; +"Reject contact request" = "KapcsolatkĆ©rĆ©s elutasĆ­tĆ”sa"; /* call status */ "rejected call" = "elutasĆ­tott hĆ­vĆ”s"; @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "EltĆ”volĆ­tĆ”s"; +/* No comment provided by engineer. */ +"Remove archive?" = "ArchĆ­vum eltĆ”volĆ­tĆ”sa?"; + /* No comment provided by engineer. */ "Remove image" = "KĆ©p eltĆ”volĆ­tĆ”sa"; @@ -3599,13 +3630,13 @@ "removed %@" = "eltĆ”volĆ­totta őt: %@"; /* profile update event chat item */ -"removed contact address" = "tƶrƶlt kapcsolattartĆ”si cĆ­m"; +"removed contact address" = "eltĆ”volĆ­totta a kapcsolattartĆ”si cĆ­met"; /* profile update event chat item */ -"removed profile picture" = "tƶrƶlte a profilkĆ©pĆ©t"; +"removed profile picture" = "eltĆ”volĆ­totta a profilkĆ©pĆ©t"; /* rcv group event chat item */ -"removed you" = "eltĆ”volĆ­tottak"; +"removed you" = "eltĆ”volĆ­totta ƶnt"; /* No comment provided by engineer. */ "Renegotiate" = "ÚjraegyzetetĆ©s"; @@ -3617,7 +3648,7 @@ "Renegotiate encryption?" = "TitkosĆ­tĆ”s ĆŗjraegyeztetĆ©se?"; /* No comment provided by engineer. */ -"Repeat connection request?" = "KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se?"; +"Repeat connection request?" = "KapcsolatkĆ©rĆ©s megismĆ©tlĆ©se?"; /* No comment provided by engineer. */ "Repeat download" = "LetƶltĆ©s ismĆ©t"; @@ -3626,7 +3657,7 @@ "Repeat import" = "ImportĆ”lĆ”s ismĆ©t"; /* No comment provided by engineer. */ -"Repeat join request?" = "CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se?"; +"Repeat join request?" = "CsatlakozĆ”skĆ©rĆ©s megismĆ©tlĆ©se?"; /* No comment provided by engineer. */ "Repeat upload" = "FeltƶltĆ©s ismĆ©t"; @@ -3729,10 +3760,10 @@ "Save archive" = "ArchĆ­vum mentĆ©se"; /* No comment provided by engineer. */ -"Save group profile" = "Csoportprofil elmentĆ©se"; +"Save group profile" = "Csoportprofil mentĆ©se"; /* No comment provided by engineer. */ -"Save passphrase and open chat" = "Jelmondat elmentĆ©se Ć©s csevegĆ©s megnyitĆ”sa"; +"Save passphrase and open chat" = "Jelmondat mentĆ©se Ć©s a csevegĆ©s megnyitĆ”sa"; /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Jelmondat mentĆ©se a kulcstartóba"; @@ -3750,7 +3781,10 @@ "Save servers?" = "KiszolgĆ”lók mentĆ©se?"; /* No comment provided by engineer. */ -"Save welcome message?" = "Üdvƶzlőszƶveg mentĆ©se?"; +"Save welcome message?" = "Üdvƶzlőüzenet mentĆ©se?"; + +/* alert title */ +"Save your profile?" = "Profil mentĆ©se?"; /* No comment provided by engineer. */ "saved" = "mentett"; @@ -3798,10 +3832,10 @@ "Search" = "KeresĆ©s"; /* No comment provided by engineer. */ -"Search bar accepts invitation links." = "A keresősĆ”v elfogadja a meghĆ­vó hivatkozĆ”sokat."; +"Search bar accepts invitation links." = "A keresősĆ”v elfogadja a meghĆ­vó-hivatkozĆ”sokat."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "KeresĆ©s, vagy SimpleX hivatkozĆ”s beillesztĆ©se"; +"Search or paste SimpleX link" = "KeresĆ©s, vagy SimpleX-hivatkozĆ”s beillesztĆ©se"; /* network option */ "sec" = "mp"; @@ -3831,10 +3865,13 @@ "security code changed" = "a biztonsĆ”gi kód megvĆ”ltozott"; /* chat item action */ -"Select" = "VĆ”lasztĆ”s"; +"Select" = "KijelƶlĆ©s"; /* No comment provided by engineer. */ -"Selected %lld" = "%lld kivĆ”lasztva"; +"Select chat profile" = "CsevegĆ©si profil kivĆ”lasztĆ”sa"; + +/* No comment provided by engineer. */ +"Selected %lld" = "%lld kijelƶlve"; /* No comment provided by engineer. */ "Selected chat preferences prohibit this message." = "A kivĆ”lasztott csevegĆ©si beĆ”llĆ­tĆ”sok tiltjĆ”k ezt az üzenetet."; @@ -3897,7 +3934,7 @@ "Send questions and ideas" = "Ɩtletek Ć©s kĆ©rdĆ©sek beküldĆ©se"; /* No comment provided by engineer. */ -"Send receipts" = "Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek"; +"Send receipts" = "KĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se"; /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Küldje el őket galĆ©riĆ”ból vagy egyedi billentyűzetekről."; @@ -3905,11 +3942,11 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Az utolsó 100 üzenet elküldĆ©se az Ćŗj tagoknak."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "A fĆ”jl küldője visszavonta az Ć”tvitelt."; /* No comment provided by engineer. */ -"Sender may have deleted the connection request." = "A küldő tƶrƶlhette a kapcsolódĆ”si kĆ©relmet."; +"Sender may have deleted the connection request." = "A küldő tƶrƶlhette a kapcsolatkĆ©rĆ©st."; /* No comment provided by engineer. */ "Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezĆ©sre kerül az ƶsszes lĆ”tható csevegĆ©si profilban lĆ©vő minden ismerős szĆ”mĆ”ra."; @@ -3921,16 +3958,16 @@ "Sending file will be stopped." = "A fĆ”jl küldĆ©se leĆ”llt."; /* No comment provided by engineer. */ -"Sending receipts is disabled for %lld contacts" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se le van tiltva %lld ismerősnĆ©l"; +"Sending receipts is disabled for %lld contacts" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %lld ismerősnĆ©l"; /* No comment provided by engineer. */ -"Sending receipts is disabled for %lld groups" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se le van tiltva %lld csoportban"; +"Sending receipts is disabled for %lld groups" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %lld csoportban"; /* No comment provided by engineer. */ -"Sending receipts is enabled for %lld contacts" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezve van %lld ismerős szĆ”mĆ”ra"; +"Sending receipts is enabled for %lld contacts" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %lld ismerősnĆ©l"; /* No comment provided by engineer. */ -"Sending receipts is enabled for %lld groups" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezve van %lld csoportban"; +"Sending receipts is enabled for %lld groups" = "A kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %lld csoportban"; /* No comment provided by engineer. */ "Sending via" = "KüldĆ©s ezen keresztül"; @@ -3945,7 +3982,7 @@ "Sent directly" = "Kƶzvetlenül küldƶtt"; /* notification */ -"Sent file event" = "Elküldƶtt fĆ”jl esemĆ©ny"; +"Sent file event" = "Elküldƶtt fĆ”jlesemĆ©ny"; /* message info title */ "Sent message" = "Elküldƶtt üzenet"; @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "BeĆ”llĆ­tĆ”sok"; +/* alert message */ +"Settings were changed." = "A beĆ”llĆ­tĆ”sok megvĆ”ltoztak."; + /* No comment provided by engineer. */ "Shape profile images" = "ProfilkĆ©p alakzat"; @@ -4053,7 +4093,7 @@ "Share" = "MegosztĆ”s"; /* No comment provided by engineer. */ -"Share 1-time link" = "Egyszer hasznĆ”latos hivatkozĆ”s megosztĆ”sa"; +"Share 1-time link" = "Egyszer hasznĆ”lható hivatkozĆ”s megosztĆ”sa"; /* No comment provided by engineer. */ "Share address" = "CĆ­m megosztĆ”sa"; @@ -4068,7 +4108,10 @@ "Share link" = "HivatkozĆ”s megosztĆ”sa"; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s megosztĆ”sa"; +"Share profile" = "Profil megosztĆ”sa"; + +/* No comment provided by engineer. */ +"Share this 1-time invite link" = "Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s megosztĆ”sa"; /* No comment provided by engineer. */ "Share to SimpleX" = "MegosztĆ”s a SimpleX-ben"; @@ -4107,10 +4150,10 @@ "SimpleX" = "SimpleX"; /* No comment provided by engineer. */ -"SimpleX address" = "SimpleX cĆ­m"; +"SimpleX address" = "SimpleX-cĆ­m"; /* No comment provided by engineer. */ -"SimpleX Address" = "SimpleX cĆ­m"; +"SimpleX Address" = "SimpleX-cĆ­m"; /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsĆ”ga a Trail of Bits Ć”ltal lett auditĆ”lva."; @@ -4125,28 +4168,28 @@ "SimpleX group link" = "SimpleX csoporthivatkozĆ”s"; /* chat feature */ -"SimpleX links" = "SimpleX hivatkozĆ”sok"; +"SimpleX links" = "SimpleX-hivatkozĆ”sok"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban."; +"SimpleX links are prohibited in this group." = "A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ -"SimpleX links not allowed" = "A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva"; +"SimpleX links not allowed" = "A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva"; /* No comment provided by engineer. */ -"SimpleX Lock" = "SimpleX zĆ”r"; +"SimpleX Lock" = "SimpleX-zĆ”r"; /* No comment provided by engineer. */ "SimpleX Lock mode" = "ZĆ”rolĆ”si mód"; /* No comment provided by engineer. */ -"SimpleX Lock not enabled!" = "A SimpleX zĆ”r nincs bekapcsolva!"; +"SimpleX Lock not enabled!" = "A SimpleX-zĆ”r nincs bekapcsolva!"; /* No comment provided by engineer. */ -"SimpleX Lock turned on" = "SimpleX zĆ”r bekapcsolva"; +"SimpleX Lock turned on" = "SimpleX-zĆ”r bekapcsolva"; /* simplex link type */ -"SimpleX one-time invitation" = "SimpleX egyszer hasznĆ”latos meghĆ­vó"; +"SimpleX one-time invitation" = "Egyszer hasznĆ”lható SimpleX-meghĆ­vó"; /* No comment provided by engineer. */ "Simplified incognito mode" = "EgyszerűsĆ­tett inkognĆ­tó mód"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Enyhe"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Egyes alkalmazĆ”sbeĆ”llĆ­tĆ”sok nem lettek Ć”tkƶltƶztetve."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "NĆ©hĆ”ny fĆ”jl nem került exportĆ”lĆ”sra:"; @@ -4224,10 +4270,10 @@ "Stop file" = "FĆ”jl megĆ”llĆ­tĆ”sa"; /* No comment provided by engineer. */ -"Stop receiving file?" = "FĆ”jl fogadĆ”s megĆ”llĆ­tĆ”sa?"; +"Stop receiving file?" = "FĆ”jlfogadĆ”s megĆ”llĆ­tĆ”sa?"; /* No comment provided by engineer. */ -"Stop sending file?" = "FĆ”jl küldĆ©s megĆ”llĆ­tĆ”sa?"; +"Stop sending file?" = "FĆ”jlküldĆ©s megĆ”llĆ­tĆ”sa?"; /* No comment provided by engineer. */ "Stop sharing" = "MegosztĆ”s megĆ”llĆ­tĆ”sa"; @@ -4268,6 +4314,9 @@ /* No comment provided by engineer. */ "System authentication" = "RendszerhitelesĆ­tĆ©s"; +/* No comment provided by engineer. */ +"Tail" = "Farok"; + /* No comment provided by engineer. */ "Take picture" = "KĆ©p kĆ©szĆ­tĆ©se"; @@ -4308,7 +4357,7 @@ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; /* No comment provided by engineer. */ -"Temporary file error" = "Ideiglenes fĆ”jlhiba"; +"Temporary file error" = "IdeiglenesfĆ”jl-hiba"; /* server test failure */ "Test failed at step %@." = "A teszt sikertelen volt a(z) %@ lĆ©pĆ©snĆ©l."; @@ -4335,7 +4384,7 @@ "The 1st platform without any user identifiers – private by design." = "Az első csevegĆ©si rendszer bĆ”rmifĆ©le felhasznĆ”ló azonosĆ­tó nĆ©lkül - privĆ”tra lett tervezre."; /* No comment provided by engineer. */ -"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazĆ”s Ć©rtesĆ­teni fogja, amikor üzeneteket vagy kapcsolatfelvĆ©teli kĆ©rĆ©seket kap – beĆ”llĆ­tĆ”sok megnyitĆ”sa az engedĆ©lyezĆ©shez."; +"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazĆ”s Ć©rtesĆ­teni fogja, amikor üzeneteket vagy kapcsolatkĆ©rĆ©seket kap – beĆ”llĆ­tĆ”sok megnyitĆ”sa az engedĆ©lyezĆ©shez."; /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Az alkalmazĆ”s kĆ©rni fogja az ismeretlen fĆ”jlkiszolgĆ”lókról (kivĆ©ve .onion) tƶrtĆ©nő letƶltĆ©sek megerősĆ­tĆ©sĆ©t."; @@ -4395,7 +4444,10 @@ "The servers for new connections of your current chat profile **%@**." = "Jelenlegi profil Ćŗj ismerőseinek kiszolgĆ”lói **%@**."; /* No comment provided by engineer. */ -"The text you pasted is not a SimpleX link." = "A beillesztett szƶveg nem egy SimpleX hivatkozĆ”s."; +"The text you pasted is not a SimpleX link." = "A beillesztett szƶveg nem egy SimpleX-hivatkozĆ”s."; + +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "A feltƶltƶtt adatbĆ”zis-archĆ­vum vĆ©glegesen eltĆ”volĆ­tĆ”sra kerül a kiszolgĆ”lókról."; /* No comment provided by engineer. */ "Themes" = "TĆ©mĆ”k"; @@ -4407,7 +4459,7 @@ "They can be overridden in contact and group settings." = "Ezek felülbĆ­rĆ”lhatóak az ismerős- Ć©s csoportbeĆ”llĆ­tĆ”sokban."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalommal együtt tƶrlĆ©sre kerülnek. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak."; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalmakkal együtt tƶrlĆ©sre kerül. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak."; /* No comment provided by engineer. */ "This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Ez a művelet nem vonható vissza - a kivĆ”lasztottnĆ”l korĆ”bban küldƶtt Ć©s fogadott üzenetek tƶrlĆ©sre kerülnek. Ez tƶbb percet is igĆ©nybe vehet."; @@ -4437,10 +4489,10 @@ "This group no longer exists." = "Ez a csoport mĆ”r nem lĆ©tezik."; /* No comment provided by engineer. */ -"This is your own one-time link!" = "Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa!"; +"This is your own one-time link!" = "Ez az ƶn egyszer hasznĆ”lható hivatkozĆ”sa!"; /* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Ez az ƶn SimpleX cĆ­me!"; +"This is your own SimpleX address!" = "Ez az ƶn SimpleX-cĆ­me!"; /* No comment provided by engineer. */ "This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozĆ”st egy mĆ”sik mobileszkƶzƶn mĆ”r hasznĆ”ltĆ”k, hozzon lĆ©tre egy Ćŗj hivatkozĆ”st az asztali szĆ”mĆ­tógĆ©pĆ©n."; @@ -4470,7 +4522,7 @@ "To protect timezone, image/voice files use UTC." = "Az időzóna vĆ©delme Ć©rdekĆ©ben a kĆ©p-/hangfĆ”jlok UTC-t hasznĆ”lnak."; /* No comment provided by engineer. */ -"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "A biztonsĆ”ga Ć©rdekĆ©ben kapcsolja be a SimpleX zĆ”r funkciót.\nA funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”llĆ­tĆ”sĆ”ra az eszkƶzĆ©n."; +"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "A biztonsĆ”ga Ć©rdekĆ©ben kapcsolja be a SimpleX-zĆ”r funkciót.\nA funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”llĆ­tĆ”sĆ”ra az eszkƶzĆ©n."; /* No comment provided by engineer. */ "To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cĆ­m vĆ©delmĆ©nek Ć©rdekĆ©ben a privĆ”t ĆŗtvĆ”lasztĆ”s az SMP kiszolgĆ”lókat hasznĆ”lja az üzenetek kĆ©zbesĆ­tĆ©sĆ©hez."; @@ -4491,7 +4543,7 @@ "Toggle chat list:" = "Csevegőlista Ć”tvĆ”ltĆ”sa:"; /* No comment provided by engineer. */ -"Toggle incognito when connecting." = "Inkognitó mód kapcsolódĆ”skor."; +"Toggle incognito when connecting." = "Inkognitómód kapcsolódĆ”skor."; /* No comment provided by engineer. */ "Toolbar opacity" = "EszkƶztĆ”r Ć”tlĆ”tszatlansĆ”ga"; @@ -4574,7 +4626,7 @@ /* No comment provided by engineer. */ "unknown servers" = "ismeretlen Ć”tjĆ”tszók"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Ismeretlen kiszolgĆ”lók!"; /* No comment provided by engineer. */ @@ -4689,7 +4741,7 @@ "Use server" = "KiszolgĆ”ló hasznĆ”lata"; /* No comment provided by engineer. */ -"Use SimpleX Chat servers?" = "SimpleX Chat kiszolgĆ”lók hasznĆ”lata?"; +"Use SimpleX Chat servers?" = "SimpleX Chat-kiszolgĆ”lók hasznĆ”lata?"; /* No comment provided by engineer. */ "Use the app while in the call." = "HasznĆ”lja az alkalmazĆ”st hĆ­vĆ”s kƶzben."; @@ -4704,7 +4756,7 @@ "User selection" = "FelhasznĆ”ló kivĆ”lasztĆ”sa"; /* No comment provided by engineer. */ -"Using SimpleX Chat servers." = "SimpleX Chat kiszolgĆ”lók hasznĆ”latban."; +"Using SimpleX Chat servers." = "SimpleX Chat-kiszolgĆ”lók hasznĆ”latban."; /* No comment provided by engineer. */ "v%@" = "v%@"; @@ -4743,7 +4795,7 @@ "via group link" = "a csoporthivatkozĆ”son keresztül"; /* chat list item description */ -"via one-time link" = "egy egyszer hasznĆ”latos hivatkozĆ”son keresztül"; +"via one-time link" = "egyszer hasznĆ”lható hivatkozĆ”son keresztül"; /* No comment provided by engineer. */ "via relay" = "Ć”tjĆ”tszón keresztül"; @@ -4800,16 +4852,16 @@ "waiting for confirmation…" = "vĆ”rakozĆ”s a visszaigazolĆ”sra…"; /* No comment provided by engineer. */ -"Waiting for desktop..." = "VĆ”rakozĆ”s az asztali kliensre..."; +"Waiting for desktop..." = "VĆ”rakozĆ”s az asztali kliensre…"; /* No comment provided by engineer. */ -"Waiting for file" = "FĆ”jlra vĆ”rakozĆ”s"; +"Waiting for file" = "VĆ”rakozĆ”s a fĆ”jlra"; /* No comment provided by engineer. */ -"Waiting for image" = "KĆ©pre vĆ”rakozĆ”s"; +"Waiting for image" = "VĆ”rakozĆ”s a kĆ©pre"; /* No comment provided by engineer. */ -"Waiting for video" = "Videóra vĆ”rakozĆ”s"; +"Waiting for video" = "VĆ”rakozĆ”s a videóra"; /* No comment provided by engineer. */ "Wallpaper accent" = "HĆ”ttĆ©rkĆ©p kiemelĆ©s"; @@ -4836,10 +4888,10 @@ "Welcome %@!" = "Üdvƶzƶllek %@!"; /* No comment provided by engineer. */ -"Welcome message" = "Üdvƶzlő üzenet"; +"Welcome message" = "Üdvƶzlőüzenet"; /* No comment provided by engineer. */ -"Welcome message is too long" = "Az üdvƶzlő üzenet tĆŗl hosszĆŗ"; +"Welcome message is too long" = "Az üdvƶzlőüzenet tĆŗl hosszĆŗ"; /* No comment provided by engineer. */ "What's new" = "Milyen ĆŗjdonsĆ”gok vannak"; @@ -4854,10 +4906,10 @@ "when IP hidden" = "ha az IP-cĆ­m rejtett"; /* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolódĆ”st kĆ©relmeznek, ƶn elfogadhatja vagy elutasĆ­thatja azokat."; +"When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolatot kĆ©rnek, ƶn elfogadhatja vagy elutasĆ­thatja azokat."; /* No comment provided by engineer. */ -"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott."; +"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitó-profil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott."; /* No comment provided by engineer. */ "WiFi" = "Wi-Fi"; @@ -4869,19 +4921,19 @@ "Wired ethernet" = "VezetĆ©kes Ethernet"; /* No comment provided by engineer. */ -"With encrypted files and media." = "TitkosĆ­tott fĆ”jlokkal Ć©s mĆ©diatartalommal."; +"With encrypted files and media." = "TitkosĆ­tott fĆ”jlokkal Ć©s mĆ©diatartalmakkal."; /* No comment provided by engineer. */ -"With optional welcome message." = "OpcionĆ”lis üdvƶzlő üzenettel."; +"With optional welcome message." = "OpcionĆ”lis üdvƶzlőüzenettel."; /* No comment provided by engineer. */ -"With reduced battery usage." = "Csƶkkentett akkumulĆ”torhasznĆ”lattal."; +"With reduced battery usage." = "Csƶkkentett akkumulĆ”tor-hasznĆ”lattal."; /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nĆ©lkül az IP-cĆ­me lĆ”tható lesz a fĆ”jlkiszolgĆ”lók szĆ”mĆ”ra."; -/* No comment provided by engineer. */ -"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nĆ©lkül az IP-cĆ­me lĆ”tható lesz ezen XFTP Ć”tjĆ”tszók szĆ”mĆ”ra: %@."; +/* alert message */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "A megosztĆ”st az AdatvĆ©delem Ć©s biztonsĆ”g / SimpleX zĆ”r menüben engedĆ©lyezheti."; /* No comment provided by engineer. */ "Wrong database passphrase" = "HibĆ”s adatbĆ”zis jelmondat"; @@ -4926,13 +4978,13 @@ "You are already connecting to %@." = "MĆ”r folyamatban van a kapcsolódĆ”s ehhez: %@."; /* No comment provided by engineer. */ -"You are already connecting via this one-time link!" = "A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”latos hivatkozĆ”son keresztül!"; +"You are already connecting via this one-time link!" = "A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”lható hivatkozĆ”son keresztül!"; /* No comment provided by engineer. */ "You are already in group %@." = "MĆ”r a(z) %@ csoport tagja."; /* No comment provided by engineer. */ -"You are already joining the group %@." = "A csatlakozĆ”s mĆ”r folyamatban van a(z) %@ csoporthoz."; +"You are already joining the group %@." = "A csatlakozĆ”s mĆ”r folyamatban van a(z) %@ nevű csoporthoz."; /* No comment provided by engineer. */ "You are already joining the group via this link!" = "A csatlakozĆ”s mĆ”r folyamatban van a csoporthoz ezen a hivatkozĆ”son keresztül!"; @@ -4941,7 +4993,7 @@ "You are already joining the group via this link." = "A csatlakozĆ”s mĆ”r folyamatban van a csoporthoz ezen a hivatkozĆ”son keresztül."; /* No comment provided by engineer. */ -"You are already joining the group!\nRepeat join request?" = "CsatlakozĆ”s folyamatban!\nCsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se?"; +"You are already joining the group!\nRepeat join request?" = "CsatlakozĆ”s folyamatban!\nCsatlakozĆ”skĆ©rĆ©s megismĆ©tlĆ©se?"; /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l."; @@ -5010,13 +5062,13 @@ "You can still view conversation with %@ in the list of chats." = "A(z) %@ nevű ismerősĆ©vel folytatott beszĆ©lgetĆ©seit tovĆ”bbra is megtekintheti a csevegĆ©sek listĆ”jĆ”ban."; /* No comment provided by engineer. */ -"You can turn on SimpleX Lock via Settings." = "A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be."; +"You can turn on SimpleX Lock via Settings." = "A SimpleX-zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be."; /* No comment provided by engineer. */ "You can use markdown to format messages:" = "Üzenetek formĆ”zĆ”sa a szƶvegbe szĆŗrt speciĆ”lis karakterekkel:"; /* No comment provided by engineer. */ -"You can view invitation link again in connection details." = "A meghĆ­vó hivatkozĆ”st Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l."; +"You can view invitation link again in connection details." = "A meghĆ­vó-hivatkozĆ”st Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l."; /* No comment provided by engineer. */ "You can't send messages!" = "Nem lehet üzeneteket küldeni!"; @@ -5040,10 +5092,10 @@ "You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbĆ”lja meg Ćŗjra."; /* No comment provided by engineer. */ -"You have already requested connection via this address!" = "MĆ”r kĆ©rt egy kapcsolódĆ”si kĆ©relmet ezen a cĆ­men keresztül!"; +"You have already requested connection via this address!" = "MĆ”r küldƶtt egy kapcsolatkĆ©rĆ©st ezen a cĆ­men keresztül!"; /* No comment provided by engineer. */ -"You have already requested connection!\nRepeat connection request?" = "MĆ”r kĆ©rt egy kapcsolódĆ”si kĆ©relmet!\nKapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se?"; +"You have already requested connection!\nRepeat connection request?" = "MĆ”r küldƶtt egy kapcsolódĆ”si kĆ©relmet!\nKapcsolatkĆ©rĆ©s megismĆ©tlĆ©se?"; /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazĆ”s elindul - nem az eszkƶzƶn kerül tĆ”rolĆ”sra."; @@ -5076,19 +5128,19 @@ "You need to allow your contact to send voice messages to be able to send them." = "Hangüzeneteket küldĆ©sĆ©hez engedĆ©lyeznie kell azok küldĆ©sĆ©t az ismerősƶk szĆ”mĆ”ra."; /* No comment provided by engineer. */ -"You rejected group invitation" = "Csoport meghĆ­vó elutasĆ­tva"; +"You rejected group invitation" = "CsoportmeghĆ­vó elutasĆ­tva"; /* snd group event chat item */ "you removed %@" = "eltĆ”volĆ­totta őt: %@"; /* No comment provided by engineer. */ -"You sent group invitation" = "Csoport meghĆ­vó elküldve"; +"You sent group invitation" = "CsoportmeghĆ­vó elküldve"; /* chat list item description */ -"you shared one-time link" = "egyszer hasznĆ”latos hivatkozĆ”st osztott meg"; +"you shared one-time link" = "egyszer hasznĆ”lható hivatkozĆ”st osztott meg"; /* chat list item description */ -"you shared one-time link incognito" = "egyszer hasznĆ”latos hivatkozĆ”st osztott meg inkognitóban"; +"you shared one-time link incognito" = "egyszer hasznĆ”lható hivatkozĆ”st osztott meg inkognitóban"; /* snd group event chat item */ "you unblocked %@" = "ƶn feloldotta %@ letiltĆ”sĆ”t"; @@ -5100,7 +5152,7 @@ "You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz kapcsolódva, amikor a csoporthivatkozĆ”s tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; /* No comment provided by engineer. */ -"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolódĆ”si kĆ©relme elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; +"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz kapcsolódva, ha a kapcsolatkĆ©rĆ©se elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; /* No comment provided by engineer. */ "You will be connected when your contact's device is online, please wait or check later!" = "Akkor le kapcsolódva, amikor az ismerőse eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb!"; @@ -5141,9 +5193,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "A csevegĆ©si adatbĆ”zis nincs titkosĆ­tva – adjon meg egy jelmondatot a titkosĆ­tĆ”shoz."; +/* alert title */ +"Your chat preferences" = "CsevegĆ©si beĆ”llĆ­tĆ”sok"; + /* No comment provided by engineer. */ "Your chat profiles" = "CsevegĆ©si profilok"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "A kapcsolata Ć”t lett helyezve ide: %@, de egy vĆ”ratlan hiba tƶrtĆ©nt a profilra való Ć”tirĆ”nyĆ­tĆ”s kƶzben."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ismerőse olyan fĆ”jlt küldƶtt, amely meghaladja a jelenleg tĆ”mogatott maximĆ”lis mĆ©retet (%@)."; @@ -5175,7 +5233,10 @@ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja megosztĆ”sra fog kerülni."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profilja az eszkƶzƶn van tĆ”rolva, Ć©s csak az ismerősƶkkel kerül megosztĆ”sra. A SimpleX kiszolgĆ”lók nem lĆ”tjhatjĆ”k profiljĆ”t."; +"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profilja az eszkƶzƶn van tĆ”rolva Ć©s csak az ismerőseivel kerül megosztĆ”sra. A SimpleX-kiszolgĆ”lók nem lĆ”thatjĆ”k a profiljĆ”t."; + +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja megvĆ”ltozott. Ha elmenti, a frissĆ­tett profil elküldĆ©sre kerül az ƶsszes ismerősĆ©nek."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Profilja, ismerősei Ć©s az elküldƶtt üzenetei az eszkƶzƶn kerülnek tĆ”rolĆ”sra."; @@ -5193,7 +5254,7 @@ "Your settings" = "BeĆ”llĆ­tĆ”sok"; /* No comment provided by engineer. */ -"Your SimpleX address" = "Profil SimpleX cĆ­me"; +"Your SimpleX address" = "Profil SimpleX-cĆ­me"; /* No comment provided by engineer. */ "Your SMP servers" = "SMP kiszolgĆ”lók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index c60c1537a6..27e7ab5fc5 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ si vuole connettere!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ e %lld membri"; @@ -649,6 +652,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Auto-accetta immagini"; +/* alert title */ +"Auto-accept settings" = "Accetta automaticamente le impostazioni"; + /* No comment provided by engineer. */ "Back" = "Indietro"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Impossibile inoltrare il messaggio"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Impossibile ricevere il file"; /* snd error text */ @@ -890,6 +896,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Preferenze della chat"; +/* alert message */ +"Chat preferences were changed." = "Le preferenze della chat sono state cambiate."; + /* No comment provided by engineer. */ "Chat theme" = "Tema della chat"; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Versione core: v%@"; +/* No comment provided by engineer. */ +"Corner" = "Angolo"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Correggere il nome a %@?"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Esegui downgrade e apri chat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Scarica"; /* No comment provided by engineer. */ @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Errore nella modifica dell'indirizzo"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Errore nel cambio di profilo di connessione"; + /* No comment provided by engineer. */ "Error changing role" = "Errore nel cambio di ruolo"; /* No comment provided by engineer. */ "Error changing setting" = "Errore nella modifica dell'impostazione"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Errore nel passaggio a incognito!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Errore di connessione al server di inoltro %@. Riprova più tardi."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Errore nel caricamento dei server %@"; /* No comment provided by engineer. */ -"Error opening chat" = "Errore di apertura della chat"; +"Error migrating settings" = "Errore nella migrazione delle impostazioni"; /* No comment provided by engineer. */ +"Error opening chat" = "Errore di apertura della chat"; + +/* alert title */ "Error receiving file" = "Errore nella ricezione del file"; /* No comment provided by engineer. */ @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Errore nell'interruzione della chat"; +/* No comment provided by engineer. */ +"Error switching profile" = "Errore nel cambio di profilo"; + /* No comment provided by engineer. */ "Error switching profile!" = "Errore nel cambio di profilo!"; @@ -2815,6 +2840,9 @@ /* No comment provided by engineer. */ "Message servers" = "Server dei messaggi"; +/* No comment provided by engineer. */ +"Message shape" = "Forma del messaggio"; + /* No comment provided by engineer. */ "Message source remains private." = "La fonte del messaggio resta privata."; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "offerto %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Rimuovi"; +/* No comment provided by engineer. */ +"Remove archive?" = "Rimuovere l'archivio?"; + /* No comment provided by engineer. */ "Remove image" = "Rimuovi immagine"; @@ -3752,6 +3783,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Salvare il messaggio di benvenuto?"; +/* alert title */ +"Save your profile?" = "Salvare il profilo?"; + /* No comment provided by engineer. */ "saved" = "salvato"; @@ -3833,6 +3867,9 @@ /* chat item action */ "Select" = "Seleziona"; +/* No comment provided by engineer. */ +"Select chat profile" = "Seleziona il profilo di chat"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld selezionato"; @@ -3905,7 +3942,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Invia fino a 100 ultimi messaggi ai nuovi membri."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Il mittente ha annullato il trasferimento del file."; /* No comment provided by engineer. */ @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Impostazioni"; +/* alert message */ +"Settings were changed." = "Le impostazioni sono state cambiate."; + /* No comment provided by engineer. */ "Shape profile images" = "Forma delle immagini del profilo"; @@ -4067,6 +4107,9 @@ /* No comment provided by engineer. */ "Share link" = "Condividi link"; +/* No comment provided by engineer. */ +"Share profile" = "Condividi il profilo"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Condividi questo link di invito una tantum"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Leggera"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Alcune impostazioni dell'app non sono state migrate."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Alcuni file non sono stati esportati:"; @@ -4268,6 +4314,9 @@ /* No comment provided by engineer. */ "System authentication" = "Autenticazione di sistema"; +/* No comment provided by engineer. */ +"Tail" = "Coda"; + /* No comment provided by engineer. */ "Take picture" = "Scatta foto"; @@ -4397,6 +4446,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Il testo che hai incollato non ĆØ un link SimpleX."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "L'archivio del database caricato verrĆ  rimosso definitivamente dai server."; + /* No comment provided by engineer. */ "Themes" = "Temi"; @@ -4574,7 +4626,7 @@ /* No comment provided by engineer. */ "unknown servers" = "relay sconosciuti"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Server sconosciuti!"; /* No comment provided by engineer. */ @@ -4880,7 +4932,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Senza Tor o VPN, il tuo indirizzo IP sarĆ  visibile ai server di file."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Senza Tor o VPN, il tuo indirizzo IP sarĆ  visibile a questi relay XFTP: %@."; /* No comment provided by engineer. */ @@ -5141,9 +5193,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Il tuo database della chat non ĆØ crittografato: imposta la password per crittografarlo."; +/* alert title */ +"Your chat preferences" = "Le tue preferenze della chat"; + /* No comment provided by engineer. */ "Your chat profiles" = "I tuoi profili di chat"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "La tua connessione ĆØ stata spostata a %@, ma si ĆØ verificato un errore imprevisto durante il reindirizzamento al profilo."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Il tuo contatto ha inviato un file più grande della dimensione massima attualmente supportata (%@)."; @@ -5177,6 +5235,9 @@ /* 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."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Il tuo profilo ĆØ stato cambiato. Se lo salvi, il profilo aggiornato verrĆ  inviato a tutti i tuoi contatti."; + /* No comment provided by engineer. */ "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."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 8a6c48532c..8420879ce4 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -628,7 +628,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "ćƒ‡ćƒ¼ć‚æćƒ™ćƒ¼ć‚¹ć®ćƒ‘ć‚¹ćƒÆćƒ¼ćƒ‰ć‚’äæå­˜ć™ć‚‹ćŸć‚ć®ć‚­ćƒ¼ćƒć‚§ćƒ¼ćƒ³ć«ć‚¢ć‚Æć‚»ć‚¹ć§ćć¾ć›ć‚“"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ćƒ•ć‚”ć‚¤ćƒ«å—äæ”ćŒć§ćć¾ć›ć‚“"; /* No comment provided by engineer. */ @@ -1431,7 +1431,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "%@ ć‚µćƒ¼ćƒćƒ¼ć®ćƒ­ćƒ¼ćƒ‰äø­ć«ć‚Øćƒ©ćƒ¼ćŒē™ŗē”Ÿ"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "ćƒ•ć‚”ć‚¤ćƒ«å—äæ”ć«ć‚Øćƒ©ćƒ¼ē™ŗē”Ÿ"; /* No comment provided by engineer. */ @@ -2235,7 +2235,7 @@ /* feature offered item */ "offered %@: %@" = "ęä¾›ć•ć‚ŒćŸ %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "OK"; /* No comment provided by engineer. */ @@ -2771,7 +2771,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "ć‚®ćƒ£ćƒ©ćƒŖćƒ¼ć¾ćŸćÆć‚«ć‚¹ć‚æćƒ  ć‚­ćƒ¼ćƒœćƒ¼ćƒ‰ć‹ć‚‰é€äæ”ć—ć¾ć™ć€‚"; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "é€äæ”č€…ćŒćƒ•ć‚”ć‚¤ćƒ«č»¢é€ć‚’ć‚­ćƒ£ćƒ³ć‚»ćƒ«ć—ć¾ć—ćŸć€‚"; /* No comment provided by engineer. */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 6c566fa7da..af66433f46 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -160,6 +160,9 @@ /* notification title */ "%@ wants to connect!" = "%@ wil verbinding maken!"; +/* format for date separator in chat */ +"%@, %@" = "%1$@, %2$@"; + /* No comment provided by engineer. */ "%@, %@ and %lld members" = "%@, %@ en %lld leden"; @@ -308,7 +311,7 @@ "A new random profile will be shared." = "Een nieuw willekeurig profiel wordt gedeeld."; /* No comment provided by engineer. */ -"A separate TCP connection will be used **for each chat profile you have in the app**." = "Er wordt een aparte TCP-verbinding gebruikt **voor elk chat profiel dat je in de app hebt**."; +"A separate TCP connection will be used **for each chat profile you have in the app**." = "Er wordt een aparte TCP-verbinding gebruikt **voor elk chatprofiel dat je in de app hebt**."; /* No comment provided by engineer. */ "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "Er wordt een aparte TCP-verbinding gebruikt **voor elk contact en groepslid**.\n**Let op**: als u veel verbindingen heeft, kan uw batterij- en verkeersverbruik aanzienlijk hoger zijn en kunnen sommige verbindingen uitvallen."; @@ -649,6 +652,9 @@ /* No comment provided by engineer. */ "Auto-accept images" = "Afbeeldingen automatisch accepteren"; +/* alert title */ +"Auto-accept settings" = "Instellingen automatisch accepteren"; + /* No comment provided by engineer. */ "Back" = "Terug"; @@ -740,7 +746,7 @@ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaars, Fins, Thais en OekraĆÆens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; /* No comment provided by engineer. */ -"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chat profiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; /* No comment provided by engineer. */ "call" = "bellen"; @@ -796,7 +802,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Kan bericht niet doorsturen"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Kan bestand niet ontvangen"; /* snd error text */ @@ -890,11 +896,14 @@ /* No comment provided by engineer. */ "Chat preferences" = "Gesprek voorkeuren"; +/* alert message */ +"Chat preferences were changed." = "Chatvoorkeuren zijn gewijzigd."; + /* No comment provided by engineer. */ "Chat theme" = "Chat thema"; /* No comment provided by engineer. */ -"Chats" = "Gesprekken"; +"Chats" = "Chats"; /* No comment provided by engineer. */ "Check server address and try again." = "Controleer het server adres en probeer het opnieuw."; @@ -1178,6 +1187,9 @@ /* No comment provided by engineer. */ "Core version: v%@" = "Core versie: v% @"; +/* No comment provided by engineer. */ +"Corner" = "Hoek"; + /* No comment provided by engineer. */ "Correct name to %@?" = "Juiste naam voor %@?"; @@ -1308,7 +1320,7 @@ "Database passphrase is different from saved in the keychain." = "Het wachtwoord van de database verschilt van het wachtwoord dat is opgeslagen in de keychain."; /* No comment provided by engineer. */ -"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je gesprekken te openen."; +"Database passphrase is required to open chat." = "Database wachtwoord is vereist om je chats te openen."; /* No comment provided by engineer. */ "Database upgrade" = "Database upgrade"; @@ -1381,10 +1393,10 @@ "Delete chat archive?" = "Chat archief verwijderen?"; /* No comment provided by engineer. */ -"Delete chat profile" = "Chat profiel verwijderen"; +"Delete chat profile" = "Chatprofiel verwijderen"; /* No comment provided by engineer. */ -"Delete chat profile?" = "Chat profiel verwijderen?"; +"Delete chat profile?" = "Chatprofiel verwijderen?"; /* No comment provided by engineer. */ "Delete connection" = "Verbinding verwijderen"; @@ -1408,7 +1420,7 @@ "Delete files and media?" = "Bestanden en media verwijderen?"; /* No comment provided by engineer. */ -"Delete files for all chat profiles" = "Verwijder bestanden voor alle chat profielen"; +"Delete files for all chat profiles" = "Verwijder bestanden voor alle chatprofielen"; /* chat feature */ "Delete for everyone" = "Verwijderen voor iedereen"; @@ -1629,7 +1641,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Downgraden en chat openen"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Downloaden"; /* No comment provided by engineer. */ @@ -1857,12 +1870,18 @@ /* No comment provided by engineer. */ "Error changing address" = "Fout bij wijzigen van adres"; +/* No comment provided by engineer. */ +"Error changing connection profile" = "Fout bij wijzigen van verbindingsprofiel"; + /* No comment provided by engineer. */ "Error changing role" = "Fout bij wisselen van rol"; /* No comment provided by engineer. */ "Error changing setting" = "Fout bij wijzigen van instelling"; +/* No comment provided by engineer. */ +"Error changing to incognito!" = "Fout bij het overschakelen naar incognito!"; + /* No comment provided by engineer. */ "Error connecting to forwarding server %@. Please try later." = "Fout bij het verbinden met doorstuurserver %@. Probeer het later opnieuw."; @@ -1936,9 +1955,12 @@ "Error loading %@ servers" = "Fout bij het laden van %@ servers"; /* No comment provided by engineer. */ -"Error opening chat" = "Fout bij het openen van de chat"; +"Error migrating settings" = "Fout bij migreren van instellingen"; /* No comment provided by engineer. */ +"Error opening chat" = "Fout bij het openen van de chat"; + +/* alert title */ "Error receiving file" = "Fout bij ontvangen van bestand"; /* No comment provided by engineer. */ @@ -1995,6 +2017,9 @@ /* No comment provided by engineer. */ "Error stopping chat" = "Fout bij het stoppen van de chat"; +/* No comment provided by engineer. */ +"Error switching profile" = "Fout bij wisselen van profiel"; + /* No comment provided by engineer. */ "Error switching profile!" = "Fout bij wisselen van profiel!"; @@ -2138,7 +2163,7 @@ "Finally, we have them! šŸš€" = "Eindelijk, we hebben ze! šŸš€"; /* No comment provided by engineer. */ -"Find chats faster" = "Vind gesprekken sneller"; +"Find chats faster" = "Vind chats sneller"; /* No comment provided by engineer. */ "Fix" = "Herstel"; @@ -2312,7 +2337,7 @@ "Hidden" = "Verborgen"; /* No comment provided by engineer. */ -"Hidden chat profiles" = "Verborgen chat profielen"; +"Hidden chat profiles" = "Verborgen chatprofielen"; /* No comment provided by engineer. */ "Hidden profile password" = "Verborgen profiel wachtwoord"; @@ -2573,7 +2598,7 @@ "Irreversible message deletion is prohibited in this group." = "Het onomkeerbaar verwijderen van berichten is verboden in deze groep."; /* No comment provided by engineer. */ -"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel."; +"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel."; /* No comment provided by engineer. */ "It can happen when you or your connection used the old database backup." = "Het kan gebeuren wanneer u of de ander een oude database back-up gebruikt."; @@ -2815,6 +2840,9 @@ /* No comment provided by engineer. */ "Message servers" = "Berichtservers"; +/* No comment provided by engineer. */ +"Message shape" = "Berichtvorm"; + /* No comment provided by engineer. */ "Message source remains private." = "Berichtbron blijft privĆ©."; @@ -2921,7 +2949,7 @@ "Most likely this connection is deleted." = "Hoogstwaarschijnlijk is deze verbinding verwijderd."; /* No comment provided by engineer. */ -"Multiple chat profiles" = "Meerdere chat profielen"; +"Multiple chat profiles" = "Meerdere chatprofielen"; /* No comment provided by engineer. */ "mute" = "dempen"; @@ -3026,7 +3054,7 @@ "no e2e encryption" = "geen e2e versleuteling"; /* No comment provided by engineer. */ -"No filtered chats" = "Geen gefilterde gesprekken"; +"No filtered chats" = "Geen gefilterde chats"; /* No comment provided by engineer. */ "No group!" = "Groep niet gevonden!"; @@ -3081,7 +3109,7 @@ /* feature offered item */ "offered %@: %@" = "voorgesteld %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "OK"; /* No comment provided by engineer. */ @@ -3271,7 +3299,7 @@ "PING interval" = "PING interval"; /* No comment provided by engineer. */ -"Play from the chat list." = "Afspelen via de gesprekken lijst."; +"Play from the chat list." = "Afspelen via de chat lijst."; /* No comment provided by engineer. */ "Please ask your contact to enable calls." = "Vraag uw contactpersoon om oproepen in te schakelen."; @@ -3316,7 +3344,7 @@ "Please restart the app and migrate the database to enable push notifications." = "Start de app opnieuw en migreer de database om push meldingen in te schakelen."; /* No comment provided by engineer. */ -"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken."; +"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats."; /* No comment provided by engineer. */ "Please store passphrase securely, you will NOT be able to change it if you lose it." = "Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u het kwijtraakt."; @@ -3418,7 +3446,7 @@ "Protect IP address" = "Bescherm het IP-adres"; /* No comment provided by engineer. */ -"Protect your chat profiles with a password!" = "Bescherm je chat profielen met een wachtwoord!"; +"Protect your chat profiles with a password!" = "Bescherm je chatprofielen met een wachtwoord!"; /* No comment provided by engineer. */ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Bescherm uw IP-adres tegen de berichtenrelais die door uw contacten zijn gekozen.\nSchakel dit in in *Netwerk en servers*-instellingen."; @@ -3580,6 +3608,9 @@ /* No comment provided by engineer. */ "Remove" = "Verwijderen"; +/* No comment provided by engineer. */ +"Remove archive?" = "Archief verwijderen?"; + /* No comment provided by engineer. */ "Remove image" = "Verwijder afbeelding"; @@ -3662,7 +3693,7 @@ "Reset to user theme" = "Terugzetten naar gebruikersthema"; /* No comment provided by engineer. */ -"Restart the app to create a new chat profile" = "Start de app opnieuw om een nieuw chat profiel aan te maken"; +"Restart the app to create a new chat profile" = "Start de app opnieuw om een nieuw chatprofiel aan te maken"; /* No comment provided by engineer. */ "Restart the app to use imported chat database" = "Start de app opnieuw om de geĆÆmporteerde chat database te gebruiken"; @@ -3732,7 +3763,7 @@ "Save group profile" = "Groep profiel opslaan"; /* No comment provided by engineer. */ -"Save passphrase and open chat" = "Bewaar het wachtwoord en open je gesprekken"; +"Save passphrase and open chat" = "Bewaar het wachtwoord en open je chats"; /* No comment provided by engineer. */ "Save passphrase in Keychain" = "Sla het wachtwoord op in de Keychain"; @@ -3744,7 +3775,7 @@ "Save profile password" = "Bewaar profiel wachtwoord"; /* No comment provided by engineer. */ -"Save servers" = "Bewaar servers"; +"Save servers" = "Servers opslaan"; /* No comment provided by engineer. */ "Save servers?" = "Servers opslaan?"; @@ -3752,6 +3783,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Welkom bericht opslaan?"; +/* alert title */ +"Save your profile?" = "Uw profiel opslaan?"; + /* No comment provided by engineer. */ "saved" = "opgeslagen"; @@ -3833,6 +3867,9 @@ /* chat item action */ "Select" = "Selecteer"; +/* No comment provided by engineer. */ +"Select chat profile" = "Selecteer chatprofiel"; + /* No comment provided by engineer. */ "Selected %lld" = "%lld geselecteerd"; @@ -3905,7 +3942,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Stuur tot 100 laatste berichten naar nieuwe leden."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Afzender heeft bestandsoverdracht geannuleerd."; /* No comment provided by engineer. */ @@ -4046,6 +4083,9 @@ /* No comment provided by engineer. */ "Settings" = "Instellingen"; +/* alert message */ +"Settings were changed." = "Instellingen zijn gewijzigd."; + /* No comment provided by engineer. */ "Shape profile images" = "Vorm profiel afbeeldingen"; @@ -4067,6 +4107,9 @@ /* No comment provided by engineer. */ "Share link" = "Deel link"; +/* No comment provided by engineer. */ +"Share profile" = "Profiel delen"; + /* No comment provided by engineer. */ "Share this 1-time invite link" = "Deel deze eenmalige uitnodigingslink"; @@ -4169,6 +4212,9 @@ /* blur media */ "Soft" = "Soft"; +/* No comment provided by engineer. */ +"Some app settings were not migrated." = "Sommige app-instellingen zijn niet gemigreerd."; + /* No comment provided by engineer. */ "Some file(s) were not exported:" = "Sommige bestanden zijn niet geĆ«xporteerd:"; @@ -4392,11 +4438,14 @@ "The sender will NOT be notified" = "De afzender wordt NIET op de hoogte gebracht"; /* No comment provided by engineer. */ -"The servers for new connections of your current chat profile **%@**." = "De servers voor nieuwe verbindingen van uw huidige chat profiel **%@**."; +"The servers for new connections of your current chat profile **%@**." = "De servers voor nieuwe verbindingen van uw huidige chatprofiel **%@**."; /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "De tekst die u hebt geplakt is geen SimpleX link."; +/* No comment provided by engineer. */ +"The uploaded database archive will be permanently removed from the servers." = "Het geüploade databasearchief wordt permanent van de servers verwijderd."; + /* No comment provided by engineer. */ "Themes" = "Thema's"; @@ -4446,7 +4495,7 @@ "This link was used with another mobile device, please create a new link on the desktop." = "Deze link is gebruikt met een ander mobiel apparaat. Maak een nieuwe link op de desktop."; /* No comment provided by engineer. */ -"This setting applies to messages in your current chat profile **%@**." = "Deze instelling is van toepassing op berichten in je huidige chat profiel **%@**."; +"This setting applies to messages in your current chat profile **%@**." = "Deze instelling is van toepassing op berichten in je huidige chatprofiel **%@**."; /* No comment provided by engineer. */ "Title" = "Titel"; @@ -4479,7 +4528,7 @@ "To record voice message please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om een spraakbericht op te nemen."; /* No comment provided by engineer. */ -"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chat profielen**."; +"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoek veld in op de pagina **Uw chatprofielen**."; /* No comment provided by engineer. */ "To support instant push notifications the chat database has to be migrated." = "Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd."; @@ -4551,7 +4600,7 @@ "Unhide" = "zichtbaar maken"; /* No comment provided by engineer. */ -"Unhide chat profile" = "Chat profiel zichtbaar maken"; +"Unhide chat profile" = "Chatprofiel zichtbaar maken"; /* No comment provided by engineer. */ "Unhide profile" = "Profiel zichtbaar maken"; @@ -4574,7 +4623,7 @@ /* No comment provided by engineer. */ "unknown servers" = "onbekende relays"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Onbekende servers!"; /* No comment provided by engineer. */ @@ -4880,7 +4929,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Zonder Tor of VPN is uw IP-adres zichtbaar voor bestandsservers."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@."; /* No comment provided by engineer. */ @@ -4917,7 +4966,7 @@ "You allow" = "Jij staat toe"; /* No comment provided by engineer. */ -"You already have a chat profile with the same display name. Please choose another name." = "Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam."; +"You already have a chat profile with the same display name. Please choose another name." = "Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam."; /* No comment provided by engineer. */ "You are already connected to %@." = "U bent al verbonden met %@."; @@ -5141,9 +5190,15 @@ /* No comment provided by engineer. */ "Your chat database is not encrypted - set passphrase to encrypt it." = "Uw chat database is niet versleuteld, stel een wachtwoord in om deze te versleutelen."; +/* alert title */ +"Your chat preferences" = "Uw chat voorkeuren"; + /* No comment provided by engineer. */ "Your chat profiles" = "Uw chat profielen"; +/* No comment provided by engineer. */ +"Your connection was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Uw verbinding is verplaatst naar %@, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel."; + /* No comment provided by engineer. */ "Your contact sent a file that is larger than currently supported maximum size (%@)." = "Uw contact heeft een bestand verzonden dat groter is dan de momenteel ondersteunde maximale grootte (%@)."; @@ -5177,6 +5232,9 @@ /* 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."; +/* alert message */ +"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden."; + /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 7def1c7e02..11e6227596 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "Nie można przekazać wiadomości"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Nie można odebrać pliku"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Obniż wersję i otwórz czat"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Pobierz"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Błąd otwierania czatu"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Błąd odbioru pliku"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "zaoferował %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Ok"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Wysyłaj do 100 ostatnich wiadomości do nowych członków."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Nadawca anulował transfer pliku."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "nieznane przekaÅŗniki"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Nieznane serwery!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Bez Tor lub VPN, Twój adres IP będzie widoczny do serwerów plików."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaÅŗników XFTP: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 038041d0e6..ec024ed12a 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "ŠŠµŠ²Š¾Š·Š¼Š¾Š¶Š½Š¾ ŠæŠµŃ€ŠµŃŠ»Š°Ń‚ŃŒ сообщение"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ŠŠµŠ²Š¾Š·Š¼Š¾Š¶Š½Š¾ ŠæŠ¾Š»ŃƒŃ‡ŠøŃ‚ŃŒ файл"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "ŠžŃ‚ŠŗŠ°Ń‚ŠøŃ‚ŃŒ Š²ŠµŃ€ŃŠøŃŽ Šø Š¾Ń‚ŠŗŃ€Ń‹Ń‚ŃŒ чат"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Š—Š°Š³Ń€ŃƒŠ·ŠøŃ‚ŃŒ"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "ŠžŃˆŠøŠ±ŠŗŠ° Š“Š¾ŃŃ‚ŃƒŠæŠ° Šŗ Ń‡Š°Ń‚Ńƒ"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "ŠžŃˆŠøŠ±ŠŗŠ° при ŠæŠ¾Š»ŃƒŃ‡ŠµŠ½ŠøŠø файла"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "преГложил(a) %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "ŠžŠŗ"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "ŠžŃ‚ŠæŃ€Š°Š²ŠøŃ‚ŃŒ Го 100 послеГних сообщений новым членам."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "ŠžŃ‚ŠæŃ€Š°Š²ŠøŃ‚ŠµŠ»ŃŒ отменил ŠæŠµŃ€ŠµŠ“Š°Ń‡Ńƒ файла."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "неизвестные серверы"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "ŠŠµŠøŠ·Š²ŠµŃŃ‚Š½Ń‹Šµ серверы!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Без Тора или Š’ŠŸŠ, Š’Š°Ńˆ IP аГрес Š±ŃƒŠ“ŠµŃ‚ Š“Š¾ŃŃ‚ŃƒŠæŠµŠ½ серверам файлов."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Тора или Š’ŠŸŠ, Š’Š°Ńˆ IP аГрес Š±ŃƒŠ“ŠµŃ‚ Š“Š¾ŃŃ‚ŃƒŠæŠµŠ½ ŃŃ‚ŠøŠ¼ серверам файлов: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 994a10c9db..8813113fce 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -532,7 +532,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ą¹€ąø‚ą¹‰ąø²ąø–ąø¶ąø‡ keychain ą¹€ąøžąø·ą¹ˆąø­ąøšąø±ąø™ąø—ąø¶ąøąø£ąø«ąø±ąøŖąøœą¹ˆąø²ąø™ąøąø²ąø™ąø‚ą¹‰ąø­ąø”ąø¹ąø„"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ą¹„ąø”ą¹ˆąøŖąø²ąø”ąø²ąø£ąø–ąø£ąø±ąøšą¹„ąøŸąø„ą¹Œą¹„ąø”ą¹‰"; /* No comment provided by engineer. */ @@ -1308,7 +1308,7 @@ /* No comment provided by engineer. */ "Error loading %@ servers" = "ą¹‚ąø«ąø„ąø”ą¹€ąø‹ąø“ąø£ą¹ŒąøŸą¹€ąø§ąø­ąø£ą¹Œ %@ ąøœąø“ąø”ąøžąø„ąø²ąø”"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "ą¹€ąøąø“ąø”ąø‚ą¹‰ąø­ąøœąø“ąø”ąøžąø„ąø²ąø”ą¹ƒąø™ąøąø²ąø£ąø£ąø±ąøšą¹„ąøŸąø„ą¹Œ"; /* No comment provided by engineer. */ @@ -2097,7 +2097,7 @@ /* feature offered item */ "offered %@: %@" = "เสนอแค้ว %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "ตกคง"; /* No comment provided by engineer. */ @@ -2630,7 +2630,7 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "ąøŖą¹ˆąø‡ąøˆąø²ąøą¹ąøąø„ą¹€ąø„ąø­ąø£ąøµąø«ąø£ąø·ąø­ą¹ąø›ą¹‰ąø™ąøžąø“ąø”ąøžą¹Œą¹ąøšąøšąøąø³ąø«ąø™ąø”ą¹€ąø­ąø‡"; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "ąøœąø¹ą¹‰ąøŖą¹ˆąø‡ąø¢ąøą¹€ąø„ąø“ąøąøąø²ąø£ą¹‚ąø­ąø™ą¹„ąøŸąø„ą¹Œ"; /* No comment provided by engineer. */ diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index f39140340b..387d72d0cf 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -715,7 +715,7 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Veritabanı şifresini kaydetmek iƧin Anahtar Zinciri'ne erişilemiyor"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "Dosya alınamıyor"; /* snd error text */ @@ -1419,7 +1419,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Sürüm düşür ve sohbeti aƧ"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "İndir"; /* No comment provided by engineer. */ @@ -1707,7 +1708,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Sohbeti aƧarken sorun oluştu"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Dosya alınırken sorun oluştu"; /* No comment provided by engineer. */ @@ -2727,7 +2728,7 @@ /* feature offered item */ "offered %@: %@" = "%1$@: %2$@ teklif etti"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "Tamam"; /* No comment provided by engineer. */ @@ -3431,7 +3432,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Yeni üyelere 100 adete kadar son mesajları gƶnderin."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "Gƶnderici dosya gƶnderimini iptal etti."; /* No comment provided by engineer. */ @@ -3986,7 +3987,7 @@ /* No comment provided by engineer. */ "unknown servers" = "bilinmeyen yƶnlendiriciler"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "Bilinmeyen sunucular!"; /* No comment provided by engineer. */ @@ -4262,7 +4263,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Tor veya VPN olmadan, IP adresiniz dosya sunucularına gƶrülebilir."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından gƶrülebilir: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 4218065a2e..478f03860c 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "ŠŠµŠ¼Š¾Š¶Š»ŠøŠ²Š¾ переслати ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ŠŠµ Š²Š“Š°Ń”Ń‚ŃŒŃŃ отримати файл"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "ŠŸŠ¾Š½ŠøŠ¶ŠµŠ½Š½Ń та віГкритий чат"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "Завантажити"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "Помилка Š²Ń–Š“ŠŗŃ€ŠøŃ‚Ń‚Ń Ń‡Š°Ń‚Ńƒ"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "Помилка Š¾Ń‚Ń€ŠøŠ¼Š°Š½Š½Ń Ń„Š°Š¹Š»Ńƒ"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "Š·Š°ŠæŃ€Š¾ŠæŠ¾Š½ŃƒŠ²Š°Š² %1$@: %2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "ГаразГ"; /* No comment provided by engineer. */ @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "ŠŠ°Š“Ń–ŃˆŠ»Ń–Ń‚ŃŒ Го 100 останніх ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½ŃŒ новим ŠŗŠ¾Ń€ŠøŃŃ‚ŃƒŠ²Š°Ń‡Š°Š¼."; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "ВіГправник скасував ŠæŠµŃ€ŠµŠ“Š°Ń‡Ńƒ Ń„Š°Š¹Š»Ńƒ."; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "невіГомі реле"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "ŠŠµŠ²Ń–Š“Š¾Š¼Ń– сервери!"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "Без Tor або VPN ваша IP-аГреса буГе Š²ŠøŠ“ŠøŠ¼Š¾ŃŽ Š“Š»Ń файлових серверів."; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Tor або VPN ваша IP-аГреса буГе Š²ŠøŠ“ŠøŠ¼Š¾ŃŽ Š“Š»Ń цих XFTP-Ń€ŠµŃ‚Ń€Š°Š½ŃŠ»ŃŃ‚Š¾Ń€Ń–Š²: %@."; /* No comment provided by engineer. */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 0f78665b83..ca2d0b5134 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -796,7 +796,7 @@ /* No comment provided by engineer. */ "Cannot forward message" = "ę— ę³•č½¬å‘ę¶ˆęÆ"; -/* No comment provided by engineer. */ +/* alert title */ "Cannot receive file" = "ę— ę³•ęŽ„ę”¶ę–‡ä»¶"; /* snd error text */ @@ -1629,7 +1629,8 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "é™ēŗ§å¹¶ę‰“å¼€čŠå¤©"; -/* chat item action */ +/* alert button + chat item action */ "Download" = "äø‹č½½"; /* No comment provided by engineer. */ @@ -1938,7 +1939,7 @@ /* No comment provided by engineer. */ "Error opening chat" = "ę‰“å¼€čŠå¤©ę—¶å‡ŗé”™"; -/* No comment provided by engineer. */ +/* alert title */ "Error receiving file" = "ęŽ„ę”¶ę–‡ä»¶é”™čÆÆ"; /* No comment provided by engineer. */ @@ -3081,7 +3082,7 @@ /* feature offered item */ "offered %@: %@" = "å·²ęä¾› %1$@:%2$@"; -/* No comment provided by engineer. */ +/* alert button */ "Ok" = "å„½ēš„"; /* No comment provided by engineer. */ @@ -3520,7 +3521,7 @@ "Receiving via" = "ęŽ„ę”¶é€ščæ‡"; /* No comment provided by engineer. */ -"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "ęœ€čæ‘ēš„åŽ†å²č®°å½•å’Œę”¹čæ›ēš„ [ē›®å½•ęœŗå™Øäŗŗ](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "ęœ€čæ‘ēš„åŽ†å²č®°å½•å’Œę”¹čæ›ēš„ [ē›®å½•ęœŗå™Øäŗŗ](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; /* No comment provided by engineer. */ "Recipient(s) can't see who this message is from." = "ę”¶ä»¶äŗŗēœ‹äøåˆ°čæ™ę”ę¶ˆęÆę„č‡Ŗä½•äŗŗć€‚"; @@ -3905,7 +3906,7 @@ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "ē»™ę–°ęˆå‘˜å‘é€ęœ€å¤š 100 ę”åŽ†å²ę¶ˆęÆć€‚"; -/* No comment provided by engineer. */ +/* alert message */ "Sender cancelled file transfer." = "å‘é€äŗŗå·²å–ę¶ˆę–‡ä»¶ä¼ č¾“ć€‚"; /* No comment provided by engineer. */ @@ -4574,7 +4575,7 @@ /* No comment provided by engineer. */ "unknown servers" = "ęœŖēŸ„ęœåŠ”å™Ø"; -/* No comment provided by engineer. */ +/* alert title */ "Unknown servers!" = "ęœŖēŸ„ęœåŠ”å™Øļ¼"; /* No comment provided by engineer. */ @@ -4880,7 +4881,7 @@ /* No comment provided by engineer. */ "Without Tor or VPN, your IP address will be visible to file servers." = "å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ę–‡ä»¶ęœåŠ”å™ØåÆč§ć€‚"; -/* No comment provided by engineer. */ +/* alert message */ "Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "å¦‚ęžœę²”ęœ‰ Tor ꈖ VPNļ¼Œę‚Øēš„ IP åœ°å€å°†åÆ¹ä»„äø‹ XFTP äø­ē»§åÆč§ļ¼š%@怂"; /* No comment provided by engineer. */ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 8f4b0158a7..405cc945cc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -31,7 +31,7 @@ أضِف Ų®ŁˆŲ§ŲÆŁ… محدّدة مسبقًا أضِف ؄لى جهاز Ų¢Ų®Ų± Ų³ŁŠŲŖŁ… حذف Ų¬Ł…ŁŠŲ¹ الدردؓات ŁˆŲ§Ł„Ų±Ų³Ų§Ų¦Ł„ - لا ŁŠŁ…ŁƒŁ† التراجع عن هذا! - Ų§Ł„ŁˆŲµŁˆŁ„ ؄لى Ų§Ł„Ų®ŁˆŲ§ŲÆŁ… Ų¹ŲØŲ± بروكسي SOCKS على المنفذ %d؟ يجب ŲØŲÆŲ” ŲŖŲ“ŲŗŁŠŁ„ Ų§Ł„ŁˆŁƒŁŠŁ„ قبل ŲŖŁ…ŁƒŁŠŁ† هذا Ų§Ł„Ų®ŁŠŲ§Ų±. + Ų§Ł„ŁˆŲµŁˆŁ„ ؄لى Ų§Ł„Ų®ŁˆŲ§ŲÆŁ… Ų¹ŲØŲ± ŁˆŁƒŁŠŁ„ SOCKS على المنفذ %d؟ يجب ŲØŲÆŲ” ŲŖŲ“ŲŗŁŠŁ„ Ų§Ł„ŁˆŁƒŁŠŁ„ قبل ŲŖŁ…ŁƒŁŠŁ† هذا Ų§Ł„Ų®ŁŠŲ§Ų±. أضِف Ų®Ų§ŲÆŁ… Ų„Ų¹ŲÆŲ§ŲÆŲ§ŲŖ Ų§Ł„Ų“ŲØŁƒŲ© المتقدمة Ų³ŁŠŲØŁ‚Ł‰ Ų¬Ł…ŁŠŲ¹ Ų£Ų¹Ų¶Ų§Ų” Ų§Ł„Ł…Ų¬Ł…ŁˆŲ¹Ų© على Ų§ŲŖŲµŲ§Ł„. @@ -50,7 +50,7 @@ السماح ŲØŲ§Ł„Ł…ŁƒŲ§Ł„Ł…Ų§ŲŖ فقط Ų„Ų°Ų§ سمحت جهة Ų§ŲŖŲµŲ§Ł„Łƒ ŲØŲ°Ł„Łƒ. اسمح بردود الفعل على الرسائل فقط Ų„Ų°Ų§ سمحت جهة Ų§ŲŖŲµŲ§Ł„Łƒ ŲØŲ°Ł„Łƒ. ŁŠŲŖŁ… Ų§Ų³ŲŖŲ®ŲÆŲ§Ł… Android Keystore Ł„ŲŖŲ®Ų²ŁŠŁ† Ų¹ŲØŲ§Ų±Ų© Ų§Ł„Ł…Ų±ŁˆŲ± ŲØŲ“ŁƒŁ„ آمن - ŁŁ‡Łˆ ŁŠŲ³Ł…Ų­ لخدمة ال؄ؓعارات بالعمل. - ŁŠŲŖŁ… ؄نؓاؔ ملف تعريف ŲÆŲ±ŲÆŲ“Ų© فارغ بالاسم المقدم ، ويفتح Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚ ŁƒŲ§Ł„Ł…Ų¹ŲŖŲ§ŲÆ. + ŁŠŲŖŁ… ؄نؓاؔ ملف تعريف ŲÆŲ±ŲÆŲ“Ų© فارغ بالاسم Ų§Ł„Ł…Ł‚ŲÆŁ…ŲŒ ويفتح Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚ ŁƒŲ§Ł„Ł…Ų¹ŲŖŲ§ŲÆ. Ų£Ų¬ŲØ الاتصال دائِماً السماح ŲØŲ„Ų±Ų³Ų§Ł„ رسائل تختفي. @@ -141,7 +141,7 @@ لا ŁŠŁ…ŁƒŁ† ŲŖŁ‡ŁŠŲ¦Ų© قاعدة Ų§Ł„ŲØŁŠŲ§Ł†Ų§ŲŖ ؄رفاق طلب لاستلام Ų§Ł„ŲµŁˆŲ±Ų© - نسخة Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚: v%s + Ų„ŲµŲÆŲ§Ų± Ų§Ł„ŲŖŲ·ŲØŁŠŁ‚: v%s Ł‚ŲØŁˆŁ„ ŲŖŁ„Ł‚Ų§Ų¦ŁŠ Ų§Ł„Ł…ŁƒŲ§Ł„Ł…Ų§ŲŖ لا ŁŠŁ…ŁƒŁ† دعوة جهات الاتصال! @@ -1768,9 +1768,9 @@ ŲŖŁ… تجاوز السعة - لم ŁŠŲŖŁ„Ł‚ Ų§Ł„Ł…ŁŲ³ŲŖŁ„Ł… الرسائل Ų§Ł„Ł…ŁŲ±Ų³Ł„Ų© مسبقًا. Ų®Ų·Ų£ في Ų®Ų§ŲÆŁ… Ų§Ł„ŁˆŲ¬Ł‡Ų©: %1$s Ų®Ų·Ų£: %1$s - Ų®Ų§ŲÆŁ… Ų„Ų¹Ų§ŲÆŲ© Ų§Ł„ŲŖŁˆŲ¬ŁŠŁ‡: %1$s + Ų®Ų§ŲÆŁ… Ų§Ł„ŲŖŲ­ŁˆŁŠŁ„: %1$s \nŲ®Ų·Ų£ في الخادم Ų§Ł„ŁˆŲ¬Ł‡Ų©: %2$s - Ų®Ų§ŲÆŁ… Ų„Ų¹Ų§ŲÆŲ© Ų§Ł„ŲŖŁˆŲ¬ŁŠŁ‡: %1$s + Ų®Ų§ŲÆŁ… Ų§Ł„ŲŖŲ­ŁˆŁŠŁ„: %1$s \nŲ®Ų·Ų£: %2$s تحذير ŲŖŲ³Ł„ŁŠŁ… الرسالة Ł…Ų“ŁƒŁ„Ų§ŲŖ Ų§Ł„Ų“ŲØŁƒŲ© - انتهت ŲµŁ„Ų§Ų­ŁŠŲ© الرسالة ŲØŲ¹ŲÆ عِدة Ł…Ų­Ų§ŁˆŁ„Ų§ŲŖ ل؄رسالها. @@ -2067,4 +2067,27 @@ صفّر كافة Ų§Ł„ŲŖŁ„Ł…ŁŠŲ­Ų§ŲŖ ŁŠŁŲ±Ų¬Ł‰ Ų§Ł„ŲŖŲ£ŁƒŲÆ من أن Ų±Ų§ŲØŲ· SimpleX صحيح. الرابط غير صالح + %1$d Ų®Ų·Ų£ في الملف: +\n%2$s + فؓل ŲŖŁ†Ų²ŁŠŁ„ %1$d ملف/Ų§ŲŖ. + لم ŁŠŲŖŁ… ŲŖŁ†Ų²ŁŠŁ„ %1$d ملف/Ų§ŲŖ. + نزّل + ؓارك ملف Ų§Ł„ŲŖŲ¹Ų±ŁŠŁ + Ų§Ų³ŲŖŲ®ŲÆŁ… ŲØŁŠŲ§Ł†Ų§ŲŖ Ų§Ų¹ŲŖŁ…Ų§ŲÆ Ų§Ł„ŁˆŁƒŁŠŁ„ المختلفة Ł„ŁƒŁ„ Ų§ŲŖŲµŲ§Ł„. + اسم المستخدم + قد ŁŠŲŖŁ… Ų„Ų±Ų³Ų§Ł„ ŲØŁŠŲ§Ł†Ų§ŲŖ الاعتماد الخاصة بك غير Ł…ŁŲ¹Ł…ŁŽŁ‘Ų§Ų©. + Ų®Ų·Ų£ في حفظ Ų§Ł„ŁˆŁƒŁŠŁ„ + ؄زالة Ų§Ł„Ų£Ų±Ų“ŁŠŁŲŸ + وضع النظام + Ų³ŁŠŲŖŁ… ؄زالة أرؓيف قاعدة Ų§Ł„ŲØŁŠŲ§Ł†Ų§ŲŖ Ų§Ł„Ł…Ų±ŁŁˆŲ¹Ų© Ł†Ł‡Ų§Ų¦ŁŠŁ‹Ų§ من Ų§Ł„Ų®ŁˆŲ§ŲÆŁ…. + Ų§Ų³ŲŖŲ®ŲÆŁ… ŲØŁŠŲ§Ł†Ų§ŲŖ Ų§Ų¹ŲŖŁ…Ų§ŲÆ Ų§Ł„ŁˆŁƒŁŠŁ„ المختلفة Ł„ŁƒŁ„ ملف تعريف. + Ų§Ų³ŲŖŲ®ŲÆŁ… ŲØŁŠŲ§Ł†Ų§ŲŖ Ų§Ų¹ŲŖŁ…Ų§ŲÆ عؓوائية + قاعدة ŲØŁŠŲ§Ł†Ų§ŲŖ الدردؓة + Ų­ŁŲ°Ł %1$d ملف/Ų§ŲŖ. + لا ŁŠŲ²Ų§Ł„ ŁŠŲŖŁ… ŲŖŁ†Ų²ŁŠŁ„ %1$d ملفًا. + لا ŲŖŲ³ŲŖŲ®ŲÆŁ… ŲØŁŠŲ§Ł†Ų§ŲŖ الاعتماد Ł…Ų¹ Ų§Ł„ŁˆŁƒŁŠŁ„. + Ų®Ų·Ų£ في ŲŖŲ­ŁˆŁŠŁ„ الرسائل + Ų®Ų·Ų£ في ŲŖŲØŲÆŁŠŁ„ الملف Ų§Ł„Ų“Ų®ŲµŁŠ + Ų­ŲÆŲÆ ملف تعريف الدردؓة + لقد ŲŖŁ… نقل Ų§ŲŖŲµŲ§Ł„Łƒ ؄لى %s ŁˆŁ„ŁƒŁ† Ų­ŲÆŲ« Ų®Ų·Ų£ غير Ł…ŲŖŁˆŁ‚Ų¹ أثناؔ Ų„Ų¹Ų§ŲÆŲ© ŲŖŁˆŲ¬ŁŠŁ‡Łƒ ؄لى الملف Ų§Ł„Ų“Ų®ŲµŁŠ. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index e7b327c4ff..8aa9c97dae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -1061,7 +1061,7 @@ Bluetooth-Unterstützung und weitere Verbesserungen. Administratoren kƶnnen nun \n- Nachrichten von Gruppenmitgliedern lƶschen -\n- Gruppenmitglieder deaktivieren (ā€žBeobachterā€œ-Rolle) +\n- Gruppenmitglieder deaktivieren (Beobachter-Rolle) Gruppen-Begrüßungsmeldung Weiter reduzierter Batterieverbrauch Weitere Verbesserungen sind bald verfügbar! @@ -2151,4 +2151,23 @@ Neue Nachricht Bitte überprüfen Sie, ob der SimpleX-Link korrekt ist. Ungültiger Link + CHAT-DATENBANK + Fehler beim Wechseln des Profils + Die Nachrichten werden gelƶscht. Dies kann nicht rückgƤngig gemacht werden! + Profil teilen + System-Modus + Das hochgeladene Datenbank-Archiv wird dauerhaft von den Servern entfernt. + Chat-Profil auswƤhlen + Archiv entfernen? + Ihre Anmeldeinformationen kƶnnen unverschlüsselt versendet werden. + Verwenden Sie keine Anmeldeinformationen mit einem Proxy. + Ihre Verbindung wurde auf %s verschoben, aber wƤhrend der Weiterleitung auf das Profil trat ein unerwarteter Fehler auf. + Stellen Sie sicher, dass die Proxy-Konfiguration richtig ist. + Fehler beim Speichern des Proxys + Passwort + Proxy-Authentifizierung + Verwenden Sie für jede Verbindung unterschiedliche Proxy-Anmeldeinformationen. + Verwenden Sie für jedes Profil unterschiedliche Proxy-Anmeldeinformationen. + Verwenden Sie zufƤllige Anmeldeinformationen + Benutzername \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 3ec6bc021b..2ee1c89791 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -9,11 +9,11 @@ 6 Ćŗj kezelőfelületi nyelv 5 perc 1 perc - A SimpleX cĆ­mről + A SimpleX-cĆ­mről CĆ­mvĆ”ltoztatĆ”s megszakĆ­tĆ”sa? MegszakĆ­tĆ”s 30 mĆ”sodperc - Egyszer hasznĆ”latos hivatkozĆ”s + Egyszer hasznĆ”lható hivatkozĆ”s %1$s szeretne kapcsolatba lĆ©pni ƶnnel ezen keresztül: A SimpleX Chatről 1 nap @@ -26,7 +26,7 @@ ElfogadĆ”s gombra fent, majd: ElfogadĆ”s inkognĆ­tóban - IsmerőskĆ©relem elfogadĆ”sa? + KapcsolatkĆ©rĆ©s elfogadĆ”sa? ElfogadĆ”s ElfogadĆ”s CĆ­m hozzĆ”adĆ”sa a profilhoz, hogy az ismerősei megoszthassĆ”k mĆ”sokkal. A profilfrissĆ­tĆ©s elküldĆ©sre kerül az ismerősƶk szĆ”mĆ”ra. @@ -38,23 +38,23 @@ %s visszavonva Előre beĆ”llĆ­tott kiszolgĆ”lók hozzĆ”adĆ”sa A hĆ­vĆ”sok kezdemĆ©nyezĆ©se le van tiltva ebben a csevegĆ©sben. - Külƶn TCP kapcsolat (Ć©s SOCKS bejelentkezĆ©si adatok) lesz hasznĆ”lva minden ismerős Ć©s csoporttag szĆ”mĆ”ra. \u0020 -\nFigyelem: ha sok ismerőse van, az akkumulĆ”tor- Ć©s az adathasznĆ”lat jelentősen megnƶvekedhet, Ć©s nĆ©hĆ”ny kapcsolódĆ”si kĆ­sĆ©rlet sikertelen lehet. + Minden egyes kapcsolathoz Ć©s csoporttaghoz külƶn TCP-kapcsolat (Ć©s SOCKS hitelesĆ­tő adat) lesz hasznĆ”lva. +\nFigyelem: ha sok kapcsolata van, az akkumulĆ”tor-hasznĆ”lat Ć©s az adatforgalom jelentősen megnƶvekedhet, Ć©s nĆ©hĆ”ny kapcsolódĆ”si kĆ­sĆ©rlet sikertelen lehet. hivatkozĆ”s előnĆ©zetĆ©nek visszavonĆ”sa - az alkalmazĆ”sban minden csevegĆ©si profiljĆ”hoz .]]> + Minden egyes kapcsolathoz Ć©s csoporttaghoz külƶn TCP-kapcsolat (Ć©s SOCKS hitelesĆ­tő adat) lesz hasznĆ”lva.]]> MindkĆ©t fĆ©l küldhet eltűnő üzeneteket. Az Android Keystore-t a jelmondat biztonsĆ”gos tĆ”rolĆ”sĆ”ra hasznĆ”ljĆ”k - lehetővĆ© teszi az Ć©rtesĆ­tĆ©si szolgĆ”ltatĆ”s műkƶdĆ©sĆ©t. HibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke HĆ”ttĆ©r - Tudnivaló: az üzenet- Ć©s fĆ”jl Ć”tjĆ”tszók SOCKS proxy Ć”ltal vannak kapcsolatban. A hĆ­vĆ”sok Ć©s URL hivatkozĆ”s előnĆ©zetek kƶzvetlen kapcsolatot hasznĆ”lnak.]]> + Figyelem: az üzenet- Ć©s fĆ”jl Ć”tjĆ”tszók SOCKS proxy Ć”ltal vannak kapcsolatban. A hĆ­vĆ”sok Ć©s URL hivatkozĆ”s előnĆ©zetek kƶzvetlen kapcsolatot hasznĆ”lnak.]]> AlkalmazĆ”sadatok biztonsĆ”gi mentĆ©se Az adatbĆ”zis inicializĆ”lĆ”sa sikertelen Ismerőseivel kapcsolatban marad. A profil vĆ”ltoztatĆ”sok frissĆ­tĆ©sre kerülnek az ismerősƶknĆ©l. A csevegĆ©si profil Ć”ltal (alap beĆ”llĆ­tĆ”s), vagy a kapcsolat Ć”ltal (BƉTA). Egy Ćŗj vĆ©letlenszerű profil lesz megosztva. - A hangüzenetek küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + A hangüzenetek küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. Az alkalmazĆ”s build szĆ”ma: %s - Hang-/videóhĆ­vĆ”sok + Hang- Ć©s videóhĆ­vĆ”sok SpeciĆ”lis hĆ”lózati beĆ”llĆ­tĆ”sok A hangüzenetek küldĆ©se engedĆ©lyezve van az ismerősei szĆ”mĆ”ra. Hang- Ć©s videóhĆ­vĆ”sok @@ -65,8 +65,8 @@ Nem lehet fogadni a fĆ”jlt HitelesĆ­tĆ©s elĆ©rhetetlen AlkalmazĆ”s verzió - Üdvƶzlő üzenet hozzĆ”adĆ”sa - a(z) %s titkosĆ­tĆ”sĆ”nak elfogadĆ”sa… + Üdvƶzlőüzenet hozzĆ”adĆ”sa + titkosĆ­tĆ”s elfogadĆ”sa %s szĆ”mĆ”ra… " \nElĆ©rhető a v5.1-ben" MindkĆ©t fĆ©l vĆ©glegesen tƶrƶlheti az elküldƶtt üzeneteket. (24 óra) @@ -84,7 +84,7 @@ TĆ©ves üzenet ID Az üzenetreakciók küldĆ©se engedĆ©lyezve van az ismerősei szĆ”mĆ”ra. A hangüzenetek küldĆ©se engedĆ©lyezve van. - Az üzenetreakciók küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + Az üzenetreakciók küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. Vissza Kikapcsolható a beĆ”llĆ­tĆ”sokban – az Ć©rtesĆ­tĆ©sek tovĆ”bbra is megjelenĆ­tĆ©sre kerülnek amĆ­g az alkalmazĆ”s fut.]]> Az adminok hivatkozĆ”sokat hozhatnak lĆ©tre a csoportokhoz való csatlakozĆ”shoz. @@ -92,13 +92,13 @@ titkosĆ­tĆ”s elfogadĆ”sa… Ismerősƶk meghĆ­vĆ”sa le van tiltva! tĆ©ves üzenet ID - KapcsolódĆ”si kĆ©relmek automatikus elfogadĆ”sa - Figyelem: NEM fogja tudni helyreĆ”llĆ­tani, vagy megvĆ”ltoztatni a jelmondatot abban az esetben, ha elveszĆ­ti.]]> + KapcsolatkĆ©rĆ©sek automatikus elfogadĆ”sa + Figyelem: NEM fogja tudni helyreĆ”llĆ­tani, vagy megvĆ”ltoztatni a jelmondatot abban az esetben, ha elveszĆ­ti.]]> hĆ­vĆ”s… TovĆ”bbi mĆ”sodlagos HozzĆ”adĆ”s egy mĆ”sik eszkƶzhƶz Az üzenetreakciók küldĆ©se engedĆ©lyezve van. - FĆ”jl előnĆ©zet visszavonĆ”sa + FĆ”jlelőnĆ©zet visszavonĆ”sa Minden csoporttag kapcsolatban marad. Tƶbb akkumulĆ”tort hasznĆ”l! Az alkalmazĆ”s mindig fut a hĆ”ttĆ©rben - az Ć©rtesĆ­tĆ©sek azonnal megjelennek.]]> LetiltĆ”s @@ -109,7 +109,7 @@ LetiltĆ”s MĆ©g nĆ©hĆ”ny dolog HitelesĆ­tĆ©s visszavonva - A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se engedĆ©lyezve van. + A fĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©se engedĆ©lyezve van. Minden csevegĆ©s Ć©s üzenet tƶrlĆ©sre kerül - ez a művelet nem vonható vissza! hanghĆ­vĆ”s fĆ©lkƶvĆ©r @@ -122,7 +122,7 @@ EngedĆ©lyezĆ©s Minden ismerősĆ©vel kapcsolatban marad. Ɖlő csevegĆ©si üzenet visszavonĆ”sa - Az üzenetek vĆ©gleges tƶrlĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra) + Az üzenetek vĆ©gleges tƶrlĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. (24 óra) Hang- Ć©s videóhĆ­vĆ”sok hibĆ”s az üzenet hasĆ­tó Ć©rtĆ©ke Mindig fut @@ -130,7 +130,7 @@ Minden alkalmazĆ”sadat tƶrƶlve. Legjobb akkumulĆ”toridő. Csak akkor kap Ć©rtesĆ­tĆ©seket, amikor az alkalmazĆ”s meg van nyitva. (NINCS hĆ”ttĆ©rszolgĆ”ltatĆ”s.)]]> MegjelenĆ©s - Az akkumulĆ”tor optimalizĆ”lĆ”sa aktĆ­v, mely kikapcsolja a hĆ”ttĆ©rszolgĆ”ltatĆ”st Ć©s az Ćŗj üzenetek rendszeres kĆ©rĆ©sĆ©t. A beĆ”llĆ­tĆ”sokon keresztül Ćŗjra engedĆ©lyezhetők. + Az akkumulĆ”tor-optimalizĆ”lĆ”s aktĆ­v, ez kikapcsolja a hĆ”ttĆ©rszolgĆ”ltatĆ”st Ć©s az Ćŗj üzenetek rendszeres lekĆ©rdezĆ©sĆ©t. A beĆ”llĆ­tĆ”sokban ĆŗjraengedĆ©lyezheti. Biztosan letiltja? %1$s hĆ­vĆ”sa befejeződƶtt Jó akkumulĆ”toridő. Az alkalmazĆ”s 10 percenkĆ©nt ellenőrzi az Ćŗj üzeneteket. Előfordulhat, hogy hĆ­vĆ”sokról, vagy a sürgős üzenetekről marad le.]]> @@ -154,7 +154,7 @@ ALKALMAZƁS IKON KiszolgĆ”ló hozzĆ”adĆ”sa QR-kód beolvasĆ”sĆ”val. Az eltűnő üzenetek küldĆ©se engedĆ©lyezve van. - Az eltűnő üzenetek küldĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + Az eltűnő üzenetek küldĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. Hang kikapcsolva A kƶzvetlen üzenetek küldĆ©se a tagok kƶzƶtt engedĆ©lyezve van. AlkalmazĆ”s @@ -164,7 +164,7 @@ Sikertelen hitelesĆ­tĆ©s Minden %s Ć”ltal Ć­rt Ćŗj üzenet elrejtĆ©sre kerül! AlkalmazĆ”s verzió: v%s - A hĆ­vĆ”sok kezdemĆ©nyezĆ©se kizĆ”rólag abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. + A hĆ­vĆ”sok kezdemĆ©nyezĆ©se csak abban az esetben van engedĆ©lyezve, ha az ismerőse is engedĆ©lyezi. KiszolgĆ”ló hozzĆ”adĆ”sa Hang bekapcsolva hanghĆ­vĆ”s (nem e2e titkosĆ­tott) @@ -187,7 +187,7 @@ kapcsolódva Szerepkƶr megvĆ”ltoztatĆ”sa Kapcsolódva - BelĆ©pĆ©si adatok megerősĆ­tĆ©se + HitelesĆ­tő adatok megerősĆ­tĆ©se MegvĆ”ltoztatja a fogadó cĆ­met? cĆ­m megvĆ”ltoztatva ƖnmegsemmisĆ­tő mód megvĆ”ltoztatĆ”sa @@ -201,7 +201,7 @@ Csoport lĆ©trehozĆ”sa vĆ©letlenszerű profillal. Az ismerős Ć©s az ƶsszes üzenet tƶrlĆ©sre kerül - ez a művelet nem vonható vissza! Az ismerősei tƶrlĆ©sre jelƶlhetnek üzeneteket; ƶn majd meg tudja nĆ©zni azokat. - KapcsolódĆ”s egyszer hasznĆ”latos hivatkozĆ”ssal? + KapcsolódĆ”s egyszer hasznĆ”lható hivatkozĆ”ssal? KapcsolódĆ”s egy hivatkozĆ”son vagy QR-kódon keresztül KapcsolódĆ”si hiba (AUTH) Ismerős neve @@ -215,7 +215,7 @@ Ismerős ellenőrizve KapcsolódĆ”s sajĆ”t magĆ”hoz? KimĆ”solva a vĆ”gólapra - KapcsolódĆ”si kĆ©rĆ©s elküldve! + KapcsolatkĆ©rĆ©s elküldve! KapcsolódĆ”s a szĆ”mĆ­tógĆ©phez Kapcsolat NĆ©v helyesbĆ­tĆ©se erre: %s? @@ -228,7 +228,7 @@ kapcsolat lĆ©trehozva az ismerősnek nincs e2e titkosĆ­tĆ”sa Ismerős engedĆ©lyezi - Ismerős elrejtve: + Rejtett nĆ©v: TĆ”rsĆ­tĆ”s szĆ”mĆ­tógĆ©ppel Kƶrnyezeti ikon KapcsolódĆ”s egy hivatkozĆ”son keresztül @@ -240,11 +240,11 @@ \n- gyorsabb Ć©s stabilabb. HozzĆ”jĆ”rulĆ”s kapcsolódĆ”s (bemutatkozó meghĆ­vó) - SimpleX cĆ­m lĆ©trehozĆ”sa + SimpleX-cĆ­m lĆ©trehozĆ”sa tƶrƶlt ismerős Csoporttag üzenetĆ©nek tƶrlĆ©se? A csevegĆ©s fut - Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s lĆ©trehozĆ”sa + Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s lĆ©trehozĆ”sa TƶrlĆ©s Új üzenetek ellenőrzĆ©se 10 percenkĆ©nt, legfeljebb 1 percen keresztül AdatbĆ”zis tƶrlĆ©se @@ -304,7 +304,7 @@ cĆ­m megvĆ”ltoztatĆ”sa… kapcsolódĆ”s… HĆ­vĆ”s kapcsolĆ”sa - FĆ”jlok Ć©s a mĆ©diatartalmak tƶrlĆ©se? + A fĆ”jlok- Ć©s a mĆ©diatartalmak tƶrlĆ©se? befejezett CSEVEGƉSI ADATBƁZIS ƖnmegsemmisĆ­tó jelkód megvĆ”ltoztatĆ”sa @@ -334,7 +334,7 @@ cĆ­m megvĆ”ltoztatĆ”sa… TĆ”rsĆ­tva a mobil eszkƶzzel Jelenlegi jelmondat… - FĆ”jl kivĆ”lasztĆ”s + FĆ”jl kivĆ”lasztĆ”sa KĆ©p tƶrlĆ©se FĆ”jl lĆ©trehozĆ”sa Tikos csoport lĆ©trehozĆ”sa @@ -343,7 +343,7 @@ KiürĆ­tĆ©s CĆ­m lĆ©trehozĆ”sa, hogy az emberek kapcsolatba lĆ©phessenek ƶnnel. BiztonsĆ”gi kódok ƶsszehasonlĆ­tĆ”sa az ismerőseiĆ©vel. - FĆ”jl ƶsszehasonlĆ­tĆ”s + FĆ”jl-ƶsszehasonlĆ­tĆ”s CsevegĆ©sek Üzenet tƶrlĆ©se? Függőben lĆ©vő ismerőskĆ©relem tƶrlĆ©se? @@ -361,7 +361,7 @@ Az adatbĆ”zis-titkosĆ­tĆ”si jelmondat megvĆ”ltoztatĆ”sra Ć©s mentĆ©sre kerül a Keystore-ban. Az adatbĆ”zis titkosĆ­tĆ”sra kerül Ć©s a jelmondat eltĆ”rolĆ”sra a beĆ”llĆ­tĆ”sokban. KiszolgĆ”ló tƶrlĆ©se - A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r ki van kapcsolva. + A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX-zĆ”r ki van kapcsolva. LetiltĆ”s LetiltĆ”s minden csoport szĆ”mĆ”ra EngedĆ©lyezĆ©s minden csoport szĆ”mĆ”ra @@ -372,7 +372,7 @@ SzĆ”mĆ­tógĆ©p cĆ­me %dmp KĆ©zbesĆ­tĆ©si jelentĆ©sek! - A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be, miutĆ”n beĆ”llĆ­totta a kĆ©pernyőzĆ”rat az eszkƶzĆ©n. + A kĆ©szülĆ©ken nincs beĆ”llĆ­tva a kĆ©pernyőzĆ”r. A SimpleX-zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be, miutĆ”n beĆ”llĆ­totta a kĆ©pernyőzĆ”rat az eszkƶzĆ©n. TitkosĆ­tĆ”s visszafejtĆ©si hiba Eltűnik ekkor: %s szerkesztve @@ -380,7 +380,7 @@ %d óra %d hónap CĆ­m tƶrlĆ©se? - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek letiltĆ”sa? + KĆ©zbesĆ­tĆ©si jelentĆ©sek letiltĆ”sa? Az adatbĆ”zis jelmondat eltĆ©r a Keystore-ban lĆ©vőtől. Kƶzvetlen üzenetek E-mail @@ -393,7 +393,7 @@ %dó %dhĆ©t FelfedezĆ©s helyi hĆ”lózaton keresztül - Helyi csoportok felfedezĆ©se Ć©s csatlakozĆ”s + Csoportok felfedezĆ©se Ć©s csatlakozĆ”s %1$d üzenet moderĆ”lva lett %2$s Ć”ltal Eltűnő üzenet Ne hozzon lĆ©tre cĆ­met @@ -411,7 +411,7 @@ A kƶzvetlen üzenetek küldĆ©se a tagok kƶzƶtt le van tiltva ebben a csoportban. %d perc Az adatbĆ”zis egy vĆ©letlenszerű jelmondattal van titkosĆ­tva. ExportĆ”lĆ”s előtt vĆ”ltoztassa meg a jelmondatot. - Üzenet kĆ©zbesĆ­tĆ©s jelentĆ©seket letiltĆ”sa a csoportok szĆ”mĆ”ra? + KĆ©zbesĆ­tĆ©s jelentĆ©sek letiltĆ”sa a csoportok szĆ”mĆ”ra? nap %d nap CsevegĆ©si archĆ­vum tƶrlĆ©se? @@ -472,9 +472,9 @@ Hiba az adatbĆ”zis titkosĆ­tĆ”sakor Hiba a csoport tƶrlĆ©sekor KilĆ©pĆ©s mentĆ©s nĆ©lkül - TĆ”rolt fĆ”jlok Ć©s mĆ©diatartalmak titkosĆ­tĆ”sa + A tĆ”rolt fĆ”jlok- Ć©s a mĆ©diatartalmak titkosĆ­tĆ”sa Hiba a cĆ­m beĆ”llĆ­tĆ”sakor - A csoport meghĆ­vó lejĆ”rt + A csoportmeghĆ­vó lejĆ”rt Hiba az ICE kiszolgĆ”lók mentĆ©sekor Hiba Hiba @@ -487,11 +487,11 @@ \nCsatlakozzon hozzĆ”m a SimpleX Chaten keresztül: %s
A megjelenĆ­tett nĆ©v nem tartalmazhat szókƶzƶket. Csoport - Üdvƶzlő üzenet megadĆ”sa… (opcionĆ”lis) + Üdvƶzlőüzenet megadĆ”sa… (opcionĆ”lis) Hiba a csevegĆ©si adatbĆ”zis exportĆ”lĆ”sakor Hiba a fĆ”jl mentĆ©sekor Helyi fĆ”jlok titkosĆ­tĆ”sa - titkosĆ­tĆ”s egyeztetve %s szĆ”mĆ”ra + titkosĆ­tĆ”s elfogadva %s szĆ”mĆ”ra %d üzenet megjelƶlve tƶrlĆ©sre titkosĆ­tĆ”s Ćŗjra egyeztetĆ©se engedĆ©lyezve ƖnmegsemmisĆ­tĆ©s engedĆ©lyezĆ©se @@ -520,31 +520,31 @@ Hiba a profil vĆ”ltĆ”sakor! KĆ­sĆ©rleti funkciók EngedĆ©lyezĆ©s (felülĆ­rĆ”sok megtartĆ”sĆ”val) - Helyes jelmondat bevitele. + Adja meg a helyes jelmondatot. A csoport tƶrlĆ©sre kerül az ƶn szĆ”mĆ”ra - ez a művelet nem vonható vissza! AdatbĆ”zis titkosĆ­tĆ”sa? A zĆ”rolĆ”si kĆ©pernyőn megjelenő hĆ­vĆ”sok engedĆ©lyezĆ©se a BeĆ”llĆ­tĆ”sokban. - titkosĆ­tĆ”s egyeztetve - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezĆ©se? + titkosĆ­tĆ”s elfogadva + KĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezĆ©se? Hiba a csoportprofil mentĆ©sekor hiba A fĆ”jl tƶrƶlve lesz a kiszolgĆ”lóról. Akkor is, ha le van tiltva a beszĆ©lgetĆ©sben. - Gyorsabb csatlakozĆ”s Ć©s megbĆ­zhatóbb üzenet kĆ©zbesĆ­tĆ©s. + Gyorsabb csatlakozĆ”s Ć©s megbĆ­zhatóbb üzenetkĆ©zbesĆ­tĆ©s. ZĆ”rolĆ”s engedĆ©lyezĆ©se SEGƍTSƉG - Teljesen decentralizĆ”lt - kizĆ”rólag tagok szĆ”mĆ”ra lĆ”tható. + Teljesen decentralizĆ”lt - csak a tagok szĆ”mĆ”ra lĆ”tható. FĆ”jl: %s HĆ­vĆ”s befejezĆ©se Hiba a csoporthivatkozĆ”s tƶrlĆ©sekor FĆ”jl elmentve Kapcsolat javĆ­tĆ”sa? - FĆ”jlok Ć©s mĆ©diatartalom + FĆ”jlok Ć©s mĆ©diatartalmak KONZOLHOZ Sikertelen titkosĆ­tĆ”s-ĆŗjraegyeztetĆ©s. Hiba a felhasznĆ”lói profil tƶrlĆ©sekor Csoporttag Ć”ltali javĆ­tĆ”s nem tĆ”mogatott - Üdvƶzlő üzenet megadĆ”sa… + Üdvƶzlőüzenet megadĆ”sa… TitkosĆ­tott adatbĆ”zis Jelszó megadĆ”sa a keresőben A fĆ”jl akkor Ć©rkezik meg, amikor a küldője befejezte annak feltƶltĆ©sĆ©t. @@ -577,7 +577,7 @@ A csoport tagjai küldhetnek egymĆ”snak kƶzvetlen üzeneteket. Hiba a tag eltĆ”volĆ­tĆ”sakor befejeződƶtt - Csoport üdvƶzlő üzenete + A csoport üdvƶzlőüzenete Csoport neve: Hiba a meghĆ­vó küldĆ©sekor Adjon meg egy nevet: @@ -585,7 +585,7 @@ TĆ©ma exportĆ”lĆ”sa EszkƶznĆ©v megadĆ”sa… Hiba - A csoport meghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője tƶrƶlte. + A csoportmeghĆ­vó mĆ”r nem Ć©rvĆ©nyes, a küldője eltĆ”volĆ­totta. A csoport teljes neve: segĆ­tsĆ©g ƖnmegsemmisĆ­tő jelkód engedĆ©lyezĆ©se @@ -593,15 +593,15 @@ Hiba a cĆ­m megvĆ”ltoztatĆ”sĆ”nak megszakĆ­tĆ”sakor Hiba a fĆ”jl fogadĆ”sakor titkosĆ­tĆ”s rendben - Hiba az ismerőskĆ©relem tƶrlĆ©sekor - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©seket engedĆ©lyezĆ©se csoportok szĆ”mĆ”ra? + Hiba a kapcsolatkĆ©rĆ©s tƶrlĆ©sekor + KĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezĆ©se a csoportok szĆ”mĆ”ra? Ismerős Ć”ltali javĆ­tĆ”s nem tĆ”mogatott FĆ”jl nem talĆ”lható Kapcsolat bontĆ”sa A csoport tagjai üzenetreakciókat adhatnak hozzĆ”. AdatbĆ”zis exportĆ”lĆ”sa Teljes nĆ©v: - TovĆ”bb csƶkkentett akkumulĆ”tor hasznĆ”lat + TovĆ”bb csƶkkentett akkumulĆ”tor-hasznĆ”lat Hiba a csevegĆ©s megĆ”llĆ­tĆ”sakor titkosĆ­tĆ”s rendben %s szĆ”mĆ”ra A csoport tƶrlĆ©sre kerül minden tag szĆ”mĆ”ra - ez a művelet nem vonható vissza! @@ -610,29 +610,29 @@ Teljes hivatkozĆ”s Hiba a cĆ­m megvĆ”ltoztatĆ”sakor A csoport tagjai küldhetnek hangüzeneteket. - Csoport beĆ”llĆ­tĆ”sok + CsoportbeĆ”llĆ­tĆ”sok Hiba: %s Eltűnő üzenetek - SimpleX zĆ”r bekapcsolĆ”sa - Hiba a kapcsolat szinkronizĆ”lĆ”sa kƶzben + SimpleX-zĆ”r bekapcsolĆ”sa + Hiba a kapcsolat szinkronizĆ”lĆ”sakor Hiba a cĆ­m lĆ©trehozĆ”sakor engedĆ©lyezve Hiba a rĆ©szletek betƶltĆ©sekor - Hiba tƶrtĆ©nt a kapcsolatfelvĆ©teli kĆ©relem elfogadĆ”sakor + Hiba tƶrtĆ©nt a kapcsolatkĆ©rĆ©s elfogadĆ”sakor titkosĆ­tĆ”s ĆŗjraegyeztetĆ©se engedĆ©lyezett %s szĆ”mĆ”ra titkosĆ­tĆ”s ĆŗjraegyeztetĆ©s szüksĆ©ges Rejtett csevegĆ©si profilok - FĆ”jlok Ć©s mĆ©dia + FĆ”jlok Ć©s mĆ©diatartalmak A kĆ©p mentve a ā€žGalĆ©riĆ”baā€ Elrejt Azonnal - A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva! + A fĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©se le van tiltva! Profil elrejtĆ©se KiszolgĆ”lók hasznĆ”lata CsevegĆ©si üzenetek gyorsabb megtalĆ”lĆ”sa TĆ©ma importĆ”lĆ”sa Hiba a tĆ©ma importĆ”lĆ”sakor - Ismerős Ć©s üzenet elrejtĆ©se + Ismerős nevĆ©nek Ć©s az üzenet tartalmĆ”nak elrejtĆ©se Nem kompatibilis adatbĆ”zis verzió Hogyan műkƶdik a SimpleX Nem kompatibilis verzió @@ -661,24 +661,24 @@ AdatbĆ”zis importĆ”lĆ”sa ImportĆ”lĆ”s Azonnali Ć©rtesĆ­tĆ©sek - Inkognitó mód + Inkognitómód CsevegĆ©si adatbĆ”zis importĆ”lĆ”sa? Az azonnali Ć©rtesĆ­tĆ©sek le vannak tiltva! Azonnali Ć©rtesĆ­tĆ©sek! KĆ©p - A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva ebben a csoportban. + A fĆ”jlok- Ć©s a mĆ©diatartalmak le vannak tiltva ebben a csoportban. Hogyan műkƶdik Elrejt: Hiba az ismerőssel tƶrtĆ©nő kapcsolat lĆ©trehozĆ”sĆ”ban ICE-kiszolgĆ”lók (soronkĆ©nt egy) - beolvashatja a QR-kódot a videohĆ­vĆ”sban, vagy az ismerőse megoszthat egy meghĆ­vó hivatkozĆ”st.]]> - Ha az alkalmazĆ”s megnyitĆ”sakor megadja ezt a jelkódot, az ƶsszes alkalmazĆ”sadat vĆ©glegesen tƶrlődik! + beolvashatja a QR-kódot a videohĆ­vĆ”sban, vagy az ismerőse megoszthat egy meghĆ­vó-hivatkozĆ”st.]]> + Ha az alkalmazĆ”s megnyitĆ”sakor megadja ezt a jelkódot, az ƶsszes alkalmazĆ”sadat vĆ©glegesen eltĆ”volĆ­tĆ”sra kerül! Ha nem tud szemĆ©lyesen talĆ”lkozni, mutassa meg a QR-kódot egy videohĆ­vĆ”s kƶzben, vagy ossza meg a hivatkozĆ”st. mutassa meg a QR-kódot a videohĆ­vĆ”sban, vagy ossza meg a hivatkozĆ”st.]]> MegerősĆ­tĆ©s esetĆ©n az üzenetküldő kiszolgĆ”lók lĆ”tni fogjĆ”k az IP-cĆ­mĆ©t Ć©s a szolgĆ”ltatójĆ”t – azt, hogy mely kiszolgĆ”lókhoz kapcsolódik. A kĆ©p akkor Ć©rkezik meg, amikor a küldője befejezte annak feltƶltĆ©sĆ©t. QR kód beolvasĆ”sĆ”val.]]> - A kapott SimpleX Chat meghĆ­vó hivatkozĆ”sĆ”t megnyithatja a bƶngĆ©szőjĆ©ben: + A kapott SimpleX Chat-meghĆ­vó-hivatkozĆ”sĆ”t megnyithatja a bƶngĆ©szőjĆ©ben: Ha az alkalmazĆ”s megnyitĆ”sakor megadja az ƶnmegsemmisĆ­tő jelkódot: MegtalĆ”lt szĆ”mĆ­tógĆ©p SzĆ”mĆ­tógĆ©pek @@ -688,7 +688,7 @@ Mobilok levĆ”lasztĆ”sa Külƶnbƶző nevek, avatarok Ć©s Ć”tviteli izolĆ”ció. ElutasĆ­tĆ”s esetĆ©n a feladó NEM kap Ć©rtesĆ­tĆ©st. - Szerepkƶr kivĆ”lasztĆ”sĆ”nak bővĆ­tĆ©se + SzerepkƶrvĆ”lasztĆ”s bővĆ­tĆ©se A kĆ©p akkor Ć©rkezik meg, amikor a küldője elĆ©rhető lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! meghĆ­va ƉrvĆ©nytelen kapcsolattartĆ”si hivatkozĆ”s @@ -704,7 +704,7 @@ Nincs kĆ©zbesĆ­tĆ©si informĆ”ció moderĆ”lt A tag eltĆ”volĆ­tĆ”sa a csoportból - ez a művelet nem vonható vissza! - Győződjƶn meg róla, hogy az XFTP kiszolgĆ”ló cĆ­mei megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva. + Győződjƶn meg arról, hogy az XFTP kiszolgĆ”ló cĆ­mei megfelelő formĆ”tumĆŗak, sorszeparĆ”ltak Ć©s nincsenek duplikĆ”lva. Nincs kivĆ”lasztva ismerős Nincsenek fogadott, vagy küldƶtt fĆ”jlok MegnyitĆ”s mobil alkalmazĆ”sban, majd koppintson a KapcsolódĆ”s gombra az alkalmazĆ”sban.]]> @@ -720,7 +720,7 @@ Nagy fĆ”jl! Helyi nĆ©v HĆ”lózat Ć©s kiszolgĆ”lók - ƉrtesĆ­tĆ©s előnĆ©zet + ƉrtesĆ­tĆ©s előnĆ©zete TĆ”rsĆ­tsa ƶssze a mobil Ć©s asztali alkalmazĆ”sokat! šŸ”— kƶzvetett (%1$s) Hamarosan tovĆ”bbi fejlesztĆ©sek Ć©rkeznek! @@ -730,7 +730,7 @@ Új asztali alkalmazĆ”s! Most mĆ”r az adminok is: \n- tƶrƶlhetik a tagok üzeneteit. -\n- letilthatnak tagokat (ā€žmegfigyelÅ‘ā€ szerepkƶr) +\n- letilthatnak tagokat (megfigyelő szerepkƶr)
meghĆ­vta őt: %1$s Az üzenetreakciók küldĆ©se le van tiltva ebben a csoportban. Nem @@ -749,7 +749,7 @@ Onion kiszolgĆ”lók nem lesznek hasznĆ”lva. perc Tudjon meg tƶbbet - Új ismerőskĆ©relem + Új kapcsolatkĆ©rĆ©s CsatlakozĆ”s a csoporthoz TĆ”rsĆ­tott szĆ”mĆ­tógĆ©p beĆ”llĆ­tĆ”sok meghĆ­va az ƶn csoporthivatkozĆ”sĆ”n keresztül @@ -792,7 +792,7 @@ Nincs hozzĆ”adandó ismerős ÜzenetvĆ”zlat meghĆ­vta, hogy csatlakozzon - Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s + Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s ƉrtesĆ­tĆ©sek Egyszerre csak 10 kĆ©p küldhető el ajĆ”nlott %s: %2s @@ -837,15 +837,15 @@ csatlakozĆ”s mint %s Nincs kivĆ”lasztva csevegĆ©s Csak helyi profiladatok - inkognitó egy egyszer hasznĆ”latos hivatkozĆ”son keresztül + inkognitó egy egyszer hasznĆ”lható hivatkozĆ”son keresztül ModerĆ”lva lett ekkor: %s - Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s + Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s ƉrvĆ©nytelen nĆ©v! BeszĆ©lgessünk a SimpleX Chatben ModerĆ”lva ekkor: Ɖlő üzenetek HitelesĆ­tĆ©s - ÜzenetkĆ©zbesĆ­tĆ©si bizonylatok! + ÜzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek! hivatkozĆ”s előnĆ©zeti kĆ©pe Csoport elhagyĆ”sa? nem @@ -876,7 +876,7 @@ Bejƶvő hanghĆ­vĆ”s Kulcstartó hiba Csatlakozik a csoporthoz? - Az inkognitó mód vĆ©di szemĆ©lyes adatait azĆ”ltal, hogy minden ismerőshƶz Ćŗj vĆ©letlenszerű profilt hasznĆ”l. + Az inkognitómód vĆ©di szemĆ©lyes adatait azĆ”ltal, hogy minden ismerőshƶz Ćŗj vĆ©letlenszerű profilt hasznĆ”l. - stabilabb üzenetkĆ©zbesĆ­tĆ©s. \n- valamivel jobb csoportok. \n- Ć©s mĆ©g sok mĆ”s! @@ -890,10 +890,10 @@ elutasĆ­tott hĆ­vĆ”s Rendszeres fogadott, tiltott - KapcsolódĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? + KapcsolatkĆ©rĆ©s megismĆ©tlĆ©se? VĆ©glegesen csak ƶn tƶrƶlhet üzeneteket (ismerőse csak tƶrlĆ©sre jelƶlheti őket ). (24 óra) Szerepkƶr - SimpleX kapcsolattartĆ”si cĆ­m + SimpleX-kapcsolattartĆ”si-cĆ­m MegĆ”llĆ­tĆ”s Előre beĆ”llĆ­tott kiszolgĆ”ló Új csevegĆ©s kezdĆ©se @@ -901,7 +901,7 @@ MegnyitĆ”s Protokoll időtĆŗllĆ©pĆ©s titkos - ElőnĆ©zet megjelenĆ­tĆ©se + Üzenetek előnĆ©zetĆ©nek megjelenĆ­tĆ©se vĆ”rakozĆ”s a visszaigazolĆ”sra… FĆ”jl megĆ”llĆ­tĆ”sa a csoporthivatkozĆ”son keresztül @@ -910,7 +910,7 @@ ƖnmegsemmisĆ­tĆ©si jelkód MentĆ©s Ć©s csoportprofil frissĆ­tĆ©se AdatvĆ©delem - Profil SimpleX cĆ­me + Profil SimpleX-cĆ­me Jelentse a fejlesztőknek. Ɩn dƶnti el, hogy kivel beszĆ©lget. Az eltűnő üzenetek küldĆ©se le van tiltva. @@ -925,17 +925,17 @@ Csak ƶn tud hĆ­vĆ”sokat indĆ­tani. BiztonsĆ”gos vĆ”rólista ƉrtĆ©kelje az alkalmazĆ”st - Egyszer hasznĆ”latos hivatkozĆ”s megosztĆ”sa + Egyszer hasznĆ”lható hivatkozĆ”s megosztĆ”sa Hiba az adatbĆ”zis visszaĆ”llĆ­tĆ”sakor %s Ć©s %s EngedĆ©lyezve - Csƶkkentett akkumulĆ”torhasznĆ”lat - MentĆ©s Ć©s ismerősƶk Ć©rtesĆ­tĆ©se + Csƶkkentett akkumulĆ”tor-hasznĆ”lat + MentĆ©s Ć©s az ismerősƶk Ć©rtesĆ­tĆ©se ElőnĆ©zet CsevegĆ©s hasznĆ”lata MegosztĆ”s Fogadott üzenet - Üdvƶzlő üzenet + Üdvƶzlőüzenet %s, %s Ć©s tovĆ”bbi %d tag kapcsolódott Csak az ismerőse tud hĆ­vĆ”st indĆ­tani. TƉMƁK @@ -944,16 +944,16 @@ Üdvƶzƶljük! ƖnmegsemmisĆ­tĆ©si jelkód (beolvasĆ”s, vagy beillesztĆ©s a vĆ”gólapról) - Videóra vĆ”rakozĆ”s + VĆ”rakozĆ”s a videóra VĆ”lasz - Ez az ƶn egyszer hasznĆ”latos hivatkozĆ”sa! + Ez az ƶn egyszer hasznĆ”lható hivatkozĆ”sa! SimpleX Chat hĆ­vĆ”sok Új inkognĆ­tó profil hasznĆ”lata FrissĆ­tse az alkalmazĆ”st, Ć©s lĆ©pjen kapcsolatba a fejlesztőkkel. SimpleX HivatkozĆ”s előnĆ©zete a biztonsĆ”gi kód megvĆ”ltozott - KizĆ”rólag ismerős megjelenĆ­tĆ©se + Csak az ismerős nevĆ©nek megjelenĆ­tĆ©se Hangszóró bekapcsolva ImportĆ”lt csevegĆ©si adatbĆ”zis hasznĆ”latĆ”hoz indĆ­tsa Ćŗjra az alkalmazĆ”st. jogosulatlan küldĆ©s @@ -978,15 +978,15 @@ AdatbĆ”zismentĆ©s visszaĆ”llĆ­tĆ”sa Visszavon Ismerős felkĆ©rĆ©se, hogy engedĆ©lyezze a hangüzenetek küldĆ©sĆ©t. - egyszer hasznĆ”latos hivatkozĆ”st osztott meg - A hivatkozĆ”s megnyitĆ”sa a bƶngĆ©szőben gyengĆ­theti az adatvĆ©delmet Ć©s a biztonsĆ”got. A megbĆ­zhatatlan SimpleX hivatkozĆ”sok pirossal vannak kiemelve. + egyszer hasznĆ”lható hivatkozĆ”st osztott meg + A hivatkozĆ”s megnyitĆ”sa a bƶngĆ©szőben gyengĆ­theti az adatvĆ©delmet Ć©s a biztonsĆ”got. A megbĆ­zhatatlan SimpleX-hivatkozĆ”sok pirossal vannak kiemelve. ICE kiszolgĆ”lók Kapcsolat lĆ©trehozĆ”sa ElutasĆ­tĆ”s - Ismerős Ć©s üzenet megjelenĆ­tĆ©se + Ismerős nevĆ©nek Ć©s az üzenet tartalmĆ”nak megjelenĆ­tĆ©se BEƁLLƍTƁSOK FelhasznĆ”lói fiók jelszavĆ”nak mentĆ©se - FĆ”jl küldĆ©s megĆ”llĆ­tĆ”sa? + FĆ”jlküldĆ©s megĆ”llĆ­tĆ”sa? SzĆ”mĆ­tógĆ©p levĆ”lasztĆ”sa? A hangüzenetek le vannak tilva! Kƶzvetlen üzenet küldĆ©se a kapcsolódĆ”shoz @@ -1010,7 +1010,7 @@ ArchĆ­vum mentĆ©se %s, %s Ć©s %d tag CsevegĆ©si szolgĆ”ltatĆ”s megĆ”llĆ­tĆ”sa - SimpleX hivatkozĆ”sok + SimpleX-hivatkozĆ”sok Az elküldƶtt üzenetek tƶrlĆ©sre kerülnek a beĆ”llĆ­tott idő utĆ”n. NĆ©mĆ­tĆ”s feloldĆ”sa Elküldve ekkor: %s @@ -1020,8 +1020,8 @@ Profiljelszó TĆ©ma Jelmondat eltĆ”volĆ­tĆ”sa a beĆ”llĆ­tĆ”sokból? - SimpleX csoporthivatkozĆ”s - KĆ©pre vĆ”rakozĆ”s + SimpleX-csoporthivatkozĆ”s + VĆ”rakozĆ”s a kĆ©pre ƖnmegsemmisĆ­tĆ©s vĆ”rakozĆ”s a vĆ”laszra… Ismerős nevĆ©nek beĆ”llĆ­tĆ”sa… @@ -1035,7 +1035,7 @@ Rendszer ElküldĆ©s BiztonsĆ”gi kód - Adja meg a helyes aktuĆ”lis jelmondatĆ”t. + Adja meg a helyes, jelenlegi jelmondatĆ”t. Az elküldƶtt üzenetek vĆ©gleges tƶrlĆ©se le van tiltva. Az üzenetreakciók küldĆ©se le van tiltva. VĆ©letlenszerű jelmondat hasznĆ”lata @@ -1057,11 +1057,11 @@ Ismerősƶk kivĆ”lasztĆ”sa ismeretlen üzenet formĆ”tum KiszolgĆ”lók mentĆ©se - Üdvƶzlő üzenet + Üdvƶzlőüzenet mp A profilfrissĆ­tĆ©s elküldĆ©sre került az ismerősƶk szĆ”mĆ”ra. EgyszerűsĆ­tett inkognĆ­tó mód - Üdvƶzlőszƶveg mentĆ©se? + Üdvƶzlőüzenet mentĆ©se? Új csevegĆ©si fiók lĆ©trehozĆ”sĆ”hoz indĆ­tsa Ćŗjra az alkalmazĆ”st. EngedĆ©ly megtagadva! Főggőben lĆ©vő hĆ­vĆ”s @@ -1082,7 +1082,7 @@ AdatbĆ”zis jelmondat beĆ”llĆ­tĆ”sa Elküldƶtt üzenet Időszakosan indul - Ez az ƶn SimpleX cĆ­me! + Ez az ƶn SimpleX-cĆ­me! eltĆ”volĆ­tva MegosztĆ”s SimpleX csapat @@ -1091,7 +1091,7 @@ tulajdonos BekapcsolĆ”s %s, %s Ć©s %s kapcsolódott - SimpleX egyszer hasznĆ”latos meghĆ­vó + Egyszer hasznĆ”lható SimpleX-meghĆ­vó HĆ­vĆ”sok nem sikerült elküldeni KEZELŐFELÜLET SZƍNEI @@ -1104,10 +1104,10 @@ Videó Automatikus elfogadĆ”si beĆ”llĆ­tĆ”sok mentĆ©se ÚjraegyzetetĆ©s - Videóra vĆ”rakozĆ”s + VĆ”rakozĆ”s a videóra XFTP kiszolgĆ”lók Videó kikapcsolva - PrivĆ”t fĆ”jl nevek + PrivĆ”t fĆ”jlnevek BeĆ”llĆ­tĆ”sok mentĆ©se? Jelkód Ismeretlen hiba @@ -1117,12 +1117,12 @@ AdatbĆ”zis jelmondat beĆ”llĆ­tĆ”sa BiztonsĆ”gi kód megtekintĆ©se Tag feloldĆ”sa? - A küldő tƶrƶlhette a kapcsolódĆ”si kĆ©relmet. + A küldő tƶrƶlhette a kapcsolatkĆ©rĆ©st. HibĆ”s adatbĆ”zis jelmondat SMP kiszolgĆ”lók - Az üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva + A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva AdatbĆ”zis mappa megnyitĆ”sa - egy egyszer hasznĆ”latos hivatkozĆ”son keresztül + egyszer hasznĆ”lható hivatkozĆ”son keresztül CsoportbeĆ”llĆ­tĆ”sok megadĆ”sa ezen keresztül: %1$s igen @@ -1136,13 +1136,13 @@ MegĆ”llĆ­tĆ”s CĆ­mmegosztĆ”s megĆ”llĆ­tĆ”sa? CsevegĆ©s profilok megnyitĆ”sa - CsatlakozĆ”si kĆ©rĆ©s megismĆ©tlĆ©se? - KĆ©pre vĆ”rakozĆ”s + CsatlakozĆ”skĆ©rĆ©s megismĆ©tlĆ©se? + VĆ”rakozĆ”s a kĆ©pre Hangüzenetek Biztosan eltĆ”volĆ­tja? BiztonsĆ”gi kód ellenőrzĆ©se - eltĆ”volĆ­tottak - SimpleX cĆ­m + eltĆ”volĆ­totta ƶnt + SimpleX-cĆ­m MegjelenĆ­tĆ©s: vĆ”lasz fogadĆ”sa… AdatbĆ”zismentĆ©s visszaĆ”llĆ­tĆ”sa? @@ -1161,7 +1161,7 @@ A jelkód megvĆ”ltozott! Akkor fut, amikor az alkalmazĆ”s meg van nyitva Ez a QR-kód nem egy hivatkozĆ”s! - FĆ”jlra vĆ”rakozĆ”s + VĆ”rakozĆ”s a fĆ”jlra simplexmq: v%s (%2s) SzĆ©tkapcsolĆ”s VĆ©letlenszerű profil @@ -1182,7 +1182,7 @@ A hangüzenetek küldĆ©se le van tiltva. Ismerős nevĆ©nek beĆ”llĆ­tĆ”sa Csak ƶn tud eltűnő üzeneteket küldeni. - KĆ©p/videó megoszĆ”sa… + MĆ©dia megosztĆ”sa… ƶn: %1$s BeĆ”llĆ­tĆ”sok SzĆ­nek visszaĆ”llĆ­tĆ”sa @@ -1202,8 +1202,8 @@ QR-kód megjelenĆ­tĆ©se videóhĆ­vĆ”s Kedvenc tƶrlĆ©se - Üzenet kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se - SimpleX cĆ­m + KĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se + SimpleX-cĆ­m Koppintson a MentĆ©s Ć©s ismerős Ć©rtesĆ­tĆ©se ElutasĆ­tott hĆ­vĆ”s @@ -1216,13 +1216,13 @@ ZĆ”rolĆ”si mód FĆ”jl visszavonĆ”sa XFTP kiszolgĆ”lók - A fĆ”jlok- Ć©s a mĆ©diatartalom küldĆ©se le van tiltva. + A fĆ”jlok- Ć©s a mĆ©diatartalmak küldĆ©se le van tiltva. FĆ”jl megosztĆ”sa… MentĆ©s Ć”tjĆ”tszón keresztül MegosztĆ”s megĆ”llĆ­tĆ”sa eltĆ”volĆ­totta őt: %1$s - Jelmondat mentĆ©se Ć©s csevegĆ©s megnyitĆ”sa + Jelmondat mentĆ©se Ć©s a csevegĆ©s megnyitĆ”sa BeĆ”llĆ­tĆ”sok mentĆ©se? Nincsenek felhasznĆ”lói azonosĆ­tók. A kƶzvetlen üzenetek küldĆ©se a tagok kƶzƶtt le van tiltva. @@ -1245,28 +1245,28 @@ FeloldĆ”s NĆ©mĆ­tĆ”s feloldĆ”sa SimpleX Chat megnyitĆ”sa a hĆ­vĆ”s fogadĆ”sĆ”hoz - FĆ”jl fogadĆ”s megĆ”llĆ­tĆ”sa? + FĆ”jlfogadĆ”s megĆ”llĆ­tĆ”sa? - opcionĆ”lis Ć©rtesĆ­tĆ©s a tƶrƶlt ismerősƶk szĆ”mĆ”ra \n- profil nevek szókƶzƶkkel \n- Ć©s tovĆ”bbiak! Lengyel kezelőfelület KiszolgĆ”ló hasznĆ”lata Fogadva ekkor: %s - SimpleX zĆ”r + SimpleX-zĆ”r MentĆ©s Ć©s csoporttagok Ć©rtesĆ­tĆ©se VisszaĆ”llĆ­tĆ”s Csak az ismerőse tud üzenetreakciókat küldeni. Hangüzenetek elhagyta a csoportot Hangüzenet rƶgzĆ­tĆ©se - SimpleX zĆ”r bekapcsolva + SimpleX-zĆ”r bekapcsolva kƶzvetlen üzenet küldĆ©se BeolvasĆ”s mobilról Kapcsolatok ellenőrzĆ©se Üzenet megosztĆ”sa… mĆ”sodperc - A SimpleX zĆ”r nincs bekapcsolva! - SimpleX zĆ”r + A SimpleX-zĆ”r nincs bekapcsolva! + SimpleX-zĆ”r BeĆ”llĆ­tĆ”sok CsevegĆ©si adatbĆ”zis eltĆ”volĆ­totta őt: %1$s @@ -1281,12 +1281,12 @@ Fogadott üzenet Csak az ismerőse tudja az üzeneteket vĆ©glegesen tƶrƶlni (ƶn csak tƶrlĆ©sre jelƶlheti meg azokat). (24 óra) Az ƶnmegsemmisĆ­tĆ©si jelkód megvĆ”ltozott! - SimpleX Chat kiszolgĆ”lók hasznĆ”latban. - SimpleX Chat kiszolgĆ”lók hasznĆ”lata? + SimpleX Chat-kiszolgĆ”lók hasznĆ”latban. + SimpleX Chat-kiszolgĆ”lók hasznĆ”lata? CsevegĆ©si profil felfedĆ©se Videók Ć©s fĆ”jlok 1Gb mĆ©retig TCP kapcsolat időtĆŗllĆ©pĆ©s - A(z) %1$s nevű profiljĆ”nak SimpleX cĆ­me megosztĆ”sra fog kerülni. + A(z) %1$s nevű profiljĆ”nak SimpleX-cĆ­me megosztĆ”sra fog kerülni. Ɩn mĆ”r kapcsolódva van ehhez: %1$s. Jelenlegi csevegĆ©si adatbĆ”zis TƖRLƉSRE Ć©s FELCSERƉLƉSRE kerül az importĆ”lt Ć”ltal! \nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegĆ©si üzenetei Ć©s fĆ”jljai vĆ©glegesen tƶrƶlve lesznek. @@ -1313,7 +1313,7 @@ kapcsolattartĆ”si cĆ­m-hivatkozĆ”son keresztül SimpleX hĆ”ttĆ©rszolgĆ”ltatĆ”st hasznĆ”lja - az akkumulĆ”tornak csak nĆ©hĆ”ny szĆ”zalĆ©kĆ”t hasznĆ”lja naponta.]]> Az ismerősĆ©nek online kell lennie ahhoz, hogy a kapcsolat lĆ©trejƶjjƶn. -\nVisszavonhatja ezt az ismerőskĆ©relmet Ć©s tƶrƶlheti az ismerőst (ezt kĆ©sőbb ismĆ©t megpróbĆ”lhatja egy Ćŗj hivatkozĆ”ssal). +\nVisszavonhatja ezt az ismerőskĆ©relmet Ć©s eltĆ”volĆ­thatja az ismerőst (ezt kĆ©sőbb ismĆ©t megpróbĆ”lhatja egy Ćŗj hivatkozĆ”ssal). A jelszó nem talĆ”lható a Keystore-ban, ezĆ©rt kĆ©zzel szüksĆ©ges megadni. Ez akkor tƶrtĆ©nhetett meg, ha visszaĆ”llĆ­totta az alkalmazĆ”s adatait egy biztonsĆ”gimentĆ©si eszkƶzzel. Ha nem Ć­gy tƶrtĆ©nt, akkor lĆ©pjen kapcsolatba a fejlesztőkkel. Az ismerősei tovĆ”bbra is kapcsolódva maradnak. A kiszolgĆ”lónak engedĆ©lyre van szüksĆ©ge a vĆ”rólistĆ”k lĆ©trehozĆ”sĆ”hoz, ellenőrizze jelszavĆ”t @@ -1335,11 +1335,11 @@ A kiszolgĆ”lónak engedĆ©lyre van szüksĆ©ge a vĆ”rólistĆ”k lĆ©trehozĆ”sĆ”hoz, ellenőrizze jelszavĆ”t Kapcsolódni fog a csoport ƶsszes tagjĆ”hoz. LehetsĆ©ges, hogy a kiszolgĆ”ló cĆ­mĆ©ben szereplő tanĆŗsĆ­tvĆ”ny-ujjlenyomat helytelen - A biztonsĆ”ga Ć©rdekĆ©ben kapcsolja be a SimpleX zĆ”r funkciót. + A biztonsĆ”ga Ć©rdekĆ©ben kapcsolja be a SimpleX-zĆ”r funkciót. \nA funkció bekapcsolĆ”sa előtt a rendszer felszólĆ­tja a kĆ©pernyőzĆ”r beĆ”llĆ­tĆ”sĆ”ra az eszkƶzĆ©n. A videó akkor Ć©rkezik meg, amikor a küldője elĆ©rhető lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! HĆ”lózati kapcsolat ellenőrzĆ©se a kƶvetkezővel: %1$s, Ć©s próbĆ”lja Ćŗjra. - A SimpleX zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be. + A SimpleX-zĆ”r az ā€žAdatvĆ©delem Ć©s biztonsĆ”gā€ menüben kapcsolható be. Az alkalmazĆ”s ƶsszeomlott Ellenőrizze, hogy a megfelelő hivatkozĆ”st hasznĆ”lta-e, vagy kĆ©rje meg az ismerősĆ©t, hogy küldjƶn egy mĆ”sikat. A kĆ©p nem dekódolható. PróbĆ”lja meg egy mĆ”sik kĆ©ppel, vagy lĆ©pjen kapcsolatba a fejlesztőkkel. @@ -1349,7 +1349,7 @@ A fĆ”jl fogadĆ”sa leĆ”llt. Ne felejtse el, vagy tĆ”rolja biztonsĆ”gosan – az elveszett jelszót nem lehet visszaĆ”llĆ­tani! A videó akkor Ć©rkezik meg, amikor a küldője befejezte annak feltƶltĆ©sĆ©t. - egyszer hasznĆ”latos hivatkozĆ”st osztott meg inkognitóban + egyszer hasznĆ”lható hivatkozĆ”st osztott meg inkognitóban MĆ”r kapcsolódott ahhoz a kiszolgĆ”lóhoz, amely az adott ismerősĆ©től Ć©rkező üzenetek fogadĆ”sĆ”ra szolgĆ”l. KĆ©sőbb engedĆ©lyezheti a BeĆ”llĆ­tĆ”sokban Akkor lesz kapcsolódva a csoporthoz, amikor a csoport tulajdonosĆ”nak eszkƶze online lesz, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! @@ -1365,19 +1365,19 @@ A csatlakozĆ”s mĆ”r folyamatban van a csoporthoz ezen a hivatkozĆ”son keresztül. MeghĆ­vĆ”st kapott a csoportba Ismerőse a jelenleg megengedett maximĆ”lis mĆ©retű (%1$s) fĆ”jlnĆ”l nagyobbat küldƶtt. - Az ismerősei Ć©s az üzenetek (kĆ©zbesĆ­tĆ©s utĆ”n) nem kerülnek tĆ”rolĆ”sra a SimpleX kiszolgĆ”lókon. + Az ismerősei Ć©s az üzenetek (kĆ©zbesĆ­tĆ©s utĆ”n) nem kerülnek tĆ”rolĆ”sra a SimpleX-kiszolgĆ”lókon. Üzenetek formĆ”zĆ”sa a szƶvegbe szĆŗrt speciĆ”lis karakterekkel: MegnyitĆ”s az alkalmazĆ”sban gombra.]]> CsevegĆ©si profilja elküldĆ©sre kerül \naz ismerőse szĆ”mĆ”ra - Egy olyan ismerősĆ©t próbĆ”lja meghĆ­vni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a sajĆ”t fő profilja van hasznĆ”latban - %1$s csoporthoz.]]> + Egy olyan ismerősĆ©t próbĆ”lja meghĆ­vni, akivel inkognitó-profilt osztott meg abban a csoportban, amelyben a sajĆ”t fő profilja van hasznĆ”latban + %1$s nevű csoporthoz.]]> Amikor az alkalmazĆ”s fut InkognĆ­tó profilt hasznĆ”l ehhez a csoporthoz - fő profilja megosztĆ”sĆ”nak elkerülĆ©se Ć©rdekĆ©ben a meghĆ­vók küldĆ©se le van tiltva Kapcsolat izolĆ”ciós mód - Akkor lesz kapcsolódva, ha a kapcsolódĆ”si kĆ©relme elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! + Akkor lesz kapcsolódva, ha a kapcsolatkĆ©rĆ©se elfogadĆ”sra kerül, vĆ”rjon, vagy ellenőrizze kĆ©sőbb! A hangüzenetek küldĆ©se le van tiltva ebben a csoportban. - AlkalmazĆ”s akkumulĆ”tor hasznĆ”lata / KorlĆ”tlan módot az alkalmazĆ”s beĆ”llĆ­tĆ”saiban.]]> + AlkalmazĆ”s akkumulĆ”tor-hasznĆ”lata / KorlĆ”tlan módot az alkalmazĆ”s beĆ”llĆ­tĆ”saiban.]]> BiztonsĆ”gos kvantumrezisztens protokollon keresztül. - 5 perc hosszĆŗsĆ”gĆŗ hangüzenetek. \n- egyedi üzenet-eltűnĆ©si időkorlĆ”t. @@ -1389,12 +1389,12 @@ HasznĆ”lja az .onion kiszolgĆ”lókat NEM Ć©rtĆ©kre, ha a SOCKS proxy nem tĆ”mogatja őket.]]> Megoszthatja a cĆ­mĆ©t egy hivatkozĆ”skĆ©nt vagy egy QR-kódkĆ©nt – Ć­gy bĆ”rki kapcsolódhat ƶnhƶz. LĆ©trehozĆ”s kĆ©sőbb - Profilja az eszkƶzƶn van tĆ”rolva Ć©s csak az ismerőseivel kerül megosztĆ”sra. A SimpleX kiszolgĆ”lók nem lĆ”thatjĆ”k a profiljĆ”t. + Profilja az eszkƶzƶn van tĆ”rolva Ć©s csak az ismerőseivel kerül megosztĆ”sra. A SimpleX-kiszolgĆ”lók nem lĆ”thatjĆ”k a profiljĆ”t. %s szerepkƶrĆ©t megvĆ”ltoztatta erre: %s - Csoport meghĆ­vó elutasĆ­tva + CsoportmeghĆ­vó elutasĆ­tva Az adatvĆ©delem Ć©rdekĆ©ben, a mĆ”s csevegĆ©si platformokon megszokott felhasznĆ”lói azonosĆ­tók helyett, a SimpleX üzenetsorokhoz rendel azonosĆ­tókat, minden egyes ismerőshƶz egy külƶnbƶzőt. (megosztĆ”s egy ismerőssel) - Csoport meghĆ­vó elküldve + CsoportmeghĆ­vó elküldve Kapcsolat izolĆ”ciós mód frissĆ­tĆ©se? Kapcsolat izolĆ”ciós mód Ettől a csoporttól nem fog Ć©rtesĆ­tĆ©seket kapni. A csevegĆ©si előzmĆ©nyek megmaradnak. @@ -1413,10 +1413,10 @@ A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazĆ”s elindul - nem az eszkƶzƶn kerül tĆ”rolĆ”sra. Ha engedĆ©lyezni szeretnĆ© a mobilalkalmazĆ”s tĆ”rsĆ­tĆ”sĆ”t a szĆ”mĆ­tógĆ©phez, akkor nyissa meg ezt a portot a tűzfalĆ”ban, ha engedĆ©lyezte azt Profilja, ismerősei Ć©s az elküldƶtt üzenetei az eszkƶzƶn kerülnek tĆ”rolĆ”sra. - AlkalmazĆ”s akkumulĆ”tor hasznĆ”lata / KorlĆ”tlan módot az alkalmazĆ”s beĆ”llĆ­tĆ”saiban.]]> - Ez a karakterlĆ”nc nem egy meghĆ­vó hivatkozĆ”s! + AlkalmazĆ”s akkumulĆ”tor-hasznĆ”lata / KorlĆ”tlan módot az alkalmazĆ”s beĆ”llĆ­tĆ”saiban.]]> + Ez a karakterlĆ”nc nem egy meghĆ­vó-hivatkozĆ”s! Új csevegĆ©s kezdĆ©se - A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”latos hivatkozĆ”son keresztül! + A kapcsolódĆ”s mĆ”r folyamatban van ezen az egyszer hasznĆ”lható hivatkozĆ”son keresztül! Nem veszĆ­ti el az ismerőseit, ha kĆ©sőbb tƶrli a cĆ­mĆ©t. A beĆ”llĆ­tĆ”sok frissĆ­tĆ©se a kiszolgĆ”lókhoz való Ćŗjra kapcsolódĆ”ssal jĆ”r. kapcsolatba akar lĆ©pni ƶnnel! @@ -1425,7 +1425,7 @@ Kód ellenőrzĆ©se a mobilon Csatlakozott ehhez a csoporthoz. KapcsolódĆ”s a meghĆ­vó csoporttaghoz. a SimpleX Chat fejlesztőivel, ahol bĆ”rmiről kĆ©rdezhet Ć©s Ć©rtesülhet az ĆŗjdonsĆ”gokról.]]> - OpcionĆ”lis üdvƶzlő üzenettel. + OpcionĆ”lis üdvƶzlőüzenettel. Ismeretlen adatbĆ”zis hiba: %s Elrejtheti vagy lenĆ©mĆ­thatja a felhasznĆ”ló profiljait - koppintson (vagy asztali alkalmazĆ”sban kattintson) hosszan a profilra a felugró menühƶz. InkognĆ­tó mód kapcsolódĆ”skor. @@ -1436,11 +1436,11 @@ Ɩn irĆ”nyĆ­tja csevegĆ©sĆ©t! Kód ellenőrzĆ©se a szĆ”mĆ­tógĆ©pen Az időzóna vĆ©delme Ć©rdekĆ©ben a kĆ©p-/hangfĆ”jlok UTC-t hasznĆ”lnak. - A kapcsolódĆ”si kĆ©relem elküldĆ©sre kerül ezen csoporttag szĆ”mĆ”ra. - Inkognitóprofil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott. - MĆ”r kĆ©rt egy kapcsolódĆ”si kĆ©relmet ezen a cĆ­men keresztül! - Megoszthatja ezt a SimpleX cĆ­met az ismerőseivel, hogy kapcsolatba lĆ©phessenek vele: %s. - Amikor az emberek kapcsolódĆ”st kĆ©relmeznek, ƶn elfogadhatja vagy elutasĆ­thatja azokat. + A kapcsolatkĆ©rĆ©s elküldĆ©sre kerül ezen csoporttag szĆ”mĆ”ra. + Inkognitó-profil megosztĆ”sa esetĆ©n a rendszer azt a profilt fogja hasznĆ”lni azokhoz a csoportokhoz, amelyekbe meghĆ­vĆ”st kapott. + MĆ”r küldƶtt egy kapcsolatkĆ©rĆ©st ezen a cĆ­men keresztül! + Megoszthatja ezt a SimpleX-cĆ­met az ismerőseivel, hogy kapcsolatba lĆ©phessenek vele: %s. + Amikor az emberek kapcsolatot kĆ©rnek, ƶn elfogadhatja vagy elutasĆ­thatja azokat. MegjelenĆ­tendő üzenet beĆ”llĆ­tĆ”sa az Ćŗj tagok szĆ”mĆ”ra! Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! A kĆ©zbesĆ­tĆ©si jelentĆ©s küldĆ©se minden ismerős szĆ”mĆ”ra engedĆ©lyezĆ©sre kerül. @@ -1450,8 +1450,8 @@ Profilja csak az ismerőseivel kerül megosztĆ”sra. NĆ©hĆ”ny kiszolgĆ”ló megbukott a teszten: Koppintson a csatlakozĆ”shoz - Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalommal együtt tƶrlĆ©sre kerülnek. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak. - KĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %d ismerősnĆ©l + Ez a művelet nem vonható vissza - az ƶsszes fogadott Ć©s küldƶtt fĆ”jl a mĆ©diatartalmakkal együtt tƶrlĆ©sre kerülnek. Az alacsony felbontĆ”sĆŗ kĆ©pek viszont megmaradnak. + A kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %d ismerősnĆ©l KüldĆ©s ezen keresztül: Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! A kĆ©zbesĆ­tĆ©si jelentĆ©sek küldĆ©se engedĆ©lyezĆ©sre kerül az ƶsszes lĆ”tható csevegĆ©si profilban lĆ©vő minden ismerős szĆ”mĆ”ra. @@ -1465,8 +1465,8 @@ \nEz kĆ©sőbb megvĆ”ltoztatható. Koppintson az inkognitóban való kapcsolódĆ”shoz Jelmondat beĆ”llĆ­tĆ”sa az exportĆ”lĆ”shoz - KĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva a(z) %d csoportban - NĆ©hĆ”ny nem vĆ©gzetes hiba tƶrtĆ©nt az importĆ”lĆ”s kƶzben: + A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %d csoportban + NĆ©hĆ”ny nem vĆ©gzetes hiba tƶrtĆ©nt az importĆ”lĆ”skor: Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! Az Ć”tjĆ”tszó kiszolgĆ”ló csak szüksĆ©g esetĆ©n kerül hasznĆ”latra. Egy mĆ”sik fĆ©l megfigyelheti az IP-cĆ­met. RendszerhitelesĆ­tĆ©s helyetti beĆ”llĆ­tĆ”s. @@ -1481,12 +1481,12 @@ TovĆ”bbi informĆ”ció a GitHub tĆ”rolónkban. Az utolsó üzenet tervezetĆ©nek megőrzĆ©se a mellĆ©kletekkel együtt. A mentett WebRTC ICE-kiszolgĆ”lók eltĆ”volĆ­tĆ”sra kerülnek. - KĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak a(z) %d csoportban - A szerepkƶr meg fog vĆ”ltozni erre: ā€ž%sā€. A csoportban mindenki Ć©rtesĆ­tve lesz. + A kĆ©zbesĆ­tĆ©si jelentĆ©sek engedĆ©lyezve vannak %d csoportban + A szerepkƶr meg fog vĆ”ltozni erre: %s. A csoportban mindenki Ć©rtesĆ­tve lesz. Profil Ć©s kiszolgĆ”lókapcsolatok Egy üzenetküldő- Ć©s alkalmazĆ”splatform, amely vĆ©di az adatait Ć©s biztonsĆ”gĆ”t. A profil aktivĆ”lĆ”sĆ”hoz koppintson az ikonra. - KĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %d ismerősnĆ©l + A kĆ©zbesĆ­tĆ©si jelentĆ©sek le vannak tiltva %d ismerősnĆ©l Munkamenet kód Kƶszƶnet a felhasznĆ”lóknak - hozzĆ”jĆ”rulĆ”s a Weblate-en! Kis csoportok (max. 20 tag) @@ -1496,13 +1496,13 @@ A kƶvetkező üzenet azonosĆ­tója hibĆ”s (kisebb vagy egyenlő az előzővel). \nEz valamilyen hiba, vagy sĆ©rült kapcsolat esetĆ©n fordulhat elő. Az eszkƶz neve megosztĆ”sra kerül a tĆ”rsĆ­tott mobil klienssel. - A cĆ­mzettek a beĆ­rĆ”s kƶzben lĆ”tjĆ”k a frissĆ­tĆ©seket. + A cĆ­mzettek a beĆ­rĆ”s kƶzben lĆ”tjĆ”k a szƶvegvĆ”ltozĆ”sokat. TĆ”rolja el biztonsĆ”gosan jelmondatĆ”t, mert ha elveszĆ­ti azt, NEM tudja megvĆ”ltoztatni. - A jelmondat a beĆ”llĆ­tĆ”sok kƶzƶtt egyszerű szƶvegkĆ©nt kerül tĆ”rolĆ”sra, miutĆ”n megvĆ”ltoztatta vagy ĆŗjraindĆ­totta az alkalmazĆ”st. + A jelmondat a beĆ”llĆ­tĆ”sokban egyszerű szƶvegkĆ©nt kerül tĆ”rolĆ”sra, miutĆ”n megvĆ”ltoztatta vagy ĆŗjraindĆ­totta az alkalmazĆ”st. A jelenlegi csevegĆ©si profilhoz tartozó Ćŗj kapcsolatok kiszolgĆ”lói FogadĆ”s ezen keresztül: TĆ”rolja el biztonsĆ”gosan jelmondĆ”t, mert ha elveszti azt, akkor NEM fĆ©rhet hozzĆ” a csevegĆ©shez. - A tag szerepkƶre erre fog vĆ”ltozni: ā€ž%sā€. A tag Ćŗj meghĆ­vót fog kapni. + A szerepkƶr meg fog vĆ”ltozni erre: %s. A tag Ćŗj meghĆ­vót fog kapni. profilkĆ©p helyőrző A titkosĆ­tĆ”s műkƶdik, Ć©s Ćŗj titkosĆ­tĆ”si egyezmĆ©nyre nincs szüksĆ©g. Ez kapcsolati hibĆ”kat eredmĆ©nyezhet! Ez a művelet nem vonható vissza - profiljai, ismerősei, üzenetei Ć©s fĆ”jljai vĆ©glegesen tƶrlĆ©sre kerülnek. @@ -1519,15 +1519,15 @@ LĆ”thatóvĆ” teheti a SimpleXbeli ismerősei szĆ”mĆ”ra a ā€žBeĆ”llĆ­tĆ”sokbanā€. Legfeljebb az utolsó 100 üzenet kerül elküldĆ©sre az Ćŗj tagok szĆ”mĆ”ra. A beolvasott QR-kód nem egy SimpleX QR-kód hivatkozĆ”s. - A beillesztett szƶveg nem egy SimpleX hivatkozĆ”s. - A meghĆ­vó hivatkozĆ”sĆ”t Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l. + A beillesztett szƶveg nem egy SimpleX-hivatkozĆ”s. + A meghĆ­vó-hivatkozĆ”sĆ”t Ćŗjra megtekintheti a kapcsolat rĆ©szleteinĆ©l. CsevegĆ©s indĆ­tĆ”sa? LĆ”tható előzmĆ©nyek AlkalmazĆ”s jelkód Ismerős hozzĆ”adĆ”sa Koppintson a beolvasĆ”shoz Koppintson a hivatkozĆ”s beillesztĆ©sĆ©hez - Ismerős hozzĆ”adĆ”sa: Ćŗj meghĆ­vó hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz.]]> + Ismerős hozzĆ”adĆ”sa: Ćŗj meghĆ­vó-hivatkozĆ”s lĆ©trehozĆ”sĆ”hoz, vagy egy kapott hivatkozĆ”son keresztül tƶrtĆ©nő kapcsolódĆ”shoz.]]> Csoport lĆ©trehozĆ”sa: Ćŗj csoport lĆ©trehozĆ”sĆ”hoz.]]> A csevegĆ©s leĆ”llt. Ha mĆ”r hasznĆ”lta ezt az adatbĆ”zist egy mĆ”sik eszkƶzƶn, Ćŗgy visszaĆ”llĆ­tĆ”s szüksĆ©ges a csevegĆ©s megkezdĆ©se előtt. Az előzmĆ©nyek nem kerülnek elküldĆ©sre az Ćŗj tagok szĆ”mĆ”ra. @@ -1538,14 +1538,14 @@ Vagy mutassa meg ezt a kódot Kamera hozzĆ”fĆ©rĆ©s engedĆ©lyezĆ©se Fel nem hasznĆ”lt meghĆ­vó megtartĆ”sa? - Egyszer hasznĆ”latos meghĆ­vó hivatkozĆ”s megosztĆ”sa + Egyszer hasznĆ”lható meghĆ­vó-hivatkozĆ”s megosztĆ”sa Új csevegĆ©s CsevegĆ©sek betƶltĆ©se… HivatkozĆ”s lĆ©trehozĆ”sa… Vagy QR-kód beolvasĆ”sa ƉrvĆ©nytelen QR-kód MegtartĆ”s - KeresĆ©s, vagy SimpleX hivatkozĆ”s beillesztĆ©se + KeresĆ©s, vagy SimpleX-hivatkozĆ”s beillesztĆ©se Belső hibĆ”k megjelenĆ­tĆ©se Kritikus hiba Belső hiba @@ -1554,7 +1554,7 @@ SzĆ”mĆ­tógĆ©p elfoglalt SzĆ”mĆ­tógĆ©p inaktĆ­v CsevegĆ©s ĆŗjraindĆ­tĆ”sa - IdőtĆŗllĆ©pĆ©s a szĆ”mĆ­tógĆ©pről való kapcsolódĆ”s kƶzben + IdőtĆŗllĆ©pĆ©s a szĆ”mĆ­tógĆ©phez való csatlakozĆ”skor SzĆ”mĆ­tógĆ©p kliens kapcsolata megszakadt A kapcsolat megszakadt A kapcsolat megszakadt @@ -1573,7 +1573,7 @@ %s problĆ©ma miatt megszakadt a kapcsolat %s mobil eszkƶz nem talĆ”lható]]> %s mobil eszkƶzzel rossz Ć”llapotban van]]> - %s mobil eszkƶzhƶz való csatlakozĆ”s kƶzben]]> + %s mobil eszkƶzhƶz való csatlakozĆ”skor]]> ismeretlen LassĆŗ funkció LassĆŗ API-hĆ­vĆ”sok megjelenĆ­tĆ©se @@ -1581,11 +1581,11 @@ Fejlesztői beĆ”llĆ­tĆ”sok A funkció vĆ©grehajtĆ”sa tĆŗl sokĆ”ig tart: %1$d mĆ”sodperc: %2$s %s mobil eszkƶz elfoglalt]]> - %1$s (mĆ”r nem tag) + (MĆ”r nem tag) %1$s ismeretlen stĆ”tusz %1$s megvĆ”ltoztatta a nevĆ©t erre: %2$s - tƶrƶlt kapcsolattartĆ”si cĆ­m - tƶrƶlte a profilkĆ©pĆ©t + eltĆ”volĆ­totta a kapcsolattartĆ”si cĆ­met + eltĆ”volĆ­totta a profilkĆ©pĆ©t Ćŗj kapcsolattartĆ”si cĆ­m beĆ”llĆ­tĆ”sa Ćŗj profilkĆ©pet Ć”llĆ­tott be frissĆ­tett profil @@ -1598,15 +1598,15 @@ Mentett üzenet Megosztva ekkor: %s Minden üzenet tƶrlĆ©sre kerül – ez a művelet nem vonható vissza! - TovĆ”bbfejlesztett üzenetküldĆ©s + TovĆ”bbfejlesztett üzenetkĆ©zbesĆ­tĆ©s CsatlakozĆ”s csoportos beszĆ©lgetĆ©sekhez HivatkozĆ”s beillesztĆ©se a kapcsolódĆ”shoz! PrivĆ”t jegyzetek - A keresősĆ”v elfogadja a meghĆ­vó hivatkozĆ”sokat. - TitkosĆ­tott fĆ”jlokkal Ć©s mĆ©diatartalommal. - Csƶkkentett akkumulĆ”torhasznĆ”lattal. + A keresősĆ”v elfogadja a meghĆ­vó-hivatkozĆ”sokat. + TitkosĆ­tott fĆ”jlokkal Ć©s mĆ©diatartalmakkal. + Csƶkkentett akkumulĆ”tor-hasznĆ”lattal. Magyar Ć©s tƶrƶk felhasznĆ”lói felület - A kƶzelmĆŗlt esemĆ©nyei Ć©s tovĆ”bbfejlesztett jegyzĆ©k bot. + A kƶzelmĆŗlt esemĆ©nyei Ć©s tovĆ”bbfejlesztett kƶnyvtĆ”rbot. feloldotta %s letiltĆ”sĆ”t ƶn feloldotta %s letiltĆ”sĆ”t letiltva @@ -1619,15 +1619,15 @@ LetiltĆ”s feloldĆ”sa mindenki szĆ”mĆ”ra Mindenki szĆ”mĆ”ra feloldja a tag letiltĆ”sĆ”t? ƶn letiltotta őt: %s - Hiba a tag mindenki szĆ”mĆ”ra való letiltĆ”sa kƶzben + Hiba a tag mindenki szĆ”mĆ”ra való letiltĆ”sakor Az üzenet tĆŗl nagy - Az üdvƶzlő üzenet tĆŗl hosszĆŗ + Az üdvƶzlőüzenet tĆŗl hosszĆŗ Az adatbĆ”zis Ć”tkƶltƶztetĆ©se folyamatban van. \nEz eltarthat nĆ©hĆ”ny percig. HanghĆ­vĆ”s A hĆ­vĆ”s befejeződƶtt VideóhĆ­vĆ”s - Hiba a bƶngĆ©sző megnyitĆ”sa kƶzben + Hiba a bƶngĆ©sző megnyitĆ”sakor A hĆ­vĆ”sokhoz egy alapĆ©rtelmezett webbƶngĆ©sző szüksĆ©ges. ƁllĆ­tson be egy alapĆ©rtelmezett webbƶngĆ©szőt az eszkƶzƶn, Ć©s osszon meg tovĆ”bbi informĆ”ciókat a SimpleX Chat fejlesztőivel. HĆ”lózati beĆ”llĆ­tĆ”sok megerősĆ­tĆ©se Hiba a csevegĆ©si adatbĆ”zis exportĆ”lĆ”sakor @@ -1683,7 +1683,7 @@ %s feltƶltve Sikertelen feltƶltĆ©s ArchĆ­vum feltƶltĆ©se - FigyelmeztetĆ©s: a csevegĆ©s elindĆ­tĆ”sa egyszerre tƶbb eszkƶzƶn nem tĆ”mogatott, tovĆ”bbĆ” üzenetkĆ©zbesĆ­tĆ©si hibĆ”kat okozhat + FigyelmeztetĆ©s: a csevegĆ©s elindĆ­tĆ”sa egyszerre tƶbb eszkƶzƶn nem tĆ”mogatott, Ć©s üzenetkĆ©zbesĆ­tĆ©si hibĆ”kat okozhat ImportĆ”lĆ”s ismĆ©t szabvĆ”nyos vĆ©gpontok kƶzƶtti titkosĆ­tĆ”s ƁtkƶltƶztetĆ©s ide @@ -1696,7 +1696,7 @@ ƁtkƶltƶztetĆ©s befejezve ƁtkƶltƶztetĆ©s egy mĆ”sik eszkƶzre QR-kód hasznĆ”latĆ”val. ƁtkƶltƶztetĆ©s - MegjegyzĆ©s: ha kĆ©t eszkƶzƶn is ugyanazt az adatbĆ”zist hasznĆ”lja, akkor biztonsĆ”gi vĆ©delemkĆ©nt megszakĆ­tja az ismerőseitől Ć©rkező üzenetek visszafejtĆ©sĆ©t.]]> + Figyelem: ha kĆ©t eszkƶzƶn is ugyanazt az adatbĆ”zist hasznĆ”lja, akkor biztonsĆ”gi vĆ©delemkĆ©nt megszakĆ­tja az ismerőseitől Ć©rkező üzenetek visszafejtĆ©sĆ©t.]]> MegpróbĆ”lhatja mĆ©g egyszer. HibĆ”s hivatkozĆ”s vĆ©gpontok kƶzƶtti kvantumrezisztens titkosĆ­tĆ”s @@ -1721,16 +1721,16 @@ TovĆ”bbi Wi-Fi tovĆ”bbĆ­tott - A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva - A csoport tagjai küldhetnek SimpleX hivatkozĆ”sokat. + A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva + A csoport tagjai küldhetnek SimpleX-hivatkozĆ”sokat. tulajdonosok adminok minden tag - SimpleX hivatkozĆ”sok + SimpleX-hivatkozĆ”sok A hangüzenetek küldĆ©se le van tiltva - A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban. - A SimpleX hivatkozĆ”sok küldĆ©se le van tiltva - FĆ”jlok Ć©s mĆ©dia tartalom küldĆ©se le van tiltva + A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva ebben a csoportban. + A SimpleX-hivatkozĆ”sok küldĆ©se le van tiltva + A fĆ”jlok- Ć©s mĆ©diatartalmak nincsenek engedĆ©lyezve A SimpleX hivatkozĆ”sok küldĆ©se engedĆ©lyezve van. SzĆ”mukra engedĆ©lyezve: mentett @@ -1769,7 +1769,7 @@ \nCĆ©lkiszolgĆ”ló hiba: %2$s Hiba: %1$s KapacitĆ”s tĆŗllĆ©pĆ©s - a cĆ­mzett nem kapta meg a korĆ”bban elküldƶtt üzeneteket. - Üzenet kĆ©zbesĆ­tĆ©si figyelmeztetĆ©s + ÜzenetkĆ©zbesĆ­tĆ©si figyelmeztetĆ©s A kiszolgĆ”ló cĆ­me nem kompatibilis a hĆ”lózati beĆ”llĆ­tĆ”sokkal. Soha Ismeretlen kiszolgĆ”lók @@ -1849,12 +1849,12 @@ kiszolgĆ”ló vĆ”rakoztatĆ”si infó: %1$s \nutoljĆ”ra kĆ©zbesĆ­tett üzenet: %2$s HibĆ”s kulcs vagy ismeretlen fĆ”jltƶredĆ©k cĆ­m - valószĆ­nűleg a fĆ”jl tƶrlődƶtt. - Ideiglenes fĆ”jlhiba + IdeiglenesfĆ”jl-hiba ÜzenetĆ”llapot ÜzenetĆ”llapot: %s FĆ”jlhiba A fĆ”jl nem talĆ”lható - valószĆ­nűleg a fĆ”jlt tƶrƶltĆ©k vagy visszavontĆ”k. - FĆ”jlkiszolgĆ”ló hiba: %1$s + FĆ”jlkiszolgĆ”ló-hiba: %1$s FĆ”jlĆ”llapot FĆ”jlĆ”llapot: %s MĆ”solĆ”si hiba @@ -1919,7 +1919,7 @@ Feltƶltƶtt fĆ”jlok Letƶltƶtt fĆ”jltƶredĆ©kek Letƶltƶtt fĆ”jlok - KiszolgĆ”ló beĆ”llĆ­tĆ”sainak megnyitĆ”sa + KiszolgĆ”ló-beĆ”llĆ­tĆ”sok megnyitĆ”sa KiszolgĆ”ló cĆ­me FeltƶltĆ©si hibĆ”k NyugtĆ”zva @@ -1946,8 +1946,8 @@ KorĆ”bban kapcsolódott kiszolgĆ”lók PrivĆ”t ĆŗtvĆ”lasztĆ”si hiba Fogadott üzenetek - Az ƶsszes kiszolgĆ”lóhoz való ĆŗjrakapcsolĆ”s az üzenetek kĆ©zbesĆ­tĆ©sĆ©nek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. - A kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenet kĆ©zbesĆ­tĆ©sĆ©nek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. + Az ƶsszes kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. + A kiszolgĆ”lóhoz való ĆŗjrakapcsolódĆ”s az üzenetkĆ©zbesĆ­tĆ©si jelentĆ©sek kikĆ©nyszerĆ­tĆ©sĆ©hez. Ez tovĆ”bbi adatforgalmat hasznĆ”l. Elküldƶtt üzenetek Munkamenetek Ć”tvitele Ɩsszesen @@ -2022,13 +2022,13 @@ Üzenet küldĆ©se a hĆ­vĆ”sok engedĆ©lyezĆ©sĆ©hez. EngedĆ©lyeznie kell a hĆ­vĆ”sokat az ismerőse szĆ”mĆ”ra, hogy fel tudjĆ”k hĆ­vni egymĆ”st. A(z) %1$s nevű ismerősĆ©vel folytatott beszĆ©lgetĆ©seit tovĆ”bbra is megtekintheti a csevegĆ©sek listĆ”jĆ”ban. - Üzenet - KivĆ”lasztĆ”s + Üzenet… + KijelƶlĆ©s Az üzenetek minden tag szĆ”mĆ”ra moderĆ”ltkĆ©nt lesznek megjelƶlve. Nincs kivĆ”lasztva semmi Az üzenetek tƶrlĆ©sre lesznek jelƶlve. A cĆ­mzett(ek) kĆ©pes(ek) lesz(nek) felfedni ezt az üzenetet. Tƶrli a tagok %d üzenetĆ©t? - %d kivĆ”lasztva + %d kijelƶlve Az üzenetek minden tag szĆ”mĆ”ra tƶrlĆ©sre kerülnek. CsevegĆ©si adatbĆ”zis exportĆ”lva Kapcsolatok- Ć©s kiszolgĆ”lók Ć”llapotĆ”nak megjelenĆ­tĆ©se. @@ -2056,11 +2056,47 @@ ElhomĆ”lyosĆ­tĆ”s a jobb adatvĆ©delemĆ©rt. Automatikus frissĆ­tĆ©s LĆ©trehozĆ”s - Új verziók letƶltĆ©se a GitHubról + Új verziók letƶltĆ©se a GitHubról. BetűmĆ©ret nƶvelĆ©se. MeghĆ­vĆ”s Új csevegĆ©si Ć©lmĆ©ny šŸŽ‰ Új üzenet ƉrvĆ©nytelen hivatkozĆ”s - Ellenőrizze, hogy a SimpleX hivatkozĆ”s helyes-e. + Ellenőrizze, hogy a SimpleX-hivatkozĆ”s helyes-e. + Hiba a profilvĆ”ltĆ”skor + A kapcsolata Ć”t lett helyezve ide: %s, de egy vĆ”ratlan hiba tƶrtĆ©nt a profilra való Ć”tirĆ”nyĆ­tĆ”skor. + Az üzenetek tƶrlĆ©sre kerülnek - ez a művelet nem vonható vissza! + ArchĆ­vum eltĆ”volĆ­tĆ”sa? + A feltƶltƶtt adatbĆ”zis-archĆ­vum vĆ©glegesen eltĆ”volĆ­tĆ”sra kerül a kiszolgĆ”lókról. + CSEVEGƉSI ADATBƁZIS + Profil megosztĆ”sa + RendszerbeĆ”llĆ­tĆ”sok hasznĆ”lata + CsevegĆ©si profil kivĆ”lasztĆ”sa + Ne hasznĆ”lja a hitelesĆ­tő adatokat proxyval. + Külƶnbƶző proxy hitelesĆ­tő adatok hasznĆ”lata minden egyes profilhoz. + Külƶnbƶző proxy hitelesĆ­tő adatok hasznĆ”lata minden egyes kapcsolathoz. + Jelszó + FelhasznĆ”lónĆ©v + A hitelesĆ­tőadatai titkosĆ­tatlanul is elküldhetőek. + Hiba a proxy mentĆ©sekor + Győződjƶn meg arról, hogy a proxy konfigurĆ”ciója helyes. + ProxyhitelesĆ­tĆ©s + VĆ©letlenszerű hitelesĆ­tő adatok hasznĆ”lata + %1$d egyĆ©b fĆ”jlhiba. + Nincs mit tovĆ”bbĆ­tani! + %1$d fĆ”jl letƶltĆ©se mĆ©g folyamatban van. + %1$d fĆ”jlt nem sikerült letƶlteni. + %1$d fĆ”jl nem lett letƶltve. + LetƶltĆ©s + %1$d fĆ”jl tƶrƶlve lett. + %1$s üzenet tovĆ”bbĆ­tĆ”sa + Üzenetek tovĆ”bbĆ­tĆ”sa… + %1$d fĆ”jlhiba: +\n%2$s + %1$s üzenet nem lett tovĆ”bbĆ­tva + %1$s üzenet tovĆ”bbĆ­tĆ”sa? + Üzenetek tovĆ”bbĆ­tĆ”sa fĆ”jlok nĆ©lkül? + Az üzeneteket tƶrƶltĆ©k miutĆ”n kivĆ”lasztotta őket. + %1$s üzenet mentĆ©se + Hiba az üzenetek tovĆ”bbĆ­tĆ”sakor \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 1bc9f6283f..088a233f6e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2069,4 +2069,40 @@ Nuovo messaggio Link non valido Controlla che il link SimpleX sia corretto. + Errore nel cambio di profilo + Seleziona il profilo di chat + Condividi il profilo + DATABASE DELLA CHAT + ModalitĆ  di sistema + Rimuovere l\'archivio? + I messaggi verranno eliminati. Non ĆØ reversibile! + L\'archivio del database caricato verrĆ  rimosso definitivamente dai server. + La tua connessione ĆØ stata spostata a %s, ma si ĆØ verificato un errore imprevisto durante il reindirizzamento al profilo. + Non usare credenziali con proxy. + Assicurati che la configurazione del proxy sia corretta. + Autenticazione del proxy + Usa diverse credenziali del proxy per ogni connessione. + Usa diverse credenziali del proxy per ogni profilo. + Usa credenziali casuali + Le credenziali potrebbero essere inviate in chiaro. + Errore di salvataggio del proxy + Password + Nome utente + %1$d file ĆØ/sono ancora in scaricamento. + %1$s messaggi non inoltrati + %1$d altro/i errore/i di file. + Errore nell\'inoltro dei messaggi + %1$d errore/i di file: +\n%2$s + Inoltrare %1$s messaggio/i? + Inoltrare i messaggi senza file? + I messaggi sono stati eliminati dopo che li hai selezionati. + Niente da inoltrare! + %1$d file ha/hanno fallito lo scaricamento. + %1$d file ĆØ/sono stato/i eliminato/i. + %1$d file non ĆØ/sono stato/i scaricato/i. + Scarica + Inoltro di %1$s messaggi + Inoltra messaggi… + Salvataggio di %1$s messaggi \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 38df6a8b16..8ca7e6fddb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -312,7 +312,7 @@ č‡Ŗåˆ†ć®ć‚µćƒ¼ćƒć®ä½æć„ę–¹ ICEć‚µćƒ¼ćƒ (1蔌に1ć‚µćƒ¼ćƒ) ćƒćƒƒćƒˆćƒÆćƒ¼ć‚ÆćØć‚µćƒ¼ćƒ - ćƒćƒƒćƒˆćƒÆćƒ¼ć‚ÆčØ­å®š + 高度な設定 ć‚¢ćƒ‰ćƒ¬ć‚¹ć‚’å‰Šé™¤ äæå­˜ć›ćšć«é–‰ć˜ć‚‹ č”Øē¤ŗć®åå‰ć«ćÆē©ŗē™½ćŒä½æē”Øć§ćć¾ć›ć‚“ć€‚ @@ -535,7 +535,7 @@ č‰²ä»˜ć åæœē­” åˆ†ę•£åž‹ - ć‚¹ćƒ‘ćƒ ć‚„ę‚Ŗč³Ŗé€äæ”ć‚’å®Œå…Øé˜²ę­¢ + ć‚¹ćƒ‘ćƒ č€ę€§ å³ę™‚ å®šęœŸēš„ é€šč©±ćÆę—¢ć«ēµ‚äŗ†ć—ć¦ć¾ć™ļ¼ @@ -1837,4 +1837,53 @@ SMPć‚µćƒ¼ćƒćƒ¼ć®ę§‹ęˆ ęŽ„ē¶šäø­ XFTPć‚µćƒ¼ćƒćƒ¼ć®ę§‹ęˆ + ćƒćƒ£ćƒˆćƒŖć‚¹ćƒˆåˆ‡ć‚Šę›æćˆ + é€£ēµ”å…ˆ + ćƒ”ćƒƒć‚»ćƒ¼ć‚øć‚µćƒ¼ćƒ + ćƒ”ćƒ‡ć‚£ć‚¢&ćƒ•ć‚”ć‚¤ćƒ«ć‚µćƒ¼ćƒ + ćƒćƒ£ćƒƒćƒˆćƒ„ćƒ¼ćƒ«ćƒćƒ¼ć‚’čæ‘ć„ć‘ć‚‹ + 招待 + 作成 + ćƒ”ćƒƒć‚»ćƒ¼ć‚ø + ćƒćƒ£ćƒƒćƒˆćƒ„ćƒ¼ćƒ«ćƒćƒ¼ć‚’čæ‘ć„ć‘ć‚‹ + QRć‚¹ć‚­ćƒ£ćƒ³ / ćƒŖćƒ³ć‚Æć®č²¼ć‚Šä»˜ć‘ + ē‰‡ę‰‹ć§ć‚¢ćƒ—ćƒŖć‚’åˆ©ē”Øć§ćć¾ć™ + 招待 + ćƒŖćƒ³ć‚Æć®č²¼ć‚Šä»˜ć‘ + ē„”åŠ¹ + ē¾åœØć®ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ« + å…Øć¦ć®ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ« + 通話 + é€£ēµ”å…ˆć®å‰Šé™¤ć‚’ē¢ŗčŖć—ć¾ć™ć‹ļ¼Ÿ + ęŽ„ē¶š + é€£ēµ”å…ˆćŒå‰Šé™¤ć•ć‚Œć¾ć™ - ć“ć®ę“ä½œćÆå–ć‚Šę¶ˆć›ć¾ć›ć‚“ļ¼ + ćƒ—ćƒ­ćƒ•ć‚£ćƒ¼ćƒ«ć®åˆ‡ć‚Šę›æćˆć‚Øćƒ©ćƒ¼ + ćƒ”ćƒ‡ć‚£ć‚¢ć®ć¼ć‹ć— + ćƒćƒ£ćƒƒćƒˆćƒ‡ćƒ¼ć‚æćƒ™ćƒ¼ć‚¹ + ē¶šć‘ć‚‹ + é€£ēµ”å…ˆć®å‰Šé™¤å®Œäŗ†ļ¼ + 詳瓰 + éžć‚¢ć‚Æćƒ†ć‚£ćƒ– + ē„”åŠ¹ + ćƒ—ćƒ­ć‚­ć‚·ć®äæå­˜ć‚Øćƒ©ćƒ¼ + é€šč©±ć‚’čØ±åÆć—ć¾ć™ć‹ļ¼Ÿ + é€£ēµ”å…ˆćŒå‰Šé™¤ć•ć‚Œć¾ć—ćŸć€‚ + ē„”åŠ¹ + äø€åŗ¦ć«ęœ€å¤§20ä»¶ć®ćƒ”ćƒƒć‚»ćƒ¼ć‚øć‚’å‰Šé™¤ć§ćć¾ć™ć€‚ + ć‚µćƒ¼ćƒć«ęŽ„ē¶šäø­ + ć‚Øćƒ©ćƒ¼ + ć‚µćƒ¼ćƒćƒ¼ćøć®å†ęŽ„ē¶šć‚Øćƒ©ćƒ¼ + ć‚Øćƒ©ćƒ¼ + ćƒ•ć‚”ć‚¤ćƒ« + å¾©å·åŒ–ć‚Øćƒ©ćƒ¼ + å‰Šé™¤ć‚Øćƒ©ćƒ¼ + 重複 + ęœŸé™åˆ‡ć‚Œ + 統計の詳瓰 + ćƒ—ćƒ­ć‚­ć‚·ć§čŖčØ¼ęƒ…å ±ć‚’ä½æē”Øć—ćŖć„ć§ćć ć•ć„ć€‚ + ć‚µćƒ¼ćƒćƒ¼ćøć®å†ęŽ„ē¶šć‚Øćƒ©ćƒ¼ + ēµ±čØˆć®ćƒŖć‚»ćƒƒćƒˆć‚Øćƒ©ćƒ¼ + ćƒ”ćƒƒć‚»ćƒ¼ć‚øć‚’é€äæ”ć™ć‚‹ć“ćØćŒć§ćć¾ć›ć‚“ + é€£ēµ”å…ˆćØé€šč©±ć™ć‚‹ć“ćØćŒć§ćć¾ć›ć‚“ + ęŽ„ē¶šå¾…ć” \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 9a8810de84..1ff95a0ac5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -72,7 +72,7 @@ Over SimpleX Over SimpleX Chat hier boven, dan: - Alle gesprekken en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! + Alle chats en berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd. Sta verdwijnende berichten alleen toe als uw contact dit toestaat. Sta spraak berichten alleen toe als uw contact ze toestaat. @@ -90,7 +90,7 @@ APP ICON App versie App versie: v%s - voor elk chat profiel dat je in de app hebt .]]> + voor elk chatprofiel dat je in de app hebt .]]> audio oproep (niet e2e versleuteld) Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn. Oproep beĆ«indigd @@ -120,9 +120,9 @@ Gesprek archief Wachtwoord database wijzigen\? Chat is gestopt - Gesprek voorkeuren - Chat profiel - GESPREKKEN + Chat voorkeuren + Chatprofiel + CHATS Praat met de ontwikkelaars Controleer het server adres en probeer het opnieuw. Bestand @@ -180,7 +180,7 @@ "De database wordt versleuteld en het wachtwoord wordt opgeslagen in de Keychain." Het wachtwoord voor database versleuteling wordt bijgewerkt. Database fout - Database wachtwoord is vereist om je gesprekken te openen. + Database wachtwoord is vereist om je chats te openen. Contact bestaat al Oproep verbinden Maak link @@ -233,7 +233,7 @@ Chat archief verwijderen\? Archief verwijderen Verwijder contact\? - Chat profiel verwijderen\? + Chatprofiel verwijderen? Verwijderen voor iedereen Link verwijderen direct @@ -250,7 +250,7 @@ Verwijder bericht\? Verwijder berichten Wachtrij verwijderen - Verwijder bestanden voor alle chat profielen + Verwijder bestanden voor alle chatprofielen Verwijder voor mij Groep verwijderen Link verwijderen\? @@ -284,7 +284,7 @@ %dmth %d uren %dh - Chat profiel verwijderen\? + Chatprofiel verwijderen? Chat profiel verwijderen voor verwijderd Beschrijving @@ -347,7 +347,7 @@ Groepsleden kunnen verzonden berichten onomkeerbaar verwijderen. (24 uur) Groepsleden kunnen directe berichten sturen Groepsleden kunnen spraak berichten verzenden. - Per chat profiel (standaard) of per verbinding (BETA). + Per chatprofiel (standaard) of per verbinding (BETA). Verschillende namen, avatars en transportisolatie. Franse interface Fout bij opslaan van groep profiel @@ -392,7 +392,7 @@ Fout bij opslaan van SMP servers Fout bij updaten van netwerk configuratie Kan het gesprek niet laden - Kan de gesprekken niet laden + Kan de chats niet laden Volledige link dubbel bericht Ongeldige verbinding link @@ -456,10 +456,10 @@ Groep verlaten\? Nieuwe leden rol Geen contacten om toe te voegen - Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chat profiel. + Het maakt het mogelijk om veel anonieme verbindingen te hebben zonder enige gedeelde gegevens tussen hen in een enkel chatprofiel. Licht nee - Meerdere chat profielen + Meerdere chatprofielen Italiaanse interface Concept bericht Meer verbeteringen volgen snel! @@ -590,7 +590,7 @@ Alleen uw contact kan spraak berichten verzenden. Verbied het onomkeerbaar verwijderen van berichten. voorgesteld %s - Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de gesprekken. + Sla het wachtwoord veilig op. Als u deze kwijtraakt, heeft u GEEN toegang tot de chats. Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u deze kwijtraakt. Chat openen Voer het vorige wachtwoord in na het herstellen van de database back-up. Deze actie kan niet ongedaan gemaakt worden. @@ -620,12 +620,12 @@ ontvangt, uw contacten de servers die u gebruikt om ze berichten te sturen.]]> Video aan Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. - Deze instelling is van toepassing op berichten in uw huidige chat profiel + Deze instelling is van toepassing op berichten in uw huidige chatprofiel Bewaar archief bijgewerkt groep profiel verwijderd - Uw chat profiel wordt verzonden naar de groepsleden - Je hebt al een chat profiel met dezelfde weergave naam. Kies een andere naam. + Uw chatprofiel wordt verzonden naar de groepsleden + Je hebt al een chatprofiel met dezelfde weergave naam. Kies een andere naam. U bent al verbonden met %1$s. Test mislukt bij stap %s. Veilige wachtrij @@ -650,8 +650,8 @@ Deze tekst is beschikbaar in instellingen Welkom! je bent uitgenodigd voor de groep - Je hebt geen gesprekken - Gesprekken + Je hebt geen chats + Chats Deel bestand… Afbeelding delen… Wachten op afbeelding @@ -680,7 +680,7 @@ SimpleX Adres Toon QR-code SimpleX-Logo - Je chat profiel wordt verzonden naar uw contact + Je chatprofiel wordt verzonden naar uw contact Je wordt verbonden met de groep wanneer het apparaat van de groep host online is, even geduld a.u.b. of controleer het later! U wordt verbonden wanneer uw verbindingsverzoek wordt geaccepteerd, even geduld a.u.b. of controleer later! Je wordt verbonden wanneer het apparaat van je contact online is, even geduld a.u.b. of controleer het later! @@ -696,7 +696,7 @@ Stuur ons een e-mail SimpleX Vergrendelen SMP servers - Bewaar servers + Servers opslaan Servertest mislukt! Sommige servers hebben de test niet doorstaan: Servers testen @@ -747,7 +747,7 @@ CHAT UITVOEREN Uw chat database Wachtwoord instellen om te exporteren - Start de app opnieuw om een nieuw chat profiel aan te maken. + Start de app opnieuw om een nieuw chatprofiel aan te maken. U mag ALLEEN de meest recente versie van uw chat-database op ƩƩn apparaat gebruiken, anders ontvangt u mogelijk geen berichten meer van sommige contacten. Start de app opnieuw om de geĆÆmporteerde chat database te gebruiken. Stop de chat om database acties mogelijk te maken. @@ -842,7 +842,7 @@ Camera Gebruik voor nieuwe verbindingen Star on GitHub - De servers voor nieuwe verbindingen van je huidige chat profiel + De servers voor nieuwe verbindingen van je huidige chatprofiel Uw SMP servers Opgeslagen WebRTC ICE servers worden verwijderd. Uw ICE servers @@ -865,7 +865,7 @@ Uw contacten kunnen volledige verwijdering van berichten toestaan. U moet elke keer dat de app start het wachtwoord invoeren, deze wordt niet op het apparaat opgeslagen. Verkeerd wachtwoord voor de database - Bewaar het wachtwoord en open je gesprekken + Bewaar het wachtwoord en open je chats De poging om het wachtwoord van de database te wijzigen is niet voltooid. Database back-up terugzetten Database back-up terugzetten\? @@ -963,7 +963,7 @@ Fout bij updaten van gebruikers privacy Verder verminderd batterij verbruik Groep welkom bericht - Verborgen chat profielen + Verborgen chatprofielen Profiel verbergen Verbergen Verborgen profiel wachtwoord @@ -974,7 +974,7 @@ Nu kunnen beheerders: \n- berichten van leden verwijderen. \n- schakel leden uit ("waarnemer" rol) - Bescherm je chat profielen met een wachtwoord! + Bescherm je chatprofielen met een wachtwoord! Wachtwoord om weer te geven Groep profiel opslaan en bijwerken Servers opslaan\? @@ -988,7 +988,7 @@ zichtbaar maken Dempen opheffen Welkom bericht - Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoekveld in op de pagina Uw chat profielen. + Om uw verborgen profiel te onthullen, voert u een volledig wachtwoord in een zoekveld in op de pagina Uw chatprofielen. Welkom bericht U ontvangt nog steeds oproepen en meldingen van gedempte profielen wanneer deze actief zijn. Database downgraden @@ -1011,9 +1011,9 @@ EXPERIMENTEEL Verwijder profiel Profiel wachtwoord - Chat profiel zichtbaar maken + Chatprofiel zichtbaar maken Profiel zichtbaar maken - Chat profiel verwijderen\? + Chatprofiel verwijderen? Gevraagd om de video te ontvangen Er kunnen slechts 10 video\'s tegelijk worden verzonden Te veel video\'s! @@ -1107,7 +1107,7 @@ Poolse interface Dank aan de gebruikers – draag bij via Weblate! Video\'s en bestanden tot 1 GB - Chat profielen openen + Open chatprofielen Over SimpleX adres Kom meer te weten Om verbinding te maken, kan uw contact de QR-code scannen of de link in de app gebruiken. @@ -1249,7 +1249,7 @@ Adres wijziging wordt afgebroken. Het oude ontvangstadres wordt gebruikt. Annuleer het wijzigen van het adres Afbreken - Geen gefilterde gesprekken + Geen gefilterde chats Alleen groep eigenaren kunnen bestanden en media inschakelen. Bestanden en media zijn verboden in deze groep. Favoriet @@ -1303,7 +1303,7 @@ Ontvangst bevestiging verzenden De tweede vink die we gemist hebben! āœ… Filter ongelezen en favoriete chats. - Vind gesprekken sneller + Vind chats sneller Repareer versleuteling na het herstellen van back-ups. Behoud uw verbindingen EĆ©n bericht laten verdwijnen @@ -1538,7 +1538,7 @@ Zichtbare geschiedenis App toegangscode Nieuw gesprek - Gesprekken laden… + Chats laden… Link maken… Of scan de QR-code Ongeldige QR-code @@ -2057,7 +2057,7 @@ U kunt dit wijzigen in de instellingen onder uiterlijk CreĆ«ren Vervagen voor betere privacy. - Afspelen via de gesprekken lijst. + Afspelen via de chatlijst. Download nieuwe versies van GitHub. Vergroot het lettertype. App automatisch upgraden @@ -2067,4 +2067,13 @@ Nieuw bericht Ongeldige link Controleer of de SimpleX-link correct is. + Fout bij wisselen van profiel + Selecteer chatprofiel + Profiel delen + Uw verbinding is verplaatst naar %s, maar er is een onverwachte fout opgetreden tijdens het omleiden naar het profiel. + CHAT DATABASE + Systeemmodus + Archief verwijderen? + Berichten worden verwijderd. Dit kan niet ongedaan worden gemaakt! + Het geüploade databasearchief wordt permanent van de servers verwijderd. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index b38348b30f..db175452a2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -949,4 +949,23 @@ Detalhes VocĆŖ pode partilhar o seu endereƧo com os seus contactos para permitir que se conectem com %s. VocĆŖ pode partilhar o seu endereƧo como uma ligação ou código QR - qualquer pessoa pode conectar-se a si. + Permitir + Todas as novas mensagens de %s serĆ£o ocultadas! + Somente vocĆŖ pode fazer ligaƧƵes. + Todos os modos de cores + administradores + "aceitando criptografia para %s…" + Adicionar contato + Permitir downgrade + Todas as mensagens serĆ£o deletadas - isso nĆ£o poderĆ” ser desfeito! + Mais algumas coisas + Permitir envio de arquivos e mĆ­dias. + Administradores podem bloquear um membro para todos. + Aceitando criptografia + ConfiguraƧƵes avanƧadas + todos os membros + Erros de reconhecimento + MudanƧa de endereƧo serĆ” cancelada. Antigo endereƧo de recebimento serĆ” usado. + Permitir ligaƧƵes? + ConexƵes ativas \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index f6b3b12431..fd3001cd0d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -776,7 +776,7 @@ Š”ŃŠŗŃƒŃ”Š¼Š¾ ŠŗŠ¾Ń€ŠøŃŃ‚ŃƒŠ²Š°Ń‡Š°Š¼ – ŠæŃ€ŠøŃ”Š“Š½ŃƒŠ¹Ń‚ŠµŃŃ через Weblate! Тепер аГміністратори Š¼Š¾Š¶ŃƒŃ‚ŃŒ: \n- Š²ŠøŠ“Š°Š»ŃŃ‚Šø ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń ŃƒŃ‡Š°ŃŠ½ŠøŠŗŃ–Š². -\n- вимикати ŃƒŃ‡Š°ŃŠ½ŠøŠŗŃ–Š² (Ń€Š¾Š»ŃŒ спостерігач) +\n- вимикати ŃƒŃ‡Š°ŃŠ½ŠøŠŗŃ–Š² (Ń€Š¾Š»ŃŒ спостерігача). Š’ŃŃ‚Š°Š½Š¾Š²Ń–Ń‚ŃŒ ŠæŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń, ŃŠŗŠµ ŠæŠ¾ŠŗŠ°Š·ŃƒŃ”Ń‚ŃŒŃŃ новим ŃƒŃ‡Š°ŃŠ½ŠøŠŗŠ°Š¼! ДоГатково зменшено Š²ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š°Š½Š½Ń батареї Š‘Ń–Š»ŃŒŃˆŠµ ŠæŠ¾Š»Ń–ŠæŃˆŠµŠ½ŃŒ незабаром! @@ -2067,4 +2067,23 @@ Š”ŠŗŠøŠ½ŃƒŃ‚Šø всі піГказки Š”Š¾ŃŃ‚ŃƒŠæŠ½Š¾ Š¾Š½Š¾Š²Š»ŠµŠ½Š½Ń: %s Š—Š°Š²Š°Š½Ń‚Š°Š¶ŠµŠ½Š½Ń Š¾Š½Š¾Š²Š»ŠµŠ½Š½Ń скасовано + БАЗА Š”ŠŠŠ˜Š„ ЧАТУ + Вибрати ŠæŃ€Š¾Ń„Ń–Š»ŃŒ Ń‡Š°Ń‚Ńƒ + Помилка при зміні ŠæŃ€Š¾Ń„Ń–Š»ŃŽ + ŠŸŠ¾Š²Ń–Š“Š¾Š¼Š»ŠµŠ½Š½Ń Š±ŃƒŠ“ŃƒŃ‚ŃŒ виГалені — це не можна ŃŠŗŠ°ŃŃƒŠ²Š°Ń‚Šø! + ВиГалити архів? + ŠŸŠ¾Š“Ń–Š»ŠøŃ‚ŠøŃŃ профілем + Завантажений архів бази Ганих буГе остаточно виГалено Š· серверів. + Š’Š°ŃˆŠµ Š·\'Ń”Š“Š½Š°Š½Š½Ń було перенесено на %s, але виникла неспоГівана помилка піГ час ŠæŠµŃ€ŠµŠ½Š°ŠæŃ€Š°Š²Š»ŠµŠ½Š½Ń на ŠæŃ€Š¾Ń„Ń–Š»ŃŒ. + Режим системи + ŠŠµ Š²ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š¾Š²ŃƒŠ¹Ń‚Šµ облікові Гані Š· проксі. + ŠŃƒŃ‚ŠµŠ½Ń‚ŠøŃ„Ń–ŠŗŠ°Ń†Ń–Ń проксі + Š’ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š¾Š²ŃƒŠ¹Ń‚Šµ різні облікові Гані проксі Š“Š»Ń кожного Š·\'Ń”Š“Š½Š°Š½Š½Ń. + Š’ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š¾Š²ŃƒŠ²Š°Ń‚Šø випаГкові облікові Гані + Š’Š°ŃˆŃ– облікові Гані Š¼Š¾Š¶ŃƒŃ‚ŃŒ Š±ŃƒŃ‚Šø наГіслані в Š½ŠµŠ·Š°ŃˆŠøŃ„Ń€Š¾Š²Š°Š½Š¾Š¼Ńƒ Š²ŠøŠ³Š»ŃŠ“Ń–. + Помилка піГ час Š·Š±ŠµŃ€ŠµŠ¶ŠµŠ½Š½Ń проксі + ŠŸŠµŃ€ŠµŠŗŠ¾Š½Š°Š¹Ń‚ŠµŃŃ, що ŠŗŠ¾Š½Ń„Ń–Š³ŃƒŃ€Š°Ń†Ń–Ń проксі ŠæŃ€Š°Š²ŠøŠ»ŃŒŠ½Š°. + ŠŸŠ°Ń€Š¾Š»ŃŒ + Ім\'я ŠŗŠ¾Ń€ŠøŃŃ‚ŃƒŠ²Š°Ń‡Š° + Š’ŠøŠŗŠ¾Ń€ŠøŃŃ‚Š¾Š²ŃƒŠ¹Ń‚Šµ різні облікові Гані проксі Š“Š»Ń кожного ŠæŃ€Š¾Ń„Ń–Š»ŃŽ. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index b16c96b636..a82160667b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -781,4 +781,52 @@ Tệp đã được lʰu Tệp sįŗ½ được nhįŗ­n khi liĆŖn hệ cį»§a bįŗ”n hoįŗ”t động, vui lòng chį» hoįŗ·c kiểm tra lįŗ”i sau! Trįŗ”ng thĆ”i tệp: %s + Lįŗ„p đầy + CĘ  Sį»ž Dį»® LIỆU SIMPLEX CHAT + Lį»—i chuyển đổi hồ sĘ” + Lį»c cĆ”c cuį»™c hį»™i thoįŗ”i chʰa Ä‘į»c vĆ  cĆ”c cuį»™c hį»™i thoįŗ”i yĆŖu thĆ­ch. + Cuối cùng, chĆŗng ta đã có chĆŗng! šŸš€ + HoĆ n tįŗ„t quĆ” trƬnh di chuyển ở thiįŗæt bị khĆ”c. + HoĆ n tįŗ„t quĆ” trƬnh di chuyển + TƬm cĆ”c cuį»™c trò chuyện nhanh hĘ”n + TƬm kiįŗæm quyền nĆ y trong phįŗ§n cĆ i đặt Android vĆ  cįŗ„p quyền theo cĆ”ch thį»§ cĆ“ng. + Sį»­a + KĆ­ch thước phù hợp + Chuyển tiįŗæp + Đã được chuyển tiįŗæp từ + đã được chuyển tiįŗæp + MĆ”y chį»§ chuyển tiįŗæp: %1$s +\nLį»—i mĆ”y chį»§ đƭch: %2$s + MĆ”y chį»§ chuyển tiįŗæp: %1$s +\nLį»—i: %2$s + Chuyển tiįŗæp vĆ  lʰu tin nhįŗÆn + Chức năng sį»­a khĆ“ng hį»— trợ bởi thĆ nh viĆŖn nhóm + Cho tįŗ„t cįŗ£ mį»i ngĘ°į»i + Đã được chuyển tiįŗæp + KhĆ“ng sį»­ dỄng thĆ“ng tin đăng nhįŗ­p vį»›i proxy. + Sį»­a kįŗæt nối + Sį»­a kįŗæt nối? + Chức năng sį»­a khĆ“ng hį»— trợ bởi liĆŖn hệ + Sį»­a mĆ£ hóa sau khi hồi phỄc dữ liệu dį»± phòng. + Lį»—i lʰu proxy + Đổi mĆ”y įŗ£nh + KĆ­ch thước font + %1$d lį»—i tệp khĆ”c. + Lį»—i chuyển tiįŗæp tin nhįŗÆn + %1$d tệp tįŗ£i khĆ“ng thĆ nh cĆ“ng. + %1$d tệp đã bị xóa. + %1$d tệp đã khĆ“ng được tįŗ£i xuống. + Tįŗ£i xuống + Chuyển tiįŗæp %1$s tin nhįŗÆn? + Chuyển tiįŗæp tin nhįŗÆn… + %1$d lį»—i tệp: +\n%2$s + %1$d tệp đang được tįŗ£i xuống. + %1$s tin nhįŗÆn khĆ“ng được chuyển tiįŗæp + Đang chuyển tiįŗæp %1$s tin nhįŗÆn + MĆ”y chį»§ chuyển tiįŗæp %1$s khĆ“ng thể kįŗæt nối tį»›i mĆ”y chį»§ đƭch %2$s. Vui lòng thį»­ lįŗ”i sau. + Địa chỉ mĆ”y chį»§ chuyển tiįŗæp khĆ“ng tʰʔng thĆ­ch vį»›i cĆ i đặt mįŗ”ng: %1$s. + PhiĆŖn bįŗ£n mĆ”y chį»§ chuyển tiįŗæp khĆ“ng tʰʔng thĆ­ch vį»›i cĆ i đặt mįŗ”ng: %1$s. + CHO CONSOLE + Chuyển tiįŗæp tin nhįŗÆn… \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 469ce9b5b1..43becf32f4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -502,7 +502,7 @@ 显示 ę‚ØåŖčƒ½åœØäø€å°č®¾å¤‡äøŠä½æē”Øęœ€ę–°ē‰ˆęœ¬ēš„čŠå¤©ę•°ę®åŗ“ļ¼Œå¦åˆ™ę‚ØåÆčƒ½ä¼šåœę­¢ęŽ„ę”¶ę„č‡ŖęŸäŗ›č”ē³»äŗŗēš„ę¶ˆęÆć€‚ 新密码…… - čÆ„č§’č‰²å°†ę›“ę”¹äøŗā€œ%sā€ć€‚ē¾¤ē»„äø­ęÆäøŖäŗŗéƒ½ä¼šę”¶åˆ°é€šēŸ„ć€‚ + 评角色将曓改为 %sć€‚ē¾¤ē»„äø­ęÆäøŖäŗŗéƒ½ä¼šę”¶åˆ°é€šēŸ„ć€‚ SimpleX 锁定 å®šęœŸé€šēŸ„ 定期启动 @@ -733,7 +733,7 @@ å›¾åƒę— ę³•č§£ē ć€‚ čÆ·å°čÆ•äøåŒēš„å›¾åƒęˆ–č”ē³»å¼€å‘č€…ć€‚ 主题 ę­¤ę“ä½œę— ę³•ę’¤ę¶ˆā€”ā€”ę‰€ęœ‰ęŽ„ę”¶å’Œå‘é€ēš„ę–‡ä»¶å’ŒåŖ’ä½“éƒ½å°†č¢«åˆ é™¤ć€‚ ä½Žåˆ†č¾ØēŽ‡å›¾ē‰‡å°†äæē•™ć€‚ - č§’č‰²å°†ę›“ę”¹äøŗā€œ%sā€ć€‚ čÆ„ęˆå‘˜å°†ę”¶åˆ°ę–°ēš„é‚€čÆ·ć€‚ + 角色将曓改为%s怂 čÆ„ęˆå‘˜å°†ę”¶åˆ°ę–°ēš„é‚€čÆ·ć€‚ ę­¤ę“ä½œę— ę³•ę’¤ę¶ˆā€”ā€”ę—©äŗŽę‰€é€‰ēš„å‘é€å’ŒęŽ„ę”¶ēš„ę¶ˆęÆå°†č¢«åˆ é™¤ć€‚ čæ™åÆčƒ½éœ€č¦å‡ åˆ†é’Ÿę—¶é—“ć€‚ ę­¤äŗŒē»“ē äøę˜Æé“¾ęŽ„ļ¼ ęŽ„ę”¶åœ°å€å°†å˜ę›“åˆ°äøåŒēš„ęœåŠ”å™Øć€‚åœ°å€ę›“ę”¹å°†åœØå‘ä»¶äŗŗäøŠēŗæåŽå®Œęˆć€‚ @@ -946,7 +946,7 @@ å°†äøŗę‰€ęœ‰ęˆå‘˜åˆ é™¤čÆ„ę¶ˆęÆć€‚ čÆ„ę¶ˆęÆå°†åÆ¹ę‰€ęœ‰ęˆå‘˜ę ‡č®°äøŗå·²č¢«ē®”ē†å‘˜ē§»é™¤ć€‚ åˆ é™¤ęˆå‘˜ę¶ˆęÆļ¼Ÿ - č§‚åÆŸč€… + č§‚åÆŸå‘˜ ę‚Øę˜Æč§‚åÆŸč€… ę›“ę–°ē¾¤ē»„é“¾ęŽ„é”™čÆÆ ę‚Øę— ę³•å‘é€ę¶ˆęÆļ¼ @@ -968,7 +968,7 @@ ę›“å¤šę”¹čæ›å³å°†ęŽØå‡ŗļ¼ ēŽ°åœØē®”ē†å‘˜åÆä»„ļ¼š \n- åˆ é™¤ęˆå‘˜ēš„ę¶ˆęÆć€‚ -\n- ē¦ē”Øęˆå‘˜ļ¼ˆā€œč§‚åÆŸå‘˜ā€č§’č‰²ļ¼‰ +\n- ē¦ē”Øęˆå‘˜ļ¼ˆč§‚åÆŸå‘˜č§’č‰²ļ¼‰ ä½æē”ØåÆ†ē äæęŠ¤ę‚Øēš„čŠå¤©čµ„ę–™ļ¼ 甮认密码 ę›“ę–°ē”Øęˆ·éšē§é”™čÆÆ @@ -2068,4 +2068,23 @@ ę–°ę¶ˆęÆ čÆ·ę£€ęŸ„ Simple X é“¾ęŽ„ę˜Æå¦ę­£ē”®ć€‚ ę— ę•ˆé“¾ęŽ„ + čŠå¤©ę•°ę®åŗ“ + ē³»ē»ŸęØ”å¼ + äøŠä¼ ēš„ę•°ę®åŗ“å­˜ę”£å°†ę°øä¹…ę€§ä»ŽęœåŠ”å™Øč¢«åˆ é™¤ć€‚ + ē”®äæä»£ē†é…ē½®ę­£ē”® + ę¶ˆęÆå°†č¢«åˆ é™¤ - ę­¤ę“ä½œę— ę³•ę’¤é”€ļ¼ + ä½ ēš„čæžęŽ„č¢«ē§»åŠØåˆ° %sļ¼Œä½†åœØå°†ä½ é‡å®šå‘åˆ°é…ē½®ę–‡ä»¶ę—¶å‘ē”Ÿäŗ†ę„ę–™ä¹‹å¤–ēš„é”™čÆÆć€‚ + ä»£ē†äøä½æē”Øčŗ«ä»½éŖŒčÆå‡­ę® + åˆ‡ę¢é…ē½®ę–‡ä»¶å‡ŗé”™ + ä»£ē†čŗ«ä»½éŖŒčÆ + åˆ é™¤å­˜ę”£ļ¼Ÿ + é€‰ę‹©čŠå¤©é…ē½®ę–‡ä»¶ + äæå­˜ä»£ē†å‡ŗé”™ + 密码 + ęÆäøŖčæžęŽ„ä½æē”ØäøåŒēš„ä»£ē†čŗ«ä»½éŖŒčÆå‡­ę®ć€‚ + ęÆäøŖé…ē½®ę–‡ä»¶ä½æē”ØäøåŒēš„ä»£ē†čŗ«ä»½éŖŒčÆć€‚ + ä½ ēš„å‡­ę®åÆčƒ½ä»„ęœŖē»åŠ åÆ†ēš„ę–¹å¼č¢«å‘é€ć€‚ + ä½æē”Øéšęœŗå‡­ę® + ē”Øęˆ·å + åˆ†äŗ«é…ē½®ę–‡ä»¶ \ No newline at end of file From 630fea42c375799cef14d0f9dc14f8a3c2a612e2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 20 Sep 2024 12:47:40 +0100 Subject: [PATCH 077/704] website: translations (#4916) * ios: update core library * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Revert "ios: update core library" This reverts commit 211f6c51f23f9178038d1a3d5085235ad6857ce4. --------- Co-authored-by: summoner001 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 ----- website/langs/hu.json | 38 +++++++++++----------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 9b96b3dee6..b0b838eef8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -2117,10 +2117,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries", - ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", @@ -2172,10 +2168,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Libraries", - ); "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = ( "$(inherited)", "$(PROJECT_DIR)/Libraries/ios", diff --git a/website/langs/hu.json b/website/langs/hu.json index 7b6b856065..3ac0a77fc0 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -4,10 +4,10 @@ "reference": "Referencia", "blog": "Blog", "features": "Funkciók", - "why-simplex": "MiĆ©rt vĆ”lassza a SimpleX-t", - "simplex-privacy": "SimpleX adatvĆ©delem", - "simplex-network": "SimpleX hĆ”lózat", - "simplex-explained": "Simplex bemutatĆ”sa", + "why-simplex": "MiĆ©rt vĆ”lassza a SimpleXet", + "simplex-privacy": "SimpleX-adatvĆ©delem", + "simplex-network": "SimpleX-hĆ”lózat", + "simplex-explained": "A Simplex bemutatĆ”sa", "simplex-explained-tab-1-text": "1. FelhasznĆ”lói Ć©lmĆ©ny", "simplex-explained-tab-2-text": "2. Hogyan műkƶdik", "simplex-explained-tab-3-text": "3. Mit lĆ”tnak a kiszolgĆ”lók", @@ -42,7 +42,7 @@ "feature-5-title": "Eltűnő üzenetek", "feature-6-title": "E2E-titkosĆ­tott
hang- Ć©s videohĆ­vĆ”sok", "feature-7-title": "Hordozható titkosĆ­tott alkalmazĆ”s-adattĆ”rolĆ”s — profil Ć”thelyezĆ©se egy mĆ”sik eszkƶzre", - "feature-8-title": "Az inkognitó mód —
egyedülĆ”lló a SimpleX Chat-ben", + "feature-8-title": "Az inkognitó mód —
egyedülĆ”lló a SimpleX Chatben", "simplex-network-overlay-1-title": "ƖsszehasonlĆ­tĆ”s mĆ”s P2P üzenetküldő protokollokkal", "simplex-private-1-title": "2 rĆ©tegű vĆ©gpontok kƶzƶtti titkosĆ­tĆ”s", "simplex-private-2-title": "TovĆ”bbi rĆ©tege a
kiszolgĆ”ló titkosĆ­tĆ”s", @@ -83,9 +83,9 @@ "simplex-unique-2-overlay-1-title": "A legjobb vĆ©delem a spam Ć©s a visszaĆ©lĆ©sek ellen", "simplex-unique-3-title": "Az ƶn adatai fƶlƶtt csak ƶn rendelkezik", "simplex-unique-3-overlay-1-title": "Az ƶn adatai fƶlƶtt csak ƶn rendelkezik", - "simplex-unique-4-title": "ƖnĆ© a SimpleX hĆ”lózat", - "simplex-unique-4-overlay-1-title": "Teljesen decentralizĆ”lt — a SimpleX hĆ”lózat a felhasznĆ”lókĆ©", - "hero-overlay-card-1-p-1": "Sok felhasznĆ”ló kĆ©rdezte: ha a SimpleX-nek nincsenek felhasznĆ”lói azonosĆ­tói, honnan tudja, hovĆ” kell eljuttatni az üzeneteket?", + "simplex-unique-4-title": "ƖnĆ© a SimpleX-hĆ”lózat", + "simplex-unique-4-overlay-1-title": "Teljesen decentralizĆ”lt — a SimpleX-hĆ”lózat a felhasznĆ”lókĆ©", + "hero-overlay-card-1-p-1": "Sok felhasznĆ”ló kĆ©rdezte: ha a SimpleXnek nincsenek felhasznĆ”lói azonosĆ­tói, honnan tudja, hogy hovĆ” kell eljuttatni az üzeneteket?", "hero-overlay-card-1-p-2": "Az üzenetek kĆ©zbesĆ­tĆ©sĆ©hez az ƶsszes tƶbbi platform Ć”ltal hasznĆ”lt felhasznĆ”lói azonosĆ­tók helyett a SimpleX az üzenetek vĆ”rakoztatĆ”sĆ”hoz ideiglenes, nĆ©vtelen, pĆ”ros azonosĆ­tókat hasznĆ”l, külƶn-külƶn minden egyes kapcsolathoz — nincsenek hosszĆŗ tĆ”vĆŗ azonosĆ­tók.", "hero-overlay-card-1-p-4": "Ez a kialakĆ­tĆ”s megakadĆ”lyozza a felhasznĆ”lók metaadatainak kiszivĆ”rgĆ”sĆ”t az alkalmazĆ”s szintjĆ©n. Az adatvĆ©delem tovĆ”bbi javĆ­tĆ”sa Ć©s az IP-cĆ­m vĆ©delme Ć©rdekĆ©ben az üzenetküldő kiszolgĆ”lókhoz Tor hĆ”lózaton keresztül is kapcsolódhat.", "hero-overlay-card-1-p-5": "Csak a kliensek tĆ”roljĆ”k a felhasznĆ”lói profilokat, kapcsolatokat Ć©s csoportokat; az üzenetek küldĆ©se 2 rĆ©tegű vĆ©gpontok kƶzƶtti titkosĆ­tĆ”ssal tƶrtĆ©nik.", @@ -95,16 +95,16 @@ "hero-overlay-card-2-p-3": "MĆ©g a Tor v3 szolgĆ”ltatĆ”sokat hasznĆ”ló, legprivĆ”tabb alkalmazĆ”sok esetĆ©ben is, ha kĆ©t külƶnbƶző kapcsolattartóval beszĆ©l ugyanazon a profilon keresztül, bizonyĆ­tani tudjĆ”k, hogy ugyanahhoz a szemĆ©lyhez kapcsolódnak.", "hero-overlay-card-2-p-4": "A SimpleX Ćŗgy vĆ©dekezik ezen tĆ”madĆ”sok ellen, hogy nem tartalmaz felhasznĆ”lói azonosĆ­tókat. Ha pedig hasznĆ”lja az inkognitó módot, akkor minden egyes lĆ©trejƶtt kapcsolatban mĆ”s-mĆ”s felhasznĆ”ló nĆ©v jelenik meg, Ć­gy elkerülhető a kƶzƶttük lĆ©vő ƶsszefüggĆ©sek bizonyĆ­tĆ”sa.", "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsĆ”gi Ć©s technológiai tanĆ”csadó cĆ©g, amelynek ügyfelei kƶzĆ© tartoznak a nagy technológiai cĆ©gek, kormĆ”nyzati ügynƶksĆ©gek Ć©s jelentős blokklĆ”nc projektek.", - "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberĆ©ben Ć”ttekintette a SimpleX platform kriptogrĆ”fiai Ć©s hĆ”lózati komponenseit.", + "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberĆ©ben Ć”ttekintette a SimpleX-platform kriptogrĆ”fiai Ć©s hĆ”lózati komponenseit.", "simplex-network-overlay-card-1-li-1": "A P2P-hĆ”lózatok az üzenetek tovĆ”bbĆ­tĆ”sĆ”ra a DHT valamelyik vĆ”ltozatĆ”t hasznĆ”ljĆ”k. A DHT kialakĆ­tĆ”sakor egyensĆŗlyt kell teremteni a kĆ©zbesĆ­tĆ©si garancia Ć©s a kĆ©sleltetĆ©s kƶzƶtt. A SimpleX jobb kĆ©zbesĆ­tĆ©si garanciĆ”val Ć©s alacsonyabb kĆ©sleltetĆ©ssel rendelkezik, mint a P2P, mivel az üzenet redundĆ”nsan, a cĆ­mzett Ć”ltal kivĆ”lasztott kiszolgĆ”lók segĆ­tsĆ©gĆ©vel tƶbb kiszolgĆ”lón keresztül pĆ”rhuzamosan tovĆ”bbĆ­tható. A P2P-hĆ”lózatokban az üzenet O(log N) csomóponton halad Ć”t szekvenciĆ”lisan, az algoritmus Ć”ltal kivĆ”lasztott csomópontok segĆ­tsĆ©gĆ©vel.", "simplex-network-overlay-card-1-li-2": "A SimpleX kialakĆ­tĆ”sa a legtƶbb P2P-hĆ”lózattól eltĆ©rően nem rendelkezik semmifĆ©le globĆ”lis felhasznĆ”lói azonosĆ­tóval, mĆ©g ideiglenesen sem, Ć©s csak ideiglenes pĆ”ros azonosĆ­tókat hasznĆ”l, ami jobb nĆ©vtelensĆ©get Ć©s metaadatvĆ©delmet biztosĆ­t.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-tĆ”madĆ”s problĆ©mĆ”t, Ć©s a legtƶbb lĆ©tező implementĆ”ció nem hasznĆ”l sĆ”von kĆ­vüli üzeneteket a kezdeti kulcscserĆ©hez. A SimpleX a kezdeti kulcscserĆ©hez sĆ”von kĆ­vüli üzeneteket, vagy bizonyos esetekben mĆ”r meglĆ©vő biztonsĆ”gos Ć©s megbĆ­zható kapcsolatokat hasznĆ”l.", - "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hĆ”lózat sebezhető Sybil tĆ”madĆ”ssal, mert minden egyes csomópont felderĆ­thető, Ć©s a hĆ”lózat egĆ©szkĆ©nt műkƶdik. A tĆ”madĆ”sok enyhĆ­tĆ©sĆ©re szolgĆ”ló ismert intĆ©zkedĆ©s lehet egy kƶzponti kiszolgĆ”ló (pl.: tracker), vagy egy drĆ”ga tanĆŗsĆ­tvĆ”ny. A SimpleX hĆ”lózat nem ismeri fel a kiszolgĆ”lókat, tƶredezett Ć©s tƶbb elszigetelt alhĆ”lózatkĆ©nt műkƶdik, ami lehetetlennĆ© teszi az egĆ©sz hĆ”lózatra kiterjedő tĆ”madĆ”sokat.", - "simplex-network-overlay-card-1-li-6": "A P2P-hĆ”lózatok sebezhetőek lehetnek a DRDoS-tĆ”madĆ”ssal szemben, amikor a kliensek kĆ©pesek a forgalmat ĆŗjrakƶzvetĆ­teni Ć©s felerősĆ­teni, ami az egĆ©sz hĆ”lózatra kiterjedő szolgĆ”ltatĆ”smegtagadĆ”st eredmĆ©nyez. A SimpleX kliensek csak az ismert kapcsolatból szĆ”rmazó forgalmat tovĆ”bbĆ­tjĆ”k, Ć©s a tĆ”madó nem hasznĆ”lhatja őket arra, hogy az egĆ©sz hĆ”lózatban felerősĆ­tse a forgalmat.", + "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hĆ”lózat sebezhető Sybil tĆ”madĆ”ssal, mert minden egyes csomópont felderĆ­thető, Ć©s a hĆ”lózat egĆ©szkĆ©nt műkƶdik. A tĆ”madĆ”sok enyhĆ­tĆ©sĆ©re szolgĆ”ló ismert intĆ©zkedĆ©s lehet egy kƶzponti kiszolgĆ”ló (pl.: tracker), vagy egy drĆ”ga tanĆŗsĆ­tvĆ”ny. A SimpleX-hĆ”lózat nem ismeri fel a kiszolgĆ”lókat, tƶredezett Ć©s tƶbb elszigetelt alhĆ”lózatkĆ©nt műkƶdik, ami lehetetlennĆ© teszi az egĆ©sz hĆ”lózatra kiterjedő tĆ”madĆ”sokat.", + "simplex-network-overlay-card-1-li-6": "A P2P-hĆ”lózatok sebezhetőek lehetnek a DRDoS-tĆ”madĆ”ssal szemben, amikor a kliensek kĆ©pesek a forgalmat ĆŗjrakƶzvetĆ­teni Ć©s felerősĆ­teni, ami az egĆ©sz hĆ”lózatra kiterjedő szolgĆ”ltatĆ”smegtagadĆ”st eredmĆ©nyez. A SimpleX-kliensek csak az ismert kapcsolatból szĆ”rmazó forgalmat tovĆ”bbĆ­tjĆ”k, Ć©s a tĆ”madó nem hasznĆ”lhatja őket arra, hogy az egĆ©sz hĆ”lózatban felerősĆ­tse a forgalmat.", "privacy-matters-overlay-card-1-p-1": "Sok nagyvĆ”llalat arra hasznĆ”lja fel az ƶnnel kapcsolatban Ć”lló szemĆ©lyek adatait, hogy megbecsülje az ƶn jƶvedelmĆ©t, hogy olyan termĆ©keket adjon el ƶnnek, amelyekre valójĆ”ban nincs is szüksĆ©ge, Ć©s hogy meghatĆ”rozza az Ć”rakat.", "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudjĆ”k, hogy az alacsonyabb jƶvedelműek nagyobb valószĆ­nűsĆ©ggel vĆ”sĆ”rolnak azonnal, ezĆ©rt magasabb Ć”rakat szĆ”mĆ­thatnak fel, vagy eltƶrƶlhetik a kedvezmĆ©nyeket.", "privacy-matters-overlay-card-1-p-3": "Egyes pĆ©nzügyi Ć©s biztosĆ­tótĆ”rsasĆ”gok szociĆ”lis grafikonokat hasznĆ”lnak a kamatlĆ”bak Ć©s a dĆ­jak meghatĆ”rozĆ”sĆ”hoz. Ez gyakran arra kĆ©szteti az alacsonyabb jƶvedelmű embereket, hogy tƶbbet fizessenek — ez az Ćŗgynevezett ā€žszegĆ©nysĆ©gi prĆ©miumā€.", - "privacy-matters-overlay-card-1-p-4": "A SimpleX platform minden alternatĆ­vĆ”nĆ”l jobban vĆ©di a kapcsolatainak adatait, teljes mĆ©rtĆ©kben megakadĆ”lyozva, hogy a ismeretsĆ©gi-hĆ”lója bĆ”rmilyen vĆ”llalat vagy szervezet szĆ”mĆ”ra elĆ©rhetővĆ© vĆ”ljon. MĆ©g ha az emberek a SimpleX Chat Ć”ltal biztosĆ­tott kiszolgĆ”lókat is hasznĆ”ljĆ”k, sem a felhasznĆ”lók szĆ”mĆ”t, sem a kapcsolataikat nem ismerjük.", + "privacy-matters-overlay-card-1-p-4": "A SimpleX-platform minden alternatĆ­vĆ”nĆ”l jobban vĆ©di a kapcsolatainak adatait, teljes mĆ©rtĆ©kben megakadĆ”lyozva, hogy a ismeretsĆ©gi-hĆ”lója bĆ”rmilyen vĆ”llalat vagy szervezet szĆ”mĆ”ra elĆ©rhetővĆ© vĆ”ljon. MĆ©g ha az emberek a SimpleX Chat Ć”ltal biztosĆ­tott kiszolgĆ”lókat is hasznĆ”ljĆ”k, sem a felhasznĆ”lók szĆ”mĆ”t, sem a kapcsolataikat nem ismerjük.", "privacy-matters-overlay-card-2-p-1": "Nem is olyan rĆ©gen megfigyelhettük, hogy a nagy vĆ”lasztĆ”sokat manipulĆ”lta egy neves tanĆ”csadó cĆ©g, amely az ismeretsĆ©gi-hĆ”ló segĆ­tsĆ©gĆ©vel eltorzĆ­totta a valós vilĆ”gról alkotott kĆ©pünket, Ć©s manipulĆ”lta a szavazatainkat.", "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektĆ­v legyen Ć©s független dƶntĆ©seket tudjon hozni, az informĆ”ciós terĆ©t is kĆ©zben kell tartania. Ez csak akkor lehetsĆ©ges, ha privĆ”t kommunikĆ”ciós platformot hasznĆ”l, amely nem fĆ©r hozzĆ” az ismeretsĆ©gi-hĆ”lójĆ”hoz.", "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan platform, amely eleve nem rendelkezik felhasznĆ”lói azonosĆ­tókkal, Ć­gy jobban vĆ©di az ismeretsĆ©gi-hĆ”lójĆ”t, mint bĆ”rmely ismert alternatĆ­va.", @@ -114,16 +114,16 @@ "privacy-matters-overlay-card-3-p-4": "Nem elĆ©g, ha csak egy vĆ©gpontok kƶzƶtt titkosĆ­tott üzenetküldőt hasznĆ”lunk, mindannyiunknak olyan üzenetküldőket kell hasznĆ”lnunk, amelyek vĆ©dik szemĆ©lyes ismerőseink magĆ”nĆ©letĆ©t — akikkel kapcsolatban Ć”llunk.", "simplex-unique-overlay-card-1-p-1": "MĆ”s üzenetküldő platformoktól eltĆ©rően a SimpleX nem rendel azonosĆ­tókat a felhasznĆ”lókhoz. Nem tĆ”maszkodik telefonszĆ”mokra, tartomĆ”ny-alapĆŗ cĆ­mekre (mint az e-mail, XMPP vagy a Matrix), felhasznĆ”lónevekre, nyilvĆ”nos kulcsokra vagy akĆ”r vĆ©letlenszerű szĆ”mokra a felhasznĆ”lók azonosĆ­tĆ”sĆ”hoz — nem tudjuk, hogy hĆ”nyan hasznĆ”ljĆ”k a SimpleX-kiszolgĆ”lóinkat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kĆ©zbesĆ­tĆ©sĆ©hez a SimpleX az egyirĆ”nyĆŗ üzenet vĆ”rakoztatĆ”st hasznĆ”l pĆ”ronkĆ©nti nĆ©vtelen cĆ­mekkel, külƶn a fogadott Ć©s külƶn az elküldƶtt üzenetek szĆ”mĆ”ra, Ć”ltalĆ”ban külƶnbƶző kiszolgĆ”lókon keresztül. A SimpleX hasznĆ”lata olyan, mintha minden egyes kapcsolatnak mĆ”s-mĆ”s “eldobható” e-mail cĆ­me vagy telefonja lenne Ć©s nem kell ezeket gondosan kezelni.", - "simplex-unique-overlay-card-1-p-3": "Ez a kialakĆ­tĆ”s megvĆ©di annak titkossĆ”gĆ”t, hogy kivel kommunikĆ”l, elrejtve azt a SimpleX platform kiszolgĆ”lói Ć©s a megfigyelők elől. IP-cĆ­mĆ©nek a kiszolgĆ”lók elől való elrejtĆ©sĆ©hez azt teheti meg, hogy Tor-on keresztül kapcsolódik a SimpleX kiszolgĆ”lókhoz.", - "simplex-unique-overlay-card-2-p-1": "Mivel ƶn nem rendelkezik azonosĆ­tóval a SimpleX platformon, senki sem tud kapcsolatba lĆ©pni ƶnnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasznĆ”lói cĆ­met, pĆ©ldĆ”ul QR-kódot vagy hivatkozĆ”st.", + "simplex-unique-overlay-card-1-p-3": "Ez a kialakĆ­tĆ”s megvĆ©di annak titkossĆ”gĆ”t, hogy kivel kommunikĆ”l, elrejtve azt a SimpleX platform kiszolgĆ”lói Ć©s a megfigyelők elől. IP-cĆ­mĆ©nek a kiszolgĆ”lók elől való elrejtĆ©sĆ©hez azt teheti meg, hogy Toron keresztül kapcsolódik a SimpleX-kiszolgĆ”lókhoz.", + "simplex-unique-overlay-card-2-p-1": "Mivel ƶn nem rendelkezik azonosĆ­tóval a SimpleX-platformon, senki sem tud kapcsolatba lĆ©pni ƶnnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasznĆ”lói cĆ­met, pĆ©ldĆ”ul QR-kódot vagy hivatkozĆ”st.", "simplex-unique-overlay-card-2-p-2": "MĆ©g az opcionĆ”lis felhasznĆ”lói cĆ­m esetĆ©ben is, bĆ”r spam kapcsolatfelvĆ©teli kĆ©rĆ©sek küldĆ©sĆ©re hasznĆ”lható, megvĆ”ltoztathatja vagy teljesen tƶrƶlheti azt anĆ©lkül, hogy elveszĆ­tenĆ© a meglĆ©vő kapcsolatait.", "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az ƶsszes felhasznĆ”lói adatot kizĆ”rólag a klienseken tĆ”rolja egy hordozható titkosĆ­tott adatbĆ”zis-formĆ”tumban, amely exportĆ”lható Ć©s Ć”tvihető bĆ”rmely mĆ”s tĆ”mogatott eszkƶzre.", - "simplex-unique-overlay-card-3-p-2": "A vĆ©gpontok kƶzƶtt titkosĆ­tott üzenetek Ć”tmenetileg a SimpleX Ć”tjĆ”tszó-kiszolgĆ”lókon tartózkodnak, amĆ­g be nem Ć©rkeznek a cĆ­mzetthez, majd vĆ©glegesen tƶrlődnek onnan.", - "simplex-unique-overlay-card-3-p-3": "A fƶderĆ”lt hĆ”lózatok kiszolgĆ”lóitól (e-mail, XMPP vagy Matrix) eltĆ©rően a SimpleX kiszolgĆ”lók nem tĆ”roljĆ”k a felhasznĆ”lói fiókokat, csak tovĆ”bbĆ­tjĆ”k az üzeneteket, Ć­gy vĆ©dve mindkĆ©t fĆ©l magĆ”nĆ©letĆ©t.", + "simplex-unique-overlay-card-3-p-2": "A vĆ©gpontok kƶzƶtt titkosĆ­tott üzenetek Ć”tmenetileg a SimpleX-Ć”tjĆ”tszókiszolgĆ”lókon tartózkodnak, amĆ­g be nem Ć©rkeznek a cĆ­mzetthez, majd vĆ©glegesen tƶrlődnek onnan.", + "simplex-unique-overlay-card-3-p-3": "A fƶderĆ”lt hĆ”lózatok kiszolgĆ”lóitól (e-mail, XMPP vagy Matrix) eltĆ©rően a SimpleX-kiszolgĆ”lók nem tĆ”roljĆ”k a felhasznĆ”lói fiókokat, csak tovĆ”bbĆ­tjĆ”k az üzeneteket, Ć­gy vĆ©dve mindkĆ©t fĆ©l magĆ”nĆ©letĆ©t.", "simplex-unique-overlay-card-3-p-4": "A küldƶtt Ć©s a fogadott kiszolgĆ”lóforgalom kƶzƶtt nincsenek kƶzƶs azonosĆ­tók vagy titkosĆ­tott szƶvegek — ha bĆ”rki megfigyeli, nem tudja kƶnnyen megĆ”llapĆ­tani, hogy ki kivel kommunikĆ”l, mĆ©g akkor sem, ha a TLS-t kompromittĆ”ljĆ”k.", - "simplex-unique-overlay-card-4-p-1": "HasznĆ”lhatja a SimpleX-et sajĆ”t kiszolgĆ”lóival, Ć©s tovĆ”bbra is kommunikĆ”lhat azokkal, akik az Ć”ltalunk biztosĆ­tott, előre konfigurĆ”lt kiszolgĆ”lókat hasznĆ”ljĆ”k.", + "simplex-unique-overlay-card-4-p-1": "HasznĆ”lhatja a SimpleXet a sajĆ”t kiszolgĆ”lóival, Ć©s tovĆ”bbra is kommunikĆ”lhat azokkal, akik az Ć”ltalunk biztosĆ­tott, előre konfigurĆ”lt kiszolgĆ”lókat hasznĆ”ljĆ”k.", "simplex-unique-overlay-card-4-p-2": "A SimpleX platform nyitott protokollt hasznĆ”l Ć©s SDK-t biztosĆ­t a chatbotok lĆ©trehozĆ”sĆ”hoz, lehetővĆ© tĆ©ve olyan szolgĆ”ltatĆ”sok megvalósĆ­tĆ”sĆ”t, amelyekkel a felhasznĆ”lók a SimpleX Chat alkalmazĆ”sokon keresztül lĆ©phetnek kapcsolatba — mi mĆ”r nagyon vĆ”rjuk, hogy milyen SimpleX szolgĆ”ltatĆ”sokat kĆ©szĆ­tenek a lelkes kƶzreműkƶdők.", - "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX platformra való fejlesztĆ©st fontolgatja, pĆ©ldĆ”ul a SimpleX-alkalmazĆ”sok felhasznĆ”lóinak szĆ”nt chatbotot, vagy a SimpleX Chat JegyzĆ©k bot integrĆ”lĆ”sĆ”t mĆ”s mobilalkalmazĆ”sba, lĆ©pjen velünk kapcsolatba, ha bĆ”rmilyen tanĆ”csot vagy tĆ”mogatĆ”st szeretne kapni.", + "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX-platformra való fejlesztĆ©st fontolgatja, pĆ©ldĆ”ul a SimpleX-alkalmazĆ”sok felhasznĆ”lóinak szĆ”nt chatbotot, vagy a SimpleX Chat-kƶnvtĆ”rbot integrĆ”lĆ”sĆ”t mĆ”s mobilalkalmazĆ”sba, lĆ©pjen velünk kapcsolatba, ha bĆ”rmilyen tanĆ”csot vagy tĆ”mogatĆ”st szeretne kapni.", "simplex-unique-card-1-p-1": "A SimpleX vĆ©di az ƶn profiljĆ”hoz tartozó kapcsolatait Ć©s metaadatait, elrejtve azokat a SimpleX platform kiszolgĆ”lói Ć©s a megfigyelők elől.", "simplex-unique-card-1-p-2": "Minden mĆ”s lĆ©tező üzenetküldő platformtól eltĆ©rően a SimpleX nem rendelkezik a felhasznĆ”lókhoz rendelt azonosĆ­tókkal — mĆ©g vĆ©letlenszerű szĆ”mokkal sem.", "simplex-unique-card-2-p-1": "Mivel a SimpleX platformon nincs azonosĆ­tója vagy Ć”llandó cĆ­me, senki sem tud kapcsolatba lĆ©pni ƶnnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasznĆ”lói cĆ­met, pĆ©ldĆ”ul QR-kódot vagy hivatkozĆ”st.", @@ -224,7 +224,7 @@ "contact-hero-header": "Kapott egy cĆ­met a SimpleX Chat-en való kapcsolódĆ”shoz", "invitation-hero-header": "Kapott egy egyszer hasznĆ”latos hivatkozĆ”st a SimpleX Chat-en való kapcsolódĆ”shoz", "simplex-network-overlay-card-1-li-4": "A P2P-megvalósĆ­tĆ”sokat egyes internetszolgĆ”ltatók blokkolhatjĆ”k (mint pĆ©ldĆ”ul a BitTorrent). A SimpleX Ć”tvitel-független - a szabvĆ”nyos webes protokollokon, pl. WebSockets-en keresztül is műkƶdik.", - "simplex-private-card-4-point-2": "A SimpleX Tor-on keresztüli hasznĆ”latĆ”hoz telepĆ­tse az Orbot alkalmazĆ”st Ć©s engedĆ©lyezze a SOCKS5 proxy-t (vagy a VPN-t az iOS-ban).", + "simplex-private-card-4-point-2": "A SimpleX Toron keresztüli hasznĆ”latĆ”hoz telepĆ­tse az Orbot alkalmazĆ”st Ć©s engedĆ©lyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", "simplex-private-card-5-point-1": "A SimpleX minden titkosĆ­tĆ”si rĆ©teghez tartalomkitƶltĆ©st hasznĆ”l, hogy meghiĆŗsĆ­tsa az üzenetmĆ©ret ellen irĆ”nyuló tĆ”madĆ”sokat.", "simplex-private-card-5-point-2": "A kiszolgĆ”lók Ć©s a hĆ”lózatot megfigyelők szĆ”mĆ”ra a külƶnbƶző mĆ©retű üzenetek egyformĆ”nak tűnnek.", "privacy-matters-1-title": "HirdetĆ©s Ć©s Ć”rdiszkriminĆ”ció", From efb7fc6c3be230389cc988bafbf2f70eb9d4b497 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 20 Sep 2024 15:01:47 +0100 Subject: [PATCH 078/704] 6.1-beta.1: ios 238, android 240, desktop 67 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 60 +++++++++++----------- apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b0b838eef8..a6bfd9d80e 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -217,11 +217,11 @@ 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 */; }; - E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */; }; - E55128E82C9AD063001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E32C9AD063001D165C /* libgmp.a */; }; - E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */; }; - E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E52C9AD063001D165C /* libgmpxx.a */; }; - E55128EB2C9AD063001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128E62C9AD063001D165C /* libffi.a */; }; + E55128F12C9DA948001D165C /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128EC2C9DA948001D165C /* libffi.a */; }; + E55128F22C9DA948001D165C /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128ED2C9DA948001D165C /* libgmpxx.a */; }; + E55128F32C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128EE2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a */; }; + E55128F42C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128EF2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a */; }; + E55128F52C9DA948001D165C /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E55128F02C9DA948001D165C /* libgmp.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 */; }; @@ -556,11 +556,11 @@ 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 = ""; }; - E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a"; sourceTree = ""; }; - E55128E32C9AD063001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a"; sourceTree = ""; }; - E55128E52C9AD063001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E55128E62C9AD063001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128EC2C9DA948001D165C /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E55128ED2C9DA948001D165C /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E55128EE2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a"; sourceTree = ""; }; + E55128EF2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a"; sourceTree = ""; }; + E55128F02C9DA948001D165C /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.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 = ""; }; @@ -651,14 +651,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E55128E72C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a in Frameworks */, - E55128E82C9AD063001D165C /* libgmp.a in Frameworks */, + E55128F32C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a in Frameworks */, + E55128F42C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a in Frameworks */, + E55128F52C9DA948001D165C /* libgmp.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E55128EB2C9AD063001D165C /* libffi.a in Frameworks */, + E55128F22C9DA948001D165C /* libgmpxx.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E55128E92C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E55128EA2C9AD063001D165C /* libgmpxx.a in Frameworks */, + E55128F12C9DA948001D165C /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -735,11 +735,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E55128E62C9AD063001D165C /* libffi.a */, - E55128E32C9AD063001D165C /* libgmp.a */, - E55128E52C9AD063001D165C /* libgmpxx.a */, - E55128E42C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt-ghc9.6.3.a */, - E55128E22C9AD063001D165C /* libHSsimplex-chat-6.1.0.2-ItgztLmvKyzFsOmChHMkFt.a */, + E55128EC2C9DA948001D165C /* libffi.a */, + E55128F02C9DA948001D165C /* libgmp.a */, + E55128ED2C9DA948001D165C /* libgmpxx.a */, + E55128EE2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx-ghc9.6.3.a */, + E55128EF2C9DA948001D165C /* libHSsimplex-chat-6.1.0.3-7yIa9Uiui2A43fFRiuUJXx.a */, ); path = Libraries; sourceTree = ""; @@ -1891,7 +1891,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1940,7 +1940,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1981,7 +1981,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2001,7 +2001,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2026,7 +2026,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2063,7 +2063,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2100,7 +2100,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2151,7 +2151,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2202,7 +2202,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2236,7 +2236,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 237; + CURRENT_PROJECT_VERSION = 238; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 6123ce156f..f07eea9ec7 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-beta.0 -android.version_code=239 +android.version_name=6.1-beta.1 +android.version_code=240 -desktop.version_name=6.1-beta.0 -desktop.version_code=66 +desktop.version_name=6.1-beta.1 +desktop.version_code=67 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 8a70bad9afba8adc415080c75efc3a2dc7189135 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 20 Sep 2024 21:13:27 +0400 Subject: [PATCH 079/704] core: process ERRS event (#4896) * core: process ERRS event * refactor * update --------- Co-authored-by: Evgeny Poberezkin --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 7fb9f6d353..f2a652d721 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: 309ef3766cc6b69e0c3aa0c140faab25383b732a + tag: bef11e4cbe0a3776f0910375f2adb60399043835 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 52dd21e8cd..730a0f9501 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."309ef3766cc6b69e0c3aa0c140faab25383b732a" = "1ch03kizvsq3m5jwravyil529mc0lcfwj43czb1nhykbg8yb3cjv"; + "https://github.com/simplex-chat/simplexmq.git"."bef11e4cbe0a3776f0910375f2adb60399043835" = "195hir9crv51iyli8yjk8sivk7wagxxjfmhzq6ahydvbkrpd3258"; "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/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 7ca2f4b948..d42603adfa 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4093,6 +4093,7 @@ processAgentMessageNoConn = \case UP srv conns -> serverEvent srv conns NSConnected CRContactsSubscribed SUSPENDED -> toView CRChatSuspended DEL_USER agentUserId -> toView $ CRAgentUserDeleted agentUserId + ERRS cErrs -> errsEvent cErrs where hostEvent :: ChatResponse -> CM () hostEvent = whenM (asks $ hostEvents . config) . toView @@ -4105,6 +4106,16 @@ processAgentMessageNoConn = \case notifyCLI = do cs <- withStore' (`getConnectionsContacts` conns) toView $ event srv cs + errsEvent :: [(ConnId, AgentErrorType)] -> CM () + errsEvent cErrs = do + vr <- chatVersionRange + errs <- lift $ rights <$> withStoreBatch' (\db -> map (getChatErr vr db) cErrs) + toView $ CRChatErrors Nothing errs + where + getChatErr :: VersionRangeChat -> DB.Connection -> (ConnId, AgentErrorType) -> IO ChatError + getChatErr vr db (connId, err) = + let acId = AgentConnId connId + in ChatErrorAgent err <$> (getUserByAConnId db acId $>>= \user -> eitherToMaybe <$> runExceptT (getConnectionEntity db vr user acId)) processAgentMsgSndFile :: ACorrId -> SndFileId -> AEvent 'AESndFile -> CM () processAgentMsgSndFile _corrId aFileId msg = do From 33e12e35a0d0b0e40d3204b21e6a87c039bc8e10 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 20 Sep 2024 21:19:06 +0100 Subject: [PATCH 080/704] ios: use translation in dropdown --- apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift index 70c33329b1..ab3388bfce 100644 --- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift @@ -808,7 +808,7 @@ struct ThemeDestinationPicker: View { @Binding var customizeThemeIsOpen: Bool var body: some View { - let values = [(nil, "All profiles")] + m.users.filter { $0.user.activeUser }.map { ($0.user.userId, $0.user.chatViewName)} + let values = [(nil, NSLocalizedString("All profiles", comment: "profile dropdown"))] + m.users.filter { $0.user.activeUser }.map { ($0.user.userId, $0.user.chatViewName)} if values.contains(where: { (userId, text) in userId == themeUserDestination?.0 }) { Picker("Apply to", selection: $themeUserDest) { From c849f5356d5f22a8c79434379118d20a86900133 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 21 Sep 2024 13:07:27 +0100 Subject: [PATCH 081/704] core: save the time user profile was opened at to order in ui (#4920) * core: save the time user profile was opened at to order in ui * replace timestamp with order --- apps/ios/SimpleXChat/ChatTypes.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 1 + simplex-chat.cabal | 1 + src/Simplex/Chat.hs | 3 +-- src/Simplex/Chat/Core.hs | 11 +++----- .../Chat/Migrations/M20240920_user_order.hs | 18 +++++++++++++ src/Simplex/Chat/Migrations/chat_schema.sql | 3 ++- src/Simplex/Chat/Store/Migrations.hs | 4 ++- src/Simplex/Chat/Store/Profiles.hs | 26 +++++++++++-------- src/Simplex/Chat/Store/Shared.hs | 8 +++--- src/Simplex/Chat/Types.hs | 1 + tests/MobileTests.hs | 10 +++---- 12 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 src/Simplex/Chat/Migrations/M20240920_user_order.hs diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 6b03833d08..272e487214 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -17,6 +17,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { public var profile: LocalProfile public var fullPreferences: FullPreferences public var activeUser: Bool + public var activeOrder: Int64 public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } 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 4fcda25855..9f52694f91 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 @@ -806,6 +806,7 @@ data class User( val profile: LocalProfile, val fullPreferences: FullChatPreferences, override val activeUser: Boolean, + val activeOrder: Long, override val showNtfs: Boolean, val sendRcptsContacts: Boolean, val sendRcptsSmallGroups: Boolean, diff --git a/simplex-chat.cabal b/simplex-chat.cabal index bf42c5117e..a6c96ecc91 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -147,6 +147,7 @@ library Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays Simplex.Chat.Migrations.M20240528_quota_err_counter Simplex.Chat.Migrations.M20240827_calls_uuid + Simplex.Chat.Migrations.M20240920_user_order Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d42603adfa..8e8ab5eb38 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -593,8 +593,7 @@ processChatCommand' vr = \case user_ <- chatReadVar currentUser user' <- privateGetUser userId' validateUserPassword_ user_ user' viewPwd_ - withFastStore' (`setActiveUser` userId') - let user'' = user' {activeUser = True} + user'' <- withFastStore' (`setActiveUser` user') chatWriteVar currentUser $ Just user'' pure $ CRActiveUser user'' SetActiveUser uName viewPwd_ -> do diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index a07968654d..ad2f1367da 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -74,9 +74,7 @@ getSelectActiveUser st = do selectUser :: [User] -> IO (Maybe User) selectUser = \case [] -> pure Nothing - [user@User {userId}] -> do - withTransaction st (`setActiveUser` userId) - pure $ Just user + [user] -> Just <$> withTransaction st (`setActiveUser` user) users -> do putStrLn "Select user profile:" forM_ (zip [1 :: Int ..] users) $ \(n, user) -> putStrLn $ show n <> ": " <> userStr user @@ -88,10 +86,9 @@ getSelectActiveUser st = do Nothing -> putStrLn "not a number" >> loop Just n | n <= 0 || n > length users -> putStrLn "invalid user number" >> loop - | otherwise -> do - let user@User {userId} = users !! (n - 1) - withTransaction st (`setActiveUser` userId) - pure $ Just user + | otherwise -> + let user = users !! (n - 1) + in Just <$> withTransaction st (`setActiveUser` user) createActiveUser :: ChatController -> IO User createActiveUser cc = do diff --git a/src/Simplex/Chat/Migrations/M20240920_user_order.hs b/src/Simplex/Chat/Migrations/M20240920_user_order.hs new file mode 100644 index 0000000000..29fd1532f2 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240920_user_order.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240920_user_order where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240920_user_order :: Query +m20240920_user_order = + [sql| +ALTER TABLE users ADD COLUMN active_order INTEGER NOT NULL DEFAULT 0; +|] + +down_m20240920_user_order :: Query +down_m20240920_user_order = + [sql| +ALTER TABLE users DROP COLUMN active_order; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 25cf886384..214c0e72cc 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -35,7 +35,8 @@ CREATE TABLE users( send_rcpts_contacts INTEGER NOT NULL DEFAULT 0, send_rcpts_small_groups INTEGER NOT NULL DEFAULT 0, user_member_profile_updated_at TEXT, - ui_themes TEXT, -- 1 for active user + ui_themes TEXT, + active_order INTEGER NOT NULL DEFAULT 0, -- 1 for active user FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE RESTRICT diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index be3f4027ca..a09baee272 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -111,6 +111,7 @@ import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays import Simplex.Chat.Migrations.M20240528_quota_err_counter import Simplex.Chat.Migrations.M20240827_calls_uuid +import Simplex.Chat.Migrations.M20240920_user_order import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -221,7 +222,8 @@ schemaMigrations = ("20240510_chat_items_via_proxy", m20240510_chat_items_via_proxy, Just down_m20240510_chat_items_via_proxy), ("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays), ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter), - ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid) + ("20240827_calls_uuid", m20240827_calls_uuid, Just down_m20240827_calls_uuid), + ("20240920_user_order", m20240920_user_order, Just down_m20240920_user_order) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index a29460d5b1..fb9774a54e 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -19,7 +19,6 @@ module Simplex.Chat.Store.Profiles getUsersInfo, getUsers, setActiveUser, - getSetActiveUser, getUser, getUserIdByName, getUserByAConnId, @@ -106,10 +105,11 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, let showNtfs = True sendRcptsContacts = True sendRcptsSmallGroups = True + order <- getNextActiveOrder db DB.execute db - "INSERT INTO users (agent_user_id, local_display_name, active_user, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,0,?,?,?,?,?)" - (auId, displayName, activeUser, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs) + "INSERT INTO users (agent_user_id, local_display_name, active_user, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, created_at, updated_at) VALUES (?,?,?,?,0,?,?,?,?,?)" + (auId, displayName, activeUser, order, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, currentTs, currentTs) userId <- insertedRowId db DB.execute db @@ -126,7 +126,7 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, (profileId, displayName, userId, True, currentTs, currentTs, currentTs) contactId <- insertedRowId db DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId) - pure $ toUser $ (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) + pure $ toUser $ (userId, auId, contactId, profileId, activeUser, order, displayName, fullName, image, Nothing, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) getUsersInfo :: DB.Connection -> IO [UserInfo] getUsersInfo db = getUsers db >>= mapM getUserInfo @@ -161,15 +161,19 @@ getUsers :: DB.Connection -> IO [User] getUsers db = map toUser <$> DB.query_ db userQuery -setActiveUser :: DB.Connection -> UserId -> IO () -setActiveUser db userId = do +setActiveUser :: DB.Connection -> User -> IO User +setActiveUser db user@User {userId} = do DB.execute_ db "UPDATE users SET active_user = 0" - DB.execute db "UPDATE users SET active_user = 1 WHERE user_id = ?" (Only userId) + activeOrder <- getNextActiveOrder db + DB.execute db "UPDATE users SET active_user = 1, active_order = ? WHERE user_id = ?" (activeOrder, userId) + pure user {activeUser = True, activeOrder} -getSetActiveUser :: DB.Connection -> UserId -> ExceptT StoreError IO User -getSetActiveUser db userId = do - liftIO $ setActiveUser db userId - getUser db userId +getNextActiveOrder :: DB.Connection -> IO Int64 +getNextActiveOrder db = do + order <- fromMaybe 0 . join <$> maybeFirstRow fromOnly (DB.query_ db "SELECT max(active_order) FROM users") + if order == maxBound + then 0 <$ DB.execute db "UPDATE users SET active_order = active_order - ?" (Only (maxBound :: Int64)) + else pure $ order + 1 getUser :: DB.Connection -> UserId -> ExceptT StoreError IO User getUser db userId = diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index a4c2d1da39..ba41cc47be 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -422,16 +422,16 @@ toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userCo userQuery :: Query userQuery = [sql| - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User -toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = - User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} +toUser :: (UserId, UserId, ContactId, ProfileId, Bool, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) :. (Bool, Bool, Bool, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User +toUser ((userId, auId, userContactId, profileId, activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = + User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} where profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""} fullPreferences = mergePreferences Nothing userPreferences diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index fe40fcfcee..71fa1d98b9 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -113,6 +113,7 @@ data User = User profile :: LocalProfile, fullPreferences :: FullPreferences, activeUser :: Bool, + activeOrder :: Int64, viewPwdHash :: Maybe UserPwdHash, showNtfs :: Bool, sendRcptsContacts :: Bool, diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index cd4b3980eb..638d3d8078 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -94,10 +94,10 @@ activeUserExists = #endif activeUserExistsSwift :: LB.ByteString -activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" +activeUserExistsSwift = "{\"resp\":{\"_owsf\":true,\"chatCmdError\":{\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"_owsf\":true,\"error\":{\"errorType\":{\"_owsf\":true,\"userExists\":{\"contactName\":\"alice\"}}}}}}}" activeUserExistsTagged :: LB.ByteString -activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" +activeUserExistsTagged = "{\"resp\":{\"type\":\"chatCmdError\",\"user_\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true},\"chatError\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}}" activeUser :: LB.ByteString activeUser = @@ -108,10 +108,10 @@ activeUser = #endif activeUserSwift :: LB.ByteString -activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" +activeUserSwift = "{\"resp\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" activeUserTagged :: LB.ByteString -activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" +activeUserTagged = "{\"resp\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" chatStarted :: LB.ByteString chatStarted = @@ -184,7 +184,7 @@ pendingSubSummaryTagged :: LB.ByteString pendingSubSummaryTagged = "{\"resp\":{\"type\":\"pendingSubSummary\",\"user\":" <> userJSON <> ",\"pendingSubscriptions\":[]}}" userJSON :: LB.ByteString -userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" +userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" parsedMarkdown :: LB.ByteString parsedMarkdown = From 560b52167329f8064abfc95b213f8ae6cfe0931c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 21 Sep 2024 19:12:53 +0100 Subject: [PATCH 082/704] ios: scrolling user profiles (#4909) * ios: scrolling user profiles --------- Co-authored-by: Levitating Pineapple --- .../Shared/Views/ChatList/UserPicker.swift | 227 +++++++----------- .../Views/Helpers/StickyScrollView.swift | 52 ++++ .../Views/UserSettings/SettingsView.swift | 18 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/SimpleXChat/ChatTypes.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 1 + 6 files changed, 159 insertions(+), 144 deletions(-) create mode 100644 apps/ios/Shared/Views/Helpers/StickyScrollView.swift diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift index efe54cb036..b3baa62f01 100644 --- a/apps/ios/Shared/Views/ChatList/UserPicker.swift +++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift @@ -14,11 +14,20 @@ struct UserPicker: View { @Environment(\.colorScheme) private var colorScheme: ColorScheme @Environment(\.dismiss) private var dismiss: DismissAction @Binding var activeSheet: UserPickerSheet? + @State private var currentUser: Int64? @State private var switchingProfile = false + @State private var frameWidth: CGFloat = 0 + + // Inset grouped list dimensions + private let imageSize: CGFloat = 44 + private let rowPadding: CGFloat = 16 + private let sectionSpacing: CGFloat = 35 + private var sectionHorizontalPadding: CGFloat { frameWidth > 375 ? 20 : 16 } + private let sectionShape = RoundedRectangle(cornerRadius: 10, style: .continuous) var body: some View { if #available(iOS 16.0, *) { - let v = viewBody.presentationDetents([.height(420)]) + let v = viewBody.presentationDetents([.height(400)]) if #available(iOS 16.4, *) { v.scrollBounceBehavior(.basedOnSize) } else { @@ -28,88 +37,80 @@ struct UserPicker: View { viewBody } } - + + @ViewBuilder private var viewBody: some View { - let otherUsers = m.users.filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } - return List { - Section(header: Text("You").foregroundColor(theme.colors.secondary)) { - if let user = m.currentUser { - openSheetOnTap(label: { - ZStack { - let v = ProfilePreview(profileOf: user) - .foregroundColor(.primary) - .padding(.leading, -8) - if #available(iOS 16.0, *) { - v + let otherUsers: [UserInfo] = m.users + .filter { u in !u.user.hidden && u.user.userId != m.currentUser?.userId } + .sorted(using: KeyPathComparator(\.user.activeOrder, order: .reverse)) + let sectionWidth = max(frameWidth - sectionHorizontalPadding * 2, 0) + let currentUserWidth = max(frameWidth - sectionHorizontalPadding - rowPadding * 2 - 14 - imageSize, 0) + VStack(spacing: 0) { + if let user = m.currentUser { + StickyScrollView { + HStack(spacing: rowPadding) { + HStack { + ProfileImage(imageStr: user.image, size: imageSize, color: Color(uiColor: .tertiarySystemGroupedBackground)) + .padding(.trailing, 6) + profileName(user).lineLimit(1) + } + .padding(rowPadding) + .frame(width: otherUsers.isEmpty ? sectionWidth : currentUserWidth, alignment: .leading) + .background(Color(.secondarySystemGroupedBackground)) + .clipShape(sectionShape) + .onTapGesture { activeSheet = .currentProfile } + ForEach(otherUsers) { u in + userView(u, size: imageSize) + .frame(maxWidth: sectionWidth * 0.618) + .fixedSize() + } + } + .padding(.horizontal, sectionHorizontalPadding) + } + .frame(height: 2 * rowPadding + imageSize) + .padding(.top, sectionSpacing) + .overlay(DetermineWidth()) + .onPreferenceChange(DetermineWidth.Key.self) { frameWidth = $0 } + } + List { + Section { + openSheetOnTap("qrcode", title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", sheet: .address) + openSheetOnTap("switch.2", title: "Chat preferences", sheet: .chatPreferences) + openSheetOnTap("person.crop.rectangle.stack", title: "Your chat profiles", sheet: .chatProfiles) + openSheetOnTap("desktopcomputer", title: "Use from desktop", sheet: .useFromDesktop) + + ZStack(alignment: .trailing) { + openSheetOnTap("gearshape", title: "Settings", sheet: .settings) + Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") + .resizable() + .symbolRenderingMode(.monochrome) + .foregroundColor(theme.colors.secondary) + .frame(maxWidth: 20, maxHeight: 20) + .onTapGesture { + if (colorScheme == .light) { + ThemeManager.applyTheme(systemDarkThemeDefault.get()) } else { - v.padding(.vertical, 4) + ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) } } - }) { - activeSheet = .currentProfile - } - - openSheetOnTap(title: m.userAddress == nil ? "Create SimpleX address" : "Your SimpleX address", icon: "qrcode") { - activeSheet = .address - } - - openSheetOnTap(title: "Chat preferences", icon: "switch.2") { - activeSheet = .chatPreferences - } - } - } - - Section { - if otherUsers.isEmpty { - openSheetOnTap(title: "Your chat profiles", icon: "person.crop.rectangle.stack") { - activeSheet = .chatProfiles - } - } else { - let v = userPickerRow(otherUsers, size: 44) - .padding(.leading, -11) - if #available(iOS 16.0, *) { - v - } else { - v.padding(.vertical, 4) - } - } - - openSheetOnTap(title: "Use from desktop", icon: "desktopcomputer") { - activeSheet = .useFromDesktop - } - - ZStack(alignment: .trailing) { - openSheetOnTap(title: "Settings", icon: "gearshape") { - activeSheet = .settings - } - Label {} icon: { - Image(systemName: colorScheme == .light ? "sun.max" : "moon.fill") - .resizable() - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - .frame(maxWidth: 20, maxHeight: 20) - } - .onTapGesture { - if (colorScheme == .light) { - ThemeManager.applyTheme(systemDarkThemeDefault.get()) - } else { - ThemeManager.applyTheme(DefaultTheme.LIGHT.themeName) + .onLongPressGesture { + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } } - .onLongPressGesture { - ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) - } } } } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onAppear { // This check prevents the call of listUsers after the app is suspended, and the database is closed. if case .active = scenePhase { + currentUser = m.currentUser?.userId Task { do { let users = try await listUsersAsync() - await MainActor.run { m.users = users } + await MainActor.run { + m.users = users + currentUser = m.currentUser?.userId + } } catch { logger.error("Error loading users \(responseError(error))") } @@ -119,71 +120,34 @@ struct UserPicker: View { .modifier(ThemedBackground(grouped: true)) .disabled(switchingProfile) } - - private func userPickerRow(_ users: [UserInfo], size: CGFloat) -> some View { - HStack(spacing: 6) { - let s = ScrollView(.horizontal) { - HStack(spacing: 27) { - ForEach(users) { u in - if !u.user.hidden && u.user.userId != m.currentUser?.userId { - userView(u, size: size) - } - } - } - .padding(.leading, 4) - .padding(.trailing, 22) - } - ZStack(alignment: .trailing) { - if #available(iOS 16.0, *) { - s.scrollIndicators(.hidden) - } else { - s - } - LinearGradient( - colors: [.clear, .black], - startPoint: .leading, - endPoint: .trailing - ) - .frame(width: size, height: size + 3) - .blendMode(.destinationOut) - .allowsHitTesting(false) - } - .compositingGroup() - .padding(.top, -3) // to fit unread badge - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(theme.colors.secondary) - .padding(.trailing, 4) - .onTapGesture { - activeSheet = .chatProfiles - } - } - } private func userView(_ u: UserInfo, size: CGFloat) -> some View { - ZStack(alignment: .topTrailing) { - ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) - .padding([.top, .trailing], 3) - if (u.unreadCount > 0) { - unreadBadge(u) + HStack { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground)) + if (u.unreadCount > 0) { + unreadBadge(u).offset(x: 4, y: -4) + } } + .padding(.trailing, 6) + Text(u.user.displayName).font(.title2).lineLimit(1) } - .frame(width: size) + .padding(rowPadding) + .background(Color(.secondarySystemGroupedBackground)) + .clipShape(sectionShape) .onTapGesture { switchingProfile = true + dismiss() Task { do { try await changeActiveUserAsync_(u.user.userId, viewPwd: nil) - await MainActor.run { - switchingProfile = false - dismiss() - } + await MainActor.run { switchingProfile = false } } catch { await MainActor.run { switchingProfile = false - AlertManager.shared.showAlertMsg( - title: "Error switching profile!", - message: "Error: \(responseError(error))" + showAlert( + NSLocalizedString("Error switching profile!", comment: "alertTitle"), + message: String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "alert message"), responseError(error)) ) } } @@ -191,21 +155,14 @@ struct UserPicker: View { } } - private func openSheetOnTap(title: LocalizedStringKey, icon: String, action: @escaping () -> Void) -> some View { - openSheetOnTap(label: { - ZStack(alignment: .leading) { - Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center) - .symbolRenderingMode(.monochrome) - .foregroundColor(theme.colors.secondary) - Text(title) - .foregroundColor(.primary) - .padding(.leading, 36) + private func openSheetOnTap(_ icon: String, title: LocalizedStringKey, sheet: UserPickerSheet) -> some View { + Button { + activeSheet = sheet + } label: { + settingsRow(icon, color: theme.colors.secondary) { + Text(title).foregroundColor(.primary) } - }, action: action) - } - - private func openSheetOnTap(label: () -> V, action: @escaping () -> Void) -> some View { - Button(action: action, label: label) + } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) } diff --git a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift new file mode 100644 index 0000000000..0ba539772f --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift @@ -0,0 +1,52 @@ +// +// StickyScrollView.swift +// SimpleX (iOS) +// +// Created by user on 20/09/2024. +// Copyright Ā© 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct StickyScrollView: UIViewRepresentable { + @ViewBuilder let content: () -> Content + + func makeUIView(context: Context) -> UIScrollView { + let hc = context.coordinator.hostingController + hc.view.backgroundColor = .clear + let sv = UIScrollView() + sv.showsHorizontalScrollIndicator = false + sv.addSubview(hc.view) + sv.delegate = context.coordinator + return sv + } + + func updateUIView(_ scrollView: UIScrollView, context: Context) { + let hc = context.coordinator.hostingController + hc.rootView = content() + hc.view.frame.size = hc.view.intrinsicContentSize + scrollView.contentSize = hc.view.intrinsicContentSize + } + + func makeCoordinator() -> Coordinator { + Coordinator(content: content()) + } + + class Coordinator: NSObject, UIScrollViewDelegate { + let hostingController: UIHostingController + + init(content: Content) { + self.hostingController = UIHostingController(rootView: content) + } + + func scrollViewWillEndDragging( + _ scrollView: UIScrollView, + withVelocity velocity: CGPoint, + targetContentOffset: UnsafeMutablePointer + ) { + if targetContentOffset.pointee.x < 64 { + targetContentOffset.pointee.x = 0 + } + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index f1140575b7..e018b181e6 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -507,18 +507,18 @@ struct ProfilePreview: View { HStack { ProfileImage(imageStr: profileOf.image, size: 44, color: color) .padding(.trailing, 6) - profileName().lineLimit(1) + profileName(profileOf).lineLimit(1) } } - - private func profileName() -> Text { - var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) - if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { - t = t + Text(" (" + profileOf.fullName + ")") +} + +func profileName(_ profileOf: NamedChat) -> Text { + var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2) + if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName { + t = t + Text(" (" + profileOf.fullName + ")") // .font(.callout) - } - return t - } + } + return t } struct SettingsView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index a6bfd9d80e..a73b3febcd 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 */; }; + 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 */; }; CEE723B12C3BD3D70009AE93 /* SimpleX SE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -543,6 +544,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 = ""; }; + 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; }; CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; @@ -796,6 +798,7 @@ CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, CE7548092C622630009579B7 /* SwipeLabel.swift */, CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */, + CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */, ); path = Helpers; sourceTree = ""; @@ -1496,6 +1499,7 @@ 5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */, 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */, 5CE4407227ADB1D0007B033A /* Emoji.swift in Sources */, + CEDB245B2C9CD71800FBC5F6 /* StickyScrollView.swift in Sources */, 5C9CC7A928C532AB00BEF955 /* DatabaseErrorView.swift in Sources */, 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */, 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */, diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 272e487214..0f319f2f9d 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -50,6 +50,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { profile: LocalProfile.sampleData, fullPreferences: FullPreferences.sampleData, activeUser: true, + activeOrder: 0, showNtfs: true, sendRcptsContacts: true, sendRcptsSmallGroups: false 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 9f52694f91..8d942222c1 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 @@ -834,6 +834,7 @@ data class User( profile = LocalProfile.sampleData, fullPreferences = FullChatPreferences.sampleData, activeUser = true, + activeOrder = 0, showNtfs = true, sendRcptsContacts = true, sendRcptsSmallGroups = false, From e79fa136a4ad19b715adb6359dbaaa0654c18d7d Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sat, 21 Sep 2024 21:26:42 +0300 Subject: [PATCH 083/704] ios: fix keyboard loosing focus when forward search results are empty (#4895) --- apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift index 79ede14be5..587957cd5d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemForwardingView.swift @@ -19,7 +19,6 @@ struct ChatItemForwardingView: View { @Binding var composeState: ComposeState @State private var searchText: String = "" - @FocusState private var searchFocused @State private var alert: SomeAlert? private let chatsToForwardTo = filterChatsToForwardTo(chats: ChatModel.shared.chats) @@ -46,8 +45,6 @@ struct ChatItemForwardingView: View { VStack(alignment: .leading) { if !chatsToForwardTo.isEmpty { List { - searchFieldView(text: $searchText, focussed: $searchFocused, theme.colors.onBackground, theme.colors.secondary) - .padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let chats = s == "" ? chatsToForwardTo : chatsToForwardTo.filter { foundChat($0, s) } ForEach(chats) { chat in @@ -55,6 +52,7 @@ struct ChatItemForwardingView: View { .disabled(chatModel.deletedChats.contains(chat.chatInfo.id)) } } + .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) .modifier(ThemedBackground(grouped: true)) } else { ZStack { From 8a906485d1e28f9b39ef5f4c7d2c75f0773c538e Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Sat, 21 Sep 2024 23:33:18 +0300 Subject: [PATCH 084/704] ios: display year in chat for previous years (#4919) * ios: display year in chat for previous years * fix chat time, show past years in the list * style --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Views/Chat/ChatView.swift | 6 +++++- apps/ios/Shared/Views/ChatList/ChatPreviewView.swift | 7 ++++++- apps/ios/SimpleXChat/ChatTypes.swift | 9 ++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e7359587df..7f6b61c1ea 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -622,7 +622,11 @@ struct ChatView: View { Text(String.localizedStringWithFormat( NSLocalizedString("%@, %@", comment: "format for date separator in chat"), date.formatted(.dateTime.weekday(.abbreviated)), - date.formatted(.dateTime.day().month(.abbreviated)) + date.formatted( + Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) + ? .dateTime.day().month(.abbreviated) + : .dateTime.day().month(.abbreviated).year() + ) )) .font(.callout) .fontWeight(.medium) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index cf9977860d..d721d546c1 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -35,11 +35,16 @@ struct ChatPreviewView: View { } .padding(.leading, 4) + let chatTs = if let cItem { + cItem.meta.itemTs + } else { + chat.chatInfo.chatTs + } VStack(spacing: 0) { HStack(alignment: .top) { chatPreviewTitle() Spacer() - (cItem?.timestampText ?? formatTimestampText(chat.chatInfo.chatTs)) + (formatTimestampText(chatTs)) .font(.subheadline) .frame(minWidth: 60, alignment: .trailing) .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 0f319f2f9d..0777503650 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2765,9 +2765,16 @@ public struct CITimed: Decodable, Hashable { let msgTimeFormat = Date.FormatStyle.dateTime.hour().minute() let msgDateFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits) +let msgDateYearFormat = Date.FormatStyle.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits) public func formatTimestampText(_ date: Date) -> Text { - Text(verbatim: date.formatted(recent(date) ? msgTimeFormat : msgDateFormat)) + Text(verbatim: date.formatted( + recent(date) + ? msgTimeFormat + : Calendar.current.isDate(date, equalTo: .now, toGranularity: .year) + ? msgDateFormat + : msgDateYearFormat + )) } public func formatTimestampMeta(_ date: Date) -> String { From 55d180466a4d6720184a2419014db88887c8b933 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Sat, 21 Sep 2024 20:34:13 +0000 Subject: [PATCH 085/704] docs: iOS notifications in FAQ (#4879) * docs: iOS notifications in FAQ * Update FAQ.md --- docs/FAQ.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index d8a8d5938f..932a4c33ee 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -148,14 +148,20 @@ Check battery settings for the app - it should be set to Unrestricted. For some devices, there may be additional options to prevent the app from being killed - e.g., on Xiaomi you need to enable Auto Start setting for the app. Please consult https://dontkillmyapp.com site for any additional settings for your device. -**iOS notifications failed to initialize correctly** +**Why my notifications aren't working on iOS** Check the color of the bolt icon next to Notifications in app settings - it should be green. If it's not, please open notifications, disable them (choose Off / Local), and then enable again - you should do it when you have Internet connection. +Check if your push server has been restarted at time of the issue (Notifications -> Push server) at https://status.simplex.chat if it has been restarted, you may not receive notifications from that time. + +If device was offline, you may need to open the app to start receiving notifications. + If the above didn't help, the reason could be that iOS failed to issue notification token - we have seen this issue several times. In this case, restarting the whole device should help. +In some cases notifications may still not work, iOS notifications are hard to do right in a decentralized app, we will be improving them soon to be more reliable. + **Messaging server or notification server is under maintenance** Please check the current status of preset servers at [https://status.simplex.chat](https://status.simplex.chat). You can also connect to status bot via QR code on that page - it will send the updates when the server is offline for maintenance, and also when the new versions of the app are released. From d5507f2fa35d7414178f68992e11767716d55ef8 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:57:59 +0700 Subject: [PATCH 086/704] android, desktop: member name position depends on length (#4918) * android, desktop: member name position depends on length * maxWidth limit * fix * optimization * paddings --------- Co-authored-by: Evgeny Poberezkin --- .../simplex/common/views/chat/ChatView.kt | 117 ++++++++++-------- .../common/views/chat/item/ChatItemView.kt | 3 +- .../common/views/chat/item/FramedItemView.kt | 53 ++++++-- 3 files changed, 112 insertions(+), 61 deletions(-) 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 835f884f98..18deb48597 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 @@ -14,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.* +import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -1033,25 +1034,6 @@ fun BoxWithConstraintsScope.ChatItemsList( // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop() ) { - val dismissState = rememberDismissState(initialValue = DismissValue.Default) { - if (it == DismissValue.DismissedToStart) { - scope.launch { - if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) { - if (composeState.value.editing) { - composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) - } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { - composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) - } - } - } - } - false - } - val swipeableModifier = SwipeToDismissModifier( - state = dismissState, - directions = setOf(DismissDirection.EndToStart), - swipeDistance = with(LocalDensity.current) { 30.dp.toPx() }, - ) val provider = { providerForGallery(i, chatModel.chatItems.value, cItem.id) { indexInReversed -> scope.launch { @@ -1066,16 +1048,35 @@ fun BoxWithConstraintsScope.ChatItemsList( val revealed = remember { mutableStateOf(false) } @Composable - fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?) { + fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?, fillMaxWidth: Boolean = true) { tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) } } @Composable fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?) { + val dismissState = rememberDismissState(initialValue = DismissValue.Default) { + if (it == DismissValue.DismissedToStart) { + scope.launch { + if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) { + if (composeState.value.editing) { + composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) + } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { + composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem)) + } + } + } + } + false + } + val swipeableModifier = SwipeToDismissModifier( + state = dismissState, + directions = setOf(DismissDirection.EndToStart), + swipeDistance = with(LocalDensity.current) { 30.dp.toPx() }, + ) val sent = cItem.chatDir.sent Box(Modifier.padding(bottom = 4.dp)) { val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null @@ -1095,43 +1096,61 @@ fun BoxWithConstraintsScope.ChatItemsList( Column( Modifier .padding(top = 8.dp) - .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp), + .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp) + .fillMaxWidth() + .then(swipeableModifier), verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.Start ) { - if (cItem.content.showMemberName) { - val memberNameStyle = SpanStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) - val memberNameString = if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { - buildAnnotatedString { - withStyle(memberNameStyle.copy(fontWeight = FontWeight.Medium)) { append(member.memberRole.text) } - append(" ") - withStyle(memberNameStyle) { append(memberNames(member, prevMember, memCount)) } - } - } else { - buildAnnotatedString { - withStyle(memberNameStyle) { append(memberNames(member, prevMember, memCount)) } + @Composable + fun MemberNameAndRole() { + Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) { + Text( + memberNames(member, prevMember, memCount), + Modifier + .padding(start = MEMBER_IMAGE_SIZE + DEFAULT_PADDING_HALF) + .weight(1f, false), + fontSize = 13.5.sp, + color = MaterialTheme.colors.secondary, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { + Text( + member.memberRole.text, + Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF), + fontSize = 13.5.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.secondary, + maxLines = 1 + ) } } - Text( - memberNameString, - Modifier.padding(start = MEMBER_IMAGE_SIZE + 10.dp), - maxLines = 2 - ) } - Box(contentAlignment = Alignment.CenterStart) { - androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { - SelectedChatItem(Modifier, cItem.id, selectedChatItems) - } - Row( - swipeableOrSelectionModifier, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { - MemberImage(member) + + @Composable + fun Item() { + Box(Modifier.layoutId(CHAT_BUBBLE_LAYOUT_ID), contentAlignment = Alignment.CenterStart) { + androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) { + SelectedChatItem(Modifier, cItem.id, selectedChatItems) + } + Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { + MemberImage(member) + } + ChatItemViewShortHand(cItem, range, false) } - ChatItemViewShortHand(cItem, range) } } + if (cItem.content.showMemberName) { + DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) { + MemberNameAndRole() + Item() + } + } else { + Item() + } } } else { Box(contentAlignment = Alignment.CenterStart) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 516e47e7ed..df30e85161 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -51,6 +51,7 @@ fun ChatItemView( revealed: MutableState, range: IntRange?, selectedChatItems: MutableState?>, + fillMaxWidth: Boolean = true, selectChatItem: () -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessages: (List) -> Unit, @@ -83,7 +84,7 @@ fun ChatItemView( val live = composeState.value.liveMessage != null Box( - modifier = Modifier.fillMaxWidth(), + modifier = if (fillMaxWidth) Modifier.fillMaxWidth() else Modifier, contentAlignment = alignment, ) { val info = cItem.meta.itemStatus.statusInto diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 8a579d5289..ddcf7c340b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -304,6 +304,7 @@ fun CIMarkdownText( } const val CHAT_IMAGE_LAYOUT_ID = "chatImage" +const val CHAT_BUBBLE_LAYOUT_ID = "chatBubble" /** * Equal to [androidx.compose.ui.unit.Constraints.MaxFocusMask], which is 0x3FFFF - 1 * Other values make a crash `java.lang.IllegalArgumentException: Can't represent a width of 123456 and height of 9909 in Constraints` @@ -311,23 +312,23 @@ const val CHAT_IMAGE_LAYOUT_ID = "chatImage" * */ const val MAX_SAFE_WIDTH = 0x3FFFF - 1 +/** + * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] + * */ +private fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31 + width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height + width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height + width < 0xFFFF /*MinFocusMask*/ -> 0x7FFF - 1 /* MinFocusMask */ // 16 bits width + 15 bits height + width < 0x3FFFF /*MaxFocusMask*/ -> 0x1FFF - 1 /* MaxNonFocusMask */ // 18 bits width + 13 bits height + else -> 0x1FFF // shouldn't happen since width is limited already +} + @Composable fun PriorityLayout( modifier: Modifier = Modifier, priorityLayoutId: String, content: @Composable () -> Unit ) { - /** - * Limiting max value for height + width in order to not crash the app, see [androidx.compose.ui.unit.Constraints.createConstraints] - * */ - fun maxSafeHeight(width: Int) = when { // width bits + height bits should be <= 31 - width < 0x1FFF /*MaxNonFocusMask*/ -> 0x3FFFF - 1 /* MaxFocusMask */ // 13 bits width + 18 bits height - width < 0x7FFF /*MinNonFocusMask*/ -> 0xFFFF - 1 /* MinFocusMask */ // 15 bits width + 16 bits height - width < 0xFFFF /*MinFocusMask*/ -> 0x7FFF - 1 /* MinFocusMask */ // 16 bits width + 15 bits height - width < 0x3FFFF /*MaxFocusMask*/ -> 0x1FFF - 1 /* MaxNonFocusMask */ // 18 bits width + 13 bits height - else -> 0x1FFF // shouldn't happen since width is limited already - } - Layout( content = content, modifier = modifier @@ -352,6 +353,36 @@ fun PriorityLayout( } } } + +@Composable +fun DependentLayout( + modifier: Modifier = Modifier, + mainLayoutId: String, + content: @Composable () -> Unit +) { + Layout( + content = content, + modifier = modifier + ) { measureable, constraints -> + // Find important element which should tell what min width it needs to draw itself. + // Expecting only one such element. Can be less than one but not more + val mainPlaceable = measureable.firstOrNull { it.layoutId == mainLayoutId }?.measure(constraints) + val placeables: List = measureable.map { + if (it.layoutId == mainLayoutId) + mainPlaceable!! + else + it.measure(constraints.copy(minWidth = mainPlaceable?.width ?: 0, maxWidth = min(MAX_SAFE_WIDTH, constraints.maxWidth))) } + val width = mainPlaceable?.measuredWidth ?: min(MAX_SAFE_WIDTH, placeables.maxOf { it.width }) + val height = minOf(maxSafeHeight(width), placeables.sumOf { it.height }) + layout(width, height) { + var y = 0 + placeables.forEach { + it.place(0, y) + y += it.measuredHeight + } + } + } +} /* class EditedProvider: PreviewParameterProvider { From 1f226dda64ee3146e0b992fee2d0ee08df7b2947 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:25:14 +0700 Subject: [PATCH 087/704] android: fix status bar color after hiding call (#4928) * android: fix status bar color after hiding call * dark status bar in call --- .../main/java/chat/simplex/app/SimplexApp.kt | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 6adaa1d4e0..40e8ffa9bc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -287,13 +287,23 @@ class SimplexApp: Application(), LifecycleEventObserver { // Blend status bar color to the animated color val colors = CurrentColors.value.colors val baseBackgroundColor = if (toolbarOnTop) colors.background.mixWith(colors.onBackground, 0.97f) else colors.background - window.statusBarColor = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() - val navBar = navBarColor.toArgb() + var statusBar = baseBackgroundColor.mixWith(drawerShadingColor.copy(1f), 1 - drawerShadingColor.alpha).toArgb() + var statusBarLight = isLight + // SimplexGreen while in call + if (window.statusBarColor == SimplexGreen.toArgb()) { + statusBarColorAfterCall.intValue = statusBar + statusBar = SimplexGreen.toArgb() + statusBarLight = false + } + window.statusBarColor = statusBar + val navBar = navBarColor.toArgb() + if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) { + windowInsetController?.isAppearanceLightStatusBars = statusBarLight + } if (window.navigationBarColor != navBar) { window.navigationBarColor = navBar } - if (windowInsetController?.isAppearanceLightNavigationBars != isLight) { windowInsetController?.isAppearanceLightNavigationBars = isLight } @@ -313,11 +323,13 @@ class SimplexApp: Application(), LifecycleEventObserver { backgroundColor } }).toArgb() + var statusBarLight = isLight // SimplexGreen while in call if (window.statusBarColor == SimplexGreen.toArgb()) { statusBarColorAfterCall.intValue = statusBar statusBar = SimplexGreen.toArgb() + statusBarLight = false } val navBar = (if (hasBottom && appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete) { backgroundColor.mixWith(CurrentColors.value.colors.onBackground, 0.97f) @@ -327,8 +339,8 @@ class SimplexApp: Application(), LifecycleEventObserver { if (window.statusBarColor != statusBar) { window.statusBarColor = statusBar } - if (windowInsetController?.isAppearanceLightStatusBars != isLight) { - windowInsetController?.isAppearanceLightStatusBars = isLight + if (windowInsetController?.isAppearanceLightStatusBars != statusBarLight) { + windowInsetController?.isAppearanceLightStatusBars = statusBarLight } if (window.navigationBarColor != navBar) { window.navigationBarColor = navBar From 0f301adc57c199a7d68961d8643fde9e8f5de2a7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 24 Sep 2024 09:25:41 +0100 Subject: [PATCH 088/704] core: xrcp encryption with forward secrecy (#4926) * core: xrcp encryption with forward secrecy (tests intermittently fail) * track and correlate keys * simplify * refactor * remove comment --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Remote.hs | 49 +++++++++-------- src/Simplex/Chat/Remote/Protocol.hs | 79 +++++++++++++++------------- src/Simplex/Chat/Remote/Transport.hs | 23 ++++---- src/Simplex/Chat/Remote/Types.hs | 54 +++++++++++++++---- 6 files changed, 127 insertions(+), 82 deletions(-) diff --git a/cabal.project b/cabal.project index f2a652d721..96208d329a 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: bef11e4cbe0a3776f0910375f2adb60399043835 + tag: 04da670d00e904149040b136ebdea112de71e218 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 730a0f9501..d9d2d27013 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."bef11e4cbe0a3776f0910375f2adb60399043835" = "195hir9crv51iyli8yjk8sivk7wagxxjfmhzq6ahydvbkrpd3258"; + "https://github.com/simplex-chat/simplexmq.git"."04da670d00e904149040b136ebdea112de71e218" = "0kbpd56b3b8dy72xicfdqmiywxabq000125523dgmn3a5pjagc08"; "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/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 76d3754f17..0d39951b90 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -54,6 +54,7 @@ import Simplex.Chat.Util (encryptFile, liftIOEither) import Simplex.FileTransfer.Description (FileDigest (..)) import Simplex.Messaging.Agent import Simplex.Messaging.Agent.Protocol (AgentErrorType (RCP)) +import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String (StrEncoding (..)) @@ -497,31 +498,32 @@ handleRemoteCommand :: (ByteString -> CM' ChatResponse) -> RemoteCrypto -> TBQue handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do logDebug "handleRemoteCommand" liftIO (tryRemoteError' parseRequest) >>= \case - Right (getNext, rc) -> do + Right (rfKN, getNext, rc) -> do chatReadVar' currentUser >>= \case Nothing -> replyError $ ChatError CENoActiveUser - Just user -> processCommand user getNext rc `catchChatError'` replyError + Just user -> processCommand user rfKN getNext rc `catchChatError'` replyError Left e -> reply $ RRProtocolError e where - parseRequest :: ExceptT RemoteProtocolError IO (GetChunk, RemoteCommand) + parseRequest :: ExceptT RemoteProtocolError IO (C.SbKeyNonce, GetChunk, RemoteCommand) parseRequest = do - (header, getNext) <- parseDecryptHTTP2Body encryption request reqBody - (getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) + (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption request reqBody + (rfKN,getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) replyError = reply . RRChatResponse . CRChatCmdError Nothing - processCommand :: User -> GetChunk -> RemoteCommand -> CM () - processCommand user getNext = \case + processCommand :: User -> C.SbKeyNonce -> GetChunk -> RemoteCommand -> CM () + processCommand user rfKN getNext = \case RCSend {command} -> lift $ handleSend execChatCommand command >>= reply RCRecv {wait = time} -> lift $ liftIO (handleRecv time remoteOutputQ) >>= reply - RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile encryption fileName fileSize fileDigest getNext >>= reply - RCGetFile {file} -> handleGetFile encryption user file replyWith + RCStoreFile {fileName, fileSize, fileDigest} -> lift $ handleStoreFile rfKN fileName fileSize fileDigest getNext >>= reply + RCGetFile {file} -> handleGetFile user file replyWith reply :: RemoteResponse -> CM' () - reply = (`replyWith` \_ -> pure ()) + reply = (`replyWith` \_ _ -> pure ()) replyWith :: Respond - replyWith rr attach = - liftIO (tryRemoteError' . encryptEncodeHTTP2Body encryption $ J.encode rr) >>= \case + replyWith rr attach = do + (corrId, cmdKN, sfKN) <- atomically $ getRemoteSndKeys encryption + liftIO (tryRemoteError' . encryptEncodeHTTP2Body corrId cmdKN encryption $ J.encode rr) >>= \case Right resp -> liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do send resp - attach send + attach sfKN send flush Left e -> toView' . CRChatError Nothing . ChatErrorRemoteCtrl $ RCEProtocolError e @@ -532,7 +534,7 @@ type GetChunk = Int -> IO ByteString type SendChunk = Builder -> IO () -type Respond = RemoteResponse -> (SendChunk -> IO ()) -> CM' () +type Respond = RemoteResponse -> (C.SbKeyNonce -> SendChunk -> IO ()) -> CM' () liftRC :: ExceptT RemoteProtocolError IO a -> CM a liftRC = liftError (ChatErrorRemoteCtrl . RCEProtocolError) @@ -559,8 +561,8 @@ handleRecv time events = do -- TODO this command could remember stored files and return IDs to allow removing files that are not needed. -- Also, there should be some process removing unused files uploaded to remote host (possibly, all unused files). -handleStoreFile :: RemoteCrypto -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse -handleStoreFile encryption fileName fileSize fileDigest getChunk = +handleStoreFile :: C.SbKeyNonce -> FilePath -> Word32 -> FileDigest -> GetChunk -> CM' RemoteResponse +handleStoreFile rfKN fileName fileSize fileDigest getChunk = either RRProtocolError RRFileStored <$> (chatReadVar' filesFolder >>= storeFile) where storeFile :: Maybe FilePath -> CM' (Either RemoteProtocolError FilePath) @@ -570,11 +572,11 @@ handleStoreFile encryption fileName fileSize fileDigest getChunk = storeFileTo :: FilePath -> CM' (Either RemoteProtocolError FilePath) storeFileTo dir = liftIO . tryRemoteError' $ do filePath <- liftIO $ dir `uniqueCombine` fileName - receiveEncryptedFile encryption getChunk fileSize fileDigest filePath + receiveEncryptedFile rfKN getChunk fileSize fileDigest filePath pure filePath -handleGetFile :: RemoteCrypto -> User -> RemoteFile -> Respond -> CM () -handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do +handleGetFile :: User -> RemoteFile -> Respond -> CM () +handleGetFile User {userId} RemoteFile {userId = commandUserId, fileId, sent, fileSource = cf'@CryptoFile {filePath}} reply = do logDebug $ "GetFile: " <> tshow filePath unless (userId == commandUserId) $ throwChatError $ CEDifferentActiveUser {commandUserId, activeUserId = userId} path <- maybe filePath ( filePath) <$> chatReadVar filesFolder @@ -582,11 +584,12 @@ handleGetFile encryption User {userId} RemoteFile {userId = commandUserId, fileI cf <- getLocalCryptoFile db commandUserId fileId sent unless (cf == cf') $ throwError $ SEFileNotFound fileId liftRC (tryRemoteError $ getFileInfo path) >>= \case - Left e -> lift $ reply (RRProtocolError e) $ \_ -> pure () + Left e -> lift $ reply (RRProtocolError e) $ \_ _ -> pure () Right (fileSize, fileDigest) -> - ExceptT . withFile path ReadMode $ \h -> runExceptT $ do - encFile <- liftRC $ prepareEncryptedFile encryption (h, fileSize) - lift $ reply RRFile {fileSize, fileDigest} $ sendEncryptedFile encFile + lift . withFile path ReadMode $ \h -> do + reply RRFile {fileSize, fileDigest} $ \sfKN send -> void . runExceptT $ do + encFile <- prepareEncryptedFile sfKN (h, fileSize) + liftIO $ sendEncryptedFile encFile send listRemoteCtrls :: CM [RemoteCtrlInfo] listRemoteCtrls = do diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index fe07a940ae..00fc56f897 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -43,6 +43,8 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..)) import Simplex.Messaging.Crypto.Lazy (LazyByteString) import Simplex.Messaging.Encoding import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON, pattern SingleFieldJSONTag, pattern TaggedObjectJSONData, pattern TaggedObjectJSONTag) +import qualified Simplex.Messaging.TMap as TM +import Simplex.Messaging.Transport (TSbChainKeys) import Simplex.Messaging.Transport.Buffer (getBuffered) import Simplex.Messaging.Transport.HTTP2 (HTTP2Body (..), HTTP2BodyChunk, getBodyChunk) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2Response (..), closeHTTP2Client, sendRequestDirect) @@ -77,11 +79,9 @@ $(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) mkRemoteHostClient :: HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> CM RemoteHostClient mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do - drg <- asks random - counter <- newTVarIO 1 - let HostSessKeys {hybridKey, idPrivKey, sessPrivKey} = sessionKeys + let HostSessKeys {chainKeys, idPrivKey, sessPrivKey} = sessionKeys signatures = RSSign {idPrivKey, sessPrivKey} - encryption = RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} + encryption <- liftIO $ mkRemoteCrypto sessionCode chainKeys signatures pure RemoteHostClient { hostEncoding = encoding, @@ -93,11 +93,16 @@ mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {enc } mkCtrlRemoteCrypto :: CtrlSessKeys -> SessionCode -> CM RemoteCrypto -mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do - drg <- asks random - counter <- newTVarIO 1 +mkCtrlRemoteCrypto CtrlSessKeys {chainKeys, idPubKey, sessPubKey} sessionCode = let signatures = RSVerify {idPubKey, sessPubKey} - pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} + in liftIO $ mkRemoteCrypto sessionCode chainKeys signatures + +mkRemoteCrypto :: SessionCode -> TSbChainKeys -> RemoteSignatures -> IO RemoteCrypto +mkRemoteCrypto sessionCode chainKeys signatures = do + sndCounter <- newTVarIO 0 + rcvCounter <- newTVarIO 0 + skippedKeys <- liftIO TM.emptyIO + pure RemoteCrypto {sessionCode, sndCounter, rcvCounter, chainKeys, skippedKeys, signatures} closeRemoteHostClient :: RemoteHostClient -> IO () closeRemoteHostClient RemoteHostClient {httpClient} = closeHTTP2Client httpClient @@ -125,26 +130,30 @@ remoteStoreFile c localPath fileName = do r -> badResponse r remoteGetFile :: RemoteHostClient -> FilePath -> RemoteFile -> ExceptT RemoteProtocolError IO () -remoteGetFile c@RemoteHostClient {encryption} destDir rf@RemoteFile {fileSource = CryptoFile {filePath}} = +remoteGetFile c destDir rf@RemoteFile {fileSource = CryptoFile {filePath}} = sendRemoteCommand c Nothing RCGetFile {file = rf} >>= \case - (getChunk, RRFile {fileSize, fileDigest}) -> do + (rfKN, getChunk, RRFile {fileSize, fileDigest}) -> do -- TODO we could optimize by checking size and hash before receiving the file let localPath = destDir takeFileName filePath - receiveEncryptedFile encryption getChunk fileSize fileDigest localPath - (_, r) -> badResponse r + receiveEncryptedFile rfKN getChunk fileSize fileDigest localPath + (_, _, r) -> badResponse r -- TODO validate there is no attachment in response sendRemoteCommand' :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO RemoteResponse -sendRemoteCommand' c attachment_ rc = snd <$> sendRemoteCommand c attachment_ rc +sendRemoteCommand' c attachment_ rc = do + (_, _, r) <- sendRemoteCommand c attachment_ rc + pure r -sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO (Int -> IO ByteString, RemoteResponse) +sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO (C.SbKeyNonce, Int -> IO ByteString, RemoteResponse) sendRemoteCommand RemoteHostClient {httpClient, hostEncoding, encryption} file_ cmd = do - encFile_ <- mapM (prepareEncryptedFile encryption) file_ - req <- httpRequest encFile_ <$> encryptEncodeHTTP2Body encryption (J.encode cmd) + (corrId, cmdKN, sfKN) <- atomically $ getRemoteSndKeys encryption + encCmd <- encryptEncodeHTTP2Body corrId cmdKN encryption $ J.encode cmd + encFile_ <- mapM (prepareEncryptedFile sfKN) file_ + let req = httpRequest encFile_ encCmd HTTP2Response {response, respBody} <- liftError' (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing - (header, getNext) <- parseDecryptHTTP2Body encryption response respBody + (rfKN, header, getNext) <- parseDecryptHTTP2Body encryption response respBody rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecode header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding - pure (getNext, rr) + pure (rfKN, getNext, rr) where httpRequest encFile_ cmdBld = H.requestStreaming N.methodPost "/" mempty $ \send flush -> do send cmdBld @@ -213,13 +222,11 @@ pattern OwsfTag = (SingleFieldJSONTag, J.Bool True) -- See https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2023-10-25-remote-control.md for encoding -encryptEncodeHTTP2Body :: RemoteCrypto -> LazyByteString -> ExceptT RemoteProtocolError IO Builder -encryptEncodeHTTP2Body RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} s = do - corrId <- atomically $ stateTVar counter $ \c -> (c, c + 1) - let pfx = smpEncode (sessionCode, corrId) - (nonce, ct) <- liftError PRERemoteControl $ RC.rcEncryptBody drg hybridKey $ LB.fromStrict pfx <> s +encryptEncodeHTTP2Body :: Word32 -> C.SbKeyNonce -> RemoteCrypto -> LazyByteString -> ExceptT RemoteProtocolError IO Builder +encryptEncodeHTTP2Body corrId cmdKN RemoteCrypto {sessionCode, signatures} s = do + ct <- liftError PRERemoteControl $ RC.rcEncryptBody cmdKN $ LB.fromStrict (smpEncode sessionCode) <> s let ctLen = encodeWord32 (fromIntegral $ LB.length ct) - signed = LB.fromStrict (smpEncode nonce <> ctLen) <> ct + signed = LB.fromStrict (encodeWord32 corrId <> ctLen) <> ct sigs <- bodySignatures signed pure $ lazyByteString signed <> sigs where @@ -235,24 +242,25 @@ encryptEncodeHTTP2Body RemoteCrypto {drg, counter, sessionCode, hybridKey, signa sign k = C.signatureBytes . C.sign' k . BA.convert . CH.hashFinalize -- | Parse and decrypt HTTP2 request/response -parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (LazyByteString, Int -> IO ByteString) -parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2Body {bodyBuffer} = do - (nonce, ct) <- getBody - s <- liftError PRERemoteControl $ RC.rcDecryptBody hybridKey nonce ct - (,getNext) <$> parseBody s +parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (C.SbKeyNonce, LazyByteString, Int -> IO ByteString) +parseDecryptHTTP2Body rc@RemoteCrypto {sessionCode, signatures} hr HTTP2Body {bodyBuffer} = do + (corrId, ct) <- getBody + (cmdKN, rfKN) <- ExceptT $ atomically $ getRemoteRcvKeys rc corrId + s <- liftError PRERemoteControl $ RC.rcDecryptBody cmdKN ct + s' <- parseBody s + pure (rfKN, s', getNext) where - getBody :: ExceptT RemoteProtocolError IO (C.CbNonce, LazyByteString) + getBody :: ExceptT RemoteProtocolError IO (Word32, LazyByteString) getBody = do - nonceStr <- liftIO $ getNext 24 - nonce <- liftEitherWith RPEInvalidBody $ smpDecode nonceStr + corrIdStr <- liftIO $ getNext 4 ctLenStr <- liftIO $ getNext 4 let ctLen = decodeWord32 ctLenStr when (ctLen > fromIntegral (maxBound :: Int)) $ throwError RPEInvalidSize chunks <- liftIO $ getLazy $ fromIntegral ctLen - let hc = CH.hashUpdates (CH.hashInit @SHA512) [nonceStr, ctLenStr] + let hc = CH.hashUpdates (CH.hashInit @SHA512) [corrIdStr, ctLenStr] hc' = CH.hashUpdates hc chunks verifySignatures hc' - pure (nonce, LB.fromChunks chunks) + pure (decodeWord32 corrIdStr, LB.fromChunks chunks) getLazy :: Int -> IO [ByteString] getLazy 0 = pure [] getLazy n = do @@ -279,9 +287,8 @@ parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2 parseBody s = case LB.uncons s of Nothing -> throwError $ RPEInvalidBody "empty body" Just (scLen, rest) -> do - (sessCode', rest') <- takeBytes (fromIntegral scLen) rest + (sessCode', s') <- takeBytes (fromIntegral scLen) rest unless (sessCode' == sessionCode) $ throwError PRESessionCode - (_corrId, s') <- takeBytes 8 rest' pure s' where takeBytes n s' = do diff --git a/src/Simplex/Chat/Remote/Transport.hs b/src/Simplex/Chat/Remote/Transport.hs index 774aeccda2..933936aa4d 100644 --- a/src/Simplex/Chat/Remote/Transport.hs +++ b/src/Simplex/Chat/Remote/Transport.hs @@ -1,5 +1,6 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} module Simplex.Chat.Remote.Transport where @@ -20,27 +21,25 @@ import Simplex.RemoteControl.Types (RCErrorType (..)) import UnliftIO import UnliftIO.Directory (getFileSize) -type EncryptedFile = ((Handle, Word32), C.CbNonce, LC.SbState) +type EncryptedFile = ((Handle, Word32), LC.SbState) -prepareEncryptedFile :: RemoteCrypto -> (Handle, Word32) -> ExceptT RemoteProtocolError IO EncryptedFile -prepareEncryptedFile RemoteCrypto {drg, hybridKey} f = do - nonce <- atomically $ C.randomCbNonce drg - sbState <- liftEitherWith (const $ PRERemoteControl RCEEncrypt) $ LC.kcbInit hybridKey nonce - pure (f, nonce, sbState) +prepareEncryptedFile :: C.SbKeyNonce -> (Handle, Word32) -> ExceptT RemoteProtocolError IO EncryptedFile +prepareEncryptedFile (sk, nonce) f = do + sbState <- liftEitherWith (const $ PRERemoteControl RCEEncrypt) $ LC.sbInit sk nonce + pure (f, sbState) sendEncryptedFile :: EncryptedFile -> (Builder -> IO ()) -> IO () -sendEncryptedFile ((h, sz), nonce, sbState) send = do - send $ byteString $ smpEncode ('\x01', nonce, sz + fromIntegral C.authTagSize) +sendEncryptedFile ((h, sz), sbState) send = do + send $ byteString $ smpEncode ('\x01', sz + fromIntegral C.authTagSize) sendEncFile h send sbState sz -receiveEncryptedFile :: RemoteCrypto -> (Int -> IO ByteString) -> Word32 -> FileDigest -> FilePath -> ExceptT RemoteProtocolError IO () -receiveEncryptedFile RemoteCrypto {hybridKey} getChunk fileSize fileDigest toPath = do +receiveEncryptedFile :: C.SbKeyNonce -> (Int -> IO ByteString) -> Word32 -> FileDigest -> FilePath -> ExceptT RemoteProtocolError IO () +receiveEncryptedFile (sk, nonce) getChunk fileSize fileDigest toPath = do c <- liftIO $ getChunk 1 unless (c == "\x01") $ throwError RPENoFile - nonce <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 24 size <- liftError' RPEInvalidBody $ smpDecode <$> getChunk 4 unless (size == fileSize + fromIntegral C.authTagSize) $ throwError RPEFileSize - sbState <- liftEitherWith (const $ PRERemoteControl RCEDecrypt) $ LC.kcbInit hybridKey nonce + sbState <- liftEitherWith (const $ PRERemoteControl RCEDecrypt) $ LC.sbInit sk nonce liftError' fErr $ withFile toPath WriteMode $ \h -> receiveSbFile getChunk h sbState fileSize digest <- liftIO $ LC.sha512Hash <$> LB.readFile toPath unless (FileDigest digest == fileDigest) $ throwError RPEFileDigest diff --git a/src/Simplex/Chat/Remote/Types.hs b/src/Simplex/Chat/Remote/Types.hs index d85dde9e87..a5396e7945 100644 --- a/src/Simplex/Chat/Remote/Types.hs +++ b/src/Simplex/Chat/Remote/Types.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} @@ -11,22 +12,22 @@ module Simplex.Chat.Remote.Types where import Control.Concurrent.Async (Async) -import Control.Concurrent.STM (TVar) +import Control.Concurrent.STM import Control.Exception (Exception) -import Crypto.Random (ChaChaDRG) +import Control.Monad (when) import qualified Data.Aeson.TH as J import Data.ByteString (ByteString) import Data.Int (Int64) import Data.Text (Text) -import Data.Word (Word16) +import Data.Word (Word16, Word32) import Simplex.Chat.Remote.AppVersion import Simplex.Chat.Types (verificationCode) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile) -import Simplex.Messaging.Crypto.SNTRUP761 (KEMHybridSecret) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) -import Simplex.Messaging.Transport (TLS (..)) +import Simplex.Messaging.Transport (TLS (..), TSbChainKeys (..)) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client) +import qualified Simplex.Messaging.TMap as TM import Simplex.RemoteControl.Client import Simplex.RemoteControl.Types @@ -40,13 +41,45 @@ data RemoteHostClient = RemoteHostClient } data RemoteCrypto = RemoteCrypto - { drg :: TVar ChaChaDRG, - counter :: TVar Int64, - sessionCode :: ByteString, - hybridKey :: KEMHybridSecret, + { sessionCode :: ByteString, + sndCounter :: TVar Word32, + rcvCounter :: TVar Word32, + chainKeys :: TSbChainKeys, + skippedKeys :: TM.TMap Word32 (C.SbKeyNonce, C.SbKeyNonce), signatures :: RemoteSignatures } +getRemoteSndKeys :: RemoteCrypto -> STM (Word32, C.SbKeyNonce, C.SbKeyNonce) +getRemoteSndKeys RemoteCrypto {sndCounter, chainKeys = TSbChainKeys {sndKey}} = do + corrId <- stateTVar sndCounter $ \c -> let !c' = c + 1 in (c', c') + cmdKN <- stateTVar sndKey C.sbcHkdf + fileKN <- stateTVar sndKey C.sbcHkdf + pure (corrId, cmdKN, fileKN) + +getRemoteRcvKeys :: RemoteCrypto -> Word32 -> STM (Either RemoteProtocolError (C.SbKeyNonce, C.SbKeyNonce)) +getRemoteRcvKeys RemoteCrypto {rcvCounter, chainKeys = TSbChainKeys {rcvKey}, skippedKeys} !corrId = + readTVar rcvCounter >>= getRcvKeys + where + getRcvKeys prevCorrId + | prevCorrId > corrId = + let err = PREEarlierId $ prevCorrId - corrId + in maybe (Left err) Right <$> TM.lookupDelete corrId skippedKeys + | prevCorrId == corrId = + pure $ Left PREDuplicateId + | prevCorrId + maxSkip < corrId = + pure $ Left $ RPEManySkippedIds (corrId - prevCorrId) + | otherwise = do -- prevCorrId < corrId + writeTVar rcvCounter corrId + skipKeys (prevCorrId + 1) + Right <$> getKeys + maxSkip = 256 + getKeys = (,) <$> stateTVar rcvKey C.sbcHkdf <*> stateTVar rcvKey C.sbcHkdf + skipKeys !cId = + when (cId < corrId) $ do + keys <- getKeys + TM.insert cId keys skippedKeys + skipKeys (cId + 1) + data RemoteSignatures = RSSign { idPrivKey :: C.PrivateKeyEd25519, @@ -110,6 +143,9 @@ data RemoteProtocolError | RPENoFile | RPEFileSize | RPEFileDigest + | RPEManySkippedIds Word32 + | PREEarlierId Word32 + | PREDuplicateId | -- | Wrong response received for the command sent RPEUnexpectedResponse {response :: Text} | -- | A file already exists in the destination position From 3685c8574378c930d87092c2c1d3400597f16213 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 09:46:54 +0100 Subject: [PATCH 089/704] ios: reduce scroll stickiness threshold for user profiles to 32px --- apps/ios/Shared/Views/Helpers/StickyScrollView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift index 0ba539772f..71bee4f548 100644 --- a/apps/ios/Shared/Views/Helpers/StickyScrollView.swift +++ b/apps/ios/Shared/Views/Helpers/StickyScrollView.swift @@ -44,7 +44,7 @@ struct StickyScrollView: UIViewRepresentable { withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer ) { - if targetContentOffset.pointee.x < 64 { + if targetContentOffset.pointee.x < 32 { targetContentOffset.pointee.x = 0 } } From c67302a5bb4d220a8ac70356a895d01ebe24a5bc Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 24 Sep 2024 09:51:21 +0100 Subject: [PATCH 090/704] core: update 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 96208d329a..d5e17ea299 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: 04da670d00e904149040b136ebdea112de71e218 + tag: 7dcac19a671f76dedbe769030742714783946bd3 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index d9d2d27013..91e5e8866f 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."04da670d00e904149040b136ebdea112de71e218" = "0kbpd56b3b8dy72xicfdqmiywxabq000125523dgmn3a5pjagc08"; + "https://github.com/simplex-chat/simplexmq.git"."7dcac19a671f76dedbe769030742714783946bd3" = "0c1jygir4c1s8g4hdz7b6vw69bvcrknbih9rq8y8rv3d8zl32qpq"; "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 4526afe7e955905fb8620b8414e8c6b90eb242d5 Mon Sep 17 00:00:00 2001 From: Diogo Date: Tue, 24 Sep 2024 11:51:02 +0100 Subject: [PATCH 091/704] desktop: wrap content of remote hosts on overflow (#4923) * desktop: wrap content of remote hosts on overflow * fix long device text and align --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/views/chatlist/UserPicker.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 41fe127093..19bc2afbd5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts @@ -457,6 +458,7 @@ fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = } } +@OptIn(ExperimentalLayoutApi::class) @Composable private fun DevicePickerRow( localDeviceActive: Boolean, @@ -465,13 +467,13 @@ private fun DevicePickerRow( onRemoteHostClick: (rh: RemoteHostInfo, connecting: MutableState) -> Unit, onRemoteHostActionButtonClick: (rh: RemoteHostInfo) -> Unit, ) { - Row( + FlowRow( Modifier .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically + verticalArrangement = Arrangement.spacedBy(12.dp) ) { val activeHost = remoteHosts.firstOrNull { h -> h.activeHost } @@ -554,7 +556,8 @@ fun DevicePill( verticalAlignment = Alignment.CenterVertically ) { Row( - Modifier.padding(horizontal = 6.dp, vertical = 4.dp) + Modifier.padding(horizontal = 6.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically ) { Icon( icon, @@ -567,6 +570,9 @@ fun DevicePill( text, color = MaterialTheme.colors.onSurface, fontSize = 12.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = if (onActionButtonClick != null && actionButtonVisible) Modifier.widthIn(max = 300.dp * fontSizeSqrtMultiplier) else Modifier ) if (onActionButtonClick != null && actionButtonVisible) { val interactionSource = remember { MutableInteractionSource() } From 54ff95f3501d654703eebcd6971d6ba31837cd8d Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:44:55 +0400 Subject: [PATCH 092/704] ios: fix theme customization changing color mode (#4936) --- apps/ios/Shared/Theme/ThemeManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Theme/ThemeManager.swift b/apps/ios/Shared/Theme/ThemeManager.swift index 9d648750d1..4166619d04 100644 --- a/apps/ios/Shared/Theme/ThemeManager.swift +++ b/apps/ios/Shared/Theme/ThemeManager.swift @@ -197,7 +197,7 @@ class ThemeManager { var themeIds = currentThemeIdsDefault.get() themeIds[nonSystemThemeName] = prevValue.themeId currentThemeIdsDefault.set(themeIds) - applyTheme(nonSystemThemeName) + applyTheme(currentThemeDefault.get()) } static func copyFromSameThemeOverrides(_ type: WallpaperType?, _ lowerLevelOverride: ThemeModeOverride?, _ pref: Binding) -> Bool { From e2e6935e5bc47f5660f39071eebeaefd2cb86760 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:16:32 +0400 Subject: [PATCH 093/704] core: fix reactions not being read on item updates (#4938) --- src/Simplex/Chat/Store/Messages.hs | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index e4715c7b12..562a865276 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -1580,7 +1580,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do CPLast count -> liftIO $ getAllChatItemsLast_ count CPAfter afterId count -> liftIO . getAllChatItemsAfter_ afterId count . aChatItemTs =<< getAChatItem_ afterId CPBefore beforeId count -> liftIO . getAllChatItemsBefore_ beforeId count . aChatItemTs =<< getAChatItem_ beforeId - mapM (uncurry (getAChatItem db vr user) >=> liftIO . getACIReactions db) itemRefs + mapM (uncurry (getAChatItem db vr user)) itemRefs where search = fromMaybe "" search_ getAChatItem_ itemId = do @@ -2279,20 +2279,22 @@ getChatRefViaItemId db User {userId} itemId = do (_, _) -> Left $ SEBadChatItem itemId Nothing getAChatItem :: DB.Connection -> VersionRangeChat -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem -getAChatItem db vr user chatRef itemId = case chatRef of - ChatRef CTDirect contactId -> do - ct <- getContact db vr user contactId - (CChatItem msgDir ci) <- getDirectChatItem db user contactId itemId - pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci - ChatRef CTGroup groupId -> do - gInfo <- getGroupInfo db vr user groupId - (CChatItem msgDir ci) <- getGroupChatItem db user groupId itemId - pure $ AChatItem SCTGroup msgDir (GroupChat gInfo) ci - ChatRef CTLocal folderId -> do - nf <- getNoteFolder db user folderId - CChatItem msgDir ci <- getLocalChatItem db user folderId itemId - pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci - _ -> throwError $ SEChatItemNotFound itemId +getAChatItem db vr user chatRef itemId = do + aci <- case chatRef of + ChatRef CTDirect contactId -> do + ct <- getContact db vr user contactId + (CChatItem msgDir ci) <- getDirectChatItem db user contactId itemId + pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci + ChatRef CTGroup groupId -> do + gInfo <- getGroupInfo db vr user groupId + (CChatItem msgDir ci) <- getGroupChatItem db user groupId itemId + pure $ AChatItem SCTGroup msgDir (GroupChat gInfo) ci + ChatRef CTLocal folderId -> do + nf <- getNoteFolder db user folderId + CChatItem msgDir ci <- getLocalChatItem db user folderId itemId + pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci + _ -> throwError $ SEChatItemNotFound itemId + liftIO $ getACIReactions db aci getAChatItemBySharedMsgId :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> SharedMsgId -> ExceptT StoreError IO AChatItem getAChatItemBySharedMsgId db user cd sharedMsgId = case cd of From 6e5eb697a2eaab9ab4f6c6bf671cb56aa9e2438e Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:33:20 +0400 Subject: [PATCH 094/704] core: use broker ts for member profile update item ts (#4940) --- src/Simplex/Chat.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 8e8ab5eb38..583f35bb2b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -4927,7 +4927,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XFile fInv -> processGroupFileInvitation' gInfo m' fInv msg brokerTs XFileCancel sharedMsgId -> xFileCancelGroup gInfo m' sharedMsgId XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m' sharedMsgId fileConnReq_ fName - XInfo p -> xInfoMember gInfo m' p + XInfo p -> xInfoMember gInfo m' p brokerTs XGrpLinkMem p -> xGrpLinkMem gInfo m' conn' p XGrpMemNew memInfo -> xGrpMemNew gInfo m' memInfo msg brokerTs XGrpMemIntro memInfo memRestrictions_ -> xGrpMemIntro gInfo m' memInfo memRestrictions_ @@ -6058,22 +6058,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Profile {displayName = n, fullName = fn, image = i, contactLink = cl} = p Profile {displayName = n', fullName = fn', image = i', contactLink = cl'} = p' - xInfoMember :: GroupInfo -> GroupMember -> Profile -> CM () - xInfoMember gInfo m p' = void $ processMemberProfileUpdate gInfo m p' True + xInfoMember :: GroupInfo -> GroupMember -> Profile -> UTCTime -> CM () + xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs) xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM () xGrpLinkMem gInfo@GroupInfo {membership} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId if viaGroupLink && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived then do - m' <- processMemberProfileUpdate gInfo m p' False + m' <- processMemberProfileUpdate gInfo m p' False Nothing withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True let connectedIncognito = memberIncognito membership probeMatchingMemberContact m' connectedIncognito else messageError "x.grp.link.mem error: invalid group link host profile update" - processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> CM GroupMember - processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems + processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember + processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_ | redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = case memberContactId of Nothing -> do @@ -6103,7 +6103,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createProfileUpdatedItem m' = when createItems $ do let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p' - createInternalChatItem user (CDGroupRcv gInfo m') ciContent Nothing + createInternalChatItem user (CDGroupRcv gInfo m') ciContent itemTs_ createFeatureEnabledItems :: Contact -> CM () createFeatureEnabledItems ct@Contact {mergedPreferences} = @@ -6700,7 +6700,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XMsgDel sharedMsgId memId -> groupMessageDelete gInfo author sharedMsgId memId rcvMsg msgTs XMsgReact sharedMsgId (Just memId) reaction add -> groupMsgReaction gInfo author sharedMsgId memId reaction add rcvMsg msgTs XFileCancel sharedMsgId -> xFileCancelGroup gInfo author sharedMsgId - XInfo p -> xInfoMember gInfo author p + XInfo p -> xInfoMember gInfo author p msgTs XGrpMemNew memInfo -> xGrpMemNew gInfo author memInfo rcvMsg msgTs XGrpMemRole memId memRole -> xGrpMemRole gInfo author memId memRole rcvMsg msgTs XGrpMemDel memId -> xGrpMemDel gInfo author memId rcvMsg msgTs From 9199fbffd5e6608f18e0976d28c354156237598b Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 26 Sep 2024 00:26:04 +0300 Subject: [PATCH 095/704] ios: fix add members search keyboard focus (#4934) * ios: fix add members search keyboard focus * use -1 as ID --------- Co-authored-by: Evgeny Poberezkin --- .../Views/Chat/Group/AddGroupMembersView.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 189ab95494..859d2dfd27 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -105,8 +105,10 @@ struct AddGroupMembersViewCommon: View { .padding(.leading, 2) let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase let members = s == "" ? membersToAdd : membersToAdd.filter { $0.chatViewName.localizedLowercase.contains(s) } - ForEach(members) { contact in - contactCheckView(contact) + ForEach(members + [dummyContact]) { contact in + if contact.contactId != dummyContact.contactId { + contactCheckView(contact) + } } } } @@ -130,6 +132,14 @@ struct AddGroupMembersViewCommon: View { .modifier(ThemedBackground(grouped: true)) } + // Resolves keyboard losing focus bug in iOS16 and iOS17, + // when there are no items inside `ForEach(memebers)` loop + private let dummyContact: Contact = { + var dummy = Contact.sampleData + dummy.contactId = -1 + return dummy + }() + private func inviteMembersButton() -> some View { Button { inviteMembers() From 67472b6285a2aaa4c5d25d72059ffca60e41964c Mon Sep 17 00:00:00 2001 From: Diogo Date: Thu, 26 Sep 2024 09:00:10 +0100 Subject: [PATCH 096/704] android, desktop: scrolling user profiles (#4939) * android, desktop: scrolling user profiles --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Avently <7953703+avently@users.noreply.github.com> --- .../views/chatlist/UserPicker.android.kt | 145 ++++---- .../common/views/chatlist/UserPicker.kt | 333 +++++++++--------- .../views/chatlist/UserPicker.desktop.kt | 55 ++- apps/multiplatform/gradle.properties | 2 +- 4 files changed, 285 insertions(+), 250 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt index 6c16a75874..b68756c669 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt @@ -5,14 +5,19 @@ import androidx.compose.foundation.* import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.DrawerDefaults.ScrimOpacity import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.User @@ -21,95 +26,101 @@ import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +private val USER_PICKER_IMAGE_SIZE = 44.dp +private val USER_PICKER_ROW_PADDING = 16.dp + @Composable -actual fun UserPickerInactiveUsersSection( +actual fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) { val scrollState = rememberScrollState() + val screenWidthDp = windowWidth() if (users.isNotEmpty()) { SectionItemView( - padding = PaddingValues( - start = 16.dp, - top = if (windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL else DEFAULT_PADDING_HALF, - bottom = DEFAULT_PADDING_HALF), + padding = PaddingValues(), disabled = stopped ) { Box { Row( - modifier = Modifier.padding(end = DEFAULT_PADDING + 30.dp).horizontalScroll(scrollState) + modifier = Modifier.horizontalScroll(scrollState), ) { - users.forEach { u -> - UserPickerInactiveUserBadge(u, stopped) { - onUserClicked(it) - withBGApi { - delay(500) - scrollState.scrollTo(0) + Spacer(Modifier.width(DEFAULT_PADDING)) + Row(horizontalArrangement = Arrangement.spacedBy(USER_PICKER_ROW_PADDING)) { + users.forEach { u -> + UserPickerUserBox(u, stopped, modifier = Modifier.userBoxWidth(u.user, users.size, screenWidthDp)) { + onUserClicked(it) + withBGApi { + delay(500) + scrollState.scrollTo(0) + } } } - Spacer(Modifier.width(20.dp)) - } - Spacer(Modifier.width(60.dp)) - } - Row( - horizontalArrangement = Arrangement.End, - modifier = Modifier - .fillMaxWidth() - .padding(end = DEFAULT_PADDING + 30.dp) - .height(60.dp) - ) { - Canvas(modifier = Modifier.size(60.dp)) { - drawRect( - brush = Brush.horizontalGradient( - colors = listOf( - Color.Transparent, - CurrentColors.value.colors.surface, - ) - ), - ) - } - } - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(60.dp) - .fillMaxWidth() - .padding(end = DEFAULT_PADDING) - ) { - IconButton( - onClick = onShowAllProfilesClicked, - enabled = !stopped - ) { - Icon( - painterResource(MR.images.ic_chevron_right), - stringResource(MR.strings.your_chat_profiles), - tint = MaterialTheme.colors.secondary, - modifier = Modifier.size(34.dp) - ) } + Spacer(Modifier.width(DEFAULT_PADDING)) } } } - } else { - UserPickerOptionRow( - painterResource(MR.images.ic_manage_accounts), - stringResource(MR.strings.your_chat_profiles), - onShowAllProfilesClicked + } +} +@Composable +fun UserPickerUserBox( + userInfo: UserInfo, + stopped: Boolean, + modifier: Modifier = Modifier, + onClick: (user: User) -> Unit, +) { + Row( + modifier = modifier + .userPickerBoxModifier() + .clickable ( + onClick = { onClick(userInfo.user) }, + enabled = !stopped + ) + .background(MaterialTheme.colors.background) + .padding(USER_PICKER_ROW_PADDING), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(USER_PICKER_ROW_PADDING) + ) { + Box { + ProfileImageForActiveCall(size = USER_PICKER_IMAGE_SIZE, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (userInfo.unreadCount > 0 && !userInfo.user.activeUser) { + unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false) + } + } + val user = userInfo.user + Text( + user.displayName, + fontWeight = if (user.activeUser) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } } +@Composable +private fun Modifier.userPickerBoxModifier(): Modifier { + val percent = remember { appPreferences.profileImageCornerRadius.state } + val r = kotlin.math.max(0f, percent.value) + + val cornerSize = when { + r >= 50 -> 50 + r <= 0 -> 0 + else -> r.toInt() + } + + val shape = RoundedCornerShape(CornerSize(cornerSize)) + return this.clip(shape).border(1.dp, MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 1 - userPickerAlpha() - 0.02f), shape) +} + + private fun calculateFraction(pos: Float) = (pos / 1f).coerceIn(0f, 1f) @@ -150,7 +161,7 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< isLight = colors.isLight, drawerShadingColor = shadingColor, toolbarOnTop = !appPrefs.oneHandUI.get(), - navBarColor = colors.surface + navBarColor = colors.background.mixWith(colors.onBackground, 1 - userPickerAlpha()) ) } else if (ModalManager.start.modalCount.value == 0) { platform.androidSetDrawerStatusAndNavBarColor( @@ -220,3 +231,13 @@ private fun Modifier.draggableBottomDrawerModifier( orientation = Orientation.Vertical, resistance = null ) + +private fun Modifier.userBoxWidth(user: User, totalUsers: Int, windowWidth: Dp): Modifier { + return if (totalUsers == 1) { + this.width(windowWidth - DEFAULT_PADDING * 2) + } else if (user.activeUser) { + this.width(windowWidth - DEFAULT_PADDING - (USER_PICKER_ROW_PADDING * 3) - USER_PICKER_IMAGE_SIZE) + } else { + this.widthIn(max = (windowWidth - (DEFAULT_PADDING * 2)) * 0.618f) + } +} \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 19bc2afbd5..8546dc4fb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -1,13 +1,11 @@ package chat.simplex.common.views.chatlist import SectionItemView -import SectionView import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -32,7 +30,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.platform.* import chat.simplex.common.views.CreateProfile import chat.simplex.common.views.localauth.VerticalDivider -import chat.simplex.common.views.newchat.* import chat.simplex.common.views.remote.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.views.usersettings.AppearanceScope.ColorModeSwitcher @@ -41,6 +38,8 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +private val USER_PICKER_SECTION_SPACING = 32.dp + @Composable fun UserPicker( chatModel: ChatModel, @@ -57,7 +56,7 @@ fun UserPicker( derivedStateOf { chatModel.users .filter { u -> u.user.activeUser || !u.user.hidden } - .sortedByDescending { it.user.activeUser } + .sortedByDescending { it.user.activeOrder } } } val remoteHosts by remember { @@ -143,10 +142,33 @@ fun UserPicker( .height(IntrinsicSize.Min) .fillMaxWidth() .then(if (newChat.isVisible()) Modifier.shadow(8.dp, clip = true) else Modifier) - .background(MaterialTheme.colors.surface) - .padding(vertical = DEFAULT_PADDING), + .background(if (appPlatform.isAndroid) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, alpha = 1 - userPickerAlpha()) else MaterialTheme.colors.surface) + .padding(bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), pickerState = userPickerState ) { + val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> + { + ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } + } + } + val stopped = remember { chatModel.chatRunning }.value == false + val onUserClicked: (user: User) -> Unit = { user -> + if (!user.activeUser) { + userPickerState.value = AnimatedViewState.HIDING + withBGApi { + controller.showProgressIfNeeded { + ModalManager.closeAllModalsEverywhere() + chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) + } + } + } else { + showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }() + withBGApi { + closePicker(userPickerState) + } + } + } + @Composable fun FirstSection() { if (remoteHosts.isNotEmpty()) { @@ -170,87 +192,24 @@ fun UserPicker( } ) } - ActiveUserSection( - chatModel = chatModel, - userPickerState = userPickerState, - ) + val currentUser = remember { chatModel.currentUser }.value + if (appPlatform.isAndroid) { + Column(modifier = Modifier.padding(top = USER_PICKER_SECTION_SPACING, bottom = USER_PICKER_SECTION_SPACING - DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL - 3.dp)) { + UserPickerUsersSection( + users = users, + onUserClicked = onUserClicked, + stopped = stopped + ) + } + } else if (currentUser != null) { + SectionItemView({ onUserClicked(currentUser) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { + ProfilePreview(currentUser.profile, stopped = stopped) + } + } } @Composable fun SecondSection() { - GlobalSettingsSection( - chatModel = chatModel, - userPickerState = userPickerState, - setPerformLA = setPerformLA, - onUserClicked = { user -> - userPickerState.value = AnimatedViewState.HIDING - if (!user.activeUser) { - withBGApi { - controller.showProgressIfNeeded { - ModalManager.closeAllModalsEverywhere() - chatModel.controller.changeActiveUser(user.remoteHostId, user.userId, null) - } - } - } - }, - onShowAllProfilesClicked = { - doWithAuth( - generalGetString(MR.strings.auth_open_chat_profiles), - generalGetString(MR.strings.auth_log_in_using_credential) - ) { - ModalManager.start.showCustomModal { close -> - val search = rememberSaveable { mutableStateOf("") } - val profileHidden = rememberSaveable { mutableStateOf(false) } - ModalView( - { close() }, - endButtons = { - SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } - }, - content = { UserProfilesView(chatModel, search, profileHidden) }) - } - } - } - ) - } - - if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { - Column { - FirstSection() - Divider(Modifier.padding(DEFAULT_PADDING)) - SecondSection() - } - } else { - Row { - Box(Modifier.weight(1f)) { - FirstSection() - } - VerticalDivider() - Box(Modifier.weight(1f)) { - SecondSection() - } - } - } - } -} - -@Composable -private fun ActiveUserSection( - chatModel: ChatModel, - userPickerState: MutableStateFlow, -) { - val showCustomModal: (@Composable() (ModalData.(ChatModel, () -> Unit) -> Unit)) -> () -> Unit = { modalView -> - { - ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } - } - } - val currentUser = remember { chatModel.currentUser }.value - val stopped = chatModel.chatRunning.value == false - - if (currentUser != null) { - SectionView { - SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) { - ProfilePreview(currentUser.profile, stopped = stopped) - } UserPickerOptionRow( painterResource(MR.images.ic_qr_code), if (chatModel.userAddress.value != null) generalGetString(MR.strings.your_simplex_contact_address) else generalGetString(MR.strings.create_simplex_address), @@ -266,9 +225,22 @@ private fun ActiveUserSection( }), disabled = stopped ) - } - } else { - SectionView { + if (appPlatform.isDesktop) { + Divider(Modifier.padding(DEFAULT_PADDING)) + + val inactiveUsers = users.filter { !it.user.activeUser } + + if (inactiveUsers.isNotEmpty()) { + Column(modifier = Modifier.padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)) { + UserPickerUsersSection( + users = inactiveUsers, + onUserClicked = onUserClicked, + stopped = stopped + ) + } + } + } + if (chatModel.desktopNoUserNoRemote) { UserPickerOptionRow( painterResource(MR.images.ic_manage_accounts), @@ -284,76 +256,121 @@ private fun ActiveUserSection( } } ) + } else { + UserPickerOptionRow( + painterResource(MR.images.ic_manage_accounts), + stringResource(MR.strings.your_chat_profiles), + { + doWithAuth( + generalGetString(MR.strings.auth_open_chat_profiles), + generalGetString(MR.strings.auth_log_in_using_credential) + ) { + ModalManager.start.showCustomModal { close -> + val search = rememberSaveable { mutableStateOf("") } + val profileHidden = rememberSaveable { mutableStateOf(false) } + ModalView( + { close() }, + endButtons = { + SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it } + }, + content = { UserProfilesView(chatModel, search, profileHidden) }) + } + } + }, + disabled = stopped + ) + } + } + + if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) { + Column { + FirstSection() + SecondSection() + GlobalSettingsSection( + userPickerState = userPickerState, + setPerformLA = setPerformLA, + ) + } + } else { + Column { + FirstSection() + Row { + Box(Modifier.weight(1f)) { + Column { + SecondSection() + } + } + VerticalDivider() + Box(Modifier.weight(1f)) { + Column { + GlobalSettingsSection( + userPickerState = userPickerState, + setPerformLA = setPerformLA, + ) + } + } + } } } } } +fun userPickerAlpha(): Float { + return when (CurrentColors.value.base) { + DefaultTheme.LIGHT -> 0.05f + DefaultTheme.DARK -> 0.05f + DefaultTheme.BLACK -> 0.075f + DefaultTheme.SIMPLEX -> 0.035f + } +} + @Composable private fun GlobalSettingsSection( - chatModel: ChatModel, userPickerState: MutableStateFlow, setPerformLA: (Boolean) -> Unit, - onUserClicked: (user: User) -> Unit, - onShowAllProfilesClicked: () -> Unit ) { - val stopped = chatModel.chatRunning.value == false - val users by remember { - derivedStateOf { - chatModel.users - .filter { u -> !u.user.hidden && !u.user.activeUser } - } - } + val stopped = remember { chatModel.chatRunning }.value == false - SectionView(headerBottomPadding = if (appPlatform.isDesktop || windowOrientation() == WindowOrientation.PORTRAIT) DEFAULT_PADDING else 0.dp) { - UserPickerInactiveUsersSection( - users = users, - onShowAllProfilesClicked = onShowAllProfilesClicked, - onUserClicked = onUserClicked, - stopped = stopped - ) + if (appPlatform.isAndroid) { + val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - if (appPlatform.isAndroid) { - val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - - UserPickerOptionRow( - painterResource(MR.images.ic_desktop), - text, - click = { - ModalManager.start.showCustomModal { close -> - ConnectDesktopView(close) - } - } - ) - } else { - UserPickerOptionRow( - icon = painterResource(MR.images.ic_smartphone_300), - text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), - click = { - userPickerState.value = AnimatedViewState.HIDING - ModalManager.start.showModal { - ConnectMobileView() - } - }, - disabled = stopped - ) - } - - SectionItemView( + UserPickerOptionRow( + painterResource(MR.images.ic_desktop), + text, click = { - ModalManager.start.showModalCloseable { close -> - SettingsView(chatModel, setPerformLA, close) + ModalManager.start.showCustomModal { close -> + ConnectDesktopView(close) + } + } + ) + } else { + UserPickerOptionRow( + icon = painterResource(MR.images.ic_smartphone_300), + text = stringResource(if (remember { chat.simplex.common.platform.chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), + click = { + userPickerState.value = AnimatedViewState.HIDING + ModalManager.start.showModal { + ConnectMobileView() } }, - padding = PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) - ) { - val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) - TextIconSpaced() - Text(text, color = Color.Unspecified) - Spacer(Modifier.weight(1f)) - ColorModeSwitcher() - } + disabled = stopped + ) + } + + SectionItemView( + click = { + ModalManager.start.showModalCloseable { close -> + SettingsView(chatModel, setPerformLA, close) + } + }, + padding = if (appPlatform.isDesktop) PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING + 2.dp) else PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF) + ) { + val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) + Icon(painterResource(MR.images.ic_settings), text, tint = MaterialTheme.colors.secondary) + TextIconSpaced() + Text(text, color = Color.Unspecified) + Spacer(Modifier.weight(1f)) + ColorModeSwitcher() } } @@ -361,7 +378,7 @@ private fun GlobalSettingsSection( fun UserProfilePickerItem( u: User, unreadCount: Int = 0, - enabled: Boolean = chatModel.chatRunning.value == true || chatModel.connectedToRemote, + enabled: Boolean = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote, onLongClick: () -> Unit = {}, openSettings: () -> Unit = {}, onClick: () -> Unit @@ -410,7 +427,7 @@ fun UserProfilePickerItem( } @Composable -fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == true || chatModel.connectedToRemote) { +fun UserProfileRow(u: User, enabled: Boolean = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote) { Row( Modifier .widthIn(max = windowWidth() * 0.7f) @@ -433,31 +450,13 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr @Composable fun UserPickerOptionRow(icon: Painter, text: String, click: (() -> Unit)? = null, disabled: Boolean = false) { - SectionItemView(click, disabled = disabled, extraPadding = true) { + SectionItemView(click, disabled = disabled, extraPadding = appPlatform.isDesktop) { Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.secondary) TextIconSpaced() Text(text = text, color = if (disabled) MaterialTheme.colors.secondary else Color.Unspecified) } } -@Composable -fun UserPickerInactiveUserBadge(userInfo: UserInfo, stopped: Boolean, size: Dp = 60.dp, onClick: (user: User) -> Unit) { - Box { - IconButton( - onClick = { onClick(userInfo.user) }, - enabled = !stopped - ) { - Box { - ProfileImage(size = size, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant) - - if (userInfo.unreadCount > 0) { - unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs) - } - } - } - } -} - @OptIn(ExperimentalLayoutApi::class) @Composable private fun DevicePickerRow( @@ -471,7 +470,7 @@ private fun DevicePickerRow( Modifier .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT) - .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), + .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING, top = DEFAULT_PADDING + DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -517,10 +516,9 @@ private fun DevicePickerRow( } @Composable -expect fun UserPickerInactiveUsersSection( +expect fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) @@ -606,14 +604,14 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) { } @Composable -private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { +fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) { Text( if (unreadCount > 0) unreadCountStr(unreadCount) else "", color = Color.White, fontSize = 10.sp, style = TextStyle(textAlign = TextAlign.Center), modifier = Modifier - .offset(y = 3.sp.toDp()) + .offset(y = if (hasPadding) 3.sp.toDp() else -4.sp.toDp(), x = if (hasPadding) 0.dp else 4.sp.toDp()) .background(if (userMuted) MaterialTheme.colors.primaryVariant else MaterialTheme.colors.secondary, shape = CircleShape) .badgeLayout() .padding(horizontal = 2.sp.toDp()) @@ -622,7 +620,6 @@ private fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean) { ) } - private suspend fun closePicker(userPickerState: MutableStateFlow) { delay(500) userPickerState.value = AnimatedViewState.HIDING diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt index 583d5437c3..d9b53b9485 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt @@ -1,31 +1,31 @@ package chat.simplex.common.views.chatlist import androidx.compose.animation.* -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.User import chat.simplex.common.model.UserInfo import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.res.MR -import dev.icerock.moko.resources.compose.painterResource -import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.MutableStateFlow @Composable -actual fun UserPickerInactiveUsersSection( +actual fun UserPickerUsersSection( users: List, stopped: Boolean, - onShowAllProfilesClicked: () -> Unit, onUserClicked: (user: User) -> Unit, ) { if (users.isNotEmpty()) { @@ -34,13 +34,13 @@ actual fun UserPickerInactiveUsersSection( val horizontalPadding = DEFAULT_PADDING_HALF + 8.dp Column(Modifier - .padding(horizontal = horizontalPadding, vertical = DEFAULT_PADDING_HALF) - .height(55.dp * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) + .padding(horizontal = horizontalPadding) + .height((55.dp + 16.sp.toDp()) * rowsToDisplay + (if (rowsToDisplay > 1) DEFAULT_PADDING else 0.dp)) ) { ColumnWithScrollBar( verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING) ) { - val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (55.dp * 5)) / 5 + val spaceBetween = (((DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) - (horizontalPadding)) - (65.dp * 5)) / 5 userRows.forEach { row -> Row( @@ -48,8 +48,31 @@ actual fun UserPickerInactiveUsersSection( horizontalArrangement = Arrangement.spacedBy(spaceBetween), ) { row.forEach { u -> - UserPickerInactiveUserBadge(u, stopped, size = 55.dp) { - onUserClicked(u.user) + Column(modifier = Modifier + .clickable ( + onClick = { onUserClicked(u.user) }, + enabled = !stopped + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val user = u.user + Box { + ProfileImage(size = 55.dp, image = user.profile.image, color = MaterialTheme.colors.secondaryVariant) + + if (u.unreadCount > 0 && !user.activeUser) { + unreadBadge(u.unreadCount, user.showNtfs, true) + } + } + + Text( + user.displayName, + fontSize = 12.sp, + fontWeight = if (user.activeUser) FontWeight.Bold else FontWeight.Normal, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.width(65.dp), + textAlign = TextAlign.Center + ) } } } @@ -57,12 +80,6 @@ actual fun UserPickerInactiveUsersSection( } } } - - UserPickerOptionRow( - painterResource(MR.images.ic_manage_accounts), - stringResource(MR.strings.your_chat_profiles), - onShowAllProfilesClicked - ) } @Composable @@ -83,4 +100,4 @@ actual fun PlatformUserPicker(modifier: Modifier, pickerState: MutableStateFlow< } } } -} \ No newline at end of file +} diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index f07eea9ec7..4507f940cb 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -27,7 +27,7 @@ kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 android.version_name=6.1-beta.1 -android.version_code=240 +android.version_code=242 desktop.version_name=6.1-beta.1 desktop.version_code=67 From 65c7ecbddffe7dbf725284591e34130f9d16c5a7 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Thu, 26 Sep 2024 13:45:12 +0100 Subject: [PATCH 097/704] core: 6.1.0.4 (simplexmq 6.1.0.1) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cabal.project b/cabal.project index d5e17ea299..3823a31790 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: 7dcac19a671f76dedbe769030742714783946bd3 + tag: 03168b9fbfd8a507a5374ff3375609e705225e29 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 4e4ba3a549..edeb47ce27 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.1.0.3 +version: 6.1.0.4 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 91e5e8866f..eaa7f0d129 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."7dcac19a671f76dedbe769030742714783946bd3" = "0c1jygir4c1s8g4hdz7b6vw69bvcrknbih9rq8y8rv3d8zl32qpq"; + "https://github.com/simplex-chat/simplexmq.git"."03168b9fbfd8a507a5374ff3375609e705225e29" = "0g6xry0far2wppfxv99hqh3ckldglmpazib5l8vsvcmivcz5zww1"; "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 a6c96ecc91..2386128bb6 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.3 +version: 6.1.0.4 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 0d39951b90..ff77ac0546 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -73,11 +73,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [6, 1, 0, 2] +minRemoteCtrlVersion = AppVersion [6, 1, 0, 4] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 1, 0, 2] +minRemoteHostVersion = AppVersion [6, 1, 0, 4] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From 4a39b481b148f04177510bbf2d20e1e4f5fe6a0f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 26 Sep 2024 17:28:14 +0100 Subject: [PATCH 098/704] ios: avoid message changing width when sent/received ticks appear (#4945) --- .../Shared/Views/Chat/ChatItem/CIMetaView.swift | 13 +++++++++---- apps/ios/SimpleXChat/ChatTypes.swift | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift index 9840b22fc8..32249506d3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMetaView.swift @@ -75,9 +75,14 @@ enum MetaColorMode { } } - var statusSpacer: Text { + func statusSpacer(_ sent: Bool) -> Text { switch self { - case .normal, .transparent: Text(Image(systemName: "circlebadge.fill")).foregroundColor(.clear) + case .normal, .transparent: + Text( + sent + ? Image("checkmark.wide") + : Image(systemName: "circlebadge.fill") + ).foregroundColor(.clear) case .invertedMaterial: Text(" ").kerning(13) } } @@ -130,10 +135,10 @@ func ciMetaText( colorMode.resolve(statusColor) } r = r + colored(Text(image), metaColor) - space = Text(" ") } else if !meta.disappearing { - space = colorMode.statusSpacer + Text(" ") + r = r + colorMode.statusSpacer(meta.itemStatus.sent) } + space = Text(" ") } if let enc = encrypted { appendSpace() diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 0777503650..6e687538c0 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2820,6 +2820,20 @@ public enum CIStatus: Decodable, Hashable { case .invalid: return "invalid" } } + + public var sent: Bool { + switch self { + case .sndNew: true + case .sndSent: true + case .sndRcvd: true + case .sndErrorAuth: true + case .sndError: true + case .sndWarning: true + case .rcvNew: false + case .rcvRead: false + case .invalid: false + } + } public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? { switch self { From 95c1d8d7982db9fe9d5d061745a18d0ea64ff754 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 27 Sep 2024 02:18:05 +0700 Subject: [PATCH 099/704] android, desktop: calls switching from audio to video and back (#4814) * android, desktop: calls switching from audio to video and back * refactor * working all 4 streams with mute handling differently * changes * changes * wrong file * changes * padding * android camera service type * icons, sizes, clickable * refactor * Revert "android camera service type" This reverts commit 9878ff38e9ef2a18e81dcdaaed24e65d88abf934. * late init camera permissions * enabling camera sooner than call establishes (not fully done) * changes * alpha * fixes for Safari * enhancements * fix Safari sound * padding between buttons on desktop * android default values for padding * changes * calls without encryption are supported and flipping camera on some devices works * unused param * logs * background color * play local video in Safari * no line height * removed one listener from per frame processing * enhancements --------- Co-authored-by: Evgeny Poberezkin --- .../main/java/chat/simplex/app/CallService.kt | 5 +- .../simplex/app/views/call/CallActivity.kt | 39 +- .../views/call/CallAudioDeviceManager.kt | 6 +- .../common/views/call/CallView.android.kt | 249 ++--- .../views/chatlist/ChatListView.android.kt | 3 +- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../simplex/common/views/call/CallManager.kt | 2 +- .../chat/simplex/common/views/call/WebRTC.kt | 41 +- .../simplex/common/views/chat/ChatView.kt | 2 +- .../helpers/ExposedDropDownSettingRow.kt | 28 +- .../commonMain/resources/MR/base/strings.xml | 1 + .../resources/MR/images/ic_volume_off.svg | 1 + .../resources/assets/www/android/call.html | 19 + .../resources/assets/www/android/style.css | 54 ++ .../commonMain/resources/assets/www/call.js | 849 ++++++++++++++--- .../resources/assets/www/desktop/call.html | 31 +- .../www/desktop/images/ic_call_end_filled.svg | 2 +- .../www/desktop/images/ic_volume_off.svg | 1 + .../resources/assets/www/desktop/style.css | 75 +- .../resources/assets/www/desktop/ui.js | 170 +++- .../common/views/call/CallView.desktop.kt | 26 +- .../views/chatlist/ChatListView.desktop.kt | 3 +- .../simplex-chat-webrtc/src/android/call.html | 19 + .../simplex-chat-webrtc/src/android/style.css | 54 ++ packages/simplex-chat-webrtc/src/call.ts | 900 +++++++++++++++--- .../simplex-chat-webrtc/src/desktop/call.html | 31 +- .../src/desktop/images/ic_call_end_filled.svg | 2 +- .../src/desktop/images/ic_volume_off.svg | 1 + .../simplex-chat-webrtc/src/desktop/style.css | 75 +- .../simplex-chat-webrtc/src/desktop/ui.ts | 166 +++- 30 files changed, 2319 insertions(+), 538 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/images/ic_volume_off.svg create mode 100644 packages/simplex-chat-webrtc/src/desktop/images/ic_volume_off.svg diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt index 3b334bf70b..6c3d96bebc 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt @@ -2,6 +2,7 @@ package chat.simplex.app import android.app.* import android.content.* +import android.content.pm.PackageManager import android.content.pm.ServiceInfo import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -83,7 +84,7 @@ class CallService: Service() { generalGetString(MR.strings.notification_preview_somebody) else call?.contact?.profile?.displayName ?: "" - val text = generalGetString(if (call?.supportsVideo() == true) MR.strings.call_service_notification_video_call else MR.strings.call_service_notification_audio_call) + val text = generalGetString(if (call?.hasVideo == true) MR.strings.call_service_notification_video_call else MR.strings.call_service_notification_audio_call) val image = call?.contact?.image val largeIcon = if (image == null || previewMode == NotificationPreviewMode.HIDDEN.name) BitmapFactory.decodeResource(resources, R.drawable.icon) @@ -105,7 +106,7 @@ class CallService: Service() { 0 } } else if (Build.VERSION.SDK_INT >= 30) { - if (call.supportsVideo()) { + if (call.hasVideo && ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA } else { ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index a9697069c0..e7503733ac 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -116,7 +116,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { private fun hasGrantedPermissions(): Boolean { val grantedAudio = ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED - val grantedCamera = !callSupportsVideo() || ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED + val grantedCamera = !callHasVideo() || ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED return grantedAudio && grantedCamera } @@ -124,7 +124,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { override fun onBackPressed() { if (isOnLockScreenNow()) { super.onBackPressed() - } else if (!hasGrantedPermissions() && !callSupportsVideo()) { + } else if (!hasGrantedPermissions() && !callHasVideo()) { val call = m.activeCall.value if (call != null) { withBGApi { chatModel.callManager.endCall(call) } @@ -142,7 +142,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { override fun onUserLeaveHint() { super.onUserLeaveHint() // On Android 12+ PiP is enabled automatically when a user hides the app - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callHasVideo() && platform.androidPictureInPictureAllowed()) { enterPictureInPictureMode() } } @@ -198,7 +198,7 @@ class CallActivity: ComponentActivity(), ServiceConnection { fun getKeyguardManager(context: Context): KeyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager -private fun callSupportsVideo() = m.activeCall.value?.supportsVideo() == true || m.activeCallInvitation.value?.callType?.media == CallMediaType.Video +private fun callHasVideo() = m.activeCall.value?.hasVideo == true || m.activeCallInvitation.value?.callType?.media == CallMediaType.Video @Composable fun CallActivityView() { @@ -212,7 +212,7 @@ fun CallActivityView() { .collect { collapsed -> when { collapsed -> { - if (!platform.androidPictureInPictureAllowed() || !callSupportsVideo()) { + if (!platform.androidPictureInPictureAllowed() || !callHasVideo()) { activity.moveTaskToBack(true) activity.startActivity(Intent(activity, MainActivity::class.java)) } else if (!activity.isInPictureInPictureMode && activity.lifecycle.currentState == Lifecycle.State.RESUMED) { @@ -221,7 +221,7 @@ fun CallActivityView() { activity.enterPictureInPictureMode() } } - callSupportsVideo() && !platform.androidPictureInPictureAllowed() -> { + callHasVideo() && !platform.androidPictureInPictureAllowed() -> { // PiP disabled by user platform.androidStartCallActivity(false) } @@ -242,28 +242,43 @@ fun CallActivityView() { Box(Modifier.background(Color.Black)) { if (call != null) { val permissionsState = rememberMultiplePermissionsState( - permissions = if (callSupportsVideo()) { + permissions = if (callHasVideo()) { listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) } else { listOf(Manifest.permission.RECORD_AUDIO) } ) - if (permissionsState.allPermissionsGranted) { + // callState == connected is needed in a situation when a peer enabled camera in audio call while a user didn't grant camera permission yet, + // so no need to hide active call view in this case + if (permissionsState.allPermissionsGranted || call.callState == CallState.Connected) { ActiveCallView() LaunchedEffect(Unit) { activity.startServiceAndBind() } - } else { - CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) { + } + if ((!permissionsState.allPermissionsGranted && call.callState != CallState.Connected) || call.wantsToEnableCamera) { + CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callHasVideo() || call.wantsToEnableCamera) { withBGApi { chatModel.callManager.endCall(call) } } + val cameraAndMicPermissions = rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)) + DisposableEffect(cameraAndMicPermissions.allPermissionsGranted) { + onDispose { + if (call.wantsToEnableCamera && cameraAndMicPermissions.allPermissionsGranted) { + val activeCall = chatModel.activeCall.value + if (activeCall != null && activeCall.contact.apiId == call.contact.apiId) { + chatModel.activeCall.value = activeCall.copy(wantsToEnableCamera = false) + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Camera, enable = true)) + } + } + } + } } val view = LocalView.current - if (callSupportsVideo()) { + if (callHasVideo()) { val scope = rememberCoroutineScope() LaunchedEffect(Unit) { scope.launch { - activity.setPipParams(callSupportsVideo(), viewRatio = Rational(view.width, view.height)) + activity.setPipParams(callHasVideo(), viewRatio = Rational(view.width, view.height)) activity.trackPipAnimationHintView(view) } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt index bada85746f..ec0fd9fea8 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallAudioDeviceManager.kt @@ -47,7 +47,7 @@ class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface { Log.d(TAG, "Added audio devices2: ${devices.value.map { it.type }}") if (devices.value.size - oldDevices.size > 0) { - selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, false) + selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, false) } } @@ -116,14 +116,14 @@ class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface { Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}") super.onAudioDevicesAdded(addedDevices) devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired() - selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, false) + selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, false) } override fun onAudioDevicesRemoved(removedDevices: Array) { Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}") super.onAudioDevicesRemoved(removedDevices) devices.value = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).filter { it.hasSupportedType() }.excludeSameType().excludeEarpieceIfWired() - selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, true) + selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.hasVideo == true, true) } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 22f0c8d70b..e7fd11f5ac 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -7,6 +7,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.* import android.content.pm.ActivityInfo +import android.content.pm.PackageManager import android.media.* import android.os.Build import android.os.PowerManager @@ -16,9 +17,12 @@ import android.view.ViewGroup import android.webkit.* import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshots.SnapshotStateList @@ -27,12 +31,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat import androidx.lifecycle.* import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewClientCompat @@ -119,26 +124,26 @@ actual fun ActiveCallView() { val callRh = call.remoteHostId when (val r = apiMsg.resp) { is WCallResponse.Capabilities -> withBGApi { - val callType = CallType(call.localMedia, r.capabilities) + val callType = CallType(call.initialCallType, r.capabilities) chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType) updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Starting is delayed to make Android <= 11 working good with Bluetooth callAudioDeviceManager.start() } else { - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.supportsVideo(), true) + callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } CallSoundsPlayer.startConnectingCallSound(scope) activeCallWaitDeliveryReceipt(scope) } is WCallResponse.Offer -> withBGApi { - chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) + chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.initialCallType, r.capabilities) updateActiveCall(call) { it.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // Starting is delayed to make Android <= 11 working good with Bluetooth callAudioDeviceManager.start() } else { - callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.supportsVideo(), true) + callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) } } is WCallResponse.Answer -> withBGApi { @@ -162,6 +167,17 @@ actual fun ActiveCallView() { is WCallResponse.Connected -> { updateActiveCall(call) { it.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo) } } + is WCallResponse.PeerMedia -> { + updateActiveCall(call) { + val sources = it.peerMediaSources + when (r.source) { + CallMediaSource.Mic -> it.copy(peerMediaSources = sources.copy(mic = r.enabled)) + CallMediaSource.Camera -> it.copy(peerMediaSources = sources.copy(camera = r.enabled)) + CallMediaSource.ScreenAudio -> it.copy(peerMediaSources = sources.copy(screenAudio = r.enabled)) + CallMediaSource.ScreenVideo -> it.copy(peerMediaSources = sources.copy(screenVideo = r.enabled)) + } + } + } is WCallResponse.End -> { withBGApi { chatModel.callManager.endCall(call) } } @@ -174,16 +190,19 @@ actual fun ActiveCallView() { updateActiveCall(call) { it.copy(callState = CallState.Negotiated) } is WCallCommand.Media -> { updateActiveCall(call) { - when (cmd.media) { - CallMediaType.Video -> it.copy(videoEnabled = cmd.enable) - CallMediaType.Audio -> it.copy(audioEnabled = cmd.enable) + val sources = it.localMediaSources + when (cmd.source) { + CallMediaSource.Mic -> it.copy(localMediaSources = sources.copy(mic = cmd.enable)) + CallMediaSource.Camera -> it.copy(localMediaSources = sources.copy(camera = cmd.enable)) + CallMediaSource.ScreenAudio -> it.copy(localMediaSources = sources.copy(screenAudio = cmd.enable)) + CallMediaSource.ScreenVideo -> it.copy(localMediaSources = sources.copy(screenVideo = cmd.enable)) } } } is WCallCommand.Camera -> { updateActiveCall(call) { it.copy(localCamera = cmd.camera) } - if (!call.audioEnabled) { - chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = false)) + if (!call.localMediaSources.mic) { + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = false)) } } is WCallCommand.End -> { @@ -200,7 +219,6 @@ actual fun ActiveCallView() { val showOverlay = when { call == null -> false !platform.androidPictureInPictureAllowed() -> true - !call.supportsVideo() -> true !chatModel.activeCallViewIsCollapsed.value -> true else -> false } @@ -208,6 +226,11 @@ actual fun ActiveCallView() { ActiveCallOverlay(call, chatModel, callAudioDeviceManager) } } + KeyChangeEffect(call?.hasVideo) { + if (call != null) { + callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.hasVideo, true) + } + } val context = LocalContext.current DisposableEffect(Unit) { val activity = context as? Activity ?: return@DisposableEffect onDispose {} @@ -237,9 +260,15 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, callAudioDeviceM devices = remember { callAudioDeviceManager.devices }.value, currentDevice = remember { callAudioDeviceManager.currentDevice }, dismiss = { withBGApi { chatModel.callManager.endCall(call) } }, - toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = !call.audioEnabled)) }, + toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = !call.localMediaSources.mic)) }, selectDevice = { callAudioDeviceManager.selectDevice(it.id) }, - toggleVideo = { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Video, enable = !call.videoEnabled)) }, + toggleVideo = { + if (ContextCompat.checkSelfPermission(androidAppContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Camera, enable = !call.localMediaSources.camera)) + } else { + updateActiveCall(call) { it.copy(wantsToEnableCamera = true) } + } + }, toggleSound = { val enableSpeaker = callAudioDeviceManager.currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE val preferredInternalDevice = callAudioDeviceManager.devices.value.firstOrNull { it.type == if (enableSpeaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE } @@ -293,30 +322,30 @@ private fun ActiveCallOverlayLayout( flipCamera: () -> Unit ) { Column { - val media = call.peerMedia ?: call.localMedia CloseSheetBar({ chatModel.activeCallViewIsCollapsed.value = true }, true, tintColor = Color(0xFFFFFFD8)) { - if (media == CallMediaType.Video) { + if (call.hasVideo) { Text(call.contact.chatViewName, Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1) } } Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { @Composable - fun SelectSoundDevice() { + fun SelectSoundDevice(size: Dp) { if (devices.size == 2 && devices.all { it.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE || it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } || currentDevice.value == null || devices.none { it.id == currentDevice.value?.id } ) { val isSpeaker = currentDevice.value?.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER - ToggleSoundButton(call, enabled, isSpeaker, toggleSound) + ToggleSoundButton(enabled, isSpeaker, !call.peerMediaSources.mic, toggleSound, size = size) } else { ExposedDropDownSettingWithIcon( - devices.map { Triple(it, it.icon, if (it.name != null) generalGetString(it.name!!) else it.productName.toString()) }, + devices.map { Triple(it, if (call.peerMediaSources.mic) it.icon else MR.images.ic_volume_off, if (it.name != null) generalGetString(it.name!!) else it.productName.toString()) }, currentDevice, fontSize = 18.sp, - iconSize = 40.dp, + boxSize = size, listIconSize = 30.dp, iconColor = Color(0xFFFFFFD8), + background = controlButtonsBackground(), minWidth = 300.dp, onSelected = { if (it != null) { @@ -327,29 +356,9 @@ private fun ActiveCallOverlayLayout( } } - when (media) { - CallMediaType.Video -> { - VideoCallInfoView(call) - Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { - DisabledBackgroundCallsButton() - } - Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - ToggleAudioButton(call, enabled, toggleAudio) - SelectSoundDevice() - IconButton(onClick = dismiss, enabled = enabled) { - Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp)) - } - if (call.videoEnabled) { - ControlButton(call, painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, enabled, flipCamera) - ControlButton(call, painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, enabled, toggleVideo) - } else { - Spacer(Modifier.size(48.dp)) - ControlButton(call, painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, enabled, toggleVideo) - } - } - } - - CallMediaType.Audio -> { + when (call.hasVideo) { + true -> VideoCallInfoView(call) + false -> { Spacer(Modifier.fillMaxHeight().weight(1f)) Column( Modifier.fillMaxWidth(), @@ -359,23 +368,26 @@ private fun ActiveCallOverlayLayout( ProfileImage(size = 192.dp, image = call.contact.profile.image) AudioCallInfoView(call) } - Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { - DisabledBackgroundCallsButton() - } - Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - IconButton(onClick = dismiss, enabled = enabled) { - Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp)) - } - } - Box(Modifier.padding(start = 32.dp)) { - ToggleAudioButton(call, enabled, toggleAudio) - } - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { - Box(Modifier.padding(end = 32.dp)) { - SelectSoundDevice() - } - } + } + } + Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { + DisabledBackgroundCallsButton() + } + + BoxWithConstraints(Modifier.padding(start = 6.dp, end = 6.dp, bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) { + val size = ((maxWidth - DEFAULT_PADDING_HALF * 4) / 5).coerceIn(0.dp, 60.dp) + // limiting max width for tablets/wide screens, will be displayed in the center + val padding = ((min(420.dp, maxWidth) - size * 5) / 4).coerceAtLeast(0.dp) + Row(horizontalArrangement = Arrangement.spacedBy(padding), verticalAlignment = Alignment.CenterVertically) { + ToggleMicButton(call, enabled, toggleAudio, size = size) + SelectSoundDevice(size = size) + ControlButton(painterResource(MR.images.ic_call_end_filled), MR.strings.icon_descr_hang_up, enabled = enabled, dismiss, background = Color.Red, size = size, iconPaddingPercent = 0.166f) + if (call.localMediaSources.camera) { + ControlButton(painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, enabled, flipCamera, size = size) + ControlButton(painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, enabled, toggleVideo, size = size) + } else { + Spacer(Modifier.size(size)) + ControlButton(painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, enabled, toggleVideo, size = size) } } } @@ -384,34 +396,52 @@ private fun ActiveCallOverlayLayout( } @Composable -private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, enabled: Boolean = true, action: () -> Unit) { - if (call.hasMedia) { - IconButton(onClick = action, enabled = enabled) { - Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp)) - } - } else { - Spacer(Modifier.size(40.dp)) +private fun ControlButton(icon: Painter, iconText: StringResource, enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, iconPaddingPercent: Float = 0.2f) { + ControlButtonWrap(enabled, action, background, size) { + Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.padding(size * iconPaddingPercent).fillMaxSize()) } } @Composable -private fun ToggleAudioButton(call: Call, enabled: Boolean = true, toggleAudio: () -> Unit) { - if (call.audioEnabled) { - ControlButton(call, painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, enabled, toggleAudio) - } else { - ControlButton(call, painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, enabled, toggleAudio) +private fun ControlButtonWrap(enabled: Boolean = true, action: () -> Unit, background: Color = controlButtonsBackground(), size: Dp, content: @Composable () -> Unit) { + Box( + Modifier + .background(background, CircleShape) + .size(size) + .clickable( + onClick = action, + role = Role.Button, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false, radius = size / 2, color = background.lighter(0.1f)), + enabled = enabled + ), + contentAlignment = Alignment.Center + ) { + content() } } @Composable -private fun ToggleSoundButton(call: Call, enabled: Boolean, speaker: Boolean, toggleSound: () -> Unit) { - if (speaker) { - ControlButton(call, painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, enabled, toggleSound) +private fun ToggleMicButton(call: Call, enabled: Boolean = true, toggleAudio: () -> Unit, size: Dp) { + if (call.localMediaSources.mic) { + ControlButton(painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, enabled, toggleAudio, size = size) } else { - ControlButton(call, painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, enabled, toggleSound) + ControlButton(painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, enabled, toggleAudio, size = size) } } +@Composable +private fun ToggleSoundButton(enabled: Boolean, speaker: Boolean, muted: Boolean, toggleSound: () -> Unit, size: Dp) { + when { + muted -> ControlButton(painterResource(MR.images.ic_volume_off), MR.strings.icon_descr_sound_muted, enabled, toggleSound, size = size) + speaker -> ControlButton(painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, enabled, toggleSound, size = size) + else -> ControlButton(painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, enabled, toggleSound, size = size) + } +} + +@Composable +fun controlButtonsBackground(): Color = if (chatModel.activeCall.value?.peerMediaSources?.hasVideo == true) Color.Black.copy(0.2f) else Color.White.copy(0.2f) + @Composable fun AudioCallInfoView(call: Call) { Column(horizontalAlignment = Alignment.CenterHorizontally) { @@ -553,38 +583,39 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni } } } else { - ColumnWithScrollBar(Modifier.fillMaxSize()) { - Spacer(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier)) - - AppBarTitle(stringResource(MR.strings.permissions_required)) - Spacer(Modifier.weight(1f)) - - val onClick = { - if (permissionsState.shouldShowRationale) { - context.showAllowPermissionInSettingsAlert() - } else { - permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert) + ModalView(background = Color.Black, showClose = false, close = {}) { + ColumnWithScrollBar(Modifier.fillMaxSize()) { + AppBarTitle(stringResource(MR.strings.permissions_required)) + Spacer(Modifier.weight(1f)) + val onClick = { + if (permissionsState.shouldShowRationale) { + context.showAllowPermissionInSettingsAlert() + } else { + permissionsState.launchMultiplePermissionRequestWithFallback(buttonEnabled, context::showAllowPermissionInSettingsAlert) + } } - } - Text(stringResource(MR.strings.permissions_grant), Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), textAlign = TextAlign.Center, color = Color(0xFFFFFFD8)) - SectionSpacer() - SectionView { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - val text = if (hasVideo && audioPermission.status is PermissionStatus.Denied && cameraPermission.status is PermissionStatus.Denied) { - stringResource(MR.strings.permissions_camera_and_record_audio) - } else if (audioPermission.status is PermissionStatus.Denied) { - stringResource(MR.strings.permissions_record_audio) - } else if (hasVideo && cameraPermission.status is PermissionStatus.Denied) { - stringResource(MR.strings.permissions_camera) - } else "" - GrantPermissionButton(text, buttonEnabled.value, onClick) + Text(stringResource(MR.strings.permissions_grant), Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), textAlign = TextAlign.Center, color = Color(0xFFFFFFD8)) + SectionSpacer() + SectionView { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + val text = if (hasVideo && audioPermission.status is PermissionStatus.Denied && cameraPermission.status is PermissionStatus.Denied) { + stringResource(MR.strings.permissions_camera_and_record_audio) + } else if (audioPermission.status is PermissionStatus.Denied) { + stringResource(MR.strings.permissions_record_audio) + } else if (hasVideo && cameraPermission.status is PermissionStatus.Denied) { + stringResource(MR.strings.permissions_camera) + } else null + if (text != null) { + GrantPermissionButton(text, buttonEnabled.value, onClick) + } + } } - } - Spacer(Modifier.weight(1f)) - Box(Modifier.fillMaxWidth().padding(bottom = if (hasVideo) 0.dp else DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.Center) { - SimpleButtonFrame(cancel, Modifier.height(64.dp)) { - Text(stringResource(MR.strings.call_service_notification_end_call), fontSize = 20.sp, color = Color(0xFFFFFFD8)) + Spacer(Modifier.weight(1f)) + Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) { + SimpleButtonFrame(cancel, Modifier.height(60.dp)) { + Text(stringResource(MR.strings.call_service_notification_end_call), fontSize = 20.sp, color = Color(0xFFFFFFD8)) + } } } } @@ -768,8 +799,8 @@ fun PreviewActiveCallOverlayVideo() { userProfile = Profile.sampleData, contact = Contact.sampleData, callState = CallState.Negotiated, - localMedia = CallMediaType.Video, - peerMedia = CallMediaType.Video, + initialCallType = CallMediaType.Video, + peerMediaSources = CallMediaSources(), callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "tcp"), @@ -798,8 +829,8 @@ fun PreviewActiveCallOverlayAudio() { userProfile = Profile.sampleData, contact = Contact.sampleData, callState = CallState.Negotiated, - localMedia = CallMediaType.Audio, - peerMedia = CallMediaType.Audio, + initialCallType = CallMediaType.Audio, + peerMediaSources = CallMediaSources(), callUUID = "", connectionInfo = ConnectionInfo( RTCIceCandidate(RTCIceCandidateType.Host, "udp"), diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt index 3283593e09..e0fd81f7b6 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt @@ -54,8 +54,7 @@ actual fun ActiveCallInteractiveArea(call: Call) { .align(Alignment.BottomCenter), contentAlignment = Alignment.Center ) { - val media = call.peerMedia ?: call.localMedia - if (media == CallMediaType.Video) { + if (call.hasVideo) { Icon(painterResource(MR.images.ic_videocam_filled), null, Modifier.size(27.dp).offset(x = 2.5.dp, y = 2.dp), tint = Color.White) } else { Icon(painterResource(MR.images.ic_call_filled), null, Modifier.size(27.dp).offset(x = -0.5.dp, y = 2.dp), tint = Color.White) 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 7bf97a6e37..09b0ececb0 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 @@ -2525,7 +2525,7 @@ object ChatController { // TODO askConfirmation? // TODO check encryption is compatible withCall(r, r.contact) { call -> - chatModel.activeCall.value = call.copy(callState = CallState.OfferReceived, peerMedia = r.callType.media, sharedKey = r.sharedKey) + chatModel.activeCall.value = call.copy(callState = CallState.OfferReceived, sharedKey = r.sharedKey) val useRelay = appPrefs.webrtcPolicyRelay.get() val iceServers = getIceServers() Log.d(TAG, ".callOffer iceServers $iceServers") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 7704509148..405094f72a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -49,7 +49,7 @@ class CallManager(val chatModel: ChatModel) { contact = invitation.contact, callUUID = invitation.callUUID, callState = CallState.InvitationAccepted, - localMedia = invitation.callType.media, + initialCallType = invitation.callType.media, sharedKey = invitation.sharedKey, ) showCallView.value = true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index 5332bc650e..f723306456 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.call import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.model.* +import chat.simplex.common.platform.appPlatform import chat.simplex.res.MR import kotlinx.datetime.Instant import kotlinx.serialization.SerialName @@ -15,18 +16,21 @@ data class Call( val contact: Contact, val callUUID: String?, val callState: CallState, - val localMedia: CallMediaType, + val initialCallType: CallMediaType, + val localMediaSources: CallMediaSources = CallMediaSources(mic = true, camera = initialCallType == CallMediaType.Video && appPlatform.isAndroid), val localCapabilities: CallCapabilities? = null, - val peerMedia: CallMediaType? = null, + val peerMediaSources: CallMediaSources = CallMediaSources(), val sharedKey: String? = null, - val audioEnabled: Boolean = true, - val videoEnabled: Boolean = localMedia == CallMediaType.Video, var localCamera: VideoCamera = VideoCamera.User, val connectionInfo: ConnectionInfo? = null, var connectedAt: Instant? = null, + + // When a user has audio call, and then he wants to enable camera but didn't grant permissions for using camera yet, + // we show permissions view without enabling camera before permissions are granted. After they are granted, enabling camera + val wantsToEnableCamera: Boolean = false ) { val encrypted: Boolean get() = localEncrypted && sharedKey != null - val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false + private val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false val encryptionStatus: String get() = when(callState) { CallState.WaitCapabilities -> "" @@ -35,10 +39,8 @@ data class Call( else -> generalGetString(if (!localEncrypted) MR.strings.status_no_e2e_encryption else if (sharedKey == null) MR.strings.status_contact_has_no_e2e_encryption else MR.strings.status_e2e_encrypted) } - val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected - - fun supportsVideo(): Boolean = peerMedia == CallMediaType.Video || localMedia == CallMediaType.Video - + val hasVideo: Boolean + get() = localMediaSources.hasVideo || peerMediaSources.hasVideo } enum class CallState { @@ -68,6 +70,16 @@ enum class CallState { @Serializable data class WVAPICall(val corrId: Int? = null, val command: WCallCommand) @Serializable data class WVAPIMessage(val corrId: Int? = null, val resp: WCallResponse, val command: WCallCommand? = null) +@Serializable data class CallMediaSources( + val mic: Boolean = false, + val camera: Boolean = false, + val screenAudio: Boolean = false, + val screenVideo: Boolean = false +) { + val hasVideo: Boolean + get() = camera || screenVideo +} + @Serializable sealed class WCallCommand { @Serializable @SerialName("capabilities") data class Capabilities(val media: CallMediaType): WCallCommand() @@ -75,7 +87,7 @@ sealed class WCallCommand { @Serializable @SerialName("offer") data class Offer(val offer: String, val iceCandidates: String, val media: CallMediaType, val aesKey: String? = null, val iceServers: List? = null, val relay: Boolean? = null): WCallCommand() @Serializable @SerialName("answer") data class Answer (val answer: String, val iceCandidates: String): WCallCommand() @Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallCommand() - @Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand() + @Serializable @SerialName("media") data class Media(val source: CallMediaSource, val enable: Boolean): WCallCommand() @Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand() @Serializable @SerialName("description") data class Description(val state: String, val description: String): WCallCommand() @Serializable @SerialName("layout") data class Layout(val layout: LayoutType): WCallCommand() @@ -90,6 +102,7 @@ sealed class WCallResponse { @Serializable @SerialName("ice") data class Ice(val iceCandidates: String): WCallResponse() @Serializable @SerialName("connection") data class Connection(val state: ConnectionState): WCallResponse() @Serializable @SerialName("connected") data class Connected(val connectionInfo: ConnectionInfo): WCallResponse() + @Serializable @SerialName("peerMedia") data class PeerMedia(val source: CallMediaSource, val enabled: Boolean): WCallResponse() @Serializable @SerialName("end") object End: WCallResponse() @Serializable @SerialName("ended") object Ended: WCallResponse() @Serializable @SerialName("ok") object Ok: WCallResponse() @@ -165,6 +178,14 @@ enum class CallMediaType { @SerialName("audio") Audio } +@Serializable +enum class CallMediaSource { + @SerialName("mic") Mic, + @SerialName("camera") Camera, + @SerialName("screenAudio") ScreenAudio, + @SerialName("screenVideo") ScreenVideo +} + @Serializable enum class VideoCamera { @SerialName("user") User, 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 18deb48597..48387f4abb 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 @@ -576,7 +576,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) if (chatInfo is ChatInfo.Direct) { val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi - chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) + chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, initialCallType = media, userProfile = profile) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt index 4141fd2ead..8349841973 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt @@ -1,7 +1,12 @@ package chat.simplex.common.views.helpers +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple import dev.icerock.moko.resources.compose.painterResource import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -9,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.res.MR @@ -85,10 +91,12 @@ fun ExposedDropDownSettingWithIcon( values: List>, selection: State, fontSize: TextUnit = 16.sp, - iconSize: Dp = 40.dp, + iconPaddingPercent: Float = 0.2f, listIconSize: Dp = 30.dp, + boxSize: Dp = 60.dp, iconColor: Color = MenuTextColor, enabled: State = mutableStateOf(true), + background: Color, minWidth: Dp = 200.dp, onSelected: (T) -> Unit ) { @@ -99,13 +107,21 @@ fun ExposedDropDownSettingWithIcon( expanded.value = !expanded.value && enabled.value } ) { - Row( - Modifier.padding(start = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End + Box( + Modifier + .background(background, CircleShape) + .size(boxSize) + .clickable( + onClick = {}, + role = Role.Button, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(bounded = false, radius = boxSize / 2, color = background.lighter(0.1f)), + enabled = enabled.value + ), + contentAlignment = Alignment.Center ) { val choice = values.first { it.first == selection.value } - Icon(painterResource(choice.second), choice.third, Modifier.size(iconSize), tint = iconColor) + Icon(painterResource(choice.second), choice.third, Modifier.padding(boxSize * iconPaddingPercent).fillMaxSize(), tint = iconColor) } DefaultExposedDropdownMenu( modifier = Modifier.widthIn(min = minWidth), 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 08a16075a4..901a0565e1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1075,6 +1075,7 @@ Audio on Speaker off Speaker on + Sound muted Flip camera diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg new file mode 100644 index 0000000000..497864dd56 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_volume_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html index cbdf7a23a3..51815e2995 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html @@ -6,6 +6,15 @@ + + + + +