+ @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 {
@@ -448,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 409cb859ea..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)
@@ -145,6 +141,7 @@ struct CreateFirstProfile: View {
TextField("Enter your name…", text: $displayName)
.focused($focusDisplayName)
.padding(.horizontal)
+ .padding(.trailing, 20)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
@@ -173,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 {
@@ -206,7 +214,7 @@ struct CreateFirstProfile: View {
}
private func nextStepDestinationView() -> some View {
- ChooseServerOperators(onboarding: true)
+ OnboardingConditionsView()
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
}
@@ -235,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 40dd29db53..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()
}
@@ -140,6 +147,8 @@ struct SimpleXInfo: View {
let textSpace = Text(verbatim: " ")
+let textNewLine = Text(verbatim: "\n")
+
struct SimpleXInfo_Previews: PreviewProvider {
static var previews: some View {
SimpleXInfo(onboarding: true)
diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
index 4547c6d20a..f65a21623a 100644
--- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
+++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift
@@ -542,7 +542,7 @@ private let versionDescriptions: [VersionDescription] = [
),
VersionDescription(
version: "v6.3",
- // post: URL(string: "https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html"),
+ post: URL(string: "https://simplex.chat/blog/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html"),
features: [
.feature(Description(
icon: "at",
@@ -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.")
}
}
}
@@ -647,8 +636,7 @@ struct WhatsNewView: View {
case .showConditions:
UsageConditionsView(
currUserServers: Binding.constant([]),
- userServers: Binding.constant([]),
- updated: true
+ userServers: Binding.constant([])
)
.modifier(ThemedBackground(grouped: true))
}
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/MarkdownHelp.swift b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift
index cf9cada592..71c284e9ab 100644
--- a/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift
+++ b/apps/ios/Shared/Views/UserSettings/MarkdownHelp.swift
@@ -19,7 +19,7 @@ struct MarkdownHelp: View {
mdFormat("_italic_", Text("italic").italic())
mdFormat("~strike~", Text("strike").strikethrough())
mdFormat("`a + b`", Text("`a + b`").font(.body.monospaced()))
- mdFormat("!1 colored!", Text("colored").foregroundColor(.red) + Text(" (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(")"))
+ mdFormat("!1 colored!", Text("colored").foregroundColor(.red) + Text(verbatim: " (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(verbatim: ")"))
(
mdFormat("#secret#", Text("secret")
.foregroundColor(.clear)
@@ -39,7 +39,7 @@ private func mdFormat(_ format: LocalizedStringKey, _ example: Text) -> some Vie
}
private func color(_ s: String, _ c: Color) -> Text {
- Text(s).foregroundColor(c) + Text(", ")
+ Text(s).foregroundColor(c) + Text(verbatim: ", ")
}
struct MarkdownHelp_Previews: PreviewProvider {
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
index 7570b1c3e0..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") {
@@ -368,8 +373,8 @@ struct AdvancedNetworkSettings: View {
let userMode = Text("A separate TCP connection will be used **for each chat profile you have in the app**.")
return switch mode {
case .user: userMode
- case .session: userMode + Text("\n") + Text("New SOCKS credentials will be used every time you start the app.")
- case .server: userMode + Text("\n") + Text("New SOCKS credentials will be used for each server.")
+ case .session: userMode + textNewLine + Text("New SOCKS credentials will be used every time you start the app.")
+ case .server: userMode + textNewLine + Text("New SOCKS credentials will be used for each server.")
case .entity: Text("A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.")
}
}
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
index 7d8424a67d..6f4710396a 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
@@ -20,11 +20,11 @@ private enum NetworkAlert: Identifiable {
}
private enum NetworkAndServersSheet: Identifiable {
- case showConditions(updated: Bool)
+ case showConditions
var id: String {
switch self {
- case let .showConditions(updated): return "showConditions \(updated)"
+ case .showConditions: return "showConditions"
}
}
}
@@ -169,11 +169,10 @@ struct NetworkAndServers: View {
}
.sheet(item: $sheetItem) { item in
switch item {
- case let .showConditions(updated):
+ case .showConditions:
UsageConditionsView(
currUserServers: $ss.servers.currUserServers,
- userServers: $ss.servers.userServers,
- updated: updated
+ userServers: $ss.servers.userServers
)
.modifier(ThemedBackground(grouped: true))
}
@@ -219,8 +218,7 @@ struct NetworkAndServers: View {
private func conditionsButton(_ conditionsAction: UsageConditionsAction) -> some View {
Button {
- let updated = if case .review = conditionsAction { true } else { false }
- sheetItem = .showConditions(updated: updated)
+ sheetItem = .showConditions
} label: {
switch conditionsAction {
case .review:
@@ -237,30 +235,26 @@ struct UsageConditionsView: View {
@EnvironmentObject var theme: AppTheme
@Binding var currUserServers: [UserOperatorServers]
@Binding var userServers: [UserOperatorServers]
- var updated: Bool
var body: some View {
VStack(alignment: .leading, spacing: 20) {
- HStack {
- if updated {
- Text("Updated conditions").font(.largeTitle).bold()
- } else {
- Text("Conditions of use").font(.largeTitle).bold()
- Spacer()
- conditionsLinkButton()
- }
- }
- .padding(.top)
- .padding(.top)
-
switch ChatModel.shared.conditions.conditionsAction {
case .none:
+ regularConditionsHeader()
+ .padding(.top)
+ .padding(.top)
ConditionsTextView()
.padding(.bottom)
.padding(.bottom)
case let .review(operators, deadline, _):
+ HStack {
+ Text("Updated conditions").font(.largeTitle).bold()
+ }
+ .padding(.top)
+ .padding(.top)
+
Text("Conditions will be accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.")
ConditionsTextView()
VStack(spacing: 8) {
@@ -272,10 +266,8 @@ struct UsageConditionsView: View {
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal, 32)
- if updated {
- conditionsDiffButton(.footnote)
- }
- } else if updated {
+ conditionsDiffButton(.footnote)
+ } else {
conditionsDiffButton()
.padding(.top)
}
@@ -285,6 +277,9 @@ struct UsageConditionsView: View {
case let .accepted(operators):
+ regularConditionsHeader()
+ .padding(.top)
+ .padding(.top)
Text("Conditions are accepted for the operator(s): **\(operators.map { $0.legalName_ }.joined(separator: ", "))**.")
ConditionsTextView()
.padding(.bottom)
@@ -340,6 +335,30 @@ struct UsageConditionsView: View {
}
}
+private func regularConditionsHeader() -> some View {
+ HStack {
+ Text("Conditions of use").font(.largeTitle).bold()
+ Spacer()
+ conditionsLinkButton()
+ }
+}
+
+struct SimpleConditionsView: View {
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 20) {
+ regularConditionsHeader()
+ .padding(.top)
+ .padding(.top)
+ ConditionsTextView()
+ .padding(.bottom)
+ .padding(.bottom)
+ }
+ .padding(.horizontal, 25)
+ .frame(maxHeight: .infinity)
+ }
+}
+
func validateServers_(_ userServers: Binding<[UserOperatorServers]>, _ serverErrors: Binding<[UserServersError]>) {
let userServersToValidate = userServers.wrappedValue
Task {
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 61dbb5d5d7..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 {
@@ -528,7 +527,7 @@ struct ProfilePreview: View {
func profileName(_ profileOf: NamedChat) -> Text {
var t = Text(profileOf.displayName).fontWeight(.semibold).font(.title2)
if profileOf.fullName != "" && profileOf.fullName != profileOf.displayName {
- t = t + Text(" (" + profileOf.fullName + ")")
+ t = t + Text(verbatim: " (" + profileOf.fullName + ")")
// .font(.callout)
}
return t
diff --git a/apps/ios/Shared/Views/UserSettings/StorageView.swift b/apps/ios/Shared/Views/UserSettings/StorageView.swift
index 2cf63692a7..094c1cb3d6 100644
--- a/apps/ios/Shared/Views/UserSettings/StorageView.swift
+++ b/apps/ios/Shared/Views/UserSettings/StorageView.swift
@@ -33,7 +33,7 @@ struct StorageView: View {
private func directoryView(_ name: LocalizedStringKey, _ contents: [String: Int64]) -> some View {
Text(name).font(.headline)
ForEach(Array(contents), id: \.key) { (key, value) in
- Text(key).bold() + Text(" ") + Text("\(ByteCountFormatter.string(fromByteCount: value, countStyle: .binary))")
+ Text(key).bold() + Text(verbatim: " ") + Text((ByteCountFormatter.string(fromByteCount: value, countStyle: .binary)))
}
}
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 ca61f88520..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.
@@ -664,56 +667,68 @@
الدردشات
No comment provided by engineer.
-
+
Check server address and try again.
+ تحقق من عنوان الخادم وحاول مرة أخرى.
No comment provided by engineer.
-
+
Choose file
+ اختر الملف
No comment provided by engineer.
-
+
Choose from library
+ اختر من المكتبة
No comment provided by engineer.
-
+
Clear
+ مسح
No comment provided by engineer.
-
+
Clear conversation
+ مسح الدردشة
No comment provided by engineer.
-
+
Clear conversation?
+ مسح الدردشة؟
No comment provided by engineer.
-
+
Clear verification
+ امسح التحقُّق
No comment provided by engineer.
Colors
No comment provided by engineer.
-
+
Compare security codes with your contacts.
+ قارن رموز الأمان مع جهات اتصالك.
No comment provided by engineer.
-
+
Configure ICE servers
+ ضبط خوادم ICE
No comment provided by engineer.
-
+
Confirm
+ تأكيد
No comment provided by engineer.
-
+
Confirm new passphrase…
+ تأكيد عبارة المرور الجديدة…
No comment provided by engineer.
-
+
Connect
+ اتصل
server test step
@@ -724,8 +739,9 @@
Connect via group link?
No comment provided by engineer.
-
+
Connect via link
+ تواصل عبر الرابط
No comment provided by engineer.
@@ -740,224 +756,273 @@
Connect via relay
No comment provided by engineer.
-
+
Connecting to server…
+ جارِ الاتصال بالخادم…
No comment provided by engineer.
-
+
Connecting to server… (error: %@)
+ الاتصال بالخادم... (الخطأ: %@)
No comment provided by engineer.
-
+
Connection
+ الاتصال
No comment provided by engineer.
-
+
Connection error
+ خطأ في الإتصال
No comment provided by engineer.
-
+
Connection error (AUTH)
+ خطأ في الإتصال (المصادقة)
No comment provided by engineer.
Connection request
No comment provided by engineer.
-
+
Connection request sent!
+ أرسلت طلب الاتصال!
No comment provided by engineer.
-
+
Connection timeout
+ انتهت مهلة الاتصال
No comment provided by engineer.
-
+
Contact allows
+ تسمح جهة الاتصال
No comment provided by engineer.
-
+
Contact already exists
+ جهة الاتصال موجودة بالفعل
No comment provided by engineer.
Contact and all messages will be deleted - this cannot be undone!
No comment provided by engineer.
-
+
Contact hidden:
+ جهة الاتصال مخفية:
notification
-
+
Contact is connected
+ تم الاتصال
notification
Contact is not connected yet!
No comment provided by engineer.
-
+
Contact name
+ اسم جهة الاتصال
No comment provided by engineer.
-
+
Contact preferences
+ تفضيلات جهة الاتصال
No comment provided by engineer.
Contact requests
No comment provided by engineer.
-
+
Contacts can mark messages for deletion; you will be able to view them.
+ يمكن لجهات الاتصال تحديد الرسائل لحذفها؛ ستتمكن من مشاهدتها.
No comment provided by engineer.
-
+
Copy
+ نسخ
chat item action
Core built at: %@
No comment provided by engineer.
-
+
Core version: v%@
+ الإصدار الأساسي: v%@
No comment provided by engineer.
-
+
Create
+ إنشاء
No comment provided by engineer.
Create address
No comment provided by engineer.
-
+
Create group link
+ إنشاء رابط المجموعة
No comment provided by engineer.
-
+
Create link
+ إنشاء رابط
No comment provided by engineer.
Create one-time invitation link
No comment provided by engineer.
-
+
Create queue
+ إنشاء قائمة انتظار
server test step
-
+
Create secret group
+ إنشاء مجموعة سرية
No comment provided by engineer.
-
+
Create your profile
+ أنشئ ملف تعريفك
No comment provided by engineer.
Created on %@
No comment provided by engineer.
-
+
Current passphrase…
+ عبارة المرور الحالية…
No comment provided by engineer.
-
+
Currently maximum supported file size is %@.
+ الحد الأقصى لحجم الملف المدعوم حاليًا هو %@.
No comment provided by engineer.
-
+
Dark
+ داكن
No comment provided by engineer.
-
+
Database ID
+ معرّف قاعدة البيانات
No comment provided by engineer.
-
+
Database encrypted!
+ قاعدة البيانات مُعمّاة!
No comment provided by engineer.
-
+
Database encryption passphrase will be updated and stored in the keychain.
+ سيتم تحديث عبارة المرور الخاصة بتشفير قاعدة البيانات وتخزينها في سلسلة المفاتيح.
+
No comment provided by engineer.
-
+
Database encryption passphrase will be updated.
+ سيتم تحديث عبارة مرور تعمية قاعدة البيانات.
+
No comment provided by engineer.
-
+
Database error
+ خطأ في قاعدة البيانات
No comment provided by engineer.
-
+
Database is encrypted using a random passphrase, you can change it.
+ قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية، يمكنك تغييرها.
No comment provided by engineer.
-
+
Database is encrypted using a random passphrase. Please change it before exporting.
+ قاعدة البيانات مُعمّاة باستخدام عبارة مرور عشوائية. يُرجى تغييره قبل التصدير.
No comment provided by engineer.
-
+
Database passphrase
+ عبارة مرور قاعدة البيانات
No comment provided by engineer.
-
+
Database passphrase & export
+ عبارة مرور قاعدة البيانات وتصديرها
No comment provided by engineer.
-
+
Database passphrase is different from saved in the keychain.
+ عبارة المرور الخاصة بقاعدة البيانات مختلفة عن تلك المحفوظة في سلسلة المفاتيح.
No comment provided by engineer.
-
+
Database passphrase is required to open chat.
+ عبارة مرور قاعدة البيانات مطلوبة لفتح الدردشة.
No comment provided by engineer.
-
+
Database will be encrypted and the passphrase stored in the keychain.
+ سيتم تشفير قاعدة البيانات وتخزين عبارة المرور في سلسلة المفاتيح.
+
No comment provided by engineer.
-
+
Database will be encrypted.
+ سيتم تعمية قاعدة البيانات.
+
No comment provided by engineer.
-
+
Database will be migrated when the app restarts
+ سيتم نقل قاعدة البيانات عند إعادة تشغيل التطبيق
No comment provided by engineer.
-
+
Decentralized
+ لامركزي
No comment provided by engineer.
-
+
Delete
+ حذف
chat item action
Delete Contact
No comment provided by engineer.
-
+
Delete address
+ حذف العنوان
No comment provided by engineer.
-
+
Delete address?
+ حذف العنوان؟
No comment provided by engineer.
-
+
Delete after
+ حذف بعد
No comment provided by engineer.
-
+
Delete all files
+ حذف جميع الملفات
No comment provided by engineer.
@@ -968,152 +1033,188 @@
Delete chat archive?
No comment provided by engineer.
-
+
Delete chat profile?
+ حذف ملف تعريف الدردشة؟
No comment provided by engineer.
-
+
Delete connection
+ حذف الاتصال
No comment provided by engineer.
-
+
Delete contact
+ حذف جهة الاتصال
No comment provided by engineer.
-
+
Delete contact?
+ حذف جهة الاتصال؟
No comment provided by engineer.
-
+
Delete database
+ حذف قاعدة البيانات
No comment provided by engineer.
-
+
Delete files and media?
+ حذف الملفات والوسائط؟
No comment provided by engineer.
-
+
Delete files for all chat profiles
+ حذف الملفات لجميع ملفات تعريف الدردشة
No comment provided by engineer.
-
+
Delete for everyone
+ حذف للجميع
chat feature
-
+
Delete for me
+ حذف بالنسبة لي
No comment provided by engineer.
-
+
Delete group
+ حذف المجموعة
No comment provided by engineer.
-
+
Delete group?
+ حذف المجموعة؟
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.
Delete pending connection
No comment provided by engineer.
-
+
Delete pending connection?
+ حذف الاتصال قيد الانتظار؟
No comment provided by engineer.
-
+
Delete queue
+ حذف قائمة الانتظار
server test step
-
+
Delete user profile?
+ حذف ملف تعريف المستخدم؟
No comment provided by engineer.
-
+
Description
+ الوصف
No comment provided by engineer.
-
+
Develop
+ يطور
No comment provided by engineer.
-
+
Developer tools
+ أدوات المطور
No comment provided by engineer.
-
+
Device
+ الجهاز
No comment provided by engineer.
-
+
Device authentication is disabled. Turning off SimpleX Lock.
+ استيثاق الجهاز مُعطَّل. جارِ إيقاف تشغيل قفل SimpleX.
No comment provided by engineer.
-
+
Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication.
+ مصادقة الجهاز غير مفعّلة. يمكنك تشغيل قفل SimpleX عبر الإعدادات، بمجرد تفعيل مصادقة الجهاز.
No comment provided by engineer.
-
+
Different names, avatars and transport isolation.
+ أسماء مختلفة، صور الأفاتار وعزل النقل.
No comment provided by engineer.
-
+
Direct messages
+ رسائل مباشرة
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
@@ -1124,124 +1225,153 @@
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.
-
+
Duplicate display name!
+ اسم العرض مكرر!
No comment provided by engineer.
-
+
Edit
+ تحرير
chat item action
-
+
Edit group profile
+ حرّر ملف تعريف المجموعة
No comment provided by engineer.
-
+
Enable
+ تفعيل
No comment provided by engineer.
-
+
Enable SimpleX Lock
+ تفعيل قفل SimpleX
authentication reason
-
+
Enable TCP keep-alive
+ تفعيل أبقِ TCP على قيد الحياة
No comment provided by engineer.
-
+
Enable automatic message deletion?
+ تفعيل الحذف التلقائي للرسائل؟
No comment provided by engineer.
-
+
Enable instant notifications?
+ تفعيل الإشعارات فورية؟
No comment provided by engineer.
-
+
Enable notifications
+ تفعيل الإشعارات
No comment provided by engineer.
-
+
Enable periodic notifications?
+ تفعيل الإشعارات دورية؟
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.
@@ -2696,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.
+
+