diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 53bb7db2d6..3ecfa70793 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -618,6 +618,16 @@ func apiUpdateProfile(profile: Profile) async throws -> Profile? { } } +func apiSetProfileAddress(on: Bool) async throws -> User? { + let userId = try currentUserId("apiSetProfileAddress") + let r = await chatSendCmd(.apiSetProfileAddress(userId: userId, on: on)) + switch r { + case .userProfileNoChange: return nil + case let .userProfileUpdated(user, _, _): return user + default: throw r + } +} + func apiSetContactPrefs(contactId: Int64, preferences: Preferences) async throws -> Contact? { let r = await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences)) if case let .contactPrefsUpdated(_, _, toContact) = r { return toContact } @@ -643,10 +653,10 @@ func apiCreateUserAddress() async throws -> String { throw r } -func apiDeleteUserAddress() async throws { +func apiDeleteUserAddress() async throws -> User? { let userId = try currentUserId("apiDeleteUserAddress") let r = await chatSendCmd(.apiDeleteMyAddress(userId: userId)) - if case .userContactLinkDeleted = r { return } + if case let .userContactLinkDeleted(user) = r { return user } throw r } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 5cab211e89..b72006b3fe 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -116,6 +116,21 @@ struct ChatInfoView: View { contactPreferencesButton() } + if let contactLink = contact.contactLink { + Section { + QRCode(uri: contactLink) + Button { + showShareSheet(items: [contactLink]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + } header: { + Text("Address") + } footer: { + Text("You can share this address with your contacts to let them connect with **\(contact.displayName)**.") + } + } + Section("Servers") { networkStatusRow() .onTapGesture { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index ed8dfd9221..efbb3c1320 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -45,8 +45,8 @@ struct GroupChatInfoView: View { Section { if groupInfo.canEdit { editGroupButton() - addOrEditWelcomeMessage() } + addOrEditWelcomeMessage() groupPreferencesButton($groupInfo) } header: { Text("") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 9f367fb472..dd13741141 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -65,6 +65,21 @@ struct GroupMemberInfoView: View { } } + if let contactLink = member.contactLink { + Section { + QRCode(uri: contactLink) + Button { + showShareSheet(items: [contactLink]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + } header: { + Text("Address") + } footer: { + Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") + } + } + Section("Member") { infoRow("Group", groupInfo.displayName) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index 7aa372d5dc..7b90a4abe7 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -15,43 +15,101 @@ struct GroupWelcomeView: View { var groupId: Int64 @Binding var groupInfo: GroupInfo @State private var welcomeText: String = "" + @State private var editMode = true @FocusState private var keyboardVisible: Bool @State private var showSaveDialog = false var body: some View { - List { - Section { - TextEditor(text: $welcomeText) - .focused($keyboardVisible) - .padding(.horizontal, -5) - .padding(.top, -8) - .frame(height: 90, alignment: .topLeading) - .frame(maxWidth: .infinity, alignment: .leading) - } - Section { - saveButton() + VStack { + if groupInfo.canEdit { + editorView() + .modifier(BackButton { + if welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil) { + dismiss() + } else { + showSaveDialog = true + } + }) + .confirmationDialog("Save welcome message?", isPresented: $showSaveDialog) { + Button("Save and update group profile") { + save() + dismiss() + } + Button("Exit without saving") { dismiss() } + } + } else { + List { + Section { + textPreview() + copyButton() + } + } } } .onAppear { welcomeText = groupInfo.groupProfile.description ?? "" - } - .modifier(BackButton { - if welcomeText == groupInfo.groupProfile.description || (welcomeText == "" && groupInfo.groupProfile.description == nil) { - dismiss() - } else { - showSaveDialog = true - } - }) - .confirmationDialog("Save welcome message?", isPresented: $showSaveDialog) { - Button("Save and update group profile") { - save() - dismiss() - } - Button("Exit without saving") { dismiss() } + keyboardVisible = true } } - @ViewBuilder private func saveButton() -> some View { + private func textPreview() -> some View { + messageText(welcomeText, parseSimpleXMarkdown(welcomeText), nil) + .allowsHitTesting(false) + } + + private func editorView() -> some View { + List { + Section { + if editMode { + ZStack { + Group { + if welcomeText.isEmpty { + TextEditor(text: Binding.constant(NSLocalizedString("Enter welcome message…", comment: "placeholder"))) + .foregroundColor(.secondary) + .disabled(true) + } + TextEditor(text: $welcomeText) + .focused($keyboardVisible) + } + .padding(.horizontal, -5) + .padding(.top, -8) + .frame(height: 90, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } else { + textPreview() + .frame(height: 90, alignment: .topLeading) + } + + Button { + editMode = !editMode + keyboardVisible = editMode + } label: { + if editMode { + Label ("Preview", systemImage: "character") + } else { + Label ("Edit", systemImage: "pencil") + } + } + .disabled(welcomeText.isEmpty) + copyButton() + } + + Section { + saveButton() + } + } + } + + private func copyButton() -> some View { + Button { + UIPasteboard.general.string = welcomeText + } label: { + Label ("Copy", systemImage: "doc.on.doc") + } + } + + private func saveButton() -> some View { Button("Save and update group profile") { save() } diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 72be57b3f7..7d850719d6 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -39,6 +39,11 @@ struct ContactConnectionInfo: View { .padding(.bottom, 16) Text(contactConnectionText(contactConnection)) + .padding(.bottom, 16) + + if let connReqInv = contactConnection.connReqInv { + OneTimeLinkProfileText(contactConnection: contactConnection, connReqInvitation: connReqInv) + } } .listRowBackground(Color.clear) .listRowSeparator(.hidden) @@ -47,11 +52,7 @@ struct ContactConnectionInfo: View { Section { if contactConnection.groupLinkId == nil { - HStack(spacing: 20) { - Image(systemName: "pencil") - .foregroundColor(.secondary) - .padding(.leading, 6) - .onTapGesture { aliasTextFieldFocused = true } + settingsRow("pencil") { TextField("Set contact name…", text: $localAlias) .autocapitalization(.none) .autocorrectionDisabled(true) @@ -59,20 +60,18 @@ struct ContactConnectionInfo: View { .submitLabel(.done) .onSubmit(setConnectionAlias) } + .onTapGesture { aliasTextFieldFocused = true } } if contactConnection.initiated, let connReqInv = contactConnection.connReqInv { - NavigationLink { - AddContactView(contactConnection: contactConnection, connReqInvitation: connReqInv) - .navigationTitle(CreateLinkTab.oneTime.title) - .navigationBarTitleDisplayMode(.large) - } label: { - Label("Show QR code", systemImage: "qrcode") - .foregroundColor(contactConnection.incognito ? .indigo : .accentColor) - } + oneTimeLinkSection(contactConnection: contactConnection, connReqInvitation: connReqInv) + } else { + oneTimeLinkLearnMoreButton() } + } + Section { Button(role: .destructive) { alert = .deleteInvitationAlert } label: { @@ -81,7 +80,6 @@ struct ContactConnectionInfo: View { } } } - .listStyle(.insetGrouped) } .alert(item: $alert) { _alert in switch _alert { @@ -96,7 +94,6 @@ struct ContactConnectionInfo: View { } .onAppear { localAlias = contactConnection.localAlias - aliasTextFieldFocused = true } } diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift new file mode 100644 index 0000000000..c746bca825 --- /dev/null +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -0,0 +1,28 @@ +// +// AddContactLearnMore.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 27.04.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct AddContactLearnMore: View { + var body: some View { + List { + VStack(alignment: .leading, spacing: 18) { + Text("To connect, your contact can scan QR code or use the link in the app.") + 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://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/README.md#connect-to-friends).") + } + .listRowBackground(Color.clear) + } + } +} + +struct AddContactLearnMore_Previews: PreviewProvider { + static var previews: some View { + AddContactLearnMore() + } +} diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift index fb3011de51..44de3c4987 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactView.swift @@ -16,50 +16,73 @@ struct AddContactView: View { var connReqInvitation: String var body: some View { - ScrollView { - VStack(alignment: .leading) { - Text("Your contact can scan it from the app.") - .padding(.bottom, 4) - if (contactConnection?.incognito ?? chatModel.incognito) { - HStack { - Image(systemName: "theatermasks").foregroundColor(.indigo).font(.footnote) - Spacer().frame(width: 8) - Text("A random profile will be sent to your contact").font(.footnote) - } - .padding(.bottom) - } else { - HStack { - Image(systemName: "info.circle").foregroundColor(.secondary).font(.footnote) - Spacer().frame(width: 8) - Text("Your chat profile will be sent to your contact").font(.footnote) - } - .padding(.bottom) - } - if connReqInvitation != "" { - QRCode(uri: connReqInvitation).padding(.bottom) - } else { - ProgressView() - .progressViewStyle(.circular) - .scaleEffect(2) - .frame(maxWidth: .infinity) - .padding(.vertical) - } - Text("If you can't meet in person, **show QR code in the video call**, or share the link.") - .padding(.bottom) - Button { - showShareSheet(items: [connReqInvitation]) - } label: { - Label("Share invitation link", systemImage: "square.and.arrow.up") - } - .frame(maxWidth: .infinity, alignment: .center) + List { + OneTimeLinkProfileText(contactConnection: contactConnection, connReqInvitation: connReqInvitation) + .listRowBackground(Color.clear) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + + Section("1-time link") { + oneTimeLinkSection(contactConnection: contactConnection, connReqInvitation: connReqInvitation) } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } .onAppear { chatModel.connReqInv = connReqInvitation } } } +@ViewBuilder func oneTimeLinkSection(contactConnection: PendingContactConnection? = nil, connReqInvitation: String) -> some View { + if connReqInvitation != "" { + QRCode(uri: connReqInvitation) + } else { + ProgressView() + .progressViewStyle(.circular) + .scaleEffect(2) + .frame(maxWidth: .infinity) + .padding(.vertical) + } + shareLinkButton(connReqInvitation) + oneTimeLinkLearnMoreButton() +} + +private func shareLinkButton(_ connReqInvitation: String) -> some View { + Button { + showShareSheet(items: [connReqInvitation]) + } label: { + settingsRow("square.and.arrow.up") { + Text("Share 1-time link") + } + } +} + +func oneTimeLinkLearnMoreButton() -> some View { + NavigationLink { + AddContactLearnMore() + .navigationTitle("One-time invitation link") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("info.circle") { + Text("Learn more") + } + } +} + +struct OneTimeLinkProfileText: View { + @EnvironmentObject private var chatModel: ChatModel + var contactConnection: PendingContactConnection? = nil + var connReqInvitation: String + + var body: some View { + HStack { + if (contactConnection?.incognito ?? chatModel.incognito) { + Image(systemName: "theatermasks").foregroundColor(.indigo) + Text("A random profile will be sent to your contact") + } else { + Image(systemName: "info.circle").foregroundColor(.secondary) + Text("Your chat profile will be sent to your contact") + } + } + } +} + struct AddContactView_Previews: PreviewProvider { static var previews: some View { AddContactView(connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D") diff --git a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift index 4504361bd7..71daf88a8c 100644 --- a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift +++ b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift @@ -15,7 +15,7 @@ enum CreateLinkTab { var title: LocalizedStringKey { switch self { case .oneTime: return "One-time invitation link" - case .longTerm: return "Your contact address" + case .longTerm: return "Your SimpleX address" } } } @@ -49,9 +49,9 @@ struct CreateLinkView: View { ) } .tag(CreateLinkTab.oneTime) - UserAddress() + UserAddressView(viaCreateLinkView: true) .tabItem { - Label("Your contact address", systemImage: "infinity.circle") + Label("Your SimpleX address", systemImage: "infinity.circle") } .tag(CreateLinkTab.longTerm) } diff --git a/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift b/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift deleted file mode 100644 index 0e61658c93..0000000000 --- a/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// AcceptRequestsView.swift -// SimpleX (iOS) -// -// Created by Evgeny on 23/10/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct AcceptRequestsView: View { - @EnvironmentObject private var m: ChatModel - @State var contactLink: UserContactLink - @State private var a = AutoAcceptState() - @State private var saved = AutoAcceptState() - @FocusState private var keyboardVisible: Bool - - var body: some View { - List { - Section { - settingsRow("checkmark") { - Toggle("Automatically", isOn: $a.enable) - } - if a.enable { - settingsRow( - a.incognito ? "theatermasks.fill" : "theatermasks", - color: a.incognito ? .indigo : .secondary - ) { - Toggle("Incognito", isOn: $a.incognito) - } - } - } header: { - Text("Accept requests") - } footer: { - saveButtons() - } - if a.enable { - Section { - TextEditor(text: $a.welcomeText) - .focused($keyboardVisible) - .padding(.horizontal, -5) - .padding(.top, -8) - .frame(height: 90, alignment: .topLeading) - .frame(maxWidth: .infinity, alignment: .leading) - } header: { - Text("Welcome message") - } - } - } - .onAppear { - a = AutoAcceptState(contactLink: contactLink) - saved = a - } - .onChange(of: a.enable) { _ in - if !a.enable { a = AutoAcceptState() } - } - } - - @ViewBuilder private func saveButtons() -> some View { - HStack { - Button { - a = saved - } label: { - Label("Cancel", systemImage: "arrow.counterclockwise") - } - Spacer() - Button { - Task { - do { - if let link = try await userAddressAutoAccept(a.autoAccept) { - contactLink = link - m.userAddress = link - saved = a - } - } catch let error { - logger.error("userAddressAutoAccept error: \(responseError(error))") - } - } - } label: { - Label("Save", systemImage: "checkmark") - } - } - .font(.body) - .disabled(a == saved) - } - - private struct AutoAcceptState: Equatable { - var enable = false - var incognito = false - var welcomeText = "" - - init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { - self.enable = enable - self.incognito = incognito - self.welcomeText = welcomeText - } - - init(contactLink: UserContactLink) { - if let aa = contactLink.autoAccept { - enable = true - incognito = aa.acceptIncognito - if let msg = aa.autoReply { - welcomeText = msg.text - } else { - welcomeText = "" - } - } else { - enable = false - incognito = false - welcomeText = "" - } - } - - var autoAccept: AutoAccept? { - if enable { - var autoReply: MsgContent? = nil - let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) - if s != "" { autoReply = .text(s) } - return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) - } - return nil - } - } -} - -struct AcceptRequestsView_Previews: PreviewProvider { - static var previews: some View { - AcceptRequestsView(contactLink: UserContactLink(connReqContact: "")) - } -} diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index d25ea45f49..4f8bb4f503 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -147,10 +147,11 @@ struct SettingsView: View { incognitoRow() NavigationLink { - CreateLinkView(selection: .longTerm, viaNavLink: true) - .navigationBarTitleDisplayMode(.inline) + UserAddressView(shareViaProfile: chatModel.currentUser!.addressShared) + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) } label: { - settingsRow("qrcode") { Text("Your SimpleX contact address") } + settingsRow("qrcode") { Text("Your SimpleX address") } } NavigationLink { diff --git a/apps/ios/Shared/Views/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/UserSettings/UserAddress.swift deleted file mode 100644 index 7564bea358..0000000000 --- a/apps/ios/Shared/Views/UserSettings/UserAddress.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// UserAddress.swift -// SimpleX -// -// Created by Evgeny Poberezkin on 31/01/2022. -// Copyright © 2022 SimpleX Chat. All rights reserved. -// - -import SwiftUI -import SimpleXChat - -struct UserAddress: View { - @EnvironmentObject private var chatModel: ChatModel - @State private var alert: UserAddressAlert? - @State private var showAcceptRequests = false - - private enum UserAddressAlert: Identifiable { - case deleteAddress - case error(title: LocalizedStringKey, error: LocalizedStringKey = "") - - var id: String { - switch self { - case .deleteAddress: return "deleteAddress" - case let .error(title, _): return "error \(title)" - } - } - } - - var body: some View { - ScrollView { - VStack (alignment: .leading) { - Text("You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it.") - .padding(.bottom) - if let userAdress = chatModel.userAddress { - QRCode(uri: userAdress.connReqContact) - HStack { - Button { - showShareSheet(items: [userAdress.connReqContact]) - } label: { - HStack { - Image(systemName: "square.and.arrow.up") - Text("Share link") - } - } - .padding() - NavigationLink { - if let contactLink = chatModel.userAddress { - AcceptRequestsView(contactLink: contactLink) - .navigationTitle("Contact requests") - .navigationBarTitleDisplayMode(.large) - } - } label: { - HStack { - Text("Contact requests") - Image(systemName: "chevron.right") - } - } - .padding() - } - .frame(maxWidth: .infinity) - Button(role: .destructive) { alert = .deleteAddress } label: { - Label("Delete address", systemImage: "trash") - } - .frame(maxWidth: .infinity) - } else { - Button { - Task { - do { - let connReqContact = try await apiCreateUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = UserContactLink(connReqContact: connReqContact) - } - } catch let error { - logger.error("UserAddress apiCreateUserAddress: \(responseError(error))") - let a = getErrorAlert(error, "Error creating address") - alert = .error(title: a.title, error: a.message) - } - } - } label: { Label("Create address", systemImage: "qrcode") } - .frame(maxWidth: .infinity) - } - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .sheet(isPresented: $showAcceptRequests) { - if let contactLink = chatModel.userAddress { - AcceptRequestsView(contactLink: contactLink) - } - } - .alert(item: $alert) { alert in - switch alert { - case .deleteAddress: - return Alert( - title: Text("Delete address?"), - message: Text("All your contacts will remain connected"), - primaryButton: .destructive(Text("Delete")) { - Task { - do { - try await apiDeleteUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = nil - } - } catch let error { - logger.error("UserAddress apiDeleteUserAddress: \(responseError(error))") - } - } - }, secondaryButton: .cancel() - ) - case let .error(title, error): - return Alert(title: Text(title), message: Text(error)) - } - } - } - } -} - -struct UserAddress_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 { - UserAddress() - .environmentObject(chatModel) - UserAddress() - .environmentObject(ChatModel()) - } - } -} diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift new file mode 100644 index 0000000000..e944dcec95 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -0,0 +1,29 @@ +// +// UserAddressLearnMore.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 27.04.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct UserAddressLearnMore: View { + var body: some View { + List { + VStack(alignment: .leading, spacing: 18) { + Text("You can share your address as a link or QR code - anybody can connect to you.") + Text("You won't lose your contacts if you later delete your address.") + Text("When people request to connect, you can accept or reject it.") + Text("Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address).") + } + .listRowBackground(Color.clear) + } + } +} + +struct UserAddressLearnMore_Previews: PreviewProvider { + static var previews: some View { + UserAddressLearnMore() + } +} diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift new file mode 100644 index 0000000000..802cf4d4a5 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -0,0 +1,414 @@ +// +// UserAddressView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 26.04.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct UserAddressView: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject private var chatModel: ChatModel + @State var viaCreateLinkView = false + @State var shareViaProfile = false + @State private var aas = AutoAcceptState() + @State private var savedAAS = AutoAcceptState() + @State private var ignoreShareViaProfileChange = false + @State private var alert: UserAddressAlert? + @State private var showSaveDialogue = false + @State private var progressIndicator = false + @FocusState private var keyboardVisible: Bool + + private enum UserAddressAlert: Identifiable { + case deleteAddress + case profileAddress(on: Bool) + case shareOnCreate + case error(title: LocalizedStringKey, error: LocalizedStringKey = "") + + var id: String { + switch self { + case .deleteAddress: return "deleteAddress" + case let .profileAddress(on): return "profileAddress \(on)" + case .shareOnCreate: return "shareOnCreate" + case let .error(title, _): return "error \(title)" + } + } + } + + var body: some View { + ZStack { + if viaCreateLinkView { + userAddressScrollView() + } else { + userAddressScrollView() + .modifier(BackButton { + 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() } + } + } + if progressIndicator { + ZStack { + if chatModel.userAddress != nil { + Circle() + .fill(.white) + .opacity(0.7) + .frame(width: 56, height: 56) + } + ProgressView().scaleEffect(2) + } + } + } + } + + @Namespace private var bottomID + + private func userAddressScrollView() -> some View { + ScrollViewReader { proxy in + userAddressView() + .onChange(of: keyboardVisible) { _ in + if keyboardVisible { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + withAnimation { + proxy.scrollTo(bottomID, anchor: .top) + } + } + } + } + } + } + + private func userAddressView() -> some View { + List { + if let userAddress = chatModel.userAddress { + existingAddressView(userAddress) + .onAppear { + aas = AutoAcceptState(userAddress: userAddress) + savedAAS = aas + } + .onChange(of: aas.enable) { _ in + if !aas.enable { aas = AutoAcceptState() } + } + } else { + Section { + createAddressButton() + } footer: { + Text("Create an address to let people connect with you.") + } + + Section { + learnMoreButton() + } + } + } + .alert(item: $alert) { alert in + switch alert { + case .deleteAddress: + return Alert( + title: Text("Delete address?"), + message: + shareViaProfile + ? Text("All your contacts will remain connected. Profile update will be sent to your contacts.") + : Text("All your contacts will remain connected."), + primaryButton: .destructive(Text("Delete")) { + progressIndicator = true + Task { + do { + if let u = try await apiDeleteUserAddress() { + DispatchQueue.main.async { + chatModel.userAddress = nil + chatModel.updateUser(u) + if shareViaProfile { + ignoreShareViaProfileChange = true + shareViaProfile = false + } + } + } + await MainActor.run { progressIndicator = false } + } catch let error { + logger.error("UserAddressView apiDeleteUserAddress: \(responseError(error))") + await MainActor.run { progressIndicator = false } + } + } + }, secondaryButton: .cancel() + ) + case let .profileAddress(on): + if on { + return Alert( + title: Text("Share address with contacts?"), + message: Text("Profile update will be sent to your contacts."), + primaryButton: .default(Text("Share")) { + setProfileAddress(on) + }, secondaryButton: .cancel() { + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ) + } else { + return Alert( + title: Text("Stop sharing address?"), + message: Text("Profile update will be sent to your contacts."), + primaryButton: .default(Text("Stop sharing")) { + setProfileAddress(on) + }, secondaryButton: .cancel() { + ignoreShareViaProfileChange = true + shareViaProfile = !on + } + ) + } + case .shareOnCreate: + return Alert( + title: Text("Share address with contacts?"), + message: Text("Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts."), + primaryButton: .default(Text("Share")) { + setProfileAddress(true) + ignoreShareViaProfileChange = true + shareViaProfile = true + }, secondaryButton: .cancel() + ) + case let .error(title, error): + return Alert(title: Text(title), message: Text(error)) + } + } + } + + @ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View { + Section { + QRCode(uri: userAddress.connReqContact) + shareQRCodeButton(userAddress) + shareWithContactsButton() + autoAcceptToggle() + learnMoreButton() + } header: { + Text("Address") + } + + if aas.enable { + autoAcceptSection() + } + + Section { + deleteAddressButton() + } footer: { + Text("Your contacts will remain connected.") + } + .id(bottomID) + } + + private func createAddressButton() -> some View { + Button { + progressIndicator = true + Task { + do { + let connReqContact = try await apiCreateUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = UserContactLink(connReqContact: connReqContact) + alert = .shareOnCreate + progressIndicator = false + } + } catch let error { + logger.error("UserAddressView apiCreateUserAddress: \(responseError(error))") + let a = getErrorAlert(error, "Error creating address") + alert = .error(title: a.title, error: a.message) + await MainActor.run { progressIndicator = false } + } + } + } label: { + Label("Create SimpleX address", systemImage: "qrcode") + } + } + + private func deleteAddressButton() -> some View { + Button(role: .destructive) { + alert = .deleteAddress + } label: { + Label("Delete address", systemImage: "trash") + .foregroundColor(Color.red) + } + } + + private func shareQRCodeButton(_ userAdress: UserContactLink) -> some View { + Button { + showShareSheet(items: [userAdress.connReqContact]) + } label: { + settingsRow("square.and.arrow.up") { + Text("Share address") + } + } + } + + private func autoAcceptToggle() -> some View { + settingsRow("checkmark") { + Toggle("Auto-accept", isOn: $aas.enable) + .onChange(of: aas.enable) { _ in + saveAAS() + } + } + } + + private func learnMoreButton() -> some View { + NavigationLink { + UserAddressLearnMore() + .navigationTitle("SimpleX address") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("info.circle") { + Text("About SimpleX address") + } + } + } + + private func shareWithContactsButton() -> some View { + settingsRow("person") { + Toggle("Share with contacts", isOn: $shareViaProfile) + .onChange(of: shareViaProfile) { on in + if ignoreShareViaProfileChange { + ignoreShareViaProfileChange = false + } else { + alert = .profileAddress(on: on) + } + } + } + } + + private func setProfileAddress(_ on: Bool) { + progressIndicator = true + Task { + do { + if let u = try await apiSetProfileAddress(on: on) { + DispatchQueue.main.async { + chatModel.updateUser(u) + } + } + await MainActor.run { progressIndicator = false } + } catch let error { + logger.error("UserAddressView apiSetProfileAddress: \(responseError(error))") + await MainActor.run { progressIndicator = false } + } + } + } + + private struct AutoAcceptState: Equatable { + var enable = false + var incognito = false + var welcomeText = "" + + init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { + self.enable = enable + self.incognito = incognito + self.welcomeText = welcomeText + } + + init(userAddress: UserContactLink) { + if let aa = userAddress.autoAccept { + enable = true + incognito = aa.acceptIncognito + if let msg = aa.autoReply { + welcomeText = msg.text + } else { + welcomeText = "" + } + } else { + enable = false + incognito = false + welcomeText = "" + } + } + + var autoAccept: AutoAccept? { + if enable { + var autoReply: MsgContent? = nil + let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) + if s != "" { autoReply = .text(s) } + return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) + } + return nil + } + } + + @ViewBuilder private func autoAcceptSection() -> some View { + Section { + acceptIncognitoToggle() + welcomeMessageEditor() + saveAASButton() + .disabled(aas == savedAAS) + } header: { + Text("Auto-accept") + } + } + + private func acceptIncognitoToggle() -> some View { + settingsRow( + aas.incognito ? "theatermasks.fill" : "theatermasks", + color: aas.incognito ? .indigo : .secondary + ) { + Toggle("Accept incognito", isOn: $aas.incognito) + } + } + + private func welcomeMessageEditor() -> some View { + ZStack { + Group { + if aas.welcomeText.isEmpty { + TextEditor(text: Binding.constant(NSLocalizedString("Enter welcome message… (optional)", comment: "placeholder"))) + .foregroundColor(.secondary) + .disabled(true) + } + TextEditor(text: $aas.welcomeText) + .focused($keyboardVisible) + } + .padding(.horizontal, -5) + .padding(.top, -8) + .frame(height: 90, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + + private func saveAASButton() -> some View { + Button { + keyboardVisible = false + saveAAS() + } label: { + Text("Save") + } + } + + private func saveAAS() { + Task { + do { + if let address = try await userAddressAutoAccept(aas.autoAccept) { + chatModel.userAddress = address + savedAAS = aas + } + } catch let error { + logger.error("userAddressAutoAccept error: \(responseError(error))") + } + } + } +} + +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) + UserAddressView() + .environmentObject(ChatModel()) + } + } +} 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 775e7c754d..0980517f78 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -361,6 +361,10 @@ Dostupné ve verzi 5.1 O SimpleX chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Zbarvení @@ -387,9 +391,8 @@ Dostupné ve verzi 5.1 Přijmout inkognito No comment provided by engineer. - - Accept requests - Přijímat žádosti + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Dostupné ve verzi 5.1 Přidat uvítací zprávu No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Správci mohou vytvářet odkazy pro připojení ke skupinám. @@ -447,9 +454,12 @@ Dostupné ve verzi 5.1 Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás. No comment provided by engineer. - - All your contacts will remain connected - Všechny vaše kontakty zůstanou připojeny + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Dostupné ve verzi 5.1 Ověřování není k dispozici No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Automatické přijímání žádostí o kontakt @@ -622,11 +636,6 @@ Dostupné ve verzi 5.1 Automaticky přijímat obrázky No comment provided by engineer. - - Automatically - Automaticky - No comment provided by engineer. - Back Zpět @@ -992,11 +1001,6 @@ Dostupné ve verzi 5.1 Předvolby kontaktů No comment provided by engineer. - - Contact requests - Žádosti o kontakt - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Kontakty mohou označit zprávy ke smazání; vy je budete moci zobrazit. @@ -1017,9 +1021,12 @@ Dostupné ve verzi 5.1 Vytvořit No comment provided by engineer. - - Create address - Vytvořit adresu + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Dostupné ve verzi 5.1 Zadejte server ručně No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Chyba @@ -2343,6 +2354,10 @@ Dostupné ve verzi 5.1 Velký soubor! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Opustit @@ -2987,6 +3002,10 @@ Dostupné ve verzi 5.1 Heslo profilu No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Zákaz audio/video hovorů. @@ -3247,6 +3266,10 @@ Dostupné ve verzi 5.1 Uložit archiv No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Uložení profilu skupiny @@ -3282,6 +3305,10 @@ Dostupné ve verzi 5.1 Uložit servery? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Uložit uvítací zprávu? @@ -3472,6 +3499,14 @@ Dostupné ve verzi 5.1 Sdílet chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Sdílet odkaz na pozvánku @@ -3487,6 +3522,10 @@ Dostupné ve verzi 5.1 Jednorázový zvací odkaz No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Zobrazit QR kód @@ -3537,6 +3576,10 @@ Dostupné ve verzi 5.1 Zapnutý zámek SimpleX Lock No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX kontaktní adresa @@ -3632,6 +3675,14 @@ Dostupné ve verzi 5.1 Zastavit odesílání souboru? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Odeslat @@ -4228,6 +4279,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Když je k dispozici No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve. @@ -4320,9 +4375,13 @@ SimpleX zámek musí být povolen. Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. O své kontakty nepřijdete, pokud ji později smažete. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Můžete sdílet svou adresu jako odkaz nebo jako QR kód - kdokoli se k vám bude moci připojit. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX zámek musí být povolen. Přestanete dostávat zprávy z této skupiny. Historie chatu bude zachována. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + O své kontakty nepřijdete, pokud ji později smažete. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Snažíte se pozvat kontakt, se kterým jste sdíleli inkognito profil, do skupiny, ve které používáte svůj hlavní profil @@ -4462,7 +4526,7 @@ SimpleX zámek musí být povolen. Your SimpleX contact address - Vaše kontaktní adresa SimpleX + Vaše adresa SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Toto připojení můžete zrušit a kontakt odebrat (a zkusit to později s nov Vaše kontakty mohou povolit úplné mazání zpráv. No comment provided by engineer. + + Your contacts will remain connected. + 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 14b40892cf..68ccbbb7a3 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -361,6 +361,10 @@ Verfügbar ab v5.1 Über SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Akzentfarbe @@ -387,9 +391,8 @@ Verfügbar ab v5.1 Inkognito akzeptieren No comment provided by engineer. - - Accept requests - Anfragen annehmen + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Verfügbar ab v5.1 Begrüßungsmeldung hinzufügen No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Administratoren können Links für den Beitritt zu Gruppen erzeugen. @@ -447,9 +454,12 @@ Verfügbar ab v5.1 Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. No comment provided by engineer. - - All your contacts will remain connected - Alle Ihre Kontakte bleiben verbunden + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Verfügbar ab v5.1 Authentifizierung nicht verfügbar No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Kontaktanfragen automatisch annehmen @@ -622,11 +636,6 @@ Verfügbar ab v5.1 Bilder automatisch akzeptieren No comment provided by engineer. - - Automatically - Automatisch - No comment provided by engineer. - Back Zurück @@ -992,11 +1001,6 @@ Verfügbar ab v5.1 Kontakt Präferenzen No comment provided by engineer. - - Contact requests - Kontaktanfragen - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Ihre Kontakte können Nachrichten zum Löschen markieren. Sie können diese Nachrichten trotzdem anschauen. @@ -1017,9 +1021,12 @@ Verfügbar ab v5.1 Erstellen No comment provided by engineer. - - Create address - Adresse erstellen + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Verfügbar ab v5.1 Geben Sie den Server manuell ein No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Fehler @@ -2343,6 +2354,10 @@ Verfügbar ab v5.1 Große Datei! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Verlassen @@ -2987,6 +3002,10 @@ Verfügbar ab v5.1 Passwort für Profil No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Audio/Video Anrufe nicht erlauben. @@ -3247,6 +3266,10 @@ Verfügbar ab v5.1 Archiv speichern No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Gruppenprofil speichern @@ -3282,6 +3305,10 @@ Verfügbar ab v5.1 Alle Server speichern? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Begrüßungsmeldung speichern? @@ -3472,6 +3499,14 @@ Verfügbar ab v5.1 Teilen chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Einladungslink teilen @@ -3487,6 +3522,10 @@ Verfügbar ab v5.1 Einmal-Einladungslink teilen No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code QR-Code anzeigen @@ -3537,6 +3576,10 @@ Verfügbar ab v5.1 SimpleX Sperre aktiviert No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX Kontaktadressen-Link @@ -3632,6 +3675,14 @@ Verfügbar ab v5.1 Das Senden der Datei beenden? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Übermitteln @@ -4228,6 +4279,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wenn verfügbar No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden. @@ -4320,9 +4375,13 @@ Dafür muss die SimpleX Sperre aktiviert sein. Sie können diesen Link oder QR-Code teilen - Damit kann jede Person der Gruppe beitreten. Wenn Sie den Link später löschen, werden Sie keine Gruppenmitglieder verlieren, die der Gruppe darüber beigetreten sind. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Sie können Ihre Adresse als Link oder als QR-Code teilen – Jede Person kann sich darüber mit Ihnen verbinden. No comment provided by engineer. @@ -4435,6 +4494,11 @@ Dafür muss die SimpleX Sperre aktiviert sein. Sie werden von dieser Gruppe keine Nachrichten mehr erhalten. Der Chatverlauf wird beibehalten. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Sie werden Ihre mit dieser Adresse verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Sie versuchen, einen Kontakt, mit dem Sie ein Inkognito-Profil geteilt haben, in die Gruppe einzuladen, in der Sie Ihr Hauptprofil verwenden @@ -4532,6 +4596,10 @@ Sie können diese Verbindung abbrechen und den Kontakt entfernen (und es später Ihre Kontakte können die unwiederbringliche Löschung von Nachrichten erlauben. No comment provided by engineer. + + Your contacts will remain connected. + 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. 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 c0a0c7cfc1..d7c662591e 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -361,6 +361,11 @@ Available in v5.1 About SimpleX Chat No comment provided by engineer. + + About SimpleX address + About SimpleX address + No comment provided by engineer. + Accent color Accent color @@ -387,9 +392,9 @@ Available in v5.1 Accept incognito No comment provided by engineer. - - Accept requests - Accept requests + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +427,11 @@ Available in v5.1 Add welcome message No comment provided by engineer. + + Address + Address + No comment provided by engineer. + Admins can create the links to join groups. Admins can create the links to join groups. @@ -447,9 +457,14 @@ Available in v5.1 All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. No comment provided by engineer. - - All your contacts will remain connected - All your contacts will remain connected + + All your contacts will remain connected. + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +627,11 @@ Available in v5.1 Authentication unavailable No comment provided by engineer. + + Auto-accept + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Auto-accept contact requests @@ -622,11 +642,6 @@ Available in v5.1 Auto-accept images No comment provided by engineer. - - Automatically - Automatically - No comment provided by engineer. - Back Back @@ -992,11 +1007,6 @@ Available in v5.1 Contact preferences No comment provided by engineer. - - Contact requests - Contact requests - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Contacts can mark messages for deletion; you will be able to view them. @@ -1017,9 +1027,14 @@ Available in v5.1 Create No comment provided by engineer. - - Create address - Create address + + Create SimpleX address + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1605,11 @@ Available in v5.1 Enter server manually No comment provided by engineer. + + Enter welcome message… (optional) + Enter welcome message… (optional) + placeholder + Error Error @@ -2343,6 +2363,11 @@ Available in v5.1 Large file! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Leave @@ -2987,6 +3012,11 @@ Available in v5.1 Profile password No comment provided by engineer. + + Profile update will be sent to your contacts. + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Prohibit audio/video calls. @@ -3247,6 +3277,11 @@ Available in v5.1 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 @@ -3282,6 +3317,11 @@ Available in v5.1 Save servers? No comment provided by engineer. + + Save settings? + Save settings? + No comment provided by engineer. + Save welcome message? Save welcome message? @@ -3472,6 +3512,16 @@ Available in v5.1 Share chat item action + + Share address + Share address + No comment provided by engineer. + + + Share address with contacts? + Share address with contacts? + No comment provided by engineer. + Share invitation link Share invitation link @@ -3487,6 +3537,11 @@ Available in v5.1 Share one-time invitation link No comment provided by engineer. + + Share with contacts + Share with contacts + No comment provided by engineer. + Show QR code Show QR code @@ -3537,6 +3592,11 @@ Available in v5.1 SimpleX Lock turned on No comment provided by engineer. + + SimpleX address + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX contact address @@ -3632,6 +3692,16 @@ Available in v5.1 Stop sending file? No comment provided by engineer. + + Stop sharing + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + Stop sharing address? + No comment provided by engineer. + Submit Submit @@ -4228,6 +4298,11 @@ To connect, please ask your contact to create another connection link and check When available No comment provided by engineer. + + When people request to connect, you can accept or reject it. + When people request to connect, you can accept or reject it. + 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. When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. @@ -4320,9 +4395,14 @@ SimpleX Lock must be enabled. You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. + + You can share this address with your contacts to let them connect with **%@**. + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + You can share your address as a link or QR code - anybody can connect to you. No comment provided by engineer. @@ -4435,6 +4515,11 @@ SimpleX Lock must be enabled. You will stop receiving messages from this group. Chat history will be preserved. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + You won't lose your contacts if you later delete your address. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile @@ -4460,9 +4545,9 @@ SimpleX Lock must be enabled. Your SMP servers No comment provided by engineer. - - Your SimpleX contact address - Your SimpleX contact address + + Your SimpleX address + Your SimpleX address No comment provided by engineer. @@ -4532,6 +4617,11 @@ You can cancel this connection and remove the contact (and try later with a new Your contacts can allow full message deletion. No comment provided by engineer. + + Your contacts will remain connected. + Your contacts will remain connected. + 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 b87a02742f..148a814c74 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -361,6 +361,10 @@ Disponible en v5.1 Acerca de SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Color @@ -387,9 +391,8 @@ Disponible en v5.1 Aceptar incógnito No comment provided by engineer. - - Accept requests - Aceptar solicitudes + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Disponible en v5.1 Agregar mensaje de bienvenida No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Los administradores pueden crear los enlaces para unirse a los grupos. @@ -447,9 +454,12 @@ Disponible en v5.1 Se eliminarán todos los mensajes SOLO para tí. ¡No puede deshacerse! No comment provided by engineer. - - All your contacts will remain connected - Todos tus contactos permanecerán conectados + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Disponible en v5.1 Autenticación no disponible No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Aceptar automáticamente solicitudes del contacto @@ -622,11 +636,6 @@ Disponible en v5.1 Aceptar automáticamente imágenes No comment provided by engineer. - - Automatically - Automáticamente - No comment provided by engineer. - Back Volver @@ -992,11 +1001,6 @@ Disponible en v5.1 Preferencias de contacto No comment provided by engineer. - - Contact requests - Solicitud del contacto - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. El contacto solo puede marcar los mensajes para eliminar. Tu podrás verlos. @@ -1017,9 +1021,12 @@ Disponible en v5.1 Crear No comment provided by engineer. - - Create address - Crear dirección + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Disponible en v5.1 Introduce el servidor manualmente No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Error @@ -2343,6 +2354,10 @@ Disponible en v5.1 ¡Archivo grande! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Salir @@ -2987,6 +3002,10 @@ Disponible en v5.1 Contraseña del perfil No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Prohibir las llamadas/videollamadas. @@ -3247,6 +3266,10 @@ Disponible en v5.1 Guardar archivo No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Guardar perfil de grupo @@ -3282,6 +3305,10 @@ Disponible en v5.1 ¿Guardar servidores? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? ¿Guardar mensaje de bienvenida? @@ -3472,6 +3499,14 @@ Disponible en v5.1 Compartir chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Compartir enlace de invitación @@ -3487,6 +3522,10 @@ Disponible en v5.1 Compartir enlace de invitación de un uso No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Mostrar código QR @@ -3537,6 +3576,10 @@ Disponible en v5.1 Bloqueo SimpleX activado No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Dirección de contacto SimpleX @@ -3632,6 +3675,14 @@ Disponible en v5.1 ¿Dejar de enviar el archivo? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Enviar @@ -4229,6 +4280,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Cuando esté disponible No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. @@ -4321,9 +4376,13 @@ Bloqueo SimpleX debe estar activado. Puedes compartir un enlace o un código QR: cualquiera podrá unirse al grupo. Si lo eliminas más tarde los miembros del grupo no se perderán. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Puedes compartir tu dirección como enlace o como código QR: cualquiera podrá conectarse contigo. Si lo eliminas más tarde tus contactos no se perderán. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Puedes compartir tu dirección como enlace o como código QR: cualquiera podrá conectarse contigo. No comment provided by engineer. @@ -4436,6 +4495,11 @@ Bloqueo SimpleX debe estar activado. Dejarás de recibir mensajes de este grupo. El historial del chat se conservará. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Si lo eliminas más tarde tus contactos no se perderán. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Estás intentando invitar a un contacto con el que compartes un perfil incógnito a un grupo en el que usas tu perfil principal @@ -4463,7 +4527,7 @@ Bloqueo SimpleX debe estar activado. Your SimpleX contact address - Mi dirección de contacto SimpleX + Mi dirección SimpleX No comment provided by engineer. @@ -4533,6 +4597,10 @@ Puedes cancelar esta conexión y eliminar el contacto (e intentarlo más tarde c Tus contactos pueden permitir la eliminación completa de mensajes. No comment provided by engineer. + + Your contacts will remain connected. + 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/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 88d5bca63d..a5c44c9ff4 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -361,6 +361,10 @@ Disponible dans la v5.1 À propos de SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Couleur principale @@ -387,9 +391,8 @@ Disponible dans la v5.1 Accepter en incognito No comment provided by engineer. - - Accept requests - Accepter les demandes + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Disponible dans la v5.1 Ajouter un message d'accueil No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Les admins peuvent créer les liens qui permettent de rejoindre les groupes. @@ -447,9 +454,12 @@ Disponible dans la v5.1 Tous les messages seront supprimés - impossible de revenir en arrière ! Les messages seront supprimés UNIQUEMENT pour vous. No comment provided by engineer. - - All your contacts will remain connected - Tous vos contacts resteront connectés + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Disponible dans la v5.1 Authentification indisponible No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Demandes de contact auto-acceptées @@ -622,11 +636,6 @@ Disponible dans la v5.1 Images auto-acceptées No comment provided by engineer. - - Automatically - Automatiquement - No comment provided by engineer. - Back Retour @@ -992,11 +1001,6 @@ Disponible dans la v5.1 Préférences de contact No comment provided by engineer. - - Contact requests - Demandes de contact - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Vos contacts peuvent marquer les messages pour les supprimer ; vous pourrez les consulter. @@ -1017,9 +1021,12 @@ Disponible dans la v5.1 Créer No comment provided by engineer. - - Create address - Créer une adresse + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Disponible dans la v5.1 Entrer un serveur manuellement No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Erreur @@ -2343,6 +2354,10 @@ Disponible dans la v5.1 Fichier trop lourd ! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Quitter @@ -2987,6 +3002,10 @@ Disponible dans la v5.1 Mot de passe de profil No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Interdire les appels audio/vidéo. @@ -3247,6 +3266,10 @@ Disponible dans la v5.1 Sauvegarder l'archive No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Sauvegarder le profil du groupe @@ -3282,6 +3305,10 @@ Disponible dans la v5.1 Sauvegarder les serveurs ? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Sauvegarder le message d'accueil ? @@ -3472,6 +3499,14 @@ Disponible dans la v5.1 Partager chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Partager le lien d'invitation @@ -3487,6 +3522,10 @@ Disponible dans la v5.1 Partager un lien d'invitation unique No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Afficher le code QR @@ -3537,6 +3576,10 @@ Disponible dans la v5.1 SimpleX Lock activé No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Adresse de contact SimpleX @@ -3632,6 +3675,14 @@ Disponible dans la v5.1 Arrêter l'envoi du fichier ? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Soumettre @@ -4228,6 +4279,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Quand disponible No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite. @@ -4320,9 +4375,13 @@ SimpleX Lock doit être activé. Vous pouvez partager un lien ou un code QR - n'importe qui pourra rejoindre le groupe. Vous ne perdrez pas les membres du groupe si vous le supprimez par la suite. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. Vous ne perdrez pas vos contacts si vous la supprimez par la suite. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock doit être activé. Vous ne recevrez plus de messages de ce groupe. L'historique du chat sera conservé. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Vous ne perdrez pas vos contacts si vous la supprimez par la suite. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Vous essayez d'inviter un contact avec lequel vous avez partagé un profil incognito à rejoindre le groupe dans lequel vous utilisez votre profil principal @@ -4462,7 +4526,7 @@ SimpleX Lock doit être activé. Your SimpleX contact address - Votre adresse de contact SimpleX + Votre adresse SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Vous pouvez annuler la connexion et supprimer le contact (et réessayer plus tar Vos contacts peuvent autoriser la suppression complète des messages. No comment provided by engineer. + + Your contacts will remain connected. + 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/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 6c0ee81008..bb549d4e9f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -361,6 +361,10 @@ Disponibile nella v5.1 Riguardo SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Colore principale @@ -387,9 +391,8 @@ Disponibile nella v5.1 Accetta in incognito No comment provided by engineer. - - Accept requests - Accetta le richieste + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Disponibile nella v5.1 Aggiungi messaggio di benvenuto No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Gli amministratori possono creare i link per entrare nei gruppi. @@ -447,9 +454,12 @@ Disponibile nella v5.1 Tutti i messaggi verranno eliminati, non è reversibile! I messaggi verranno eliminati SOLO per te. No comment provided by engineer. - - All your contacts will remain connected - Tutti i tuoi contatti resteranno connessi + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Disponibile nella v5.1 Autenticazione non disponibile No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Auto-accetta richieste di contatto @@ -622,11 +636,6 @@ Disponibile nella v5.1 Auto-accetta immagini No comment provided by engineer. - - Automatically - Automaticamente - No comment provided by engineer. - Back Indietro @@ -992,11 +1001,6 @@ Disponibile nella v5.1 Preferenze del contatto No comment provided by engineer. - - Contact requests - Richieste del contatto - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. I contatti possono contrassegnare i messaggi per l'eliminazione; potrai vederli. @@ -1017,9 +1021,12 @@ Disponibile nella v5.1 Crea No comment provided by engineer. - - Create address - Crea indirizzo + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Disponibile nella v5.1 Inserisci il server a mano No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Errore @@ -2343,6 +2354,10 @@ Disponibile nella v5.1 File grande! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Esci @@ -2987,6 +3002,10 @@ Disponibile nella v5.1 Password del profilo No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Proibisci le chiamate audio/video. @@ -3247,6 +3266,10 @@ Disponibile nella v5.1 Salva archivio No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Salva il profilo del gruppo @@ -3282,6 +3305,10 @@ Disponibile nella v5.1 Salvare i server? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Salvare il messaggio di benvenuto? @@ -3472,6 +3499,14 @@ Disponibile nella v5.1 Condividi chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Condividi link di invito @@ -3487,6 +3522,10 @@ Disponibile nella v5.1 Condividi link di invito una tantum No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Mostra codice QR @@ -3537,6 +3576,10 @@ Disponibile nella v5.1 SimpleX Lock attivato No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Indirizzo del contatto SimpleX @@ -3632,6 +3675,14 @@ Disponibile nella v5.1 Fermare l'invio del file? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Invia @@ -4228,6 +4279,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Quando disponibili No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano. @@ -4320,9 +4375,13 @@ SimpleX Lock deve essere attivato. Puoi condividere un link o un codice QR: chiunque potrà unirsi al gruppo. Non perderai i membri del gruppo se in seguito lo elimini. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. Non perderai i tuoi contatti se in seguito lo elimini. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Puoi condividere il tuo indirizzo come link o come codice QR: chiunque potrà connettersi a te. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock deve essere attivato. Non riceverai più messaggi da questo gruppo. La cronologia della chat verrà conservata. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Non perderai i tuoi contatti se in seguito lo elimini. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Stai tentando di invitare un contatto con cui hai condiviso un profilo in incognito nel gruppo in cui stai usando il tuo profilo principale @@ -4462,7 +4526,7 @@ SimpleX Lock deve essere attivato. Your SimpleX contact address - Il tuo indirizzo di contatto SimpleX + Il tuo indirizzo SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Puoi annullare questa connessione e rimuovere il contatto (e riprovare più tard I tuoi contatti possono consentire l'eliminazione completa dei messaggi. No comment provided by engineer. + + Your contacts will remain connected. + 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. 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 a144792be3..ff753124e3 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -361,6 +361,10 @@ Beschikbaar in v5.1 Over SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Accent kleur @@ -387,9 +391,8 @@ Beschikbaar in v5.1 Accepteer incognito No comment provided by engineer. - - Accept requests - Verzoeken accepteren + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Beschikbaar in v5.1 Welkomst bericht toevoegen No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Beheerders kunnen de uitnodiging links naar groepen aanmaken. @@ -447,9 +454,12 @@ Beschikbaar in v5.1 Alle berichten worden verwijderd, dit kan niet ongedaan worden gemaakt! De berichten worden ALLEEN voor jou verwijderd. No comment provided by engineer. - - All your contacts will remain connected - Al uw contacten blijven verbonden + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Beschikbaar in v5.1 Verificatie niet beschikbaar No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Contact verzoeken automatisch accepteren @@ -622,11 +636,6 @@ Beschikbaar in v5.1 Afbeeldingen automatisch accepteren No comment provided by engineer. - - Automatically - Automatisch - No comment provided by engineer. - Back Terug @@ -992,11 +1001,6 @@ Beschikbaar in v5.1 Contact voorkeuren No comment provided by engineer. - - Contact requests - Contact verzoeken - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Contact personen kunnen berichten markeren voor verwijdering; u kunt ze wel bekijken. @@ -1017,9 +1021,12 @@ Beschikbaar in v5.1 Maak No comment provided by engineer. - - Create address - Adres aanmaken + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Beschikbaar in v5.1 Voer de server handmatig in No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Fout @@ -2343,6 +2354,10 @@ Beschikbaar in v5.1 Groot bestand! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Verlaten @@ -2987,6 +3002,10 @@ Beschikbaar in v5.1 Profiel wachtwoord No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Audio/video gesprekken verbieden. @@ -3247,6 +3266,10 @@ Beschikbaar in v5.1 Bewaar archief No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Groep profiel opslaan @@ -3282,6 +3305,10 @@ Beschikbaar in v5.1 Servers opslaan? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Welkomst bericht opslaan? @@ -3472,6 +3499,14 @@ Beschikbaar in v5.1 Deel chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Uitnodiging link delen @@ -3487,6 +3522,10 @@ Beschikbaar in v5.1 Eenmalige uitnodiging link delen No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Toon QR-code @@ -3537,6 +3576,10 @@ Beschikbaar in v5.1 SimpleX Vergrendelen ingeschakeld No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX contact adres @@ -3632,6 +3675,14 @@ Beschikbaar in v5.1 Bestand verzenden stoppen? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Indienen @@ -4228,6 +4279,10 @@ Om verbinding te maken, vraagt u uw contactpersoon om een andere verbinding link Wanneer beschikbaar No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen. @@ -4320,9 +4375,13 @@ SimpleX Lock moet ingeschakeld zijn. U kunt een link of een QR-code delen. Iedereen kan lid worden van de groep. U verliest geen leden van de groep als u deze later verwijdert. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. U verliest uw contacten niet als u deze later verwijdert. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + U kunt uw adres delen als een link of als een QR-code. Iedereen kan verbinding met u maken. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock moet ingeschakeld zijn. Je ontvangt geen berichten meer van deze groep. Je gesprek geschiedenis blijft behouden. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + U verliest uw contacten niet als u deze later verwijdert. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Je probeert een contact met wie je een incognito profiel hebt gedeeld, uit te nodigen voor de groep waarin je je hoofdprofiel gebruikt @@ -4462,7 +4526,7 @@ SimpleX Lock moet ingeschakeld zijn. Your SimpleX contact address - Uw SimpleX contact adres + Uw SimpleX adres No comment provided by engineer. @@ -4532,6 +4596,10 @@ U kunt deze verbinding verbreken en het contact verwijderen (en later proberen m Uw contacten kunnen volledige verwijdering van berichten toestaan. No comment provided by engineer. + + Your contacts will remain connected. + 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. 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 78156e1239..3e71f25887 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -361,6 +361,10 @@ Dostępny w v5.1 O SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Kolor akcentu @@ -387,9 +391,8 @@ Dostępny w v5.1 Akceptuj incognito No comment provided by engineer. - - Accept requests - Akceptuj prośby + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Dostępny w v5.1 Dodaj wiadomość powitalną No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Administratorzy mogą tworzyć linki do dołączania do grup. @@ -447,9 +454,12 @@ Dostępny w v5.1 Wszystkie wiadomości zostaną usunięte - nie można tego cofnąć! Wiadomości zostaną usunięte TYLKO dla Ciebie. No comment provided by engineer. - - All your contacts will remain connected - Wszystkie Twoje kontakty pozostaną połączone + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Dostępny w v5.1 Uwierzytelnianie niedostępne No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Automatyczne akceptowanie próśb o kontakt @@ -622,11 +636,6 @@ Dostępny w v5.1 Automatyczne akceptowanie obrazów No comment provided by engineer. - - Automatically - Automatycznie - No comment provided by engineer. - Back Wstecz @@ -992,11 +1001,6 @@ Dostępny w v5.1 Preferencje kontaktu No comment provided by engineer. - - Contact requests - Prośby kontaktu - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Kontakty mogą oznaczać wiadomości do usunięcia; będziesz mógł je zobaczyć. @@ -1017,9 +1021,12 @@ Dostępny w v5.1 Utwórz No comment provided by engineer. - - Create address - Utwórz adres + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Dostępny w v5.1 Wprowadź serwer ręcznie No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Błąd @@ -2343,6 +2354,10 @@ Dostępny w v5.1 Duży plik! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Opuść @@ -2987,6 +3002,10 @@ Dostępny w v5.1 Hasło profilu No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Zabroń połączeń audio/wideo. @@ -3247,6 +3266,10 @@ Dostępny w v5.1 Zapisz archiwum No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Zapisz profil grupy @@ -3282,6 +3305,10 @@ Dostępny w v5.1 Zapisać serwery? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Zapisać wiadomość powitalną? @@ -3472,6 +3499,14 @@ Dostępny w v5.1 Udostępnij chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Udostępnij link zaproszenia @@ -3487,6 +3522,10 @@ Dostępny w v5.1 Jednorazowy link zaproszenia No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Pokaż kod QR @@ -3537,6 +3576,10 @@ Dostępny w v5.1 Blokada SimpleX włączona No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address Adres kontaktowy SimpleX @@ -3632,6 +3675,14 @@ Dostępny w v5.1 Przestać wysyłać plik? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Zatwierdź @@ -4228,6 +4279,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Gdy dostępny No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi. @@ -4320,9 +4375,13 @@ Funkcja blokady SimpleX musi być włączona. Możesz udostępnić link lub kod QR - każdy będzie mógł dołączyć do grupy. Nie stracisz członków grupy, jeśli później ją usuniesz. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. Nie stracisz swoich kontaktów, jeśli później go usuniesz. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Możesz udostępnić swój adres jako link lub jako kod QR - każdy będzie mógł się z Tobą połączyć. No comment provided by engineer. @@ -4435,6 +4494,11 @@ Funkcja blokady SimpleX musi być włączona. Przestaniesz otrzymywać wiadomości od tej grupy. Historia czatu zostanie zachowana. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Nie stracisz swoich kontaktów, jeśli później go usuniesz. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Próbujesz zaprosić osobę, z którą masz wspólny profil incognito do grupy, w której używasz swojego głównego profilu @@ -4462,7 +4526,7 @@ Funkcja blokady SimpleX musi być włączona. Your SimpleX contact address - Twój adres kontaktowy SimpleX + Twój adres SimpleX No comment provided by engineer. @@ -4532,6 +4596,10 @@ Możesz anulować to połączenie i usunąć kontakt (i spróbować później z Twoje kontakty mogą zezwolić na pełne usunięcie wiadomości. No comment provided by engineer. + + Your contacts will remain connected. + 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 6bb8229a32..a0e4c600b1 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -361,6 +361,10 @@ Available in v5.1 Информация о SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color Основной цвет @@ -387,9 +391,8 @@ Available in v5.1 Принять инкогнито No comment provided by engineer. - - Accept requests - Принимать запросы + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Available in v5.1 Добавить приветственное сообщение No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. Админы могут создать ссылки для вступления в группу. @@ -447,9 +454,12 @@ Available in v5.1 Все сообщения будут удалены - это действие нельзя отменить! Сообщения будут удалены только для Вас. No comment provided by engineer. - - All your contacts will remain connected - Все контакты, которые соединились через этот адрес, сохранятся. + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Available in v5.1 Аутентификация недоступна No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests Автоматически принимать запросы контактов @@ -622,11 +636,6 @@ Available in v5.1 Автоприем изображений No comment provided by engineer. - - Automatically - Автоматически - No comment provided by engineer. - Back Назад @@ -992,11 +1001,6 @@ Available in v5.1 Предпочтения контакта No comment provided by engineer. - - Contact requests - Запросы контактов - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. Контакты могут помечать сообщения для удаления; Вы сможете просмотреть их. @@ -1017,9 +1021,12 @@ Available in v5.1 Создать No comment provided by engineer. - - Create address - Создать адрес + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Available in v5.1 Ввести сервер вручную No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error Ошибка @@ -2343,6 +2354,10 @@ Available in v5.1 Большой файл! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave Выйти @@ -2987,6 +3002,10 @@ Available in v5.1 Пароль профиля No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. Запретить аудио/видео звонки. @@ -3247,6 +3266,10 @@ Available in v5.1 Сохранить архив No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile Сохранить профиль группы @@ -3282,6 +3305,10 @@ Available in v5.1 Сохранить серверы? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? Сохранить приветственное сообщение? @@ -3472,6 +3499,14 @@ Available in v5.1 Поделиться chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link Поделиться ссылкой @@ -3487,6 +3522,10 @@ Available in v5.1 Поделиться ссылкой-приглашением No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code Показать QR код @@ -3537,6 +3576,10 @@ Available in v5.1 Блокировка SimpleX включена No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX ссылка-контакт @@ -3632,6 +3675,14 @@ Available in v5.1 Остановить отправку файла? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit Продолжить @@ -4228,6 +4279,10 @@ To connect, please ask your contact to create another connection link and check Когда возможно No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом. @@ -4320,9 +4375,13 @@ SimpleX Lock must be enabled. Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились. No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. Вы сможете удалить адрес, сохранив контакты, которые через него соединились. + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Вы можете использовать Ваш адрес как ссылку или как QR код - кто угодно сможет соединиться с Вами. No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock must be enabled. Вы перестанете получать сообщения от этой группы. История чата будет сохранена. No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + Вы сможете удалить адрес, сохранив контакты, которые через него соединились. + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile Вы пытаетесь пригласить инкогнито контакт в группу, где Вы используете свой основной профиль @@ -4532,6 +4596,10 @@ You can cancel this connection and remove the contact (and try later with a new Ваши контакты могут разрешить окончательное удаление сообщений. No comment provided by engineer. + + Your contacts will remain connected. + 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 a518d5f499..1a525f62b6 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 @@ -361,6 +361,10 @@ Available in v5.1 关于SimpleX Chat No comment provided by engineer. + + About SimpleX address + No comment provided by engineer. + Accent color 色调 @@ -387,9 +391,8 @@ Available in v5.1 接受隐身聊天 No comment provided by engineer. - - Accept requests - 接受请求 + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. No comment provided by engineer. @@ -422,6 +425,10 @@ Available in v5.1 添加欢迎信息 No comment provided by engineer. + + Address + No comment provided by engineer. + Admins can create the links to join groups. 管理员可以创建链接以加入群组。 @@ -447,9 +454,12 @@ Available in v5.1 所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。 No comment provided by engineer. - - All your contacts will remain connected - 您的所有联系人将保持连接 + + All your contacts will remain connected. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. @@ -612,6 +622,10 @@ Available in v5.1 身份验证不可用 No comment provided by engineer. + + Auto-accept + No comment provided by engineer. + Auto-accept contact requests 自动接受联系人请求 @@ -622,11 +636,6 @@ Available in v5.1 自动接受图片 No comment provided by engineer. - - Automatically - 自动 - No comment provided by engineer. - Back 返回 @@ -992,11 +1001,6 @@ Available in v5.1 联系人偏好设置 No comment provided by engineer. - - Contact requests - 联系人请求 - No comment provided by engineer. - Contacts can mark messages for deletion; you will be able to view them. 联系人可以将信息标记为删除;您将可以查看这些信息。 @@ -1017,9 +1021,12 @@ Available in v5.1 创建 No comment provided by engineer. - - Create address - 创建地址 + + Create SimpleX address + No comment provided by engineer. + + + Create an address to let people connect with you. No comment provided by engineer. @@ -1590,6 +1597,10 @@ Available in v5.1 手动输入服务器 No comment provided by engineer. + + Enter welcome message… (optional) + placeholder + Error 错误 @@ -2343,6 +2354,10 @@ Available in v5.1 大文件! No comment provided by engineer. + + Read more in [User Guide](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/guide/app-settings.md#your-simplex-contact-address). + No comment provided by engineer. + Leave 离开 @@ -2987,6 +3002,10 @@ Available in v5.1 个人资料密码 No comment provided by engineer. + + Profile update will be sent to your contacts. + No comment provided by engineer. + Prohibit audio/video calls. 禁止音频/视频通话。 @@ -3247,6 +3266,10 @@ Available in v5.1 保存存档 No comment provided by engineer. + + Save auto-accept settings + No comment provided by engineer. + Save group profile 保存群组资料 @@ -3282,6 +3305,10 @@ Available in v5.1 保存服务器? No comment provided by engineer. + + Save settings? + No comment provided by engineer. + Save welcome message? 保存欢迎信息? @@ -3472,6 +3499,14 @@ Available in v5.1 分享 chat item action + + Share address + No comment provided by engineer. + + + Share address with contacts? + No comment provided by engineer. + Share invitation link 分享邀请链接 @@ -3487,6 +3522,10 @@ Available in v5.1 分享一次性邀请链接 No comment provided by engineer. + + Share with contacts + No comment provided by engineer. + Show QR code 显示二维码 @@ -3537,6 +3576,10 @@ Available in v5.1 已开启 SimpleX 锁定 No comment provided by engineer. + + SimpleX address + No comment provided by engineer. + SimpleX contact address SimpleX 联系地址 @@ -3632,6 +3675,14 @@ Available in v5.1 停止发送文件? No comment provided by engineer. + + Stop sharing + No comment provided by engineer. + + + Stop sharing address? + No comment provided by engineer. + Submit 提交 @@ -4228,6 +4279,10 @@ To connect, please ask your contact to create another connection link and check 当可用时 No comment provided by engineer. + + When people request to connect, you can accept or reject it. + 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. 当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。 @@ -4320,9 +4375,13 @@ SimpleX Lock must be enabled. 您可以共享链接或二维码——任何人都可以加入该群组。如果您稍后将其删除,您不会失去该组的成员。 No comment provided by engineer. - - You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it. - 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 如果您以后删除它,您不会丢失您的联系人。 + + You can share this address with your contacts to let them connect with **%@**. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + 您可以将您的地址作为链接或二维码共享——任何人都可以连接到您。 No comment provided by engineer. @@ -4435,6 +4494,11 @@ SimpleX Lock must be enabled. 您将停止接收来自该群组的消息。聊天记录将被保留。 No comment provided by engineer. + + You won't lose your contacts if you later delete your address. + 如果您以后删除它,您不会丢失您的联系人。 + No comment provided by engineer. + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile 您正在尝试邀请与您共享隐身个人资料的联系人加入您使用主要个人资料的群组 @@ -4532,6 +4596,10 @@ You can cancel this connection and remove the contact (and try later with a new 您的联系人可以允许完全删除消息。 No comment provided by engineer. + + Your contacts will remain connected. + No comment provided by engineer. + Your current chat database will be DELETED and REPLACED with the imported one. 您当前的聊天数据库将被删除并替换为导入的数据库。 diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 8bed72bb71..3d666c7578 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -104,7 +104,6 @@ 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB634B029E5EFEA0066AD6B /* PasscodeView.swift */; }; 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; }; 5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; }; - 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E327A8683A00ACCCDD /* UserAddress.swift */; }; 5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */; }; 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; }; 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */; }; @@ -116,7 +115,6 @@ 5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; }; 5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; }; 5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; }; - 5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */; }; 5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; }; 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; }; 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; }; @@ -172,6 +170,9 @@ 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; + 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; + 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; + 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; }; 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; }; 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; @@ -368,7 +369,6 @@ 5CB634B029E5EFEA0066AD6B /* PasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; - 5CB924E327A8683A00ACCCDD /* UserAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddress.swift; sourceTree = ""; }; 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListNavLink.swift; sourceTree = ""; }; 5CBD285529565CAE00EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 5CBD285629565CAE00EC2CF4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -384,7 +384,6 @@ 5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = ""; }; - 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptRequestsView.swift; sourceTree = ""; }; 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = ""; }; 5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = ""; }; @@ -439,6 +438,9 @@ 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; 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 = ""; }; + 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; + 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; + 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactLearnMore.swift; sourceTree = ""; }; 64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = ""; }; 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = ""; }; @@ -694,6 +696,7 @@ 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */, 5CB2085028DB64CA00D024EC /* CreateLinkView.swift */, 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */, + 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */, ); path = NewChat; sourceTree = ""; @@ -709,8 +712,6 @@ 5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */, 5C05DF522840AA1D00C683F9 /* CallSettings.swift */, 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */, - 5CB924E327A8683A00ACCCDD /* UserAddress.swift */, - 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */, 5CB924E027A867BA00ACCCDD /* UserProfile.swift */, 5CC036DF29C488D500C0EF20 /* HiddenProfileView.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, @@ -722,6 +723,8 @@ 18415845648CA4F5A8BCA272 /* UserProfilesView.swift */, 5C65F341297D3F3600B67AF3 /* VersionView.swift */, 5C65DAF829D0CC20003CEE45 /* DeveloperView.swift */, + 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */, + 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */, ); path = UserSettings; sourceTree = ""; @@ -1069,7 +1072,6 @@ 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */, 5C029EAA283942EA004A9677 /* CallController.swift in Sources */, - 5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */, 5CBE6C142944CC12002D9531 /* ScanCodeView.swift in Sources */, 5CC036E029C488D500C0EF20 /* HiddenProfileView.swift in Sources */, 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */, @@ -1088,8 +1090,8 @@ 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */, D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */, 5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */, + 64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */, 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, - 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */, 5C65F343297D45E100B67AF3 /* VersionView.swift in Sources */, 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */, 5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */, @@ -1126,6 +1128,7 @@ 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */, + 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */, 5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */, 5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */, 6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */, @@ -1137,6 +1140,7 @@ 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */, 644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */, 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */, + 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */, 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */, 5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */, 5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index b1021c5130..53ae7e8849 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -85,6 +85,7 @@ public enum ChatCommand { case apiCreateMyAddress(userId: Int64) case apiDeleteMyAddress(userId: Int64) case apiShowMyAddress(userId: Int64) + case apiSetProfileAddress(userId: Int64, on: Bool) case apiAddressAutoAccept(userId: Int64, autoAccept: AutoAccept?) case apiAcceptContact(contactReqId: Int64) case apiRejectContact(contactReqId: Int64) @@ -189,6 +190,7 @@ public enum ChatCommand { case let .apiCreateMyAddress(userId): return "/_address \(userId)" case let .apiDeleteMyAddress(userId): return "/_delete_address \(userId)" case let .apiShowMyAddress(userId): return "/_show_address \(userId)" + case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))" case let .apiAddressAutoAccept(userId, autoAccept): return "/_auto_accept \(userId) \(AutoAccept.cmdString(autoAccept))" case let .apiAcceptContact(contactReqId): return "/_accept \(contactReqId)" case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)" @@ -290,6 +292,7 @@ public enum ChatCommand { case .apiCreateMyAddress: return "apiCreateMyAddress" case .apiDeleteMyAddress: return "apiDeleteMyAddress" case .apiShowMyAddress: return "apiShowMyAddress" + case .apiSetProfileAddress: return "apiSetProfileAddress" case .apiAddressAutoAccept: return "apiAddressAutoAccept" case .apiAcceptContact: return "apiAcceptContact" case .apiRejectContact: return "apiRejectContact" diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index a449af1c3c..f82bb844bc 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -33,6 +33,10 @@ public struct User: Decodable, NamedChat, Identifiable { activeUser || showNtfs } + public var addressShared: Bool { + profile.contactLink != nil + } + public static let sampleData = User( userId: 1, userContactId: 1, @@ -71,16 +75,24 @@ public typealias ContactName = String public typealias GroupName = String public struct Profile: Codable, NamedChat { - public init(displayName: String, fullName: String, image: String? = nil, preferences: Preferences? = nil) { + public init( + displayName: String, + fullName: String, + image: String? = nil, + contactLink: String? = nil, + preferences: Preferences? = nil + ) { self.displayName = displayName self.fullName = fullName self.image = image + self.contactLink = contactLink self.preferences = preferences } public var displayName: String public var fullName: String public var image: String? + public var contactLink: String? public var preferences: Preferences? public var localAlias: String { get { "" } } @@ -95,11 +107,20 @@ public struct Profile: Codable, NamedChat { } public struct LocalProfile: Codable, NamedChat { - public init(profileId: Int64, displayName: String, fullName: String, image: String? = nil, preferences: Preferences? = nil, localAlias: String) { + public init( + profileId: Int64, + displayName: String, + fullName: String, + image: String? = nil, + contactLink: String? = nil, + preferences: Preferences? = nil, + localAlias: String + ) { self.profileId = profileId self.displayName = displayName self.fullName = fullName self.image = image + self.contactLink = contactLink self.preferences = preferences self.localAlias = localAlias } @@ -108,6 +129,7 @@ public struct LocalProfile: Codable, NamedChat { public var displayName: String public var fullName: String public var image: String? + public var contactLink: String? public var preferences: Preferences? public var localAlias: String @@ -127,11 +149,11 @@ public struct LocalProfile: Codable, NamedChat { } public func toLocalProfile (_ profileId: Int64, _ profile: Profile, _ localAlias: String) -> LocalProfile { - LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, preferences: profile.preferences, localAlias: localAlias) + LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences, localAlias: localAlias) } public func fromLocalProfile (_ profile: LocalProfile) -> Profile { - Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, preferences: profile.preferences) + Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences) } public enum ChatType: String { @@ -1164,6 +1186,7 @@ public struct Contact: Identifiable, Decodable, NamedChat { public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } public var image: String? { get { profile.image } } + public var contactLink: String? { get { profile.contactLink } } public var localAlias: String { profile.localAlias } public var verified: Bool { activeConn.connectionCode != nil } @@ -1508,6 +1531,7 @@ public struct GroupMember: Identifiable, Decodable { } public var fullName: String { get { memberProfile.fullName } } public var image: String? { get { memberProfile.image } } + public var contactLink: String? { get { memberProfile.contactLink } } public var verified: Bool { activeConn?.connectionCode != nil } var directChatId: ChatId? { diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 7cfe9ead06..995f5c3cb6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -92,6 +92,7 @@ library Simplex.Chat.Migrations.M20230402_protocol_servers Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions Simplex.Chat.Migrations.M20230420_rcv_files_to_receive + Simplex.Chat.Migrations.M20230422_profile_contact_links Simplex.Chat.Mobile Simplex.Chat.Mobile.WebRTC Simplex.Chat.Options diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index e241e0357f..2cf37abb54 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1123,18 +1123,32 @@ processChatCommand = \case pure $ CRUserContactLinkCreated user cReq CreateMyAddress -> withUser $ \User {userId} -> processChatCommand $ APICreateMyAddress userId - APIDeleteMyAddress userId -> withUserId userId $ \user -> withChatLock "deleteMyAddress" $ do + APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do conns <- withStore (`getUserAddressConnections` user) - procCmd $ do + withChatLock "deleteMyAddress" $ do deleteAgentConnectionsAsync user $ map aConnId conns withStore' (`deleteUserAddress` user) - pure $ CRUserContactLinkDeleted user + let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} + r <- updateProfile_ user p' $ withStore' $ \db -> setUserProfileContactLink db user Nothing + let user' = case r of + CRUserProfileUpdated u' _ _ -> u' + _ -> user + pure $ CRUserContactLinkDeleted user' DeleteMyAddress -> withUser $ \User {userId} -> processChatCommand $ APIDeleteMyAddress userId APIShowMyAddress userId -> withUserId userId $ \user -> CRUserContactLink user <$> withStore (`getUserAddress` user) ShowMyAddress -> withUser $ \User {userId} -> processChatCommand $ APIShowMyAddress userId + APISetProfileAddress userId False -> withUserId userId $ \user@User {profile = p} -> do + let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing} + updateProfile_ user p' $ withStore' $ \db -> setUserProfileContactLink db user Nothing + APISetProfileAddress userId True -> withUserId userId $ \user@User {profile = p} -> do + ucl@UserContactLink {connReqContact} <- withStore (`getUserAddress` user) + let p' = (fromLocalProfile p :: Profile) {contactLink = Just connReqContact} + updateProfile_ user p' $ withStore' $ \db -> setUserProfileContactLink db user $ Just ucl + SetProfileAddress onOff -> withUser $ \User {userId} -> + processChatCommand $ APISetProfileAddress userId onOff APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do contactLink <- withStore (\db -> updateUserAddressAutoAccept db user autoAccept_) pure $ CRUserContactLinkUpdated user contactLink @@ -1613,7 +1627,9 @@ processChatCommand = \case | chunks <= sendChunks && chunks * n <= totalSendChunks && isVoice mc = Just IFMSent | otherwise = Just IFMOffer updateProfile :: User -> Profile -> m ChatResponse - updateProfile user@User {profile = p} p' + updateProfile user p' = updateProfile_ user p' $ withStore $ \db -> updateUserProfile db user p' + updateProfile_ :: User -> Profile -> m User -> m ChatResponse + updateProfile_ user@User {profile = p} p' updateUser | p' == fromLocalProfile p = pure $ CRUserProfileNoChange user | otherwise = do -- read contacts before user update to correctly merge preferences @@ -1621,7 +1637,7 @@ processChatCommand = \case contacts <- filter (\ct -> isReady ct && not (contactConnIncognito ct)) <$> withStore' (`getUserContacts` user) - user' <- withStore $ \db -> updateUserProfile db user p' + user' <- updateUser asks currentUser >>= atomically . (`writeTVar` Just user') withChatLock "updateProfile" . procCmd $ do forM_ contacts $ \ct -> do @@ -4407,7 +4423,7 @@ getCreateActiveUser st = do loop = do displayName <- getContactName fullName <- T.pack <$> getWithPrompt "full name (optional)" - withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName, image = Nothing, preferences = Nothing} True) >>= \case + withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} True) >>= \case Left SEDuplicateName -> do putStrLn "chosen display name is already used by another profile on this device, choose another one" loop @@ -4713,6 +4729,8 @@ chatCommandP = ("/delete_address" <|> "/da") $> DeleteMyAddress, "/_show_address " *> (APIShowMyAddress <$> A.decimal), ("/show_address" <|> "/sa") $> ShowMyAddress, + "/_profile_address " *> (APISetProfileAddress <$> A.decimal <* A.space <*> onOffP), + ("/profile_address " <|> "/pa ") *> (SetProfileAddress <$> onOffP), "/_auto_accept " *> (APIAddressAutoAccept <$> A.decimal <* A.space <*> autoAcceptP), "/auto_accept " *> (AddressAutoAccept <$> autoAcceptP), ("/accept " <|> "/ac ") *> char_ '@' *> (AcceptContact <$> displayName), @@ -4766,7 +4784,7 @@ chatCommandP = pure (cName, fullName) userProfile = do (cName, fullName) <- userNames - pure Profile {displayName = cName, fullName, image = Nothing, preferences = Nothing} + pure Profile {displayName = cName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} jsonP :: J.FromJSON a => Parser a jsonP = J.eitherDecodeStrict' <$?> A.takeByteString groupProfile = do diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index bd0299d78f..54a780384e 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -304,6 +304,8 @@ data ChatCommand | DeleteMyAddress | APIShowMyAddress UserId | ShowMyAddress + | APISetProfileAddress UserId Bool + | SetProfileAddress Bool | APIAddressAutoAccept UserId (Maybe AutoAccept) | AddressAutoAccept (Maybe AutoAccept) | AcceptContact ContactName diff --git a/src/Simplex/Chat/Help.hs b/src/Simplex/Chat/Help.hs index 37770d1bd1..1f77617af1 100644 --- a/src/Simplex/Chat/Help.hs +++ b/src/Simplex/Chat/Help.hs @@ -201,6 +201,7 @@ myAddressHelpInfo = styleMarkdown [ green "Your contact address commands:", indent <> highlight "/address " <> " - create your address", + indent <> highlight "/profile_address on/off " <> " - share address with your contacts (it will be added to your profile)", indent <> highlight "/delete_address " <> " - delete your address (accepted contacts will remain connected)", indent <> highlight "/show_address " <> " - show your address", indent <> highlight "/accept " <> " - accept contact request", diff --git a/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs b/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs new file mode 100644 index 0000000000..ee7ff053d5 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20230422_profile_contact_links.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20230422_profile_contact_links where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20230422_profile_contact_links :: Query +m20230422_profile_contact_links = + [sql| +ALTER TABLE contact_profiles ADD COLUMN contact_link BLOB; +|] + +down_m20230422_profile_contact_links :: Query +down_m20230422_profile_contact_links = + [sql| +ALTER TABLE contact_profiles DROP COLUMN contact_link; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 5f24c8a3c3..c14ed3d211 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -17,7 +17,8 @@ CREATE TABLE contact_profiles( user_id INTEGER DEFAULT NULL REFERENCES users ON DELETE CASCADE, incognito INTEGER, local_alias TEXT DEFAULT '' CHECK(local_alias NOT NULL), - preferences TEXT + preferences TEXT, + contact_link BLOB ); CREATE INDEX contact_profiles_index ON contact_profiles( display_name, diff --git a/src/Simplex/Chat/ProfileGenerator.hs b/src/Simplex/Chat/ProfileGenerator.hs index 59c6b8a10c..55a051ad80 100644 --- a/src/Simplex/Chat/ProfileGenerator.hs +++ b/src/Simplex/Chat/ProfileGenerator.hs @@ -10,7 +10,7 @@ generateRandomProfile :: IO Profile generateRandomProfile = do adjective <- pick adjectives noun <- pickNoun adjective 2 - pure $ Profile {displayName = adjective <> noun, fullName = "", image = Nothing, preferences = Nothing} + pure $ Profile {displayName = adjective <> noun, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} where pick :: [a] -> IO a pick xs = (xs !!) <$> randomRIO (0, length xs - 1) diff --git a/src/Simplex/Chat/Store.hs b/src/Simplex/Chat/Store.hs index 44bab60132..489a7a266d 100644 --- a/src/Simplex/Chat/Store.hs +++ b/src/Simplex/Chat/Store.hs @@ -55,6 +55,7 @@ module Simplex.Chat.Store getContact, getContactIdByName, updateUserProfile, + setUserProfileContactLink, updateContactProfile, updateContactUserPreferences, updateContactAlias, @@ -373,6 +374,7 @@ import Simplex.Chat.Migrations.M20230328_files_protocol import Simplex.Chat.Migrations.M20230402_protocol_servers import Simplex.Chat.Migrations.M20230411_extra_xftp_file_descriptions import Simplex.Chat.Migrations.M20230420_rcv_files_to_receive +import Simplex.Chat.Migrations.M20230422_profile_contact_links import Simplex.Chat.Protocol import Simplex.Chat.Types import Simplex.Chat.Util (week) @@ -447,7 +449,8 @@ schemaMigrations = ("20230328_files_protocol", m20230328_files_protocol, Just down_m20230328_files_protocol), ("20230402_protocol_servers", m20230402_protocol_servers, Just down_m20230402_protocol_servers), ("20230411_extra_xftp_file_descriptions", m20230411_extra_xftp_file_descriptions, Just down_m20230411_extra_xftp_file_descriptions), - ("20230420_rcv_files_to_receive", m20230420_rcv_files_to_receive, Just down_m20230420_rcv_files_to_receive) + ("20230420_rcv_files_to_receive", m20230420_rcv_files_to_receive, Just down_m20230420_rcv_files_to_receive), + ("20230422_profile_contact_links", m20230422_profile_contact_links, Just down_m20230422_profile_contact_links) ] -- | The list of migrations in ascending order by date @@ -501,7 +504,7 @@ createUserRecord db (AgentUserId auId) Profile {displayName, fullName, image, pr (profileId, displayName, userId, True, 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, userPreferences, True) :. (Nothing, Nothing) + pure $ toUser $ (userId, auId, contactId, profileId, activeUser, displayName, fullName, image, Nothing, userPreferences, True) :. (Nothing, Nothing) getUsersInfo :: DB.Connection -> IO [UserInfo] getUsersInfo db = getUsers db >>= mapM getUserInfo @@ -539,17 +542,17 @@ getUsers db = 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.preferences, u.show_ntfs, u.view_pwd_hash, u.view_pwd_salt + 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, u.show_ntfs, u.view_pwd_hash, u.view_pwd_salt 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 Preferences, Bool) :. (Maybe B64UrlByteString, Maybe B64UrlByteString) -> User -toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, userPreferences, showNtfs) :. (viewPwdHash_, viewPwdSalt_)) = +toUser :: (UserId, UserId, ContactId, ProfileId, Bool, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences, Bool) :. (Maybe B64UrlByteString, Maybe B64UrlByteString) -> User +toUser ((userId, auId, userContactId, profileId, activeUser, displayName, fullName, image, contactLink, userPreferences, showNtfs) :. (viewPwdHash_, viewPwdSalt_)) = User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, fullPreferences, showNtfs, viewPwdHash} where - profile = LocalProfile {profileId, displayName, fullName, image, preferences = userPreferences, localAlias = ""} + profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""} fullPreferences = mergePreferences Nothing userPreferences viewPwdHash = UserPwdHash <$> viewPwdHash_ <*> viewPwdSalt_ @@ -672,7 +675,7 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -724,15 +727,15 @@ getProfileById db userId profileId = DB.query db [sql| - SELECT cp.display_name, cp.full_name, cp.image, cp.local_alias, cp.preferences -- , ct.user_preferences + SELECT cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences FROM contact_profiles cp -- JOIN contacts ct ON cp.contact_profile_id = ct.contact_profile_id WHERE cp.user_id = ? AND cp.contact_profile_id = ? |] (userId, profileId) where - toProfile :: (ContactName, Text, Maybe ImageData, LocalAlias, Maybe Preferences) -> LocalProfile - toProfile (displayName, fullName, image, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} + toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences) -> LocalProfile + toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> IO Connection createConnection_ db userId connType entityId acId viaContact viaUserContactLink customUserProfileId connLevel currentTs = do @@ -765,12 +768,12 @@ createDirectContact db user@User {userId} activeConn@Connection {connId, localAl pure $ Contact {contactId, localDisplayName, profile, activeConn, viaGroup = Nothing, contactUsed = False, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt, updatedAt = createdAt, chatTs = Just createdAt} createContact_ :: DB.Connection -> UserId -> Int64 -> Profile -> LocalAlias -> Maybe Int64 -> UTCTime -> Maybe UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) -createContact_ db userId connId Profile {displayName, fullName, image, preferences} localAlias viaGroup currentTs chatTs = +createContact_ db userId connId Profile {displayName, fullName, image, contactLink, preferences} localAlias viaGroup currentTs chatTs = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, localAlias, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, localAlias, preferences, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -872,6 +875,23 @@ updateUserProfile db user p' profile = toLocalProfile profileId p' localAlias fullPreferences = mergePreferences Nothing preferences +setUserProfileContactLink :: DB.Connection -> User -> Maybe UserContactLink -> IO User +setUserProfileContactLink db user@User {userId, profile = p@LocalProfile {profileId}} ucl_ = do + ts <- getCurrentTime + DB.execute + db + [sql| + UPDATE contact_profiles + SET contact_link = ?, updated_at = ? + WHERE user_id = ? AND contact_profile_id = ? + |] + (connReqContact_, ts, userId, profileId) + pure (user :: User) {profile = p {contactLink = connReqContact_}} + where + connReqContact_ = case ucl_ of + Just UserContactLink {connReqContact} -> Just connReqContact + _ -> Nothing + updateContactProfile :: DB.Connection -> User -> Contact -> Profile -> ExceptT StoreError IO Contact updateContactProfile db user@User {userId} c p' | displayName == newName = do @@ -964,15 +984,15 @@ updateContactProfile_ db userId profileId profile = do updateContactProfile_' db userId profileId profile currentTs updateContactProfile_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO () -updateContactProfile_' db userId profileId Profile {displayName, fullName, image, preferences} updatedAt = do +updateContactProfile_' db userId profileId Profile {displayName, fullName, image, contactLink, preferences} updatedAt = do DB.execute db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, preferences = ?, updated_at = ? + SET display_name = ?, full_name = ?, image = ?, contact_link = ?, preferences = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - (displayName, fullName, image, preferences, updatedAt, userId, profileId) + (displayName, fullName, image, contactLink, preferences, updatedAt, userId, profileId) updateContact_ :: DB.Connection -> UserId -> Int64 -> ContactName -> ContactName -> UTCTime -> IO () updateContact_ db userId contactId displayName newName updatedAt = do @@ -986,19 +1006,19 @@ updateContact_ db userId contactId displayName newName updatedAt = do (newName, updatedAt, userId, contactId) DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (displayName, userId) -type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, LocalAlias, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) +type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) toContact :: User -> ContactRow :. ConnectionRow -> Contact -toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} activeConn = toConnection connRow chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} toContactOrError :: User -> ContactRow :. MaybeConnectionRow -> Either StoreError Contact -toContactOrError user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toContactOrError user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} in case toMaybeConnection connRow of Just activeConn -> @@ -1023,14 +1043,14 @@ getUserContactProfiles db User {userId} = <$> DB.query db [sql| - SELECT display_name, full_name, image, preferences + SELECT display_name, full_name, image, contact_link, preferences FROM contact_profiles WHERE user_id = ? |] (Only userId) where - toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe Preferences) -> (Profile) - toContactProfile (displayName, fullName, image, preferences) = Profile {displayName, fullName, image, preferences} + toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnReqContact, Maybe Preferences) -> (Profile) + toContactProfile (displayName, fullName, image, contactLink, preferences) = Profile {displayName, fullName, image, contactLink, preferences} createUserContactLink :: DB.Connection -> User -> ConnId -> ConnReqContact -> ExceptT StoreError IO () createUserContactLink db User {userId} agentConnId cReq = @@ -1081,7 +1101,7 @@ getUserContactLinks db User {userId} = toUserContactConnection (connRow :. (userContactLinkId, connReqContact, groupId)) = (toConnection connRow, UserContact {userContactLinkId, connReqContact, groupId}) deleteUserAddress :: DB.Connection -> User -> IO () -deleteUserAddress db User {userId} = do +deleteUserAddress db user@User {userId} = do DB.execute db [sql| @@ -1118,6 +1138,7 @@ deleteUserAddress db User {userId} = do ) |] [":user_id" := userId] + void $ setUserProfileContactLink db user Nothing DB.execute db "DELETE FROM user_contact_links WHERE user_id = ? AND local_display_name = '' AND group_id IS NULL" (Only userId) data UserContactLink = UserContactLink @@ -1266,7 +1287,7 @@ setGroupLinkMemberRole db User {userId} userContactLinkId memberRole = DB.execute db "UPDATE user_contact_links SET group_link_member_role = ? WHERE user_id = ? AND user_contact_link_id = ?" (memberRole, userId, userContactLinkId) createOrUpdateContactRequest :: DB.Connection -> User -> Int64 -> InvitationId -> Profile -> Maybe XContactId -> ExceptT StoreError IO ContactOrRequest -createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profile {displayName, fullName, image, preferences} xContactId_ = +createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profile {displayName, fullName, image, contactLink, preferences} xContactId_ = liftIO (maybeM getContact' xContactId_) >>= \case Just contact -> pure $ CORContact contact Nothing -> CORRequest <$> createOrUpdate_ @@ -1288,8 +1309,8 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi createContactRequest_ currentTs ldn = do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -1308,7 +1329,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -1329,7 +1350,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, - c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at + c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at FROM contact_requests cr JOIN connections c USING (user_contact_link_id) JOIN contact_profiles p USING (contact_profile_id) @@ -1357,6 +1378,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi SET display_name = ?, full_name = ?, image = ?, + contact_link = ?, updated_at = ? WHERE contact_profile_id IN ( SELECT contact_profile_id @@ -1365,7 +1387,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId Profi AND contact_request_id = ? ) |] - (displayName, fullName, image, currentTs, userId, cReqId) + (displayName, fullName, image, contactLink, currentTs, userId, cReqId) getContactRequest' :: DB.Connection -> Int64 -> ExceptT StoreError IO (User, UserContactRequest) getContactRequest' db contactRequestId = do @@ -1380,7 +1402,7 @@ getContactRequest db User {userId} contactRequestId = [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, - c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at + c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at FROM contact_requests cr JOIN connections c USING (user_contact_link_id) JOIN contact_profiles p USING (contact_profile_id) @@ -1389,11 +1411,11 @@ getContactRequest db User {userId} contactRequestId = |] (userId, contactRequestId) -type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData) :. (Maybe XContactId, Maybe Preferences, UTCTime, UTCTime) +type ContactRequestRow = (Int64, ContactName, AgentInvId, Int64, AgentConnId, Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact) :. (Maybe XContactId, Maybe Preferences, UTCTime, UTCTime) toContactRequest :: ContactRequestRow -> UserContactRequest -toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image) :. (xContactId, preferences, createdAt, updatedAt)) = do - let profile = Profile {displayName, fullName, image, preferences} +toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, preferences, createdAt, updatedAt)) = do + let profile = Profile {displayName, fullName, image, contactLink, preferences} in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, localDisplayName, profileId, profile, xContactId, createdAt, updatedAt} getContactRequestIdByName :: DB.Connection -> UserId -> ContactName -> ExceptT StoreError IO Int64 @@ -1754,16 +1776,16 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do db [sql| SELECT - c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, c.via_group, c.contact_used, c.enable_ntfs, + c.contact_profile_id, c.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.via_group, c.contact_used, c.enable_ntfs, p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts FROM contacts c JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id WHERE c.user_id = ? AND c.contact_id = ? |] (userId, contactId) - toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)] -> Either StoreError Contact - toContact' contactId activeConn [(profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)] = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} + toContact' :: Int64 -> Connection -> [(ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)] -> Either StoreError Contact + toContact' contactId activeConn [(profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)] = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn in Right Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs} @@ -1781,10 +1803,10 @@ getConnectionEntity db user@User {userId, userContactId} agentConnId = do mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) JOIN groups g ON g.group_id = m.group_id @@ -1884,10 +1906,10 @@ getGroupAndMember db User {userId, userContactId} groupMemberId = mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter FROM group_members m @@ -2120,7 +2142,7 @@ getUserGroupDetails db User {userId, userContactId} = [sql| SELECT g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image, g.host_conn_custom_user_profile_id, g.enable_ntfs, gp.preferences, g.created_at, g.updated_at, g.chat_ts, mu.group_member_id, g.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, - mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences + mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g JOIN group_profiles gp USING (group_profile_id) JOIN group_members mu USING (group_id) @@ -2167,7 +2189,7 @@ groupMemberQuery = [sql| SELECT m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter FROM group_members m @@ -2239,20 +2261,20 @@ getGroupInvitation db user groupId = firstRow fromOnly (SEGroupNotFound groupId) $ DB.query db "SELECT g.inv_queue_info FROM groups g WHERE g.group_id = ? AND g.user_id = ?" (groupId, userId) -type GroupMemberRow = ((Int64, Int64, MemberId, GroupMemberRole, GroupMemberCategory, GroupMemberStatus) :. (Maybe Int64, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, LocalAlias, Maybe Preferences)) +type GroupMemberRow = ((Int64, Int64, MemberId, GroupMemberRole, GroupMemberCategory, GroupMemberStatus) :. (Maybe Int64, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) -type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus) :. (Maybe Int64, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe LocalAlias, Maybe Preferences)) +type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus) :. (Maybe Int64, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, localAlias, preferences)) = - let memberProfile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences)) = + let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} invitedBy = toInvitedBy userContactId invitedById activeConn = Nothing in GroupMember {..} toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember -toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just memberRole, Just memberCategory, Just memberStatus) :. (invitedById, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, Just localAlias, contactPreferences)) = - Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, localAlias, contactPreferences)) +toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just memberRole, Just memberCategory, Just memberStatus) :. (invitedById, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences)) = + Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, memberRole, memberCategory, memberStatus) :. (invitedById, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences)) toMaybeGroupMember _ _ = Nothing createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupId -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> ExceptT StoreError IO GroupMember @@ -2325,7 +2347,7 @@ getContactViaMember db user@User {userId} GroupMember {groupMemberId} = [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -2380,13 +2402,13 @@ updateGroupMemberStatusById db userId groupMemberId memStatus = do -- | add new member with profile createNewGroupMember :: DB.Connection -> User -> GroupInfo -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember -createNewGroupMember db user@User {userId} gInfo memInfo@(MemberInfo _ _ Profile {displayName, fullName, image, preferences}) memCategory memStatus = +createNewGroupMember db user@User {userId} gInfo memInfo@(MemberInfo _ _ Profile {displayName, fullName, image, contactLink, preferences}) memCategory memStatus = ExceptT . withLocalDisplayName db userId displayName $ \localDisplayName -> do currentTs <- getCurrentTime DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) memProfileId <- insertedRowId db let newMember = NewGroupMember @@ -2629,10 +2651,10 @@ getViaGroupMember db User {userId, userContactId} Contact {contactId} = mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- via GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, - m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter FROM group_members m @@ -2664,7 +2686,7 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = db [sql| SELECT - ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, ct.via_group, ct.contact_used, ct.enable_ntfs, p.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter @@ -2681,9 +2703,9 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = |] (userId, groupMemberId) where - toContact' :: ((ContactId, ProfileId, ContactName, Text, Text, Maybe ImageData, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)) :. ConnectionRow -> Contact - toContact' (((contactId, profileId, localDisplayName, displayName, fullName, image, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} + toContact' :: ((ContactId, ProfileId, ContactName, Text, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Int64, Bool, Maybe Bool) :. (Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime)) :. ConnectionRow -> Contact + toContact' (((contactId, profileId, localDisplayName, displayName, fullName, image, contactLink, localAlias, viaGroup, contactUsed, enableNtfs_) :. (preferences, userPreferences, createdAt, updatedAt, chatTs)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe True enableNtfs_} activeConn = toConnection connRow mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito activeConn @@ -3698,7 +3720,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe -- GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.local_alias, p.preferences + p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) LEFT JOIN contacts c ON m.contact_id = c.contact_id @@ -3738,7 +3760,7 @@ getDirectChatPreviews_ db user@User {userId} = do [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -3807,7 +3829,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, -- ChatStats COALESCE(ChatStats.UnreadCount, 0), COALESCE(ChatStats.MinUnread, 0), g.unread_chat, -- ChatItem @@ -3817,17 +3839,17 @@ getGroupChatPreviews_ db User {userId, userContactId} = do -- Maybe GroupMember - sender m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, -- quoted ChatItem ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, -- quoted GroupMember rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category, rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.image, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category, dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, - dbp.display_name, dbp.full_name, dbp.image, dbp.local_alias, dbp.preferences + dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences FROM groups g JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id JOIN group_members mu ON mu.group_id = g.group_id @@ -3873,7 +3895,7 @@ getContactRequestChatPreviews_ db User {userId} = [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.user_contact_link_id, - c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at + c.agent_conn_id, cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, p.preferences, cr.created_at, cr.updated_at FROM contact_requests cr JOIN connections c ON c.user_contact_link_id = cr.user_contact_link_id JOIN contact_profiles p ON p.contact_profile_id = cr.contact_profile_id @@ -4068,7 +4090,7 @@ getContact db user@User {userId} contactId = [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.local_alias, ct.contact_used, ct.enable_ntfs, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.enable_ntfs, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.local_alias, @@ -4197,7 +4219,7 @@ getGroupInfo db User {userId, userContactId} groupId = -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.member_role, mu.member_category, mu.member_status, mu.invited_by, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.local_alias, pu.preferences + pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences FROM groups g JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id JOIN group_members mu ON mu.group_id = g.group_id @@ -4595,17 +4617,17 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do -- GroupMember m.group_member_id, m.group_id, m.member_id, m.member_role, m.member_category, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.local_alias, p.preferences, + p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, -- quoted ChatItem ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, -- quoted GroupMember rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category, rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.image, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category, dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, - dbp.display_name, dbp.full_name, dbp.image, dbp.local_alias, dbp.preferences + dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences FROM chat_items i LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN group_members m ON m.group_member_id = i.group_member_id diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 22b0041281..de89599f2d 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1071,6 +1071,7 @@ data Profile = Profile { displayName :: ContactName, fullName :: Text, image :: Maybe ImageData, + contactLink :: Maybe ConnReqContact, preferences :: Maybe Preferences -- fields that should not be read into this data type to prevent sending them as part of profile to contacts: -- - contact_profile_id @@ -1099,6 +1100,7 @@ data LocalProfile = LocalProfile displayName :: ContactName, fullName :: Text, image :: Maybe ImageData, + contactLink :: Maybe ConnReqContact, preferences :: Maybe Preferences, localAlias :: LocalAlias } @@ -1112,12 +1114,12 @@ localProfileId :: LocalProfile -> ProfileId localProfileId = profileId toLocalProfile :: ProfileId -> Profile -> LocalAlias -> LocalProfile -toLocalProfile profileId Profile {displayName, fullName, image, preferences} localAlias = - LocalProfile {profileId, displayName, fullName, image, preferences, localAlias} +toLocalProfile profileId Profile {displayName, fullName, image, contactLink, preferences} localAlias = + LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} fromLocalProfile :: LocalProfile -> Profile -fromLocalProfile LocalProfile {displayName, fullName, image, preferences} = - Profile {displayName, fullName, image, preferences} +fromLocalProfile LocalProfile {displayName, fullName, image, contactLink, preferences} = + Profile {displayName, fullName, image, contactLink, preferences} data GroupProfile = GroupProfile { displayName :: GroupName, diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5eedb8e638..f4ae01c863 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -811,8 +811,9 @@ viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} = ] viewContactInfo :: Contact -> ConnectionStats -> Maybe Profile -> [StyledString] -viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias}} stats incognitoProfile = +viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}} stats incognitoProfile = ["contact ID: " <> sShow contactId] <> viewConnectionStats stats + <> maybe [] (\l -> ["contact address: " <> (plain . strEncode) l]) contactLink <> maybe ["you've shared main profile with this contact"] (\p -> ["you've shared incognito profile with this contact: " <> incognitoProfile' p]) @@ -872,11 +873,12 @@ viewSwitchPhase SPCompleted = "changed address" viewSwitchPhase phase = plain (strEncode phase) <> " changing address" viewUserProfileUpdated :: Profile -> Profile -> [StyledString] -viewUserProfileUpdated Profile {displayName = n, fullName, image, preferences} Profile {displayName = n', fullName = fullName', image = image', preferences = prefs'} = +viewUserProfileUpdated Profile {displayName = n, fullName, image, contactLink, preferences} Profile {displayName = n', fullName = fullName', image = image', contactLink = contactLink', preferences = prefs'} = profileUpdated <> viewPrefsUpdated preferences prefs' where profileUpdated - | n == n' && fullName == fullName' && image == image' = [] + | n == n' && fullName == fullName' && image == image' && contactLink == contactLink' = [] + | n == n' && fullName == fullName' && image == image' = [if isNothing contactLink' then "contact address removed" else "new contact address set"] | n == n' && fullName == fullName' = [if isNothing image' then "profile image removed" else "profile image updated"] | n == n' = ["user full name " <> (if T.null fullName' || fullName' == n' then "removed" else "changed to " <> plain fullName') <> notified] | otherwise = ["user profile is changed to " <> ttyFullName n' fullName' <> notified] @@ -980,9 +982,13 @@ viewConnectionAliasUpdated PendingContactConnection {pccConnId, localAlias} viewContactUpdated :: Contact -> Contact -> [StyledString] viewContactUpdated - Contact {localDisplayName = n, profile = LocalProfile {fullName}} - Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName'}} - | n == n' && fullName == fullName' = [] + Contact {localDisplayName = n, profile = LocalProfile {fullName, contactLink}} + Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName', contactLink = contactLink'}} + | n == n' && fullName == fullName' && contactLink == contactLink' = [] + | n == n' && fullName == fullName' = + if isNothing contactLink' + then [ttyContact n <> " removed contact address"] + else [ttyContact n <> " set new contact address, use " <> highlight ("/info " <> n) <> " to view"] | n == n' = ["contact " <> ttyContact n <> fullNameUpdate] | otherwise = [ "contact " <> ttyContact n <> " changed to " <> ttyFullName n' fullName', diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index cba818295f..a5dc083f71 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -19,6 +19,7 @@ chatProfileTests = do it "update user profile with image" testUpdateProfileImage describe "user contact link" $ do describe "create and connect via contact link" testUserContactLink + it "add contact link to profile" testProfileLink it "auto accept contact requests" testUserContactLinkAutoAccept it "deduplicate contact requests" testDeduplicateContactRequests it "deduplicate contact requests with profile change" testDeduplicateContactRequestsProfileChange @@ -134,6 +135,83 @@ testUserContactLink = versionTestMatrix3 $ \alice bob cath -> do alice @@@ [("@cath", lastChatFeature), ("@bob", "hey")] alice <##> cath +testProfileLink :: HasCallStack => FilePath -> IO () +testProfileLink = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + alice ##> "/ad" + cLink <- getContactLink alice True + + bob ##> ("/c " <> cLink) + alice <#? bob + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request..." + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + alice <##> bob + + alice ##> "/pa on" + alice <## "new contact address set" + + bob <## "alice set new contact address, use /info alice to view" + checkAliceProfileLink bob cLink + + cath ##> ("/c " <> cLink) + alice <#? cath + alice ##> "/ac cath" + alice <## "cath (Catherine): accepting contact request..." + concurrently_ + (cath <## "alice (Alice): contact is connected") + (alice <## "cath (Catherine): contact is connected") + alice <##> cath + + checkAliceProfileLink cath cLink + + alice ##> "/pa off" + alice <## "contact address removed" + + bob <## "alice removed contact address" + checkAliceNoProfileLink bob + + cath <## "alice removed contact address" + checkAliceNoProfileLink cath + + alice ##> "/pa on" + alice <## "new contact address set" + + bob <## "alice set new contact address, use /info alice to view" + checkAliceProfileLink bob cLink + + cath <## "alice set new contact address, use /info alice to view" + checkAliceProfileLink cath cLink + + alice ##> "/da" + alice <## "Your chat address is deleted - accepted contacts will remain connected." + alice <## "To create a new chat address use /ad" + + bob <## "alice removed contact address" + checkAliceNoProfileLink bob + + cath <## "alice removed contact address" + checkAliceNoProfileLink cath + where + checkAliceProfileLink cc cLink = do + cc ##> "/info alice" + cc <## "contact ID: 2" + cc <##. "receiving messages via" + cc <##. "sending messages via" + cc <## ("contact address: " <> cLink) + cc <## "you've shared main profile with this contact" + cc <## "connection not verified, use /code command to see security code" + checkAliceNoProfileLink cc = do + cc ##> "/info alice" + cc <## "contact ID: 2" + cc <##. "receiving messages via" + cc <##. "sending messages via" + cc <## "you've shared main profile with this contact" + cc <## "connection not verified, use /code command to see security code" + testUserContactLinkAutoAccept :: HasCallStack => FilePath -> IO () testUserContactLinkAutoAccept = testChat4 aliceProfile bobProfile cathProfile danProfile $ diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index a373b7cea6..18866fe2d1 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -31,16 +31,16 @@ defaultPrefs :: Maybe Preferences defaultPrefs = Just $ toChatPrefs defaultChatPrefs aliceProfile :: Profile -aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, preferences = defaultPrefs} +aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} bobProfile :: Profile -bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,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"), preferences = defaultPrefs} +bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,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"), contactLink = Nothing, preferences = defaultPrefs} cathProfile :: Profile -cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, preferences = defaultPrefs} +cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} danProfile :: Profile -danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, preferences = defaultPrefs} +danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} xit' :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a) xit' = if os == "linux" then xit else it diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 4c1d056bf7..dcc7e88c9f 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -92,7 +92,7 @@ testGroupPreferences :: Maybe GroupPreferences testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, voice = Just VoiceGroupPreference {enable = FEOn}, fullDelete = Nothing} testProfile :: Profile -testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), preferences = testChatPreferences} +testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences} testGroupProfile :: GroupProfile testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, image = Nothing, groupPreferences = testGroupPreferences} @@ -198,7 +198,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do #==# XInfo testProfile it "x.info with empty full name" $ "{\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"voice\":{\"allow\":\"yes\"}}}}}" - #==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, preferences = testChatPreferences} + #==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, contactLink = Nothing, preferences = testChatPreferences} it "x.contact with xContactId" $ "{\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"voice\":{\"allow\":\"yes\"}}}}}" #==# XContact testProfile (Just $ XContactId "\1\2\3\4")