- @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 {
@@ -408,5 +448,5 @@ struct ChooseServerOperatorsInfoView: View {
}
#Preview {
- OnboardingConditionsView()
+ ChooseServerOperators(onboarding: true)
}
diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
index ae72cb1be5..409cb859ea 100644
--- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
+++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift
@@ -62,7 +62,8 @@ struct CreateProfile: View {
.frame(height: 20)
} footer: {
VStack(alignment: .leading, spacing: 8) {
- Text("Your profile is stored on your device and only shared with your contacts.")
+ Text("Your profile, contacts and delivered messages are stored on your device.")
+ Text("The profile is only shared with your contacts.")
}
.foregroundColor(theme.colors.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -117,22 +118,25 @@ struct CreateFirstProfile: View {
@State private var nextStepNavLinkActive = false
var body: some View {
- let v = VStack(alignment: .leading, spacing: 16) {
- VStack(alignment: .center, spacing: 16) {
- Text("Create profile")
+ VStack(alignment: .leading, spacing: 20) {
+ VStack(alignment: .center, spacing: 20) {
+ Text("Create your profile")
.font(.largeTitle)
.bold()
.multilineTextAlignment(.center)
-
- Text("Your profile is stored on your device and only shared with your contacts.")
+
+ 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.")
.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)
@@ -141,7 +145,6 @@ 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)
@@ -170,23 +173,12 @@ struct CreateFirstProfile: View {
}
}
.onAppear() {
- 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
- }
- }
+ 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 {
@@ -214,7 +206,7 @@ struct CreateFirstProfile: View {
}
private func nextStepDestinationView() -> some View {
- OnboardingConditionsView()
+ ChooseServerOperators(onboarding: true)
.navigationBarBackButtonHidden(true)
.modifier(ThemedBackground())
}
@@ -243,15 +235,15 @@ private func showCreateProfileAlert(
_ error: Error
) {
let m = ChatModel.shared
- switch error as? ChatError {
- case .errorStore(.duplicateName),
- .error(.userExists):
+ switch error as? ChatResponse {
+ case .chatCmdError(_, .errorStore(.duplicateName)),
+ .chatCmdError(_, .error(.userExists)):
if m.currentUser == nil {
AlertManager.shared.showAlert(duplicateUserAlert)
} else {
showAlert(.duplicateUserError)
}
- case .error(.invalidDisplayName):
+ case .chatCmdError(_, .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 a2f5db7f03..befb34b318 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 {
- SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: Binding.constant(false))
+ SimpleXLinkQRCode(uri: userAddress.connReqContact)
.frame(maxHeight: g.size.width)
shareQRCodeButton(userAddress)
.frame(maxWidth: .infinity)
@@ -77,9 +77,9 @@ struct CreateSimpleXAddress: View {
progressIndicator = true
Task {
do {
- let connLinkContact = try await apiCreateUserAddress(short: false)
+ let connReqContact = try await apiCreateUserAddress()
DispatchQueue.main.async {
- m.userAddress = UserContactLink(connLinkContact: connLinkContact)
+ m.userAddress = UserContactLink(connReqContact: connReqContact)
}
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.connLinkContact.simplexChatUri(short: false))])
+ showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
} 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.connLinkContact.simplexChatUri(short: false)))
+ """, comment: "email text"), simplexChatLink(userAddress.connReqContact))
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 8f448dc508..b2b1b8fa68 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:
- OnboardingConditionsView()
+ ChooseServerOperators(onboarding: true)
.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 // changed to simplified conditions
+ case step3_ChooseServerOperators
case step4_SetNotificationsMode
case onboardingComplete
diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift
index 31865e7af9..97e1f49382 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
- let v = ScrollView {
+ ScrollView {
VStack(alignment: .center, spacing: 20) {
Text("Push notifications")
.font(.largeTitle)
@@ -57,17 +57,11 @@ 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 9f41a37b1d..40dd29db53 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
- let v = ScrollView {
+ 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,9 +66,6 @@ struct SimpleXInfo: View {
}
}
}
- .padding(.horizontal, 25)
- .padding(.top, 75)
- .padding(.bottom, 25)
.frame(minHeight: g.size.height)
}
.sheet(isPresented: Binding(
@@ -91,17 +88,14 @@ struct SimpleXInfo: View {
createProfileNavLinkActive: $createProfileNavLinkActive
)
}
- if #available(iOS 16.4, *) {
- v.scrollBounceBehavior(.basedOnSize)
- } else {
- v
- }
}
.onAppear() {
setLastVersionDefault()
}
.frame(maxHeight: .infinity)
- .navigationBarHidden(true) // necessary on iOS 15
+ .padding(.horizontal, 25)
+ .padding(.top, 75)
+ .padding(.bottom, 25)
}
private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View {
@@ -135,7 +129,6 @@ struct SimpleXInfo: View {
NavigationLink(isActive: $createProfileNavLinkActive) {
CreateFirstProfile()
- .modifier(ThemedBackground())
} label: {
EmptyView()
}
@@ -147,8 +140,6 @@ 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 f65a21623a..4547c6d20a 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/20250308-simplex-chat-v6-3-new-user-experience-safety-in-public-groups.html"),
+ // post: URL(string: "https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html"),
features: [
.feature(Description(
icon: "at",
@@ -594,6 +594,8 @@ 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)
@@ -604,7 +606,16 @@ fileprivate struct NewOperatorsView: View {
.multilineTextAlignment(.leading)
.lineLimit(10)
HStack {
- Text("Enable Flux in Network & servers settings for better metadata privacy.")
+ Button("Enable Flux") {
+ showOperatorsSheet = true
+ }
+ Text("for better metadata privacy.")
+ }
+ }
+ .sheet(isPresented: $showOperatorsSheet) {
+ NavigationView {
+ ChooseServerOperators(onboarding: false)
+ .modifier(ThemedBackground())
}
}
}
@@ -636,7 +647,8 @@ struct WhatsNewView: View {
case .showConditions:
UsageConditionsView(
currUserServers: Binding.constant([]),
- userServers: Binding.constant([])
+ userServers: Binding.constant([]),
+ updated: true
)
.modifier(ThemedBackground(grouped: true))
}
diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift
index 01b25baed8..67020e09e7 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? 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
+ 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
default: errorAlert(e)
}
}
diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift
index 554219eb69..2b58abef65 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: APIResult = APIResult.error(ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
+ let resp = ChatResponse.chatCmdError(user_: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
Task {
- await TerminalItems.shared.addCommand(.now, .string(composeState.message), resp)
+ await TerminalItems.shared.addCommand(.now, cmd, resp)
}
} else {
- let cmd = composeState.message
DispatchQueue.global().async {
Task {
- await MainActor.run { composeState.inProgress = true }
- await sendTerminalCmd(cmd)
- await MainActor.run { composeState.inProgress = false }
+ composeState.inProgress = true
+ _ = await chatSendCmd(cmd)
+ composeState.inProgress = false
}
}
}
@@ -164,38 +164,12 @@ 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 = [
- .err(.now, APIResult.invalid(type: "contactSubscribed", json: "{}".data(using: .utf8)!).unexpected),
- .err(.now, APIResult.invalid(type: "newChatItems", json: "{}".data(using: .utf8)!).unexpected)
+ .resp(.now, ChatResponse.response(type: "contactSubscribed", json: "{}")),
+ .resp(.now, ChatResponse.response(type: "newChatItems", json: "{}"))
]
return NavigationView {
TerminalView()
diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift
index 44e0b20958..00532c0a8e 100644
--- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift
@@ -38,6 +38,7 @@ 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) }
@@ -77,6 +78,7 @@ 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 71c284e9ab..cf9cada592 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(verbatim: " (") + color("1", .red) + color("2", .green) + color("3", .blue) + color("4", .yellow) + color("5", .cyan) + Text("6").foregroundColor(.purple) + Text(verbatim: ")"))
+ 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("#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(verbatim: ", ")
+ Text(s).foregroundColor(c) + Text(", ")
}
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 fa698f8b7c..7570b1c3e0 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift
@@ -209,16 +209,11 @@ struct AdvancedNetworkSettings: View {
}
Section {
- Picker("Use web port", selection: $netCfg.smpWebPortServers) {
- ForEach(SMPWebPortServers.allCases, id: \.self) { Text($0.text) }
- }
- .frame(height: 36)
+ Toggle("Use web port", isOn: $netCfg.smpWebPort)
} header: {
Text("TCP port for messaging")
} footer: {
- 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.")
+ Text("Use TCP port \(netCfg.smpWebPort ? "443" : "5223") when no port is specified.")
}
Section("TCP connection") {
@@ -373,8 +368,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 + 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 .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 .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 6f4710396a..7d8424a67d 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
+ case showConditions(updated: Bool)
var id: String {
switch self {
- case .showConditions: return "showConditions"
+ case let .showConditions(updated): return "showConditions \(updated)"
}
}
}
@@ -169,10 +169,11 @@ struct NetworkAndServers: View {
}
.sheet(item: $sheetItem) { item in
switch item {
- case .showConditions:
+ case let .showConditions(updated):
UsageConditionsView(
currUserServers: $ss.servers.currUserServers,
- userServers: $ss.servers.userServers
+ userServers: $ss.servers.userServers,
+ updated: updated
)
.modifier(ThemedBackground(grouped: true))
}
@@ -218,7 +219,8 @@ struct NetworkAndServers: View {
private func conditionsButton(_ conditionsAction: UsageConditionsAction) -> some View {
Button {
- sheetItem = .showConditions
+ let updated = if case .review = conditionsAction { true } else { false }
+ sheetItem = .showConditions(updated: updated)
} label: {
switch conditionsAction {
case .review:
@@ -235,26 +237,30 @@ 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) {
@@ -266,8 +272,10 @@ struct UsageConditionsView: View {
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.horizontal, 32)
- conditionsDiffButton(.footnote)
- } else {
+ if updated {
+ conditionsDiffButton(.footnote)
+ }
+ } else if updated {
conditionsDiffButton()
.padding(.top)
}
@@ -277,9 +285,6 @@ 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)
@@ -335,30 +340,6 @@ 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 afbccc109c..24da6a94a8 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)
}
- private func operatorView() -> some View {
+ @ViewBuilder private func operatorView() -> some View {
let duplicateHosts = findDuplicateHosts(serverErrors)
- return VStack {
+ VStack {
List {
Section {
infoViewLink()
@@ -500,14 +500,14 @@ struct SingleOperatorUsageConditionsView: View {
}
}
- private func acceptConditionsButton() -> some View {
+ @ViewBuilder 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 }
- return Button {
+ 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 b9737914ec..ed3c5c773c 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)
}
- private func yourServersView() -> some View {
+ @ViewBuilder private func yourServersView() -> some View {
let duplicateHosts = findDuplicateHosts(serverErrors)
- return List {
+ 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 eba7f8066a..0b9d1ef76c 100644
--- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
@@ -14,13 +14,12 @@ 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()
@@ -76,6 +75,17 @@ 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)
}
@@ -101,11 +111,6 @@ 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 e06b1c4dd3..61dbb5d5d7 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,7 +99,6 @@ 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,
@@ -184,6 +183,8 @@ 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)
@@ -280,159 +281,159 @@ struct SettingsView: View {
}
}
- 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)
-
- 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 {
+ @ViewBuilder func settingsView() -> some View {
+ let user = chatModel.currentUser
+ List {
+ Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) {
NavigationLink {
- AppearanceSettings()
- .navigationTitle("Appearance")
+ NotificationsView()
+ .navigationTitle("Notifications")
.modifier(ThemedBackground(grouped: true))
} label: {
- settingsRow("sun.max", color: theme.colors.secondary) { Text("Appearance") }
+ 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") }
- }
- }
-
- Section(header: Text("Help").foregroundColor(theme.colors.secondary)) {
- if let user = user {
+
NavigationLink {
- ChatHelp(dismissSettingsSheet: dismiss)
- .navigationTitle("Welcome \(user.displayName)!")
+ 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)
+ }
+ }
+
+ 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 {
+ 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") }
+ }
+ }
+ 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("questionmark", color: theme.colors.secondary) { Text("How to use it") }
+ settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") }
}
- }
- 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)
+ 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 {
@@ -527,7 +528,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(verbatim: " (" + profileOf.fullName + ")")
+ t = t + Text(" (" + 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 094c1cb3d6..2cf63692a7 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(verbatim: " ") + Text((ByteCountFormatter.string(fromByteCount: value, countStyle: .binary)))
+ Text(key).bold() + Text(" ") + 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 4813edf96c..7965215b49 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
-@preconcurrency import SimpleXChat
+import SimpleXChat
struct UserAddressView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@@ -16,7 +16,6 @@ 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
@@ -136,8 +135,8 @@ struct UserAddressView: View {
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
Section {
- SimpleXCreatedLinkQRCode(link: userAddress.connLinkContact, short: $showShortLink)
- .id("simplex-contact-address-qrcode-\(userAddress.connLinkContact.simplexChatUri(short: showShortLink))")
+ SimpleXLinkQRCode(uri: userAddress.connReqContact)
+ .id("simplex-contact-address-qrcode-\(userAddress.connReqContact)")
shareQRCodeButton(userAddress)
// if MFMailComposeViewController.canSendMail() {
// shareViaEmailButton(userAddress)
@@ -154,7 +153,8 @@ struct UserAddressView: View {
}
addressSettingsButton(userAddress)
} header: {
- ToggleShortLinkHeader(text: Text("For social media"), link: userAddress.connLinkContact, short: $showShortLink)
+ Text("For social media")
+ .foregroundColor(theme.colors.secondary)
} footer: {
if aas.business {
Text("Add your team members to the conversations.")
@@ -193,10 +193,9 @@ struct UserAddressView: View {
progressIndicator = true
Task {
do {
- let short = UserDefaults.standard.bool(forKey: DEFAULT_PRIVACY_SHORT_LINKS)
- let connLinkContact = try await apiCreateUserAddress(short: short)
+ let connReqContact = try await apiCreateUserAddress()
DispatchQueue.main.async {
- chatModel.userAddress = UserContactLink(connLinkContact: connLinkContact)
+ chatModel.userAddress = UserContactLink(connReqContact: connReqContact)
alert = .shareOnCreate
progressIndicator = false
}
@@ -232,7 +231,7 @@ struct UserAddressView: View {
private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View {
Button {
- showShareSheet(items: [simplexChatLink(userAddress.connLinkContact.simplexChatUri(short: showShortLink))])
+ showShareSheet(items: [simplexChatLink(userAddress.connReqContact)])
} label: {
settingsRow("square.and.arrow.up", color: theme.colors.secondary) {
Text("Share address")
@@ -295,28 +294,6 @@ 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
@@ -565,7 +542,7 @@ private func saveAAS(_ aas: Binding, _ savedAAS: Binding some View {
+ @ViewBuilder 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) }
- return List {
+ 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 2ec32def0a..c78a7cb941 100644
--- a/apps/ios/SimpleX (iOS).entitlements
+++ b/apps/ios/SimpleX (iOS).entitlements
@@ -9,10 +9,6 @@
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 e965e5a1a5..ca61f88520 100644
--- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
+++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
@@ -553,9 +553,8 @@
يمكنك أنت وجهة اتصالك إرسال رسائل صوتية.
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.
@@ -583,9 +582,8 @@
إلغاء
No comment provided by engineer.
-
+
Cannot access keychain to save database password
- لا يمكن الوصول إلى سلسلة المفاتيح لحفظ كلمة مرور قاعدة البيانات
No comment provided by engineer.
@@ -603,9 +601,8 @@
تغيير عبارة مرور قاعدة البيانات؟
No comment provided by engineer.
-
+
Change member role?
- تغيير دور العضو؟
No comment provided by engineer.
@@ -667,68 +664,56 @@
الدردشات
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
@@ -739,9 +724,8 @@
Connect via group link?
No comment provided by engineer.
-
+
Connect via link
- تواصل عبر الرابط
No comment provided by engineer.
@@ -756,273 +740,224 @@
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.
@@ -1033,188 +968,152 @@
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
@@ -1225,153 +1124,124 @@
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.
-
+