+ @State private var sheetItem: ChooseServerOperatorsSheet? = nil
+
+ var body: some View {
+ GeometryReader { g in
+ ScrollView {
+ VStack(alignment: .leading, spacing: 20) {
+ Text("Server operators")
+ .font(.largeTitle)
+ .bold()
+ .frame(maxWidth: .infinity, alignment: .center)
+ .padding(.top, 25)
+
+ infoText()
+ .frame(maxWidth: .infinity, alignment: .center)
+
+ Spacer()
+
+ ForEach(serverOperators) { srvOperator in
+ operatorCheckView(srvOperator)
+ }
+ VStack {
+ Text("SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app.").padding(.bottom, 8)
+ Text("You can configure servers via settings.")
+ }
+ .font(.footnote)
+ .multilineTextAlignment(.center)
+ .frame(maxWidth: .infinity, alignment: .center)
+ .padding(.horizontal, 16)
+
+ Spacer()
+
+ VStack(spacing: 8) {
+ setOperatorsButton()
+ onboardingButtonPlaceholder()
+ }
+ }
+ .frame(minHeight: g.size.height)
+ }
+ .sheet(item: $sheetItem) { item in
+ switch item {
+ case .showInfo:
+ ChooseServerOperatorsInfoView()
+ }
+ }
+ .frame(maxHeight: .infinity, alignment: .top)
+ }
+ .frame(maxHeight: .infinity, alignment: .top)
+ .padding(25)
+ .interactiveDismissDisabled(selectedOperatorIds.isEmpty)
+ }
+
+ private func infoText() -> some View {
+ Button {
+ sheetItem = .showInfo
+ } label: {
+ Label("How it helps privacy", systemImage: "info.circle")
+ .font(.headline)
+ }
+ }
+
+ private func operatorCheckView(_ serverOperator: ServerOperator) -> some View {
+ let checked = selectedOperatorIds.contains(serverOperator.operatorId)
+ let icon = checked ? "checkmark.circle.fill" : "circle"
+ let iconColor = checked ? theme.colors.primary : Color(uiColor: .tertiaryLabel).asAnotherColorFromSecondary(theme)
+ return HStack(spacing: 10) {
+ Image(serverOperator.largeLogo(colorScheme))
+ .resizable()
+ .scaledToFit()
+ .frame(height: 48)
+ Spacer()
+ Image(systemName: icon)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 26, height: 26)
+ .foregroundColor(iconColor)
+ }
+ .background(theme.colors.background)
+ .padding()
+ .clipShape(RoundedRectangle(cornerRadius: 18))
+ .overlay(
+ RoundedRectangle(cornerRadius: 18)
+ .stroke(Color(uiColor: .secondarySystemFill), lineWidth: 2)
+ )
+ .padding(.horizontal, 2)
+ .onTapGesture {
+ if checked {
+ selectedOperatorIds.remove(serverOperator.operatorId)
+ } else {
+ selectedOperatorIds.insert(serverOperator.operatorId)
+ }
+ }
+ }
+
+ private func setOperatorsButton() -> some View {
+ Button {
+ dismiss()
+ } label: {
+ Text("OK")
+ }
+ .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty))
+ .disabled(selectedOperatorIds.isEmpty)
+ }
+}
+
let operatorsPostLink = URL(string: "https://simplex.chat/blog/20241125-servers-operated-by-flux-true-privacy-and-decentralization-for-all-users.html")!
struct ChooseServerOperatorsInfoView: View {
@@ -444,5 +408,5 @@ struct ChooseServerOperatorsInfoView: View {
}
#Preview {
- ChooseServerOperators(onboarding: true)
+ OnboardingConditionsView()
}
diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
index 53cf73f1c9..ae72cb1be5 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
@@ -62,8 +62,7 @@ struct CreateProfile: View {
.frame(height: 20)
} footer: {
VStack(alignment: .leading, spacing: 8) {
- Text("Your profile, contacts and delivered messages are stored on your device.")
- Text("The profile is only shared with your contacts.")
+ Text("Your profile is stored on your device and only shared with your contacts.")
}
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -118,25 +117,22 @@ struct CreateFirstProfile: View {
@State private var nextStepNavLinkActive = false
var body: some View {
- VStack(alignment: .leading, spacing: 20) {
- VStack(alignment: .center, spacing: 20) {
- Text("Create your profile")
+ let v = VStack(alignment: .leading, spacing: 16) {
+ VStack(alignment: .center, spacing: 16) {
+ Text("Create profile")
.font(.largeTitle)
.bold()
.multilineTextAlignment(.center)
-
- Text("Your profile, contacts and delivered messages are stored on your device.")
- .font(.callout)
- .foregroundColor(theme.colors.secondary)
- .multilineTextAlignment(.center)
-
- Text("The profile is only shared with your contacts.")
+
+ Text("Your profile is stored on your device and only shared with your contacts.")
.font(.callout)
.foregroundColor(theme.colors.secondary)
.multilineTextAlignment(.center)
}
+ .fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity) // Ensures it takes up the full width
.padding(.horizontal, 10)
+ .onTapGesture { focusDisplayName = false }
HStack {
let name = displayName.trimmingCharacters(in: .whitespaces)
@@ -174,12 +170,23 @@ struct CreateFirstProfile: View {
}
}
.onAppear() {
- focusDisplayName = true
+ if #available(iOS 16, *) {
+ focusDisplayName = true
+ } else {
+ // it does not work before animation completes on iOS 15
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ focusDisplayName = true
+ }
+ }
}
.padding(.horizontal, 25)
- .padding(.top, 10)
.padding(.bottom, 25)
.frame(maxWidth: .infinity, alignment: .leading)
+ if #available(iOS 16, *) {
+ return v.padding(.top, 10)
+ } else {
+ return v.padding(.top, 75).ignoresSafeArea(.all, edges: .top)
+ }
}
func createProfileButton() -> some View {
@@ -207,7 +214,7 @@ struct CreateFirstProfile: View {
}
private func nextStepDestinationView() -> some View {
- ChooseServerOperators(onboarding: true)
+ OnboardingConditionsView()
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
}
@@ -236,15 +243,15 @@ private func showCreateProfileAlert(
_ error: Error
) {
let m = ChatModel.shared
- switch error as? ChatResponse {
- case .chatCmdError(_, .errorStore(.duplicateName)),
- .chatCmdError(_, .error(.userExists)):
+ switch error as? ChatError {
+ case .errorStore(.duplicateName),
+ .error(.userExists):
if m.currentUser == nil {
AlertManager.shared.showAlert(duplicateUserAlert)
} else {
showAlert(.duplicateUserError)
}
- case .chatCmdError(_, .error(.invalidDisplayName)):
+ case .error(.invalidDisplayName):
if m.currentUser == nil {
AlertManager.shared.showAlert(invalidDisplayNameAlert)
} else {
diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
index befb34b318..a2f5db7f03 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift
@@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View {
Spacer()
if let userAddress = m.userAddress {
- SimpleXLinkQRCode(uri: userAddress.connReqContact)
+ SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: Binding.constant(false))
.frame(maxHeight: g.size.width)
shareQRCodeButton(userAddress)
.frame(maxWidth: .infinity)
@@ -77,9 +77,9 @@ struct CreateSimpleXAddress: View {
progressIndicator = true
Task {
do {
- let connReqContact = try await apiCreateUserAddress()
+ let connLinkContact = try await apiCreateUserAddress(short: false)
DispatchQueue.main.async {
- m.userAddress = UserContactLink(connReqContact: connReqContact)
+ m.userAddress = UserContactLink(connLinkContact: connLinkContact)
}
await MainActor.run { progressIndicator = false }
} catch let error {
@@ -121,7 +121,7 @@ struct CreateSimpleXAddress: View {
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
- showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
+ showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false))])
} label: {
Label("Share", systemImage: "square.and.arrow.up")
}
@@ -189,7 +189,7 @@ struct SendAddressMailView: View {
let messageBody = String(format: NSLocalizedString("""
Hi!
Connect to me via SimpleX Chat
- """, comment: "email text"), simplexChatLink(userAddress.connReqContact))
+ """, comment: "email text"), simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: false)))
MailView(
isShowing: self.$showMailView,
result: $mailViewResult,
diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift
index b2b1b8fa68..8f448dc508 100644
--- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift
+++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift
@@ -23,7 +23,7 @@ struct OnboardingView: View {
case .step3_CreateSimpleXAddress: // deprecated
CreateSimpleXAddress()
case .step3_ChooseServerOperators:
- ChooseServerOperators(onboarding: true)
+ OnboardingConditionsView()
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
case .step4_SetNotificationsMode:
@@ -44,7 +44,7 @@ enum OnboardingStage: String, Identifiable {
case step1_SimpleXInfo
case step2_CreateProfile // deprecated
case step3_CreateSimpleXAddress // deprecated
- case step3_ChooseServerOperators
+ case step3_ChooseServerOperators // changed to simplified conditions
case step4_SetNotificationsMode
case onboardingComplete
diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
index 97e1f49382..31865e7af9 100644
--- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
+++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
@@ -17,7 +17,7 @@ struct SetNotificationsMode: View {
var body: some View {
GeometryReader { g in
- ScrollView {
+ let v = ScrollView {
VStack(alignment: .center, spacing: 20) {
Text("Push notifications")
.font(.largeTitle)
@@ -57,11 +57,17 @@ struct SetNotificationsMode: View {
.padding(25)
.frame(minHeight: g.size.height)
}
+ if #available(iOS 16.4, *) {
+ v.scrollBounceBehavior(.basedOnSize)
+ } else {
+ v
+ }
}
.frame(maxHeight: .infinity)
.sheet(isPresented: $showInfo) {
NotificationsInfoView()
}
+ .navigationBarHidden(true) // necessary on iOS 15
}
private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) {
diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift
index dbae3e9fb3..9f41a37b1d 100644
--- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift
+++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift
@@ -18,7 +18,7 @@ struct SimpleXInfo: View {
var body: some View {
GeometryReader { g in
- ScrollView {
+ let v = ScrollView {
VStack(alignment: .leading) {
VStack(alignment: .center, spacing: 10) {
Image(colorScheme == .light ? "logo" : "logo-light")
@@ -36,7 +36,7 @@ struct SimpleXInfo: View {
.font(.headline)
}
}
-
+
Spacer()
VStack(alignment: .leading) {
@@ -66,6 +66,9 @@ struct SimpleXInfo: View {
}
}
}
+ .padding(.horizontal, 25)
+ .padding(.top, 75)
+ .padding(.bottom, 25)
.frame(minHeight: g.size.height)
}
.sheet(isPresented: Binding(
@@ -88,14 +91,17 @@ struct SimpleXInfo: View {
createProfileNavLinkActive: $createProfileNavLinkActive
)
}
+ if #available(iOS 16.4, *) {
+ v.scrollBounceBehavior(.basedOnSize)
+ } else {
+ v
+ }
}
.onAppear() {
setLastVersionDefault()
}
.frame(maxHeight: .infinity)
- .padding(.horizontal, 25)
- .padding(.top, 75)
- .padding(.bottom, 25)
+ .navigationBarHidden(true) // necessary on iOS 15
}
private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View {
@@ -129,6 +135,7 @@ struct SimpleXInfo: View {
NavigationLink(isActive: $createProfileNavLinkActive) {
CreateFirstProfile()
+ .modifier(ThemedBackground())
} label: {
EmptyView()
}
diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
index f7c7145dcc..f65a21623a 100644
--- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
+++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
@@ -594,8 +594,6 @@ func shouldShowWhatsNew() -> Bool {
}
fileprivate struct NewOperatorsView: View {
- @State private var showOperatorsSheet = false
-
var body: some View {
VStack(alignment: .leading) {
Image((operatorsInfo[.flux] ?? ServerOperator.dummyOperatorInfo).largeLogo)
@@ -606,16 +604,7 @@ fileprivate struct NewOperatorsView: View {
.multilineTextAlignment(.leading)
.lineLimit(10)
HStack {
- Button("Enable Flux") {
- showOperatorsSheet = true
- }
- Text("for better metadata privacy.")
- }
- }
- .sheet(isPresented: $showOperatorsSheet) {
- NavigationView {
- ChooseServerOperators(onboarding: false)
- .modifier(ThemedBackground())
+ Text("Enable Flux in Network & servers settings for better metadata privacy.")
}
}
}
diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift
index 67020e09e7..01b25baed8 100644
--- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift
+++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift
@@ -456,12 +456,12 @@ struct ConnectDesktopView: View {
}
} catch let e {
await MainActor.run {
- switch e as? ChatResponse {
- case .chatCmdError(_, .errorRemoteCtrl(.badInvitation)): alert = .badInvitationError
- case .chatCmdError(_, .error(.commandError)): alert = .badInvitationError
- case let .chatCmdError(_, .errorRemoteCtrl(.badVersion(v))): alert = .badVersionError(version: v)
- case .chatCmdError(_, .errorAgent(.RCP(.version))): alert = .badVersionError(version: nil)
- case .chatCmdError(_, .errorAgent(.RCP(.ctrlAuth))): alert = .desktopDisconnectedError
+ switch e as? ChatError {
+ case .errorRemoteCtrl(.badInvitation): alert = .badInvitationError
+ case .error(.commandError): alert = .badInvitationError
+ case let .errorRemoteCtrl(.badVersion(v)): alert = .badVersionError(version: v)
+ case .errorAgent(.RCP(.version)): alert = .badVersionError(version: nil)
+ case .errorAgent(.RCP(.ctrlAuth)): alert = .desktopDisconnectedError
default: errorAlert(e)
}
}
diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift
index 2b58abef65..554219eb69 100644
--- a/apps/ios/Shared/Views/TerminalView.swift
+++ b/apps/ios/Shared/Views/TerminalView.swift
@@ -145,18 +145,18 @@ struct TerminalView: View {
}
func consoleSendMessage() {
- let cmd = ChatCommand.string(composeState.message)
if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) {
- let resp = ChatResponse.chatCmdError(user_: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
+ let resp: APIResult = APIResult.error(ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
Task {
- await TerminalItems.shared.addCommand(.now, cmd, resp)
+ await TerminalItems.shared.addCommand(.now, .string(composeState.message), resp)
}
} else {
+ let cmd = composeState.message
DispatchQueue.global().async {
Task {
- composeState.inProgress = true
- _ = await chatSendCmd(cmd)
- composeState.inProgress = false
+ await MainActor.run { composeState.inProgress = true }
+ await sendTerminalCmd(cmd)
+ await MainActor.run { composeState.inProgress = false }
}
}
}
@@ -164,12 +164,38 @@ struct TerminalView: View {
}
}
+func sendTerminalCmd(_ cmd: String) async {
+ let start: Date = .now
+ await withCheckedContinuation { (cont: CheckedContinuation) in
+ let d = sendSimpleXCmdStr(cmd)
+ Task {
+ guard let d else {
+ await TerminalItems.shared.addCommand(start, ChatCommand.string(cmd), APIResult.error(.invalidJSON(json: nil)))
+ return
+ }
+ let r0: APIResult = decodeAPIResult(d)
+ guard case .invalid = r0 else {
+ await TerminalItems.shared.addCommand(start, .string(cmd), r0)
+ return
+ }
+ let r1: APIResult = decodeAPIResult(d)
+ guard case .invalid = r1 else {
+ await TerminalItems.shared.addCommand(start, .string(cmd), r1)
+ return
+ }
+ let r2: APIResult = decodeAPIResult(d)
+ await TerminalItems.shared.addCommand(start, .string(cmd), r2)
+ }
+ cont.resume(returning: ())
+ }
+}
+
struct TerminalView_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
chatModel.terminalItems = [
- .resp(.now, ChatResponse.response(type: "contactSubscribed", json: "{}")),
- .resp(.now, ChatResponse.response(type: "newChatItems", json: "{}"))
+ .err(.now, APIResult.invalid(type: "contactSubscribed", json: "{}".data(using: .utf8)!).unexpected),
+ .err(.now, APIResult.invalid(type: "newChatItems", json: "{}".data(using: .utf8)!).unexpected)
]
return NavigationView {
TerminalView()
diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift
index 00532c0a8e..44e0b20958 100644
--- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift
@@ -38,7 +38,6 @@ extension AppSettings {
privacyLinkPreviewsGroupDefault.set(val)
def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS)
}
- if let val = privacyChatListOpenLinks { privacyChatListOpenLinksDefault.set(val) }
if let val = privacyShowChatPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) }
if let val = privacySaveLastDraft { def.setValue(val, forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) }
if let val = privacyProtectScreen { def.setValue(val, forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) }
@@ -78,7 +77,6 @@ extension AppSettings {
c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get()
c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get()
c.privacyLinkPreviews = def.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS)
- c.privacyChatListOpenLinks = privacyChatListOpenLinksDefault.get()
c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS)
c.privacySaveLastDraft = def.bool(forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT)
c.privacyProtectScreen = def.bool(forKey: DEFAULT_PRIVACY_PROTECT_SCREEN)
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
index 55f2e837b8..fa698f8b7c 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
@@ -209,11 +209,16 @@ struct AdvancedNetworkSettings: View {
}
Section {
- Toggle("Use web port", isOn: $netCfg.smpWebPort)
+ Picker("Use web port", selection: $netCfg.smpWebPortServers) {
+ ForEach(SMPWebPortServers.allCases, id: \.self) { Text($0.text) }
+ }
+ .frame(height: 36)
} header: {
Text("TCP port for messaging")
} footer: {
- Text("Use TCP port \(netCfg.smpWebPort ? "443" : "5223") when no port is specified.")
+ netCfg.smpWebPortServers == .preset
+ ? Text("Use TCP port 443 for preset servers only.")
+ : Text("Use TCP port \(netCfg.smpWebPortServers == .all ? "443" : "5223") when no port is specified.")
}
Section("TCP connection") {
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift
index 24da6a94a8..afbccc109c 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift
@@ -38,9 +38,9 @@ struct OperatorView: View {
.allowsHitTesting(!testing)
}
- @ViewBuilder private func operatorView() -> some View {
+ private func operatorView() -> some View {
let duplicateHosts = findDuplicateHosts(serverErrors)
- VStack {
+ return VStack {
List {
Section {
infoViewLink()
@@ -500,14 +500,14 @@ struct SingleOperatorUsageConditionsView: View {
}
}
- @ViewBuilder private func acceptConditionsButton() -> some View {
+ private func acceptConditionsButton() -> some View {
let operatorIds = ChatModel.shared.conditions.serverOperators
.filter {
$0.operatorId == userServers[operatorIndex].operator_.operatorId || // Opened operator
($0.enabled && !$0.conditionsAcceptance.conditionsAccepted) // Other enabled operators with conditions not accepted
}
.map { $0.operatorId }
- Button {
+ return Button {
acceptForOperators(operatorIds, operatorIndex)
} label: {
Text("Accept conditions")
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift
index ed3c5c773c..b9737914ec 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift
@@ -38,9 +38,9 @@ struct YourServersView: View {
.allowsHitTesting(!testing)
}
- @ViewBuilder private func yourServersView() -> some View {
+ private func yourServersView() -> some View {
let duplicateHosts = findDuplicateHosts(serverErrors)
- List {
+ return List {
if !userServers[operatorIndex].smpServers.filter({ !$0.deleted }).isEmpty {
Section {
ForEach($userServers[operatorIndex].smpServers) { srv in
diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
index 0b9d1ef76c..eba7f8066a 100644
--- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
@@ -14,12 +14,13 @@ struct PrivacySettings: View {
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
- @State private var chatListOpenLinks = privacyChatListOpenLinksDefault.get()
@AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true
@AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true
@AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true
@AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
+ @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
+ @AppStorage(DEFAULT_PRIVACY_SHORT_LINKS) private var shortSimplexLinks = false
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@State private var currentLAMode = privacyLocalAuthModeDefault.get()
@@ -75,17 +76,6 @@ struct PrivacySettings: View {
privacyLinkPreviewsGroupDefault.set(linkPreviews)
}
}
- settingsRow("arrow.up.right.circle", color: theme.colors.secondary) {
- Picker("Open links from chat list", selection: $chatListOpenLinks) {
- ForEach(PrivacyChatListOpenLinksMode.allCases) { mode in
- Text(mode.text)
- }
- }
- }
- .frame(height: 36)
- .onChange(of: chatListOpenLinks) { mode in
- privacyChatListOpenLinksDefault.set(mode)
- }
settingsRow("message", color: theme.colors.secondary) {
Toggle("Show last messages", isOn: $showChatPreviews)
}
@@ -111,6 +101,11 @@ struct PrivacySettings: View {
.onChange(of: simplexLinkMode) { mode in
privacySimplexLinkModeDefault.set(mode)
}
+ if developerTools {
+ settingsRow("link.badge.plus", color: theme.colors.secondary) {
+ Toggle("Use short links (BETA)", isOn: $shortSimplexLinks)
+ }
+ }
} header: {
Text("Chats")
.foregroundColor(theme.colors.secondary)
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index 80e2a537da..e06b1c4dd3 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -29,10 +29,10 @@ let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents"
let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" // unused. Use GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES instead
let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" // deprecated, moved to app group
-let DEFAULT_PRIVACY_CHAT_LIST_OPEN_LINKS = "privacyChatListOpenLinks"
let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode"
let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews"
let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft"
+let DEFAULT_PRIVACY_SHORT_LINKS = "privacyShortLinks"
let DEFAULT_PRIVACY_PROTECT_SCREEN = "privacyProtectScreen"
let DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET = "privacyDeliveryReceiptsSet"
let DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS = "privacyMediaBlurRadius"
@@ -99,6 +99,7 @@ let appDefaults: [String: Any] = [
DEFAULT_PRIVACY_SIMPLEX_LINK_MODE: SimpleXLinkMode.description.rawValue,
DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS: true,
DEFAULT_PRIVACY_SAVE_LAST_DRAFT: true,
+ DEFAULT_PRIVACY_SHORT_LINKS: false,
DEFAULT_PRIVACY_PROTECT_SCREEN: false,
DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false,
DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS: 0,
@@ -183,8 +184,6 @@ let connectViaLinkTabDefault = EnumDefault(defaults: UserDefa
let privacySimplexLinkModeDefault = EnumDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_SIMPLEX_LINK_MODE, withDefault: .description)
-let privacyChatListOpenLinksDefault = EnumDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_CHAT_LIST_OPEN_LINKS, withDefault: PrivacyChatListOpenLinksMode.ask)
-
let privacyLocalAuthModeDefault = EnumDefault(defaults: UserDefaults.standard, forKey: DEFAULT_LA_MODE, withDefault: .system)
let privacyDeliveryReceiptsSet = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET)
@@ -281,159 +280,159 @@ struct SettingsView: View {
}
}
- @ViewBuilder func settingsView() -> some View {
- let user = chatModel.currentUser
- List {
- Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) {
- NavigationLink {
- NotificationsView()
- .navigationTitle("Notifications")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- HStack {
- notificationsIcon()
- Text("Notifications")
- }
- }
- .disabled(chatModel.chatRunning != true)
-
- NavigationLink {
- NetworkAndServers()
- .navigationTitle("Network & servers")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") }
- }
- .disabled(chatModel.chatRunning != true)
-
- NavigationLink {
- CallSettings()
- .navigationTitle("Your calls")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") }
- }
- .disabled(chatModel.chatRunning != true)
-
- NavigationLink {
- PrivacySettings()
- .navigationTitle("Your privacy")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") }
- }
- .disabled(chatModel.chatRunning != true)
-
- if UIApplication.shared.supportsAlternateIcons {
- NavigationLink {
- AppearanceSettings()
- .navigationTitle("Appearance")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") }
- }
- .disabled(chatModel.chatRunning != true)
+ func settingsView() -> some View {
+ List {
+ let user = chatModel.currentUser
+ Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) {
+ NavigationLink {
+ NotificationsView()
+ .navigationTitle("Notifications")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ HStack {
+ notificationsIcon()
+ Text("Notifications")
}
}
+ .disabled(chatModel.chatRunning != true)
- Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) {
- chatDatabaseRow()
- NavigationLink {
- MigrateFromDevice(showProgressOnSettings: $showProgress)
- .toolbar {
- // Redaction broken for `.navigationTitle` - using a toolbar item instead.
- ToolbarItem(placement: .principal) {
- Text("Migrate device").font(.headline)
- }
- }
- .modifier(ThemedBackground(grouped: true))
- .navigationBarTitleDisplayMode(.large)
- } label: {
- settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") }
- }
+ NavigationLink {
+ NetworkAndServers()
+ .navigationTitle("Network & servers")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") }
}
-
- Section(header: Text("Help").foregroundColor(theme.colors.secondary)) {
- if let user = user {
- NavigationLink {
- ChatHelp(dismissSettingsSheet: dismiss)
- .navigationTitle("Welcome \(user.displayName)!")
- .modifier(ThemedBackground())
- .frame(maxHeight: .infinity, alignment: .top)
- } label: {
- settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") }
- }
- }
+ .disabled(chatModel.chatRunning != true)
+
+ NavigationLink {
+ CallSettings()
+ .navigationTitle("Your calls")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") }
+ }
+ .disabled(chatModel.chatRunning != true)
+
+ NavigationLink {
+ PrivacySettings()
+ .navigationTitle("Your privacy")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") }
+ }
+ .disabled(chatModel.chatRunning != true)
+
+ if UIApplication.shared.supportsAlternateIcons {
NavigationLink {
- WhatsNewView(viaSettings: true, updatedConditions: false)
- .modifier(ThemedBackground())
- .navigationBarTitleDisplayMode(.inline)
+ AppearanceSettings()
+ .navigationTitle("Appearance")
+ .modifier(ThemedBackground(grouped: true))
} label: {
- settingsRow("plus", color: theme.colors.secondary) { Text("What's new") }
+ settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") }
}
+ .disabled(chatModel.chatRunning != true)
+ }
+ }
+
+ Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) {
+ chatDatabaseRow()
+ NavigationLink {
+ MigrateFromDevice(showProgressOnSettings: $showProgress)
+ .toolbar {
+ // Redaction broken for `.navigationTitle` - using a toolbar item instead.
+ ToolbarItem(placement: .principal) {
+ Text("Migrate device").font(.headline)
+ }
+ }
+ .modifier(ThemedBackground(grouped: true))
+ .navigationBarTitleDisplayMode(.large)
+ } label: {
+ settingsRow("tray.and.arrow.up", color: theme.colors.secondary) { Text("Migrate to another device") }
+ }
+ }
+
+ Section(header: Text("Help").foregroundColor(theme.colors.secondary)) {
+ if let user = user {
NavigationLink {
- SimpleXInfo(onboarding: false)
- .navigationBarTitle("", displayMode: .inline)
+ ChatHelp(dismissSettingsSheet: dismiss)
+ .navigationTitle("Welcome \(user.displayName)!")
.modifier(ThemedBackground())
.frame(maxHeight: .infinity, alignment: .top)
} label: {
- settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") }
+ settingsRow("questionmark", color: theme.colors.secondary) { Text("How to use it") }
}
- settingsRow("number", color: theme.colors.secondary) {
- Button("Send questions and ideas") {
- dismiss()
- DispatchQueue.main.async {
- UIApplication.shared.open(simplexTeamURL)
- }
+ }
+ NavigationLink {
+ WhatsNewView(viaSettings: true, updatedConditions: false)
+ .modifier(ThemedBackground())
+ .navigationBarTitleDisplayMode(.inline)
+ } label: {
+ settingsRow("plus", color: theme.colors.secondary) { Text("What's new") }
+ }
+ NavigationLink {
+ SimpleXInfo(onboarding: false)
+ .navigationBarTitle("", displayMode: .inline)
+ .modifier(ThemedBackground())
+ .frame(maxHeight: .infinity, alignment: .top)
+ } label: {
+ settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") }
+ }
+ settingsRow("number", color: theme.colors.secondary) {
+ Button("Send questions and ideas") {
+ dismiss()
+ DispatchQueue.main.async {
+ UIApplication.shared.open(simplexTeamURL)
}
}
- .disabled(chatModel.chatRunning != true)
- settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") }
}
+ .disabled(chatModel.chatRunning != true)
+ settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") }
+ }
- Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) {
- settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") }
- settingsRow("star", color: theme.colors.secondary) {
- Button("Rate the app") {
- if let scene = sceneDelegate.windowScene {
- SKStoreReviewController.requestReview(in: scene)
- }
+ Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) {
+ settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") }
+ settingsRow("star", color: theme.colors.secondary) {
+ Button("Rate the app") {
+ if let scene = sceneDelegate.windowScene {
+ SKStoreReviewController.requestReview(in: scene)
}
}
- ZStack(alignment: .leading) {
- Image(colorScheme == .dark ? "github_light" : "github")
- .resizable()
- .frame(width: 24, height: 24)
- .opacity(0.5)
- .colorMultiply(theme.colors.secondary)
- Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)")
- .padding(.leading, indent)
- }
}
+ ZStack(alignment: .leading) {
+ Image(colorScheme == .dark ? "github_light" : "github")
+ .resizable()
+ .frame(width: 24, height: 24)
+ .opacity(0.5)
+ .colorMultiply(theme.colors.secondary)
+ Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)")
+ .padding(.leading, indent)
+ }
+ }
- Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) {
- NavigationLink {
- DeveloperView()
- .navigationTitle("Developer tools")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") }
- }
- NavigationLink {
- VersionView()
- .navigationBarTitle("App version")
- .modifier(ThemedBackground())
- } label: {
- Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
- }
+ Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) {
+ NavigationLink {
+ DeveloperView()
+ .navigationTitle("Developer tools")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") }
+ }
+ NavigationLink {
+ VersionView()
+ .navigationBarTitle("App version")
+ .modifier(ThemedBackground())
+ } label: {
+ Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
}
}
- .navigationTitle("Your settings")
- .modifier(ThemedBackground(grouped: true))
- .onDisappear {
- chatModel.showingTerminal = false
- chatModel.terminalItems = []
- }
+ }
+ .navigationTitle("Your settings")
+ .modifier(ThemedBackground(grouped: true))
+ .onDisappear {
+ chatModel.showingTerminal = false
+ chatModel.terminalItems = []
+ }
}
private func chatDatabaseRow() -> some View {
diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
index 7965215b49..4813edf96c 100644
--- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift
@@ -8,7 +8,7 @@
import SwiftUI
import MessageUI
-import SimpleXChat
+@preconcurrency import SimpleXChat
struct UserAddressView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@@ -16,6 +16,7 @@ struct UserAddressView: View {
@EnvironmentObject var theme: AppTheme
@State var shareViaProfile = false
@State var autoCreate = false
+ @State private var showShortLink = true
@State private var aas = AutoAcceptState()
@State private var savedAAS = AutoAcceptState()
@State private var showMailView = false
@@ -135,8 +136,8 @@ struct UserAddressView: View {
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
Section {
- SimpleXLinkQRCode(uri: userAddress.connReqContact)
- .id("simplex-contact-address-qrcode-\(userAddress.connReqContact)")
+ SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: $showShortLink)
+ .id("simplex-contact-address-qrcode-\(userAddress.connLinkContact.simplexChatUri(short: showShortLink))")
shareQRCodeButton(userAddress)
// if MFMailComposeViewController.canSendMail() {
// shareViaEmailButton(userAddress)
@@ -153,8 +154,7 @@ struct UserAddressView: View {
}
addressSettingsButton(userAddress)
} header: {
- Text("For social media")
- .foregroundColor(theme.colors.secondary)
+ ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink)
} footer: {
if aas.business {
Text("Add your team members to the conversations.")
@@ -193,9 +193,10 @@ struct UserAddressView: View {
progressIndicator = true
Task {
do {
- let connReqContact = try await apiCreateUserAddress()
+ let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
+ let connLinkContact = try await apiCreateUserAddress(short: short)
DispatchQueue.main.async {
- chatModel.userAddress = UserContactLink(connReqContact: connReqContact)
+ chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact)
alert = .shareOnCreate
progressIndicator = false
}
@@ -231,7 +232,7 @@ struct UserAddressView: View {
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
- showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
+ showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: showShortLink))])
} label: {
settingsRow("square.and.arrow.up", color: theme.colors.secondary) {
Text("Share address")
@@ -294,6 +295,28 @@ struct UserAddressView: View {
}
}
+struct ToggleShortLinkHeader: View {
+ @EnvironmentObject var theme: AppTheme
+ let text: Text
+ var link: CreatedConnLink
+ @Binding var short: Bool
+
+ var body: some View {
+ if link.connShortLink == nil {
+ text.foregroundColor(theme.colors.secondary)
+ } else {
+ HStack {
+ text.foregroundColor(theme.colors.secondary)
+ Spacer()
+ Text(short ? "Full link" : "Short link")
+ .textCase(.none)
+ .foregroundColor(theme.colors.primary)
+ .onTapGesture { short.toggle() }
+ }
+ }
+ }
+}
+
private struct AutoAcceptState: Equatable {
var enable = false
var incognito = false
@@ -542,7 +565,7 @@ private func saveAAS(_ aas: Binding, _ savedAAS: Binding some View {
+ private func profileActionView(_ action: UserProfileAction) -> some View {
let passwordValid = actionPassword == actionPassword.trimmingCharacters(in: .whitespaces)
let passwordField = PassphraseField(key: $actionPassword, placeholder: "Profile password", valid: passwordValid)
let actionEnabled: (User) -> Bool = { user in actionPassword != "" && passwordValid && correctPassword(user, actionPassword) }
- List {
+ return List {
switch action {
case let .deleteUser(user, delSMPQueues):
actionHeader("Delete profile", user)
diff --git a/apps/ios/SimpleX (iOS).entitlements b/apps/ios/SimpleX (iOS).entitlements
index c78a7cb941..2ec32def0a 100644
--- a/apps/ios/SimpleX (iOS).entitlements
+++ b/apps/ios/SimpleX (iOS).entitlements
@@ -9,6 +9,10 @@
applinks:simplex.chat
applinks:www.simplex.chat
applinks:simplex.chat?mode=developer
+ applinks:*.simplex.im
+ applinks:*.simplex.im?mode=developer
+ applinks:*.simplexonflux.com
+ applinks:*.simplexonflux.com?mode=developer
com.apple.security.application-groups
diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
index aba5e1384e..e965e5a1a5 100644
--- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
+++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
@@ -553,8 +553,9 @@
يمكنك أنت وجهة اتصالك إرسال رسائل صوتية.
No comment provided by engineer.
-
+
By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).
+ حسب ملف تعريف الدردشة (افتراضي) أو [حسب الاتصال] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).
No comment provided by engineer.
@@ -582,8 +583,9 @@
إلغاء
No comment provided by engineer.
-
+
Cannot access keychain to save database password
+ لا يمكن الوصول إلى سلسلة المفاتيح لحفظ كلمة مرور قاعدة البيانات
No comment provided by engineer.
@@ -601,8 +603,9 @@
تغيير عبارة مرور قاعدة البيانات؟
No comment provided by engineer.
-
+
Change member role?
+ تغيير دور العضو؟
No comment provided by engineer.
@@ -669,12 +672,14 @@
تحقق من عنوان الخادم وحاول مرة أخرى.
No comment provided by engineer.
-
+
Choose file
+ اختر الملف
No comment provided by engineer.
-
+
Choose from library
+ اختر من المكتبة
No comment provided by engineer.
@@ -756,8 +761,9 @@
جارِ الاتصال بالخادم…
No comment provided by engineer.
-
+
Connecting to server… (error: %@)
+ الاتصال بالخادم... (الخطأ: %@)
No comment provided by engineer.
@@ -808,8 +814,9 @@
جهة الاتصال مخفية:
notification
-
+
Contact is connected
+ تم الاتصال
notification
@@ -844,8 +851,9 @@
Core built at: %@
No comment provided by engineer.
-
+
Core version: v%@
+ الإصدار الأساسي: v%@
No comment provided by engineer.
@@ -895,8 +903,9 @@
عبارة المرور الحالية…
No comment provided by engineer.
-
+
Currently maximum supported file size is %@.
+ الحد الأقصى لحجم الملف المدعوم حاليًا هو %@.
No comment provided by engineer.
@@ -914,9 +923,11 @@
قاعدة البيانات مُعمّاة!
No comment provided by engineer.
-
+
Database encryption passphrase will be updated and stored in the keychain.
+ سيتم تحديث عبارة المرور الخاصة بتشفير قاعدة البيانات وتخزينها في سلسلة المفاتيح.
+
No comment provided by engineer.
@@ -951,8 +962,9 @@
عبارة مرور قاعدة البيانات وتصديرها
No comment provided by engineer.
-
+
Database passphrase is different from saved in the keychain.
+ عبارة المرور الخاصة بقاعدة البيانات مختلفة عن تلك المحفوظة في سلسلة المفاتيح.
No comment provided by engineer.
@@ -960,9 +972,11 @@
عبارة مرور قاعدة البيانات مطلوبة لفتح الدردشة.
No comment provided by engineer.
-
+
Database will be encrypted and the passphrase stored in the keychain.
+ سيتم تشفير قاعدة البيانات وتخزين عبارة المرور في سلسلة المفاتيح.
+
No comment provided by engineer.
@@ -972,8 +986,9 @@
No comment provided by engineer.
-
+
Database will be migrated when the app restarts
+ سيتم نقل قاعدة البيانات عند إعادة تشغيل التطبيق
No comment provided by engineer.
@@ -1073,36 +1088,44 @@
حذف المجموعة؟
No comment provided by engineer.
-
+
Delete invitation
+ حذف الدعوة
No comment provided by engineer.
-
+
Delete link
+ حذف الرابط
No comment provided by engineer.
-
+
Delete link?
+ حذف الرابط؟
No comment provided by engineer.
-
+
Delete message?
+ حذف الرسالة؟
No comment provided by engineer.
-
+
Delete messages
+ حذف الرسائل
No comment provided by engineer.
-
+
Delete messages after
+ حذف الرسائل بعد
No comment provided by engineer.
-
+
Delete old database
+ حذف قاعدة البيانات القديمة
No comment provided by engineer.
-
+
Delete old database?
+ حذف قاعدة البيانات القديمة؟
No comment provided by engineer.
@@ -1119,8 +1142,9 @@
حذف قائمة الانتظار
server test step
-
+
Delete user profile?
+ حذف ملف تعريف المستخدم؟
No comment provided by engineer.
@@ -1128,8 +1152,9 @@
الوصف
No comment provided by engineer.
-
+
Develop
+ يطور
No comment provided by engineer.
@@ -1162,28 +1187,34 @@
رسائل مباشرة
chat feature
-
+
Direct messages between members are prohibited.
+ الرسائل المباشرة بين الأعضاء ممنوعة.
No comment provided by engineer.
-
+
Disable SimpleX Lock
+ تعطيل قفل SimpleX
authentication reason
-
+
Disappearing messages
+ الرسائل المختفية
chat feature
-
+
Disappearing messages are prohibited in this chat.
+ يُحظر اختفاء الرسائل في هذه الدردشة.
No comment provided by engineer.
-
+
Disappearing messages are prohibited.
+ الرسائل المختفية ممنوعة.
No comment provided by engineer.
-
+
Disconnect
+ قطع الاتصال
server test step
@@ -1194,12 +1225,14 @@
Display name:
No comment provided by engineer.
-
+
Do NOT use SimpleX for emergency calls.
+ لا تستخدم SimpleX لإجراء مكالمات الطوارئ.
No comment provided by engineer.
-
+
Do it later
+ افعل ذلك لاحقا
No comment provided by engineer.
@@ -1252,76 +1285,93 @@
تفعيل الإشعارات دورية؟
No comment provided by engineer.
-
+
Encrypt
+ التشفير
No comment provided by engineer.
-
+
Encrypt database?
+ تشفير قاعدة البيانات؟
No comment provided by engineer.
-
+
Encrypted database
+ قاعدة بيانات مشفرة
No comment provided by engineer.
-
+
Encrypted message or another event
+ رسالة مشفرة أو حدث آخر
notification
-
+
Encrypted message: database error
+ رسالة مشفرة: خطأ في قاعدة البيانات
notification
-
+
Encrypted message: keychain error
+ رسالة مشفرة: خطأ في سلسلة المفاتيح
notification
-
+
Encrypted message: no passphrase
+ الرسالة المشفرة: لا توجد عبارة مرور
notification
-
+
Encrypted message: unexpected error
+ رسالة مشفرة: خطأ غير متوقع
notification
-
+
Enter correct passphrase.
+ أدخل عبارة المرور الصحيحة.
No comment provided by engineer.
-
+
Enter passphrase…
+ أدخل عبارة المرور…
No comment provided by engineer.
-
+
Enter server manually
+ أدخل الخادم يدوياً
No comment provided by engineer.
-
+
Error
+ خطأ
No comment provided by engineer.
-
+
Error accepting contact request
+ خطأ في قبول طلب الاتصال
No comment provided by engineer.
Error accessing database file
No comment provided by engineer.
-
+
Error adding member(s)
+ خطأ في إضافة عضو (أعضاء)
No comment provided by engineer.
-
+
Error changing address
+ خطأ في تغيير العنوان
No comment provided by engineer.
-
+
Error changing role
+ خطأ في تغيير الدور المتغير
No comment provided by engineer.
-
+
Error changing setting
+ خطأ في تغيير الإعدادات
No comment provided by engineer.
@@ -2776,8 +2826,8 @@ We will be adding server redundancy to prevent lost messages.
The old database was not removed during the migration, it can be deleted.
No comment provided by engineer.
-
- The profile is only shared with your contacts.
+
+ Your profile is stored on your device and only shared with your contacts.
No comment provided by engineer.
@@ -5389,6 +5439,324 @@ This is your own one-time link!
Enable self-destruct passcode
تفعيل رمز التدمير الذاتي
+
+ Can't message member
+ لا يمكن الاتصال بالعضو
+
+
+ Color chats with the new themes.
+ محادثات ملونة مع السمات الجديدة.
+
+
+ All chats will be removed from the list %@, and the list deleted.
+ ستتم إزالة جميع الدردشات من القائمة %@، وسيتم حذف القائمة.
+
+
+ Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!
+ البلغارية والفنلندية والتايلاندية والأوكرانية - شكرًا للمستخدمين و[Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!
+
+
+ Choose _Migrate from another device_ on the new device and scan QR code.
+ اختر _الترحيل من جهاز آخر_ على الجهاز الجديد وامسح رمز الاستجابة السريعة.
+
+
+ Conditions will be accepted for the operator(s): **%@**.
+ سيتم قبول شروط المشغل (المشغلين): **%@**.
+
+
+ Conditions will be accepted on: %@.
+ سيتم قبول الشروط على: %@.
+
+
+ Confirmed
+ تم التأكيد
+
+
+ Connection is blocked by server operator:
+%@
+ تم حظر الاتصال من قبل مشغل الخادم:
+%@
+
+
+ Can't call member
+ لا يمكن الاتصال بالعضو
+
+
+ Chat already exists
+ الدردشة موجودة بالفعل
+
+
+ Check messages every 20 min.
+ تحقق من الرسائل كل 20 دقيقة.
+
+
+ Check messages when allowed.
+ تحقق من الرسائل عندما يُسمح بذلك.
+
+
+ Cannot forward message
+ لا يمكن إعادة توجيه الرسالة
+
+
+ Chat preferences were changed.
+ تم تغيير تفضيلات المحادثة.
+
+
+ Conditions are already accepted for these operator(s): **%@**.
+ الشروط مقبولة بالفعل لهذا المشغل (المشغلين): **%@**.
+
+
+ Conditions will be accepted for operator(s): **%@**.
+ سيتم قبول شروط المشغل (المشغلين): **%@**.
+
+
+ Conditions accepted on: %@.
+ الشروط المقبولة على: %@.
+
+
+ Conditions are accepted for the operator(s): **%@**.
+ يتم قبول شروط المشغل (المشغلين): **%@**.
+
+
+ Conditions will be automatically accepted for enabled operators on: %@.
+ سيتم قبول الشروط تلقائيًا للمشغلين الممكّنين على: %@.
+
+
+ Create new profile in [desktop app](https://simplex.chat/downloads/). 💻
+ أنشئ ملفًا شخصيًا جديدًا في [تطبيق سطح المكتب](https://simplex.chat/downloads/). 💻
+
+
+ Error adding server
+ خطأ في إضافة الخادم
+
+
+ Created at: %@
+ تم الإنشاء في: %@
+
+
+ Delete %lld messages of members?
+ حذف %lld الرسائل القديمة للأعضاء؟
+
+
+ Disappearing message
+ رسالة اختفاء
+
+
+ Enabled
+ ممكّنة
+
+
+ Encrypted message: database migration error
+ رسالة مشفرة: خطأ في ترحيل قاعدة البيانات
+
+
+ Delete list?
+ Delete list?
+
+
+ Delivered even when Apple drops them.
+ يتم تسليمها حتى عندما تسقطها شركة Apple.
+
+
+ Destination server address of %@ is incompatible with forwarding server %@ settings.
+ عنوان خادم الوجهة %@ غير متوافق مع إعدادات خادم التوجيه %@.
+
+
+ Destination server version of %@ is incompatible with forwarding server %@.
+ إصدار خادم الوجهة لـ %@ غير متوافق مع خادم التوجيه %@.
+
+
+ Don't create address
+ لا تنشئ عنوان
+
+
+ Done
+ تم
+
+
+ Duration
+ المدة
+
+
+ Encrypt local files
+ تشفير الملفات المحلية
+
+
+ Encryption renegotiation in progress.
+ إعادة التفاوض على التشفير قيد التنفيذ.
+
+
+ Enter Passcode
+ أدخل رمز المرور
+
+
+ Enter passphrase
+ قم بأدخل عبارة المرور
+
+
+ Enter welcome message…
+ أدخل رسالة ترحيب…
+
+
+ Enter your name…
+ أدخل اسمك…
+
+
+ Error changing to incognito!
+ خطأ في التغيير إلى التصفح المتخفي!
+
+
+ Delete %lld messages?
+ حذف %lld رسائل؟
+
+
+ Error aborting address change
+ خطأ في إجهاض تغيير العنوان
+
+
+ Disappears at
+ يختفي عند
+
+
+ Do not use credentials with proxy.
+ لا تستخدم بيانات الاعتماد مع البروكسي.
+
+
+ Error accepting conditions
+ خطأ في قبول الشروط
+
+
+ Enter password above to show!
+ أدخل كلمة المرور أعلاه للعرض!
+
+
+ Error changing connection profile
+ خطأ في تغيير ملف تعريف الاتصال
+
+
+ Desktop app version %@ is not compatible with this app.
+ إصدار تطبيق سطح المكتب %@ غير متوافق مع هذا التطبيق.
+
+
+ Encrypt stored files & media
+ تشفير الملفات والوسائط المخزنة
+
+
+ Enter this device name…
+ أدخل اسم الجهاز…
+
+
+ Enter welcome message… (optional)
+ أدخل رسالة ترحيب... (اختياري)
+
+
+ Correct name to %@?
+ الاسم الصحيح ل %@؟
+
+
+ Delete member message?
+ حذف رسالة العضو؟
+
+
+ Disable automatic message deletion?
+ تعطيل حذف الرسائل التلقائي؟
+
+
+ Disable delete messages
+ تعطيل حذف الرسائل
+
+
+ Disable for all
+ تعطيل للجميع
+
+
+ Disabled
+ عاجز
+
+
+ Documents:
+ المستندات:
+
+
+ By using SimpleX Chat you agree to:
+- send only legal content in public groups.
+- respect other users – no spam.
+ باستخدامك SimpleX Chat، فإنك توافق على:
+- إرسال محتوى قانوني فقط في المجموعات العامة.
+- احترام المستخدمين الآخرين - ممنوع إرسال رسائل مزعجة.
+
+
+ Configure server operators
+ تكوين مشغلي الخادم
+
+
+ Enable Flux in Network & servers settings for better metadata privacy.
+ تمكين التدفق في إعدادات الشبكة والخوادم لتحسين خصوصية البيانات الوصفية.
+
+
+ Discover and join groups
+ اكتشف المجموعات وانضم إليها
+
+
+ Discover via local network
+ اكتشف عبر الشبكة المحلية
+
+
+ Enabled for
+ ممكّن ل
+
+
+ Encrypted message: app is stopped
+ رسالة مشفرة: تم إيقاف التطبيق
+
+
+ Enter group name…
+ أدخل اسم المجموعة…
+
+
+ Do NOT use private routing.
+ لا تستخدم التوجيه الخاص.
+
+
+ Encryption re-negotiation error
+ خطأ في إعادة تفاوض التشفير
+
+
+ Connection with desktop stopped
+ تم إيقاف الاتصال بسطح المكتب
+
+
+ Destination server error: %@
+ خطأ خادم الوجهة: %@
+
+
+ Do NOT send messages directly, even if your or destination server does not support private routing.
+ لا ترسل الرسائل بشكل مباشر، حتى لو كان خادمك أو خادم الوجهة لا يدعم التوجيه الخاص.
+
+
+ Direct messages between members are prohibited in this chat.
+ يُحظر إرسال الرسائل المباشرة بين الأعضاء في هذه الدردشة.
+
+
+ Disconnect desktop?
+ فصل سطح المكتب؟
+
+
+ Disable (keep overrides)
+ تعطيل (الاحتفاظ بالتجاوزات)
+
+
+ Disappears at: %@
+ يختفي عند: %@
+
+
+ Do not send history to new members.
+ لا ترسل التاريخ إلى الأعضاء الجدد.
+
+
+ Encryption re-negotiation failed.
+ فشل إعادة التفاوض على التشفير.
+