diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift index ba1619bfd3..1f32466fed 100644 --- a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -103,6 +103,7 @@ struct MigrateToDevice: View { // Prevent from hiding the view until migration is finished or app deleted @State private var backDisabled: Bool = false @State private var showQRCodeScanner: Bool = true + @State private var scannerPaused: Bool = false @State private var pasteboardHasStrings = UIPasteboard.general.hasStrings @State private var importingArchiveFromFileProgressIndicator = false @@ -201,10 +202,10 @@ struct MigrateToDevice: View { ZStack { List { Section(header: Text("Scan QR code").foregroundColor(theme.colors.secondary)) { - ScannerInView(showQRCodeScanner: $showQRCodeScanner) { resp in + ScannerInView(showQRCodeScanner: $showQRCodeScanner, scannerPaused: $scannerPaused) { resp in switch resp { case let .success(r): - handleScan(r.string, expected: .migrationLink, theme: theme) { qr in + handleScan(r.string, expected: .migrationLink, theme: theme, scannerPaused: $scannerPaused) { qr in migrationState = .linkDownloading(link: qr.text) } case let .failure(e): diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 70dbf5df6b..70f958a946 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -700,7 +700,7 @@ private struct ConnectView: View { private func processQRCode(_ resp: Result) { switch resp { case let .success(r): - handleScan(r.string, expected: .connectionLink, theme: theme) { qr in connect(qr.text) } + handleScan(r.string, expected: .connectionLink, theme: theme, scannerPaused: $scannerPaused) { qr in connect(qr.text) } case let .failure(e): logger.error("processQRCode QR code error: \(e.localizedDescription)") alert = .newChatSomeAlert(alert: SomeAlert( @@ -849,16 +849,6 @@ struct InfoSheetButton: View { } } -func strIsSimplexLink(_ str: String) -> Bool { - if let parsedMd = parseSimpleXMarkdown(str), - parsedMd.count == 1, - case .simplexLink = parsedMd[0].format { - return true - } else { - return false - } -} - enum ConnectTarget { case link(text: String, linkType: SimplexLinkType, linkText: String) case name(SimplexNameInfo) diff --git a/apps/ios/Shared/Views/NewChat/QRCodeScan.swift b/apps/ios/Shared/Views/NewChat/QRCodeScan.swift index e0f68484eb..6ce8ed89b0 100644 --- a/apps/ios/Shared/Views/NewChat/QRCodeScan.swift +++ b/apps/ios/Shared/Views/NewChat/QRCodeScan.swift @@ -14,21 +14,25 @@ import SimpleXChat // The shared gate every scanner routes through. If the scan is the accepted kind, run the // screen's onMatch; otherwise show the one wrong-type alert. iOS has no Bool return / de-dup // (the scanner controls re-scanning via scanMode / scannerPaused). -func handleScan(_ raw: String, expected: QRCodeKind, theme: AppTheme, onMatch: (QRCodeType) -> Void) { +func handleScan(_ raw: String, expected: QRCodeKind, theme: AppTheme, scannerPaused: Binding? = nil, onMatch: (QRCodeType) -> Void) { let qr = parseQRCode(raw) if qr.kind == expected { onMatch(qr) } else { - showWrongQRCodeAlert(qr, expected: expected, theme: theme) + // Pause a continuous scanner while the wrong-type alert is up so it doesn't re-fire the + // alert every scanInterval; resume on dismissal. oncePerCode scanners pass nil (no-op). + scannerPaused?.wrappedValue = true + showWrongQRCodeAlert(qr, expected: expected, theme: theme) { scannerPaused?.wrappedValue = false } } } -private func showWrongQRCodeAlert(_ scanned: QRCodeType, expected: QRCodeKind, theme: AppTheme) { +private func showWrongQRCodeAlert(_ scanned: QRCodeType, expected: QRCodeKind, theme: AppTheme, onDismiss: @escaping () -> Void) { // Relay link: it cannot be used to connect — reuse the existing relay alert (title + message). if case .connectionLink(_, .relay) = scanned { - AlertManager.shared.showAlert(mkAlert( - title: "Relay address", - message: "This is a chat relay address, it cannot be used to connect." + AlertManager.shared.showAlert(Alert( + title: Text("Relay address"), + message: Text("This is a chat relay address, it cannot be used to connect."), + dismissButton: .default(Text("Ok"), action: onDismiss) )) return } @@ -36,14 +40,18 @@ private func showWrongQRCodeAlert(_ scanned: QRCodeType, expected: QRCodeKind, t // the server screen says "Invalid server address!", the verify screen says "Incorrect security // code!" (it scans a code, not a link), everything else the generic "not a SimpleX link". if case .unknown = scanned { + let title: LocalizedStringKey + let message: LocalizedStringKey? switch expected { - case .serverAddress: - AlertManager.shared.showAlert(mkAlert(title: "Invalid server address!", message: "Check server address and try again.")) - case .securityCode: - AlertManager.shared.showAlert(mkAlert(title: "Incorrect security code!")) - default: - AlertManager.shared.showAlert(mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code.")) + case .serverAddress: title = "Invalid server address!"; message = "Check server address and try again." + case .securityCode: title = "Incorrect security code!"; message = nil + default: title = "Invalid QR code"; message = "The code you scanned is not a SimpleX link QR code." } + AlertManager.shared.showAlert(Alert( + title: Text(title), + message: message.map { Text($0) }, + dismissButton: .default(Text("Ok"), action: onDismiss) + )) return } // Recognised wrong kind: "\n\n". @@ -58,14 +66,16 @@ private func showWrongQRCodeAlert(_ scanned: QRCodeType, expected: QRCodeKind, t title: Text("Wrong QR code"), message: Text(verbatim: message), primaryButton: .default(Text("Connect")) { + onDismiss() planAndConnect(text, theme: theme, dismiss: true) }, - secondaryButton: .cancel() + secondaryButton: .cancel(onDismiss) )) } else { AlertManager.shared.showAlert(Alert( title: Text("Wrong QR code"), - message: Text(verbatim: message) + message: Text(verbatim: message), + dismissButton: .default(Text("Ok"), action: onDismiss) )) } } @@ -91,7 +101,7 @@ private extension QRCodeType { case let .connectionLink(_, linkType): return linkType == .relay ? nil : NSLocalizedString("Open New chat, then scan or paste the link.", comment: "qr where to scan") case .serverAddress: - return NSLocalizedString("Open Settings, Network & servers, your servers, then Scan server QR code.", comment: "qr where to scan") + return NSLocalizedString("Open Settings, Network & servers, Your servers, then Scan server QR code.", comment: "qr where to scan") case .migrationLink: return NSLocalizedString("On the new device, when first setting up the app, choose Migrate from another device.", comment: "qr where to scan") case .desktopAddress: diff --git a/apps/ios/Shared/Views/NewChat/QRCodeType.swift b/apps/ios/Shared/Views/NewChat/QRCodeType.swift index 821f60a211..66bae7a42c 100644 --- a/apps/ios/Shared/Views/NewChat/QRCodeType.swift +++ b/apps/ios/Shared/Views/NewChat/QRCodeType.swift @@ -65,10 +65,10 @@ func parseQRCode(_ raw: String) -> QRCodeType { let t = raw.trimmingCharacters(in: .whitespacesAndNewlines) if strHasSimplexFileLink(t) { return .migrationLink(text: t) } if t.hasPrefix(desktopAddressScheme) { return .desktopAddress(text: t) } - // Match only when the WHOLE string is exactly one SimpleX link — same as `strIsSimplexLink` - // and the master connect scanner — not a link embedded among other text. A SimpleX *name* - // (Format.simplexName) is therefore not a connectionLink here; it falls through to .unknown, - // exactly as the master scanner treats it (names are handled only by the paste path). + // Match only when the WHOLE string is exactly one SimpleX link — not a link embedded among + // other text. A SimpleX *name* (Format.simplexName) is therefore not a connectionLink here; it + // falls through to .unknown, the same "not a SimpleX link" the connect scanner showed before + // this change (names are handled only by the paste path, via strConnectTarget). if let md = parseSimpleXMarkdown(t), md.count == 1, case let .simplexLink(_, linkType, _, _) = md[0].format { return .connectionLink(text: t, linkType: linkType) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index bc2c57a98f..46bc5e2625 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -206,7 +206,7 @@ private fun MutableState.PasteOrScanLinkView(close: () -> Uni if (appPlatform.isAndroid) { SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' ')) { QRCodeScanner(showQRCodeScanner = remember { mutableStateOf(true) }) { text -> - handleScan(null, text, QRCodeType.MigrationLink::class, close = {}) { checkUserLink(it.text) } + handleScan(null, text, QRCodeType.MigrationLink::class, close) { checkUserLink(it.text) } } } SectionSpacer() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index fbff547eef..010557743c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -801,11 +801,6 @@ private fun createInvitation( } } -fun strIsSimplexLink(str: String): Boolean { - val parsedMd = parseToMarkdown(str) - return parsedMd != null && parsedMd.size == 1 && parsedMd[0].format is Format.SimplexLink -} - sealed class ConnectTarget { class Link(val text: String, val linkType: SimplexLinkType, val linkText: String) : ConnectTarget() class Name(val nameInfo: SimplexNameInfo) : ConnectTarget() diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 1cab8d4545..67028da679 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -778,7 +778,7 @@ This is an address for linking a mobile to a SimpleX desktop app. This is a contact\'s security code. Tap New chat, then Paste link / Scan. - Tap your profile image, then Settings, Network & servers, your servers, Scan server QR code. + Tap your profile image, then Settings, Network & servers, Your servers, Scan server QR code. Tap your profile image, then Use from desktop, Scan QR code from desktop. On the new device, when first setting up the app, choose Migrate from another device. Open the chat, tap the contact\'s name, then Verify security code. diff --git a/plans/plan-unify-qr-scanning-impl.md b/plans/plan-unify-qr-scanning-impl.md index 515b3a228a..f7b757fdf2 100644 --- a/plans/plan-unify-qr-scanning-impl.md +++ b/plans/plan-unify-qr-scanning-impl.md @@ -270,7 +270,7 @@ qr_type_migration_link = "This is a link to migrate to another device." qr_type_desktop_address = "This is an address for linking a mobile to a SimpleX desktop app." qr_type_security_code = "This is a contact's security code." qr_where_connection = "Tap New chat, then Paste link / Scan." -qr_where_server = "Tap your profile image, then Settings, Network & servers, your servers, Scan server QR code." +qr_where_server = "Tap your profile image, then Settings, Network & servers, Your servers, Scan server QR code." qr_where_desktop = "Tap your profile image, then Use from desktop, Scan QR code from desktop." qr_where_migration = "On the new device, when first setting up the app, choose Migrate from another device." qr_where_security_code = "Open the chat, tap the contact's name, then Verify security code."