diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index b5c5b8392..64eeb2fc1 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -13,27 +13,22 @@ struct ContentView: View { @State private var showNotificationAlert = false var body: some View { - if let user = chatModel.currentUser { - ChatListView(user: user) - .onAppear { - do { - try apiStartChat() - try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path) - chatModel.userAddress = try apiGetUserAddress() - chatModel.userSMPServers = try getUserSMPServers() - chatModel.chats = try apiGetChats() - } catch { - fatalError("Failed to start or load chats: \(error)") + ZStack { + if let step = chatModel.onboardingStage { + if case .onboardingComplete = step, + let user = chatModel.currentUser { + ChatListView(user: user) + .onAppear { + NtfManager.shared.requestAuthorization(onDeny: { + alertManager.showAlert(notificationAlert()) + }) } - ChatReceiver.shared.start() - NtfManager.shared.requestAuthorization(onDeny: { - alertManager.showAlert(notificationAlert()) - }) + } else { + OnboardingView(onboarding: step) } - .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } - } else { - WelcomeView() + } } + .alert(isPresented: $alertManager.presentAlert) { alertManager.alertView! } } func notificationAlert() -> Alert { @@ -50,6 +45,37 @@ struct ContentView: View { } } +func connectViaUrl() { + let m = ChatModel.shared + if let url = m.appOpenUrl { + m.appOpenUrl = nil + AlertManager.shared.showAlert(connectViaUrlAlert(url)) + } +} + +func connectViaUrlAlert(_ url: URL) -> Alert { + var path = url.path + logger.debug("ChatListView.connectViaUrlAlert path: \(path)") + if (path == "/contact" || path == "/invitation") { + path.removeFirst() + let action: ConnReqType = path == "contact" ? .contact : .invitation + let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") + let title: LocalizedStringKey + if case .contact = action { title = "Connect via contact link?" } + else { title = "Connect via one-time link?" } + return Alert( + title: Text(title), + message: Text("Your profile will be sent to the contact that you received this link from"), + primaryButton: .default(Text("Connect")) { + connectViaLink(link) + }, + secondaryButton: .cancel() + ) + } else { + return Alert(title: Text("Error: URL is invalid")) + } +} + final class AlertManager: ObservableObject { static let shared = AlertManager() @Published var presentAlert = false diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 3fcc40b16..63b9f04ff 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -11,6 +11,7 @@ import Combine import SwiftUI final class ChatModel: ObservableObject { + @Published var onboardingStage: OnboardingStage? @Published var currentUser: User? // list of chat "previews" @Published var chats: [Chat] = [] diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 394fb6c26..9f02f5220 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -10,6 +10,7 @@ import Foundation import UIKit import Dispatch import BackgroundTasks +import SwiftUI private var chatController: chat_ctrl? @@ -261,6 +262,16 @@ func apiConnect(connReq: String) async throws -> ConnReqType? { message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." ) return nil + case let .chatCmdError(.errorAgent(.INTERNAL(internalErr))): + if internalErr == "SEUniqueID" { + am.showAlertMsg( + title: "Already connected?", + message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))." + ) + return nil + } else { + throw r + } default: throw r } } @@ -424,13 +435,40 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws { } func initializeChat() { + logger.debug("initializeChat") do { - ChatModel.shared.currentUser = try apiGetActiveUser() + let m = ChatModel.shared + m.currentUser = try apiGetActiveUser() + if m.currentUser == nil { + m.onboardingStage = .step1_SimpleXInfo + } else { + startChat() + } } catch { fatalError("Failed to initialize chat controller or database: \(error)") } } +func startChat() { + logger.debug("startChat") + do { + let m = ChatModel.shared + try apiStartChat() + try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path) + m.userAddress = try apiGetUserAddress() + m.userSMPServers = try getUserSMPServers() + m.chats = try apiGetChats() + withAnimation { + m.onboardingStage = m.chats.isEmpty + ? .step3_MakeConnection + : .onboardingComplete + } + ChatReceiver.shared.start() + } catch { + fatalError("Failed to start or load chats: \(error)") + } +} + class ChatReceiver { private var receiveLoop: Task? private var receiveMessages = true diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 5f83f22d9..7c02bd64e 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -28,30 +28,19 @@ struct ChatHelp: View { } VStack(alignment: .leading, spacing: 10) { - Text("To start a new chat") + Text("To make a new connection") .font(.title2) .fontWeight(.bold) HStack(spacing: 8) { Text("Tap button ") NewChatButton() - Text("above, then:") + Text("above, then choose:") } - Text("**Add new contact**: to create your one-time QR Code for your contact.") - Text("**Scan QR code**: to connect to your contact who shows QR code to you.") - } - .padding(.top, 24) - - VStack(alignment: .leading, spacing: 10) { - Text("To connect via link") - .font(.title2) - .fontWeight(.bold) - - Text("If you received SimpleX Chat invitation link you can open it in your browser:") - - Text("💻 desktop: scan displayed QR code from the app, via **Scan QR code**.") - Text("📱 mobile: tap **Open in mobile app**, then tap **Connect** in the app.") + Text("**Create link / QR code** for your contact to use.") + Text("**Paste received link** or open it in the browser and tap **Open in mobile app**.") + Text("**Scan QR code**: to connect to your contact in person or via video call.") } .padding(.top, 24) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 0761350b5..6b0af5da7 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -21,13 +21,9 @@ struct ChatListView: View { var body: some View { let v = NavigationView { List { - if chatModel.chats.isEmpty { - ChatHelp(showSettings: $showSettings) - } else { - ForEach(filteredChats()) { chat in - ChatListNavLink(chat: chat, showCallView: $showCallView) - .padding(.trailing, -16) - } + ForEach(filteredChats()) { chat in + ChatListNavLink(chat: chat, showCallView: $showCallView) + .padding(.trailing, -16) } } .onChange(of: chatModel.chatId) { _ in @@ -36,15 +32,15 @@ struct ChatListView: View { chatModel.popChat(chatId) } } - .onChange(of: chatModel.appOpenUrl) { _ in - if let url = chatModel.appOpenUrl { - chatModel.appOpenUrl = nil - AlertManager.shared.showAlert(connectViaUrlAlert(url)) - } + .onChange(of: chatModel.chats.isEmpty) { empty in + if !empty { return } + withAnimation { chatModel.onboardingStage = .step3_MakeConnection } } + .onChange(of: chatModel.appOpenUrl) { _ in connectViaUrl() } + .onAppear() { connectViaUrl() } .offset(x: -8) .listStyle(.plain) - .navigationTitle(chatModel.chats.isEmpty ? "Welcome \(user.displayName)!" : "Your chats") + .navigationTitle("Your chats") .navigationBarTitleDisplayMode(chatModel.chats.count > 8 ? .inline : .large) .toolbar { ToolbarItem(placement: .navigationBarLeading) { @@ -101,29 +97,6 @@ struct ChatListView: View { } } - private func connectViaUrlAlert(_ url: URL) -> Alert { - var path = url.path - logger.debug("ChatListView.connectViaUrlAlert path: \(path)") - if (path == "/contact" || path == "/invitation") { - path.removeFirst() - let action: ConnReqType = path == "contact" ? .contact : .invitation - let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") - let title: LocalizedStringKey - if case .contact = action { title = "Connect via contact link?" } - else { title = "Connect via one-time link?" } - return Alert( - title: Text(title), - message: Text("Your profile will be sent to the contact that you received this link from"), - primaryButton: .default(Text("Connect")) { - connectViaLink(link) - }, - secondaryButton: .cancel() - ) - } else { - return Alert(title: Text("Error: URL is invalid")) - } - } - private func answerCallAlert(_ contact: Contact, _ invitation: CallInvitation) { AlertManager.shared.showAlert(Alert( title: Text("Incoming call"), diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift index ecd3815fc..99f62e65c 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactView.swift @@ -12,26 +12,25 @@ import CoreImage.CIFilterBuiltins struct AddContactView: View { var connReqInvitation: String var body: some View { - VStack { - Text("Add contact") + VStack(alignment: .leading) { + Text("One-time invitation link") .font(.title) + .padding(.vertical) + Text("Your contact can scan it from the app") .padding(.bottom) - Text("Show QR code to your contact\nto scan from the app") - .font(.title2) - .multilineTextAlignment(.center) QRCode(uri: connReqInvitation) - .padding() - Text("If you cannot meet in person, you can **show QR code in the video call**, or you can share the invitation link via any other channel.") - .font(.subheadline) - .multilineTextAlignment(.center) - .padding(.horizontal) + .padding(.bottom) + 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") } - .padding() + .frame(maxWidth: .infinity, alignment: .center) } + .padding() + .frame(maxHeight: .infinity, alignment: .top) } } diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift index ad0a133a5..ddd693c8f 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatButton.swift @@ -8,12 +8,18 @@ import SwiftUI +enum NewChatAction: Identifiable { + case createLink + case pasteLink + case scanQRCode + + var id: NewChatAction { get { self } } +} + struct NewChatButton: View { @State private var showAddChat = false - @State private var addContact = false - @State private var connReqInvitation: String = "" - @State private var scanToConnect = false - @State private var pasteToConnect = false + @State private var connReq: String = "" + @State private var actionSheet: NewChatAction? var body: some View { Button { showAddChat = true } label: { @@ -21,24 +27,22 @@ struct NewChatButton: View { } .confirmationDialog("Add contact to start a new chat", isPresented: $showAddChat, titleVisibility: .visible) { Button("Create link / QR code") { addContactAction() } - Button("Paste received link") { pasteToConnect = true } - Button("Scan QR code") { scanToConnect = true } + Button("Paste received link") { actionSheet = .pasteLink } + Button("Scan QR code") { actionSheet = .scanQRCode } + } + .sheet(item: $actionSheet) { sheet in + switch sheet { + case .createLink: AddContactView(connReqInvitation: connReq) + case .pasteLink: PasteToConnectView(openedSheet: $actionSheet) + case .scanQRCode: ScanToConnectView(openedSheet: $actionSheet) + } } - .sheet(isPresented: $addContact, content: { - AddContactView(connReqInvitation: connReqInvitation) - }) - .sheet(isPresented: $scanToConnect, content: { - ScanToConnectView(openedSheet: $scanToConnect) - }) - .sheet(isPresented: $pasteToConnect, content: { - PasteToConnectView(openedSheet: $pasteToConnect) - }) } func addContactAction() { do { - connReqInvitation = try apiAddContact() - addContact = true + connReq = try apiAddContact() + actionSheet = .createLink } catch { DispatchQueue.global().async { connectionErrorAlert(error) @@ -53,12 +57,12 @@ enum ConnReqType: Equatable { case invitation } -func connectViaLink(_ connectionLink: String, _ openedSheet: Binding? = nil) { +func connectViaLink(_ connectionLink: String, _ openedSheet: Binding? = nil) { Task { do { let res = try await apiConnect(connReq: connectionLink) DispatchQueue.main.async { - openedSheet?.wrappedValue = false + openedSheet?.wrappedValue = nil if let connReqType = res { connectionReqSentAlert(connReqType) } @@ -66,7 +70,7 @@ func connectViaLink(_ connectionLink: String, _ openedSheet: Binding? = ni } catch { logger.error("connectViaLink apiConnect error: \(responseError(error))") DispatchQueue.main.async { - openedSheet?.wrappedValue = false + openedSheet?.wrappedValue = nil connectionErrorAlert(error) } } @@ -74,7 +78,7 @@ func connectViaLink(_ connectionLink: String, _ openedSheet: Binding? = ni } func connectionErrorAlert(_ error: Error) { - AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(error.localizedDescription)") + AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(responseError(error))") } func connectionReqSentAlert(_ type: ConnReqType) { diff --git a/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift b/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift index e3f9f354a..2d167671b 100644 --- a/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift +++ b/apps/ios/Shared/Views/NewChat/PasteToConnectView.swift @@ -9,23 +9,19 @@ import SwiftUI struct PasteToConnectView: View { - @Binding var openedSheet: Bool + @Binding var openedSheet: NewChatAction? @State private var connectionLink: String = "" var body: some View { VStack(alignment: .leading) { Text("Connect via link") .font(.title) - .padding([.bottom]) - .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical) Text("Paste the link you received into the box below to connect with your contact.") - .multilineTextAlignment(.leading) Text("Your profile will be sent to the contact that you received this link from") - .multilineTextAlignment(.leading) .padding(.bottom) TextEditor(text: $connectionLink) .onSubmit(connect) - .font(.body) .textInputAutocapitalization(.never) .disableAutocorrection(true) .allowsTightening(false) @@ -60,9 +56,9 @@ struct PasteToConnectView: View { .padding(.bottom) Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button") - .multilineTextAlignment(.leading) } .padding() + .frame(maxHeight: .infinity, alignment: .top) } private func connect() { @@ -72,7 +68,7 @@ struct PasteToConnectView: View { struct PasteToConnectView_Previews: PreviewProvider { static var previews: some View { - @State var openedSheet: Bool = true + @State var openedSheet: NewChatAction? = nil return PasteToConnectView(openedSheet: $openedSheet) } } diff --git a/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift b/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift index aed53a2d5..80db91d52 100644 --- a/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift +++ b/apps/ios/Shared/Views/NewChat/ScanToConnectView.swift @@ -10,28 +10,26 @@ import SwiftUI import CodeScanner struct ScanToConnectView: View { - @Binding var openedSheet: Bool + @Binding var openedSheet: NewChatAction? var body: some View { - VStack { + VStack(alignment: .leading) { Text("Scan QR code") .font(.title) - .padding(.bottom) + .padding(.vertical) Text("Your chat profile will be sent to your contact") - .font(.title2) - .multilineTextAlignment(.center) - .padding() + .padding(.bottom) ZStack { CodeScannerView(codeTypes: [.qr], completion: processQRCode) .aspectRatio(1, contentMode: .fit) .border(.gray) } - .padding(12) + .padding(.bottom) Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.") - .font(.subheadline) - .multilineTextAlignment(.center) - .padding(.horizontal) + .padding(.bottom) } + .padding() + .frame(maxHeight: .infinity, alignment: .top) } func processQRCode(_ resp: Result) { @@ -40,14 +38,14 @@ struct ScanToConnectView: View { Task { connectViaLink(r.string, $openedSheet) } case let .failure(e): logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)") - openedSheet = false + openedSheet = nil } } } struct ConnectContactView_Previews: PreviewProvider { static var previews: some View { - @State var openedSheet: Bool = true + @State var openedSheet: NewChatAction? = nil return ScanToConnectView(openedSheet: $openedSheet) } } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift new file mode 100644 index 000000000..b6e2bbe7e --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -0,0 +1,124 @@ +// +// CreateProfile.swift +// SimpleX (iOS) +// +// Created by Evgeny on 07/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct CreateProfile: View { + @EnvironmentObject var m: ChatModel + @State private var displayName: String = "" + @State private var fullName: String = "" + @FocusState private var focusDisplayName + @FocusState private var focusFullName + + var body: some View { + GeometryReader { g in + VStack(alignment: .leading) { + Text("Create your profile") + .font(.largeTitle) + .padding(.bottom, 4) + Text("Your profile, contacts and delivered messages are stored on your device.") + .padding(.bottom, 4) + Text("The profile is only shared with your contacts.") + .padding(.bottom) + ZStack(alignment: .topLeading) { + if !validDisplayName(displayName) { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .padding(.top, 4) + } + textField("Display name", text: $displayName) + .focused($focusDisplayName) + .submitLabel(.next) + .onSubmit { + if canCreateProfile() { focusFullName = true } + else { focusDisplayName = true } + } + } + textField("Full name (optional)", text: $fullName) + .focused($focusFullName) + .submitLabel(.go) + .onSubmit { + if canCreateProfile() { createProfile() } + else { focusFullName = true } + } + + Spacer() + + HStack { + Button { + hideKeyboard() + withAnimation { m.onboardingStage = .step1_SimpleXInfo } + } label: { + HStack { + Image(systemName: "lessthan") + Text("About SimpleX") + } + } + + Spacer() + + HStack { + Button { + createProfile() + } label: { + Text("Create") + Image(systemName: "greaterthan") + } + .disabled(!canCreateProfile()) + } + } + } + .onAppear() { + focusDisplayName = true + } + } + .padding() + } + + func textField(_ placeholder: LocalizedStringKey, text: Binding) -> some View { + TextField(placeholder, text: text) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .padding(.leading, 28) + .padding(.bottom) + } + + func createProfile() { + hideKeyboard() + let profile = Profile( + displayName: displayName, + fullName: fullName + ) + do { + m.currentUser = try apiCreateActiveUser(profile) + startChat() + withAnimation { m.onboardingStage = .step3_MakeConnection } + + } catch { + fatalError("Failed to create user: \(error)") + } + } + + func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } + + func validDisplayName(_ name: String) -> Bool { + name.firstIndex(of: " ") == nil + } + + func canCreateProfile() -> Bool { + displayName != "" && validDisplayName(displayName) + } +} + +struct CreateProfile_Previews: PreviewProvider { + static var previews: some View { + CreateProfile() + } +} diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift new file mode 100644 index 000000000..fdd73d263 --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -0,0 +1,54 @@ +// +// HowItWorks.swift +// SimpleX (iOS) +// +// Created by Evgeny on 08/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct HowItWorks: View { + @EnvironmentObject var m: ChatModel + var onboarding: Bool + + var body: some View { + VStack(alignment: .leading) { + Text("How SimpleX works") + .font(.largeTitle) + .padding(.vertical) + ScrollView { + VStack(alignment: .leading) { + Group { + Text("Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*") + Text("To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.") + Text("You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them.") + Text("Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.") + if onboarding { + Text("Read more in our GitHub repository.") + } else { + Text("Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme).") + } + } + .padding(.bottom) + } + } + + Spacer() + + if onboarding { + OnboardingActionButton() + .padding(.bottom, 8) + } + } + .lineLimit(10) + .padding() + .frame(maxHeight: .infinity, alignment: .top) + } +} + +struct HowItWorks_Previews: PreviewProvider { + static var previews: some View { + HowItWorks(onboarding: true) + } +} diff --git a/apps/ios/Shared/Views/Onboarding/MakeConnection.swift b/apps/ios/Shared/Views/Onboarding/MakeConnection.swift new file mode 100644 index 000000000..2d0b7b88e --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/MakeConnection.swift @@ -0,0 +1,146 @@ +// +// MakeConnection.swift +// SimpleX (iOS) +// +// Created by Evgeny on 07/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct MakeConnection: View { + @EnvironmentObject var m: ChatModel + @State private var connReq: String = "" + @State private var actionSheet: NewChatAction? + + var body: some View { + VStack(alignment: .leading) { + SettingsButton().padding(.bottom, 1) + + if let user = m.currentUser { + Text("Welcome \(user.displayName)!") + .font(.largeTitle) + .multilineTextAlignment(.leading) + .padding(.bottom, 8) + } else { + Text("Make a private connection") + .font(.largeTitle) + .padding(.bottom) + } + + ScrollView { + VStack(alignment: .leading) { + Text("To make your first private connection, choose **one of the following**:") + .padding(.bottom) + + actionRow( + icon: "qrcode", + title: "Create 1-time link / QR code", + text: "It's secure to share - only one contact can use it." + ) { addContactAction() } + + actionRow( + icon: "link", + title: "Paste the link you received", + text: "Or open the link in the browser and tap **Open in mobile**." + ) { actionSheet = .pasteLink } + + actionRow( + icon: "qrcode.viewfinder", + title: "Scan contact's QR code", + text: "In person or via a video call – the most secure way to connect." + ) { actionSheet = .scanQRCode } + + Text("or") + .padding(.bottom) + .frame(maxWidth: .infinity) + + actionRow( + icon: "number", + title: "Connect with the developers", + text: "To ask any questions and to receive SimpleX Chat updates." + ) { + DispatchQueue.main.async { + UIApplication.shared.open(simplexTeamURL) + } + } + } + } + + Spacer() + + Button { + withAnimation { m.onboardingStage = .step1_SimpleXInfo } + } label: { + HStack { + Image(systemName: "lessthan") + Text("About SimpleX") + } + } + .padding(.bottom, 8) + .padding(.bottom) + } + .sheet(item: $actionSheet) { sheet in + switch sheet { + case .createLink: AddContactView(connReqInvitation: connReq) + case .pasteLink: PasteToConnectView(openedSheet: $actionSheet) + case .scanQRCode: ScanToConnectView(openedSheet: $actionSheet) + } + } + .onChange(of: actionSheet) { _ in checkOnboarding() } + .onChange(of: m.chats.isEmpty) { _ in checkOnboarding() } + .onChange(of: m.appOpenUrl) { _ in connectViaUrl() } + .onAppear() { connectViaUrl() } + .padding(.horizontal) + .frame(maxHeight: .infinity, alignment: .top) + } + + private func checkOnboarding() { + if actionSheet == nil && !m.chats.isEmpty { + withAnimation { m.onboardingStage = .onboardingComplete } + } + } + + private func addContactAction() { + do { + connReq = try apiAddContact() + actionSheet = .createLink + } catch { + DispatchQueue.global().async { + connectionErrorAlert(error) + } + logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") + } + } + + private func actionRow(icon: String, title: LocalizedStringKey, text: LocalizedStringKey, action: @escaping () -> Void) -> some View { + HStack(alignment: .top, spacing: 20) { + Button(action: action, label: { + Image(systemName: icon) + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + .padding(.leading, 6) + .padding(.top, 6) + }) + VStack(alignment: .leading) { + Button(action: action, label: { + Text(title) + .font(.headline) + .multilineTextAlignment(.leading) + }) + Text(text) + } + } + .padding(.bottom) + } +} + +struct MakeConnection_Previews: PreviewProvider { + static var previews: some View { + let chatModel = ChatModel() + chatModel.currentUser = User.sampleData + return MakeConnection() + .environmentObject(chatModel) + } +} diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift new file mode 100644 index 000000000..04fcbf42e --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -0,0 +1,35 @@ +// +// OnboardingStepsView.swift +// SimpleX (iOS) +// +// Created by Evgeny on 07/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct OnboardingView: View { + var onboarding: OnboardingStage + + var body: some View { + switch onboarding { + case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) + case .step2_CreateProfile: CreateProfile() + case .step3_MakeConnection: MakeConnection() + case .onboardingComplete: EmptyView() + } + } +} + +enum OnboardingStage { + case step1_SimpleXInfo + case step2_CreateProfile + case step3_MakeConnection + case onboardingComplete +} + +struct OnboardingStepsView_Previews: PreviewProvider { + static var previews: some View { + OnboardingView(onboarding: .step1_SimpleXInfo) + } +} diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift new file mode 100644 index 000000000..2468b7290 --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -0,0 +1,102 @@ +// +// SimpleXInfo.swift +// SimpleX (iOS) +// +// Created by Evgeny on 07/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct SimpleXInfo: View { + @EnvironmentObject var m: ChatModel + @State private var showHowItWorks = false + var onboarding: Bool + + var body: some View { + GeometryReader { g in + VStack(alignment: .leading) { + Image("logo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: g.size.width * 0.7) + .padding(.bottom) + Text("The next generation of private messaging") + .font(.title) + .padding(.bottom) + + infoRow("🎭", "Privacy redefined", + "The 1st platform without any user identifiers – private by design.") + infoRow("📭", "Immune to spam and abuse", + "People can connect to you only via the links you share.") + infoRow("🤝", "Decentralized", + "Open-source protocol and code – anybody can run the servers.") + + Spacer() + + if onboarding { + OnboardingActionButton() + Spacer() + } + + Button { + showHowItWorks = true + } label: { + Label("How it works", systemImage: "info.circle") + .font(.subheadline) + } + .padding(.bottom, 8) + .frame(maxWidth: .infinity) + } + .sheet(isPresented: $showHowItWorks) { + HowItWorks(onboarding: onboarding) + } + } + .padding() + } + + private func infoRow(_ emoji: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey) -> some View { + HStack(alignment: .top) { + Text(emoji) + .font(mediumEmojiFont) + .frame(width: 40) + VStack(alignment: .leading) { + Text(title).font(.headline) + Text(text) + } + } + .padding(.bottom) + } +} + +struct OnboardingActionButton: View { + @EnvironmentObject var m: ChatModel + + var body: some View { + if m.currentUser == nil { + actionButton("Create your profile", onboarding: .step2_CreateProfile) + } else { + actionButton("Make a private connection", onboarding: .step3_MakeConnection) + } + } + + private func actionButton(_ label: LocalizedStringKey, onboarding: OnboardingStage) -> some View { + Button { + withAnimation { + m.onboardingStage = onboarding + } + } label: { + HStack { + Text(label).font(.title2) + Image(systemName: "greaterthan") + } + } + .frame(maxWidth: .infinity) + } +} + +struct SimpleXInfo_Previews: PreviewProvider { + static var previews: some View { + SimpleXInfo(onboarding: true) + } +} diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 5e716b76b..05af85086 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -17,6 +17,8 @@ let appBuild = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? let DEFAULT_USE_NOTIFICATIONS = "useNotifications" let DEFAULT_PENDING_CONNECTIONS = "pendingConnections" +private var indent: CGFloat = 36 + struct SettingsView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var chatModel: ChatModel @@ -54,29 +56,19 @@ struct SettingsView: View { UserAddress() .navigationTitle("Your chat address") } label: { - HStack { - Image(systemName: "qrcode") - .padding(.trailing, 8) - Text("Your SimpleX contact address") - } + settingsRow("qrcode") { Text("Your SimpleX contact address") } } } Section("Settings") { - HStack { - Image(systemName: "link") - .padding(.trailing, 8) + settingsRow("link") { Toggle("Show pending connections", isOn: $pendingConnections) } NavigationLink { SMPServers() .navigationTitle("Your SMP servers") } label: { - HStack { - Image(systemName: "server.rack") - .padding(.trailing, 4) - Text("SMP servers") - } + settingsRow("server.rack") { Text("SMP servers") } } } @@ -86,26 +78,23 @@ struct SettingsView: View { .navigationTitle("Welcome \(user.displayName)!") .frame(maxHeight: .infinity, alignment: .top) } label: { - HStack { - Image(systemName: "questionmark.circle") - .padding(.trailing, 8) - Text("How to use SimpleX Chat") - } + settingsRow("questionmark") { Text("How to use it") } + } + NavigationLink { + SimpleXInfo(onboarding: false) + .navigationBarTitle("", displayMode: .inline) + .frame(maxHeight: .infinity, alignment: .top) + } label: { + settingsRow("info") { Text("About SimpleX Chat") } } NavigationLink { MarkdownHelp() .navigationTitle("How to use markdown") .frame(maxHeight: .infinity, alignment: .top) } label: { - HStack { - Image(systemName: "textformat") - .padding(.trailing, 4) - Text("Markdown in messages") - } + settingsRow("textformat") { Text("Markdown in messages") } } - HStack { - Image(systemName: "number") - .padding(.trailing, 8) + settingsRow("number") { Button { showSettings = false DispatchQueue.main.async { @@ -115,30 +104,21 @@ struct SettingsView: View { Text("Chat with the developers") } } - HStack { - Image(systemName: "envelope") - .padding(.trailing, 4) - Text("[Send us email](mailto:chat@simplex.chat)") - } + settingsRow("envelope") { Text("[Send us email](mailto:chat@simplex.chat)") } } Section("Develop") { NavigationLink { TerminalView() } label: { - HStack { - Image(systemName: "terminal") - .frame(maxWidth: 24) - .padding(.trailing, 8) - Text("Chat console") - } + settingsRow("terminal") { Text("Chat console") } } - HStack { + ZStack(alignment: .leading) { Image(colorScheme == .dark ? "github_light" : "github") .resizable() .frame(width: 24, height: 24) - .padding(.trailing, 8) Text("Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)") + .padding(.leading, indent) } // if let token = chatModel.deviceToken { // HStack { @@ -158,6 +138,13 @@ struct SettingsView: View { } } + private func settingsRow(_ icon: String, content: @escaping () -> Content) -> some View { + ZStack(alignment: .leading) { + Image(systemName: icon).frame(maxWidth: 24, maxHeight: 24, alignment: .center) + content().padding(.leading, indent) + } + } + enum NotificationAlert { case enable case error(LocalizedStringKey, String) diff --git a/apps/ios/Shared/Views/WelcomeView.swift b/apps/ios/Shared/Views/WelcomeView.swift deleted file mode 100644 index 3f2d80000..000000000 --- a/apps/ios/Shared/Views/WelcomeView.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// WelcomeView.swift -// SimpleX -// -// Created by Evgeny Poberezkin on 18/01/2022. -// - -import SwiftUI - -struct WelcomeView: View { - @EnvironmentObject var chatModel: ChatModel - @State var displayName: String = "" - @State var fullName: String = "" - - var body: some View { - GeometryReader { g in - VStack(alignment: .leading) { - Image("logo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: g.size.width * 0.7) - .padding(.vertical) - Text("You control your chat!") - .font(.title) - .padding(.bottom) - Text("The messaging and application platform 100% private by design!") - .padding(.bottom, 8) - Text("Your profile, contacts and messages (once delivered) are only stored locally on your device.") - .padding(.bottom, 8) - Text("Create profile") - .font(.largeTitle) - .padding(.bottom, 4) - Text("(shared only with your contacts)") - .padding(.bottom) - ZStack(alignment: .topLeading) { - if !validDisplayName(displayName) { - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .padding(.top, 4) - } - TextField("Display name", text: $displayName) - .textInputAutocapitalization(.never) - .disableAutocorrection(true) - .padding(.leading, 28) - .padding(.bottom, 2) - } - .padding(.bottom) - TextField("Full name (optional)", text: $fullName) - .textInputAutocapitalization(.never) - .disableAutocorrection(true) - .padding(.leading, 28) - .padding(.bottom) - Button("Create") { - let profile = Profile( - displayName: displayName, - fullName: fullName - ) - do { - let user = try apiCreateActiveUser(profile) - chatModel.currentUser = user - } catch { - fatalError("Failed to create user: \(error)") - } - } - .disabled(!validDisplayName(displayName) || displayName == "") - } - } - .padding() - } - - func validDisplayName(_ name: String) -> Bool { - name.firstIndex(of: " ") == nil - } -} - -struct WelcomeView_Previews: PreviewProvider { - static var previews: some View { - WelcomeView() - } -} 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 70fcfcdd2..f4bd24766 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -50,24 +50,29 @@ %lldk No comment provided by engineer. - - (shared only with your contacts) - (shared only with your contacts) - No comment provided by engineer. - ) ) No comment provided by engineer. - **Add new contact**: to create your one-time QR Code for your contact. - **Add new contact**: to create your one-time QR Code for your contact. + **Add new contact**: to create your one-time QR Code or link for your contact. + **Add new contact**: to create your one-time QR Code or link for your contact. No comment provided by engineer. - - **Scan QR code**: to connect to your contact who shows QR code to you. - **Scan QR code**: to connect to your contact who shows QR code to you. + + **Create link / QR code** for your contact to use. + **Create link / QR code** for your contact to use. + No comment provided by engineer. + + + **Paste received link** or open it in the browser and tap **Open in mobile app**. + **Paste received link** or open it in the browser and tap **Open in mobile app**. + No comment provided by engineer. + + + **Scan QR code**: to connect to your contact in person or via video call. + **Scan QR code**: to connect to your contact in person or via video call. No comment provided by engineer. @@ -95,6 +100,16 @@ : %@ No comment provided by engineer. + + About SimpleX + About SimpleX + No comment provided by engineer. + + + About SimpleX Chat + About SimpleX Chat + No comment provided by engineer. + Accept Accept @@ -110,11 +125,6 @@ Accept contact request from %@? notification body - - Add contact - Add contact - No comment provided by engineer. - Add contact to start a new chat Add contact to start a new chat @@ -125,6 +135,11 @@ All your contacts will remain connected No comment provided by engineer. + + Already connected? + Already connected? + No comment provided by engineer. + Answer Answer @@ -171,7 +186,7 @@ notification - Choose file + Choose file (new in v2.0) Choose file (new in v2.0) No comment provided by engineer. @@ -215,6 +230,11 @@ Connect via one-time link? No comment provided by engineer. + + Connect with the developers + Connect with the developers + No comment provided by engineer. + Connecting to server… Connecting to server… @@ -285,6 +305,11 @@ Create No comment provided by engineer. + + Create 1-time link / QR code + Create 1-time link / QR code + No comment provided by engineer. + Create address Create address @@ -295,9 +320,9 @@ Create link / QR code No comment provided by engineer. - - Create profile - Create profile + + Create your profile + Create your profile No comment provided by engineer. @@ -305,6 +330,11 @@ Currently maximum supported file size is %@. No comment provided by engineer. + + Decentralized + Decentralized + No comment provided by engineer. + Delete Delete @@ -435,14 +465,24 @@ Help No comment provided by engineer. + + How SimpleX works + How SimpleX works + No comment provided by engineer. + + + How it works + How it works + No comment provided by engineer. + How to How to No comment provided by engineer. - - How to use SimpleX Chat - How to use SimpleX Chat + + How to use it + How to use it No comment provided by engineer. @@ -450,21 +490,16 @@ How to use markdown No comment provided by engineer. + + If you can't meet in person, **show QR code in the video call**, or share the link. + If you can't meet in person, **show QR code in the video call**, or share the link. + No comment provided by engineer. + If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. No comment provided by engineer. - - If you cannot meet in person, you can **show QR code in the video call**, or you can share the invitation link via any other channel. - If you cannot meet in person, you can **show QR code in the video call**, or you can share the invitation link via any other channel. - No comment provided by engineer. - - - If you received SimpleX Chat invitation link you can open it in your browser: - If you received SimpleX Chat invitation link you can open it in your browser: - No comment provided by engineer. - Ignore Ignore @@ -475,6 +510,16 @@ Image will be received when your contact is online, please wait or check later! No comment provided by engineer. + + Immune to spam and abuse + Immune to spam and abuse + No comment provided by engineer. + + + In person or via a video call – the most secure way to connect. + In person or via a video call – the most secure way to connect. + No comment provided by engineer. + Incoming %@ call Incoming %@ call @@ -495,16 +540,36 @@ Invalid connection link No comment provided by engineer. + + It seems like you are already connected via this link. If it is not the case, there was an error (%@). + It seems like you are already connected via this link. If it is not the case, there was an error (%@). + No comment provided by engineer. + + + It's secure to share - only one contact can use it. + It's secure to share - only one contact can use it. + No comment provided by engineer. + Large file! Large file! No comment provided by engineer. + + Make a private connection + Make a private connection + No comment provided by engineer. + Make sure SMP server addresses are in correct format, line separated and are not duplicated. Make sure SMP server addresses are in correct format, line separated and are not duplicated. No comment provided by engineer. + + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* + No comment provided by engineer. + Markdown in messages Markdown in messages @@ -535,11 +600,31 @@ Notifications are disabled! No comment provided by engineer. + + One-time invitation link + One-time invitation link + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + No comment provided by engineer. + Open Settings Open Settings No comment provided by engineer. + + Open-source protocol – anybody can run the servers. + Open-source protocol – anybody can run the servers. + No comment provided by engineer. + + + Or open the link in the browser and tap **Open in mobile**. + Or open the link in the browser and tap **Open in mobile**. + No comment provided by engineer. + Paste Paste @@ -550,11 +635,21 @@ Paste received link No comment provided by engineer. + + Paste the link you received + Paste the link you received + No comment provided by engineer. + Paste the link you received into the box below to connect with your contact. Paste the link you received into the box below to connect with your contact. No comment provided by engineer. + + People can connect to you only via the links you share. + People can connect to you only via the links you share. + No comment provided by engineer. + Please check that you used the correct link or ask your contact to send you another one. Please check that you used the correct link or ask your contact to send you another one. @@ -565,6 +660,11 @@ Please check your network connection and try again. No comment provided by engineer. + + Privacy redefined + Privacy redefined + No comment provided by engineer. + Profile image Profile image @@ -575,6 +675,16 @@ Read No comment provided by engineer. + + Read more in our GitHub repository. + Read more in our GitHub repository. + No comment provided by engineer. + + + Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + No comment provided by engineer. + Reject Reject @@ -620,6 +730,11 @@ Scan QR code No comment provided by engineer. + + Scan contact's QR code + Scan contact's QR code + No comment provided by engineer. + Server connected Server connected @@ -645,13 +760,6 @@ Share link No comment provided by engineer. - - Show QR code to your contact -to scan from the app - Show QR code to your contact -to scan from the app - No comment provided by engineer. - Show pending connections Show pending connections @@ -672,6 +780,11 @@ to scan from the app Thank you for installing SimpleX Chat! No comment provided by engineer. + + The 1st platform without any user identifiers – private by design. + The 1st platform without any user identifiers – private by design. + No comment provided by engineer. + The app can notify you when you receive messages or contact requests - please open settings to enable. The app can notify you when you receive messages or contact requests - please open settings to enable. @@ -694,9 +807,14 @@ to scan from the app The contact you shared this link with will NOT be able to connect! No comment provided by engineer. - - The messaging and application platform 100% private by design! - The messaging and application platform 100% private by design! + + The next generation of private messaging + The next generation of private messaging + No comment provided by engineer. + + + The profile is only shared with your contacts. + The profile is only shared with your contacts. No comment provided by engineer. @@ -704,19 +822,29 @@ to scan from the app The sender will NOT be notified No comment provided by engineer. + + To ask any questions and to receive SimpleX Chat updates. + To ask any questions and to receive SimpleX Chat updates. + No comment provided by engineer. + To ask any questions and to receive updates: To ask any questions and to receive updates: No comment provided by engineer. - - To connect via link - To connect via link + + To make a new connection + To make a new connection No comment provided by engineer. - - To start a new chat - To start a new chat + + To make your first private connection, choose **one of the following**: + To make your first private connection, choose **one of the following**: + No comment provided by engineer. + + + To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. No comment provided by engineer. @@ -806,9 +934,9 @@ To connect, please ask your contact to create another connection link and check You can use markdown to format messages: No comment provided by engineer. - - You control your chat! - You control your chat! + + You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. + You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. No comment provided by engineer. @@ -856,6 +984,11 @@ To connect, please ask your contact to create another connection link and check Your chats No comment provided by engineer. + + Your contact can scan it from the app + Your contact can scan it from the app + No comment provided by engineer. + Your contact needs to be online for the connection to complete. You can cancel this connection and remove the contact (and try later with a new link). @@ -880,9 +1013,9 @@ SimpleX servers cannot see your profile. Your profile will be sent to the contact that you received this link from No comment provided by engineer. - - Your profile, contacts and messages (once delivered) are only stored locally on your device. - Your profile, contacts and messages (once delivered) are only stored locally on your device. + + Your profile, contacts and delivered messages are stored on your device. + Your profile, contacts and delivered messages are stored on your device. No comment provided by engineer. @@ -905,9 +1038,9 @@ SimpleX servers cannot see your profile. `a + b` No comment provided by engineer. - - above, then: - above, then: + + above, then choose: + above, then choose: No comment provided by engineer. @@ -1001,6 +1134,11 @@ SimpleX servers cannot see your profile. no end-to-end encryption No comment provided by engineer. + + or + or + No comment provided by engineer. + received answer… received answer… @@ -1066,16 +1204,6 @@ SimpleX servers cannot see your profile. ~strike~ No comment provided by engineer. - - 💻 desktop: scan displayed QR code from the app, via **Scan QR code**. - 💻 desktop: scan displayed QR code from the app, via **Scan QR code**. - No comment provided by engineer. - - - 📱 mobile: tap **Open in mobile app**, then tap **Connect** in the app. - 📱 mobile: tap **Open in mobile app**, then tap **Connect** in the app. - No comment provided by engineer. - diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings index 97efd9d6d..7bbeebb05 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/en.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,6 +1,11 @@ +/* No comment provided by engineer. */ +"Choose file" = "Choose file (new in v2.0)"; + /* No comment provided by engineer. */ "Connecting server…" = "Connecting to server…"; /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Connecting to server… (error: %@)"; +/* No comment provided by engineer. */ +"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; 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 4b3491999..4544bbaef 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -50,24 +50,29 @@ %lldk No comment provided by engineer. - - (shared only with your contacts) - (отправляется только вашим контактам) - No comment provided by engineer. - ) ) No comment provided by engineer. - **Add new contact**: to create your one-time QR Code for your contact. + **Add new contact**: to create your one-time QR Code or link for your contact. **Добавить новый контакт**: чтобы создать одноразовый QR код или ссылку для вашего контакта. No comment provided by engineer. - - **Scan QR code**: to connect to your contact who shows QR code to you. - **Сканировать QR код**: чтобы соединиться с вашим контактом (который показывает вам QR код). + + **Create link / QR code** for your contact to use. + **Создать ссылку / QR код** для вашего контакта. + No comment provided by engineer. + + + **Paste received link** or open it in the browser and tap **Open in mobile app**. + **Вставить полученную ссылку**, или откройте её в браузере и нажмите **Open in mobile app**. + No comment provided by engineer. + + + **Scan QR code**: to connect to your contact in person or via video call. + **Сканировать QR код**: соединиться с вашим контактом при встрече или во время видеозвонка. No comment provided by engineer. @@ -95,6 +100,16 @@ : %@ No comment provided by engineer. + + About SimpleX + О SimpleX + No comment provided by engineer. + + + About SimpleX Chat + Информация о SimpleX Chat + No comment provided by engineer. + Accept Принять @@ -110,11 +125,6 @@ Принять запрос на соединение от %@? notification body - - Add contact - Добавить контакт - No comment provided by engineer. - Add contact to start a new chat Добавьте контакт, чтобы начать разговор @@ -125,6 +135,11 @@ Все контакты, которые соединились через этот адрес, сохранятся. No comment provided by engineer. + + Already connected? + Соединение уже установлено? + No comment provided by engineer. + Answer Ответить @@ -171,7 +186,7 @@ notification - Choose file + Choose file (new in v2.0) Выбрать файл (v2.0) No comment provided by engineer. @@ -215,6 +230,11 @@ Соединиться через одноразовую ссылку? No comment provided by engineer. + + Connect with the developers + Соединиться с разработчиками + No comment provided by engineer. + Connecting to server… Устанавливается соединение с сервером… @@ -285,6 +305,11 @@ Создать No comment provided by engineer. + + Create 1-time link / QR code + Создать ссылку / QR код + No comment provided by engineer. + Create address Создать адрес @@ -295,8 +320,8 @@ Создать ссылку / QR код No comment provided by engineer. - - Create profile + + Create your profile Создать профиль No comment provided by engineer. @@ -305,6 +330,11 @@ Максимальный размер файла - %@. No comment provided by engineer. + + Decentralized + Децентрализованный + No comment provided by engineer. + Delete Удалить @@ -435,14 +465,24 @@ Помощь No comment provided by engineer. + + How SimpleX works + Как SimpleX работает + No comment provided by engineer. + + + How it works + Как это работает + No comment provided by engineer. + How to Инфо No comment provided by engineer. - - How to use SimpleX Chat - Как использовать SimpleX Chat + + How to use it + Как использовать No comment provided by engineer. @@ -450,21 +490,16 @@ Как форматировать No comment provided by engineer. + + If you can't meet in person, **show QR code in the video call**, or share the link. + Если вы не можете встретиться лично, вы можете **показать QR код во время видеозвонка**, или поделиться ссылкой. + No comment provided by engineer. + If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link. Если вы не можете встретиться лично, вы можете **сосканировать QR код во время видеозвонка**, или ваш контакт может отправить вам ссылку. No comment provided by engineer. - - If you cannot meet in person, you can **show QR code in the video call**, or you can share the invitation link via any other channel. - Если вы не можете встретиться лично, вы можете **показать QR код во время видеозвонка** или отправить ссылку через любой другой канал связи. - No comment provided by engineer. - - - If you received SimpleX Chat invitation link you can open it in your browser: - Если вы получили ссылку с приглашением из SimpleX Chat, вы можете открыть её в браузере: - No comment provided by engineer. - Ignore Не отвечать @@ -475,6 +510,16 @@ Изображение будет принято, когда ваш контакт будет в сети, подождите или проверьте позже! No comment provided by engineer. + + Immune to spam and abuse + Защищен от спама + No comment provided by engineer. + + + In person or via a video call – the most secure way to connect. + При встрече или в видеозвонке – самый безопасный способ установить соединение + No comment provided by engineer. + Incoming %@ call Входящий звонок @@ -495,16 +540,36 @@ Ошибка в ссылке контакта No comment provided by engineer. + + It seems like you are already connected via this link. If it is not the case, there was an error (%@). + Возможно, вы уже соединились через эту ссылку. Если это не так, то это ошибка (%@). + No comment provided by engineer. + + + It's secure to share - only one contact can use it. + Ей безопасно поделиться - только один контакт может использовать её. + No comment provided by engineer. + Large file! Большой файл! No comment provided by engineer. + + Make a private connection + Создайте соединение + No comment provided by engineer. + Make sure SMP server addresses are in correct format, line separated and are not duplicated. Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется. No comment provided by engineer. + + Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?* + Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?* + No comment provided by engineer. + Markdown in messages Форматирование сообщений @@ -535,11 +600,31 @@ Уведомления выключены No comment provided by engineer. + + One-time invitation link + Одноразовая ссылка + No comment provided by engineer. + + + Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**. + Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием** + No comment provided by engineer. + Open Settings Открыть Настройки No comment provided by engineer. + + Open-source protocol and code – anybody can run the servers. + Открытый протокол и код - кто угодно может запускать серверы. + No comment provided by engineer. + + + Or open the link in the browser and tap **Open in mobile**. + Или откройте ссылку в браузере и нажмите **Open in mobile**. + No comment provided by engineer. + Paste Вставить @@ -550,11 +635,21 @@ Вставить полученную ссылку No comment provided by engineer. + + Paste the link you received + Вставьте полученную ссылку + No comment provided by engineer. + Paste the link you received into the box below to connect with your contact. Чтобы соединиться, вставьте в это поле ссылку, полученную от вашего контакта. No comment provided by engineer. + + People can connect to you only via the links you share. + С вами можно соединиться только через созданные вами ссылки. + No comment provided by engineer. + Please check that you used the correct link or ask your contact to send you another one. Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку. @@ -565,6 +660,11 @@ Пожалуйста, проверьте ваше соединение с сетью и попробуйте еще раз. No comment provided by engineer. + + Privacy redefined + Более конфиденциальный + No comment provided by engineer. + Profile image Аватар @@ -575,6 +675,16 @@ Прочитано No comment provided by engineer. + + Read more in our GitHub repository. + Узнайте больше из нашего GitHub репозитория. + No comment provided by engineer. + + + Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + Узнайте больше из нашего [GitHub репозитория](https://github.com/simplex-chat/simplex-chat#readme). + No comment provided by engineer. + Reject Отклонить @@ -620,6 +730,11 @@ Сканировать QR код No comment provided by engineer. + + Scan contact's QR code + Сосканировать QR код контакта + No comment provided by engineer. + Server connected Установлено соединение с сервером @@ -645,12 +760,6 @@ Поделиться ссылкой No comment provided by engineer. - - Show QR code to your contact -to scan from the app - Покажите QR код вашему контакту для сканирования в приложении - No comment provided by engineer. - Show pending connections Показать ожидаемые соединения @@ -671,6 +780,11 @@ to scan from the app Спасибо, что установили SimpleX Chat! No comment provided by engineer. + + The 1st platform without any user identifiers – private by design. + Первая в мире платформа без идентификации пользователей. + No comment provided by engineer. + The app can notify you when you receive messages or contact requests - please open settings to enable. Приложение может посылать вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках. @@ -693,9 +807,14 @@ to scan from the app Контакт, которому вы отправили эту ссылку, не сможет соединиться! No comment provided by engineer. - - The messaging and application platform 100% private by design! - Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность. + + The next generation of private messaging + Новое поколение приватных сообщений + No comment provided by engineer. + + + The profile is only shared with your contacts. + Профиль отправляется только вашим контактам. No comment provided by engineer. @@ -703,19 +822,29 @@ to scan from the app Отправитель не будет уведомлён No comment provided by engineer. + + To ask any questions and to receive SimpleX Chat updates. + Чтобы задать вопросы и получать уведомления о SimpleX Chat. + No comment provided by engineer. + To ask any questions and to receive updates: Чтобы задать вопросы и получать уведомления о новых версиях, No comment provided by engineer. - - To connect via link - Соединиться через ссылку + + To make a new connection + Чтобы соединиться No comment provided by engineer. - - To start a new chat - Начать новый разговор + + To make your first private connection, choose **one of the following**: + Чтобы сделать ваше первое приватное соединение, выберите **одно из**: + No comment provided by engineer. + + + To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts. + Чтобы защитить вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта. No comment provided by engineer. @@ -805,9 +934,9 @@ To connect, please ask your contact to create another connection link and check Вы можете форматировать сообщения: No comment provided by engineer. - - You control your chat! - Вы котролируете Ваш чат! + + You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. + Вы определяете через какие серверы вы **получаете сообщения**, ваши контакты - серверы, которые вы используете для отправки. No comment provided by engineer. @@ -855,6 +984,11 @@ To connect, please ask your contact to create another connection link and check Ваши чаты No comment provided by engineer. + + Your contact can scan it from the app + Ваш контакт может сосканировать QR в приложении + No comment provided by engineer. + Your contact needs to be online for the connection to complete. You can cancel this connection and remove the contact (and try later with a new link). @@ -879,9 +1013,9 @@ SimpleX серверы не могут получить доступ к ваше Ваш профиль будет отправлен контакту, от которого вы получили эту ссылку. No comment provided by engineer. - - Your profile, contacts and messages (once delivered) are only stored locally on your device. - Ваш профиль, контакты и сообщения (после доставки) хранятся только на вашем устройстве. + + Your profile, contacts and delivered messages are stored on your device. + Ваш профиль, контакты и доставленные сообщения хранятся на вашем устройстве. No comment provided by engineer. @@ -904,9 +1038,9 @@ SimpleX серверы не могут получить доступ к ваше \`a + b` No comment provided by engineer. - - above, then: - наверху, затем: + + above, then choose: + наверху, затем выберите: No comment provided by engineer. @@ -1000,6 +1134,11 @@ SimpleX серверы не могут получить доступ к ваше end-to-end шифрование не поддерживается No comment provided by engineer. + + or + или + No comment provided by engineer. + received answer… получен ответ… @@ -1065,16 +1204,6 @@ SimpleX серверы не могут получить доступ к ваше \~зачеркнуть~ No comment provided by engineer. - - 💻 desktop: scan displayed QR code from the app, via **Scan QR code**. - 💻 на компьютере: сосканируйте QR код из приложения через **Сканировать QR код**. - No comment provided by engineer. - - - 📱 mobile: tap **Open in mobile app**, then tap **Connect** in the app. - 📱 на мобильном: намжите кнопку **Open in mobile app** на веб странице, затем нажмите **Соединиться** в приложении. - No comment provided by engineer. - diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings index 97efd9d6d..7bbeebb05 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Source Contents/en.lproj/Localizable.strings @@ -1,6 +1,11 @@ +/* No comment provided by engineer. */ +"Choose file" = "Choose file (new in v2.0)"; + /* No comment provided by engineer. */ "Connecting server…" = "Connecting to server…"; /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Connecting to server… (error: %@)"; +/* No comment provided by engineer. */ +"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 98d363265..d4832f917 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -56,9 +56,13 @@ 5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C3279559F40002BEB4 /* SimpleXApp.swift */; }; 5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C4279559F40002BEB4 /* ContentView.swift */; }; 5CA059EF279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; }; - 5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */; }; 5CB0BA882826CB3A00B3292C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */; }; 5CB0BA8B2826CB3A00B3292C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA892826CB3A00B3292C /* Localizable.strings */; }; + 5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA8D2827126500B3292C /* OnboardingView.swift */; }; + 5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */; }; + 5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA91282713FD00B3292C /* CreateProfile.swift */; }; + 5CB0BA962827143500B3292C /* MakeConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA952827143500B3292C /* MakeConnection.swift */; }; + 5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; }; 5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D327A853F100ACCCDD /* SettingsButton.swift */; }; 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; }; 5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; }; @@ -191,9 +195,13 @@ 5CA059D7279559F40002BEB4 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; 5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = ""; }; - 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 5CB0BA872826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 5CB0BA8A2826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 5CB0BA8D2827126500B3292C /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXInfo.swift; sourceTree = ""; }; + 5CB0BA91282713FD00B3292C /* CreateProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateProfile.swift; sourceTree = ""; }; + 5CB0BA952827143500B3292C /* MakeConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeConnection.swift; sourceTree = ""; }; + 5CB0BA992827FD8800B3292C /* HowItWorks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorks.swift; sourceTree = ""; }; 5CB924D327A853F100ACCCDD /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.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 = ""; }; @@ -287,6 +295,7 @@ 5C2E260D27A30E2400F70299 /* Views */ = { isa = PBXGroup; children = ( + 5CB0BA8C282711BC00B3292C /* Onboarding */, 3C714775281C080100CB4D4B /* Call */, 5C971E1F27AEBF7000C8A3CE /* Helpers */, 5C5F4AC227A5E9AF00B51EF1 /* Chat */, @@ -294,7 +303,6 @@ 5CB924DD27A8622200ACCCDD /* NewChat */, 5CB924DF27A8678B00ACCCDD /* UserSettings */, 5C2E261127A30FEA00F70299 /* TerminalView.swift */, - 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */, ); path = Views; sourceTree = ""; @@ -419,6 +427,18 @@ path = "Tests iOS"; sourceTree = ""; }; + 5CB0BA8C282711BC00B3292C /* Onboarding */ = { + isa = PBXGroup; + children = ( + 5CB0BA8D2827126500B3292C /* OnboardingView.swift */, + 5CB0BA8F282713D900B3292C /* SimpleXInfo.swift */, + 5CB0BA91282713FD00B3292C /* CreateProfile.swift */, + 5CB0BA952827143500B3292C /* MakeConnection.swift */, + 5CB0BA992827FD8800B3292C /* HowItWorks.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; 5CB924DD27A8622200ACCCDD /* NewChat */ = { isa = PBXGroup; children = ( @@ -660,6 +680,7 @@ 5CDCAD7728188D3800503DA2 /* ChatTypes.swift in Sources */, 5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */, 5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */, + 5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */, 5CDCAD5328186F9500503DA2 /* GroupDefaults.swift in Sources */, 5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */, 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, @@ -672,10 +693,12 @@ 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */, 5CB924E427A8683A00ACCCDD /* UserAddress.swift in Sources */, 640F50E327CF991C001E05C2 /* SMPServers.swift in Sources */, + 5CB0BA90282713D900B3292C /* SimpleXInfo.swift in Sources */, 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */, 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */, 3C8C548928133C84000A3EC7 /* PasteToConnectView.swift in Sources */, 5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */, + 5CB0BA8E2827126500B3292C /* OnboardingView.swift in Sources */, 5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */, 5CDCAD7628188D3600503DA2 /* APITypes.swift in Sources */, 5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */, @@ -687,10 +710,10 @@ 5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */, 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */, 5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */, + 5CB0BA962827143500B3292C /* MakeConnection.swift in Sources */, 5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */, 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */, 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */, - 5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */, 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */, @@ -710,6 +733,7 @@ 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */, 5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */, 3C714777281C081000CB4D4B /* WebRTCView.swift in Sources */, + 5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */, 5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */, 5C5E5D3D282447AB00B0488A /* CallTypes.swift in Sources */, 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */, diff --git a/apps/ios/en.lproj/Localizable.strings b/apps/ios/en.lproj/Localizable.strings index 400ef08d2..7bbeebb05 100644 --- a/apps/ios/en.lproj/Localizable.strings +++ b/apps/ios/en.lproj/Localizable.strings @@ -7,3 +7,5 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "Connecting to server… (error: %@)"; +/* No comment provided by engineer. */ +"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 69ce78f78..fb69b8701 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -22,9 +22,6 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 цвет!"; -/* No comment provided by engineer. */ -"(shared only with your contacts)" = "(отправляется только вашим контактам)"; - /* No comment provided by engineer. */ ")" = ")"; @@ -35,7 +32,13 @@ "**Add new contact**: to create your one-time QR Code for your contact." = "**Добавить новый контакт**: чтобы создать одноразовый QR код или ссылку для вашего контакта."; /* No comment provided by engineer. */ -"**Scan QR code**: to connect to your contact who shows QR code to you." = "**Сканировать QR код**: чтобы соединиться с вашим контактом (который показывает вам QR код)."; +"**Create link / QR code** for your contact to use." = "**Создать ссылку / QR код** для вашего контакта."; + +/* No comment provided by engineer. */ +"**Paste received link** or open it in the browser and tap **Open in mobile app**." = "**Вставить полученную ссылку**, или откройте её в браузере и нажмите **Open in mobile app**."; + +/* No comment provided by engineer. */ +"**Scan QR code**: to connect to your contact in person or via video call." = "**Сканировать QR код**: соединиться с вашим контактом при встрече или во время видеозвонка."; /* No comment provided by engineer. */ "*bold*" = "\\*жирный*"; @@ -61,17 +64,17 @@ /* No comment provided by engineer. */ "~strike~" = "\\~зачеркнуть~"; -/* No comment provided by engineer. */ -"💻 desktop: scan displayed QR code from the app, via **Scan QR code**." = "💻 на компьютере: сосканируйте QR код из приложения через **Сканировать QR код**."; - -/* No comment provided by engineer. */ -"📱 mobile: tap **Open in mobile app**, then tap **Connect** in the app." = "📱 на мобильном: намжите кнопку **Open in mobile app** на веб странице, затем нажмите **Соединиться** в приложении."; - /* No comment provided by engineer. */ "6" = "6"; /* No comment provided by engineer. */ -"above, then:" = "наверху, затем:"; +"About SimpleX" = "О SimpleX"; + +/* No comment provided by engineer. */ +"About SimpleX Chat" = "Информация о SimpleX Chat"; + +/* No comment provided by engineer. */ +"above, then choose:" = "наверху, затем выберите:"; /* accept contact request via notification */ "Accept" = "Принять"; @@ -85,15 +88,15 @@ /* call status */ "accepted" = "принятый звонок"; -/* No comment provided by engineer. */ -"Add contact" = "Добавить контакт"; - /* No comment provided by engineer. */ "Add contact to start a new chat" = "Добавьте контакт, чтобы начать разговор"; /* No comment provided by engineer. */ "All your contacts will remain connected" = "Все контакты, которые соединились через этот адрес, сохранятся."; +/* No comment provided by engineer. */ +"Already connected?" = "Соединение уже установлено?"; + /* accept incoming call via notification */ "Answer" = "Ответить"; @@ -160,6 +163,9 @@ /* No comment provided by engineer. */ "Connect via one-time link?" = "Соединиться через одноразовую ссылку?"; +/* No comment provided by engineer. */ +"Connect with the developers" = "Соединиться с разработчиками"; + /* No comment provided by engineer. */ "connected" = "соединение установлено"; @@ -215,6 +221,9 @@ /* No comment provided by engineer. */ "Create" = "Создать"; +/* No comment provided by engineer. */ +"Create 1-time link / QR code" = "Создать ссылку / QR код"; + /* No comment provided by engineer. */ "Create address" = "Создать адрес"; @@ -222,11 +231,14 @@ "Create link / QR code" = "Создать ссылку / QR код"; /* No comment provided by engineer. */ -"Create profile" = "Создать профиль"; +"Create your profile" = "Создать профиль"; /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Максимальный размер файла - %@."; +/* No comment provided by engineer. */ +"Decentralized" = "Децентрализованный"; + /* No comment provided by engineer. */ "Delete" = "Удалить"; @@ -317,30 +329,39 @@ /* No comment provided by engineer. */ "Help" = "Помощь"; +/* No comment provided by engineer. */ +"How it works" = "Как это работает"; + +/* No comment provided by engineer. */ +"How SimpleX works" = "Как SimpleX работает"; + /* No comment provided by engineer. */ "How to" = "Инфо"; +/* No comment provided by engineer. */ +"How to use it" = "Как использовать"; + /* No comment provided by engineer. */ "How to use markdown" = "Как форматировать"; /* No comment provided by engineer. */ -"How to use SimpleX Chat" = "Как использовать SimpleX Chat"; +"If you can't meet in person, **show QR code in the video call**, or share the link." = "Если вы не можете встретиться лично, вы можете **показать QR код во время видеозвонка**, или поделиться ссылкой."; /* No comment provided by engineer. */ "If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link." = "Если вы не можете встретиться лично, вы можете **сосканировать QR код во время видеозвонка**, или ваш контакт может отправить вам ссылку."; -/* No comment provided by engineer. */ -"If you cannot meet in person, you can **show QR code in the video call**, or you can share the invitation link via any other channel." = "Если вы не можете встретиться лично, вы можете **показать QR код во время видеозвонка** или отправить ссылку через любой другой канал связи."; - -/* No comment provided by engineer. */ -"If you received SimpleX Chat invitation link you can open it in your browser:" = "Если вы получили ссылку с приглашением из SimpleX Chat, вы можете открыть её в браузере:"; - /* ignore incoming call via notification */ "Ignore" = "Не отвечать"; /* No comment provided by engineer. */ "Image will be received when your contact is online, please wait or check later!" = "Изображение будет принято, когда ваш контакт будет в сети, подождите или проверьте позже!"; +/* No comment provided by engineer. */ +"Immune to spam and abuse" = "Защищен от спама"; + +/* No comment provided by engineer. */ +"In person or via a video call – the most secure way to connect." = "При встрече или в видеозвонке – самый безопасный способ установить соединение"; + /* call status */ "in progress" = "активный звонок"; @@ -359,15 +380,27 @@ /* chat list item title */ "invited to connect" = "приглашение соединиться"; +/* No comment provided by engineer. */ +"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Возможно, вы уже соединились через эту ссылку. Если это не так, то это ошибка (%@)."; + +/* No comment provided by engineer. */ +"It's secure to share - only one contact can use it." = "Ей безопасно поделиться - только один контакт может использовать её."; + /* No comment provided by engineer. */ "italic" = "курсив"; /* No comment provided by engineer. */ "Large file!" = "Большой файл!"; +/* No comment provided by engineer. */ +"Make a private connection" = "Создайте соединение"; + /* No comment provided by engineer. */ "Make sure SMP server addresses are in correct format, line separated and are not duplicated." = "Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется."; +/* No comment provided by engineer. */ +"Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Много пользователей спросили: *как SimpleX доставляет сообщения без идентификаторов пользователей?*"; + /* No comment provided by engineer. */ "Markdown in messages" = "Форматирование сообщений"; @@ -392,30 +425,60 @@ /* No comment provided by engineer. */ "Notifications are disabled!" = "Уведомления выключены"; +/* No comment provided by engineer. */ +"One-time invitation link" = "Одноразовая ссылка"; + +/* No comment provided by engineer. */ +"Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." = "Только пользовательские устройства хранят контакты, группы и сообщения, которые отправляются **с двухуровневым end-to-end шифрованием**"; + /* No comment provided by engineer. */ "Open Settings" = "Открыть Настройки"; +/* No comment provided by engineer. */ +"Open-source protocol and code – anybody can run the servers." = "Открытый протокол и код - кто угодно может запускать серверы."; + +/* No comment provided by engineer. */ +"or" = "или"; + +/* No comment provided by engineer. */ +"Or open the link in the browser and tap **Open in mobile**." = "Или откройте ссылку в браузере и нажмите **Open in mobile**."; + /* No comment provided by engineer. */ "Paste" = "Вставить"; /* No comment provided by engineer. */ "Paste received link" = "Вставить полученную ссылку"; +/* No comment provided by engineer. */ +"Paste the link you received" = "Вставьте полученную ссылку"; + /* No comment provided by engineer. */ "Paste the link you received into the box below to connect with your contact." = "Чтобы соединиться, вставьте в это поле ссылку, полученную от вашего контакта."; +/* No comment provided by engineer. */ +"People can connect to you only via the links you share." = "С вами можно соединиться только через созданные вами ссылки."; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку."; /* No comment provided by engineer. */ "Please check your network connection and try again." = "Пожалуйста, проверьте ваше соединение с сетью и попробуйте еще раз."; +/* No comment provided by engineer. */ +"Privacy redefined" = "Более конфиденциальный"; + /* No comment provided by engineer. */ "Profile image" = "Аватар"; /* No comment provided by engineer. */ "Read" = "Прочитано"; +/* No comment provided by engineer. */ +"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Узнайте больше из нашего [GitHub репозитория](https://github.com/simplex-chat/simplex-chat#readme)."; + +/* No comment provided by engineer. */ +"Read more in our GitHub repository." = "Узнайте больше из нашего GitHub репозитория."; + /* No comment provided by engineer. */ "received answer…" = "получен ответ…"; @@ -443,6 +506,9 @@ /* No comment provided by engineer. */ "Saved SMP servers will be removed" = "Сохраненные SMP серверы будут удалены"; +/* No comment provided by engineer. */ +"Scan contact's QR code" = "Сосканировать QR код контакта"; + /* No comment provided by engineer. */ "Scan QR code" = "Сканировать QR код"; @@ -467,9 +533,6 @@ /* No comment provided by engineer. */ "Show pending connections" = "Показать ожидаемые соединения"; -/* No comment provided by engineer. */ -"Show QR code to your contact\nto scan from the app" = "Покажите QR код вашему контакту для сканирования в приложении"; - /* No comment provided by engineer. */ "SMP servers" = "SMP серверы"; @@ -488,6 +551,9 @@ /* No comment provided by engineer. */ "Thank you for installing SimpleX Chat!" = "Спасибо, что установили SimpleX Chat!"; +/* No comment provided by engineer. */ +"The 1st platform without any user identifiers – private by design." = "Первая в мире платформа без идентификации пользователей."; + /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; @@ -501,19 +567,28 @@ "The contact you shared this link with will NOT be able to connect!" = "Контакт, которому вы отправили эту ссылку, не сможет соединиться!"; /* No comment provided by engineer. */ -"The messaging and application platform 100% private by design!" = "Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность."; +"The next generation of private messaging" = "Новое поколение приватных сообщений"; + +/* No comment provided by engineer. */ +"The profile is only shared with your contacts." = "Профиль отправляется только вашим контактам."; /* No comment provided by engineer. */ "The sender will NOT be notified" = "Отправитель не будет уведомлён"; +/* No comment provided by engineer. */ +"To ask any questions and to receive SimpleX Chat updates." = "Чтобы задать вопросы и получать уведомления о SimpleX Chat."; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "Чтобы задать вопросы и получать уведомления о новых версиях,"; /* No comment provided by engineer. */ -"To connect via link" = "Соединиться через ссылку"; +"To make a new connection" = "Чтобы соединиться"; /* No comment provided by engineer. */ -"To start a new chat" = "Начать новый разговор"; +"To make your first private connection, choose **one of the following**:" = "Чтобы сделать ваше первое приватное соединение, выберите **одно из**:"; + +/* No comment provided by engineer. */ +"To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." = "Чтобы защитить вашу конфиденциальность, вместо ID пользователей, которые есть в других платформах, SimpleX использует ID для очередей сообщений, разные для каждого контакта."; /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Устанавливается соединение с сервером, через который вы получаете сообщения от этого контакта (ошибка: %@)."; @@ -585,7 +660,7 @@ "You can use markdown to format messages:" = "Вы можете форматировать сообщения:"; /* No comment provided by engineer. */ -"You control your chat!" = "Вы котролируете Ваш чат!"; +"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Вы определяете через какие серверы вы **получаете сообщения**, ваши контакты - серверы, которые вы используете для отправки."; /* No comment provided by engineer. */ "You invited your contact" = "Вы пригласили ваш контакт"; @@ -611,6 +686,9 @@ /* No comment provided by engineer. */ "Your chats" = "Ваши чаты"; +/* No comment provided by engineer. */ +"Your contact can scan it from the app" = "Ваш контакт может сосканировать QR в приложении"; + /* No comment provided by engineer. */ "Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Ваш контакт должен быть в сети чтобы установить соединение.\nВы можете отменить соединение и удалить контакт (и попробовать позже с другой ссылкой)."; @@ -624,7 +702,7 @@ "Your profile will be sent to the contact that you received this link from" = "Ваш профиль будет отправлен контакту, от которого вы получили эту ссылку."; /* No comment provided by engineer. */ -"Your profile, contacts and messages (once delivered) are only stored locally on your device." = "Ваш профиль, контакты и сообщения (после доставки) хранятся только на вашем устройстве."; +"Your profile, contacts and delivered messages are stored on your device." = "Ваш профиль, контакты и доставленные сообщения хранятся на вашем устройстве."; /* No comment provided by engineer. */ "Your settings" = "Настройки";