From 1e78940ee1075bb1eed66e325c9258cd5bfba98a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 9 Apr 2026 13:27:55 -0300 Subject: [PATCH 1/5] feat: create and implement the connection issues view --- Bitkit/Components/ConnectionIssuesView.swift | 112 ++++++++++++++++++ .../Localization/en.lproj/Localizable.strings | 2 + Bitkit/Views/Sheets/ForceTransferSheet.swift | 2 + .../Views/Transfer/SavingsConfirmView.swift | 2 + Bitkit/Views/Transfer/SpendingAmount.swift | 2 + Bitkit/Views/Transfer/SpendingConfirm.swift | 2 + .../Views/Wallets/Receive/ReceiveSheet.swift | 2 + Bitkit/Views/Wallets/Send/SendSheet.swift | 2 + CHANGELOG.md | 3 + 9 files changed, 129 insertions(+) create mode 100644 Bitkit/Components/ConnectionIssuesView.swift diff --git a/Bitkit/Components/ConnectionIssuesView.swift b/Bitkit/Components/ConnectionIssuesView.swift new file mode 100644 index 000000000..4fcec9d44 --- /dev/null +++ b/Bitkit/Components/ConnectionIssuesView.swift @@ -0,0 +1,112 @@ +import SwiftUI + +/// A full-screen overlay displayed when the device loses internet connectivity. +/// Shows a phone illustration with animated dashed gradient rings and a loading spinner. +struct ConnectionIssuesView: View { + let title: String + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + SheetHeader(title: title, showBackButton: false) + + Spacer().frame(height: 24) + + ZStack(alignment: .center) { + DashedRingsLayer(radii: [200]) + + Image("phone") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 311) + + DashedRingsLayer(radii: [150, 100, 50]) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + + DisplayText( + t("other__connection_issues_title"), + accentColor: .yellowAccent + ) + + Spacer().frame(height: 8) + + BodyMText( + t("other__connection_issues_explain"), + textColor: .white64 + ) + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer().frame(height: 24) + + ActivityIndicator() + .frame(maxWidth: .infinity) + + Spacer().frame(height: 16) + } + .navigationBarHidden(true) + .padding(.horizontal, 16) + .sheetBackground() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .accessibilityIdentifier("ConnectionIssuesView") + } +} + +// MARK: - Dashed Gradient Rings + +private struct DashedRingsLayer: View { + let radii: [CGFloat] + + var body: some View { + Canvas { context, size in + let center = CGPoint(x: size.width * 0.25, y: size.height * 0.40) + + for radius in radii { + let rect = CGRect( + x: center.x - radius, + y: center.y - radius, + width: radius * 2, + height: radius * 2 + ) + + var path = Path() + path.addEllipse(in: rect) + + let gradient = Gradient(colors: [.black, .yellowAccent]) + let startPoint = CGPoint(x: rect.minX, y: rect.minY) + let endPoint = CGPoint(x: rect.maxX, y: rect.maxY) + + context.stroke( + path, + with: .linearGradient(gradient, startPoint: startPoint, endPoint: endPoint), + style: StrokeStyle(lineWidth: 1, dash: [8, 6]) + ) + } + } + .allowsHitTesting(false) + } +} + +// MARK: - View Modifier + +extension View { + /// Overlays a `ConnectionIssuesView` when the device is offline. + /// The underlying content remains mounted so navigation state and inputs are preserved. + func connectionIssuesOverlay(title: String, isOffline: Bool) -> some View { + ZStack { + self + + if isOffline { + ConnectionIssuesView(title: title) + .transition(.opacity) + } + } + .animation(.easeInOut(duration: 0.3), value: isOffline) + } +} + +// MARK: - Preview + +#Preview { + ConnectionIssuesView(title: "Send Bitcoin") + .preferredColorScheme(.dark) +} diff --git a/Bitkit/Resources/Localization/en.lproj/Localizable.strings b/Bitkit/Resources/Localization/en.lproj/Localizable.strings index 99b505fe3..13c57c60a 100644 --- a/Bitkit/Resources/Localization/en.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/en.lproj/Localizable.strings @@ -389,6 +389,8 @@ "other__connection_reconnect_msg" = "Lost connection to Electrum, trying to reconnect..."; "other__connection_back_title" = "Internet Connection Restored"; "other__connection_back_msg" = "Bitkit successfully reconnected to the Internet."; +"other__connection_issues_title" = "Connection\nIssues"; +"other__connection_issues_explain" = "It appears you're disconnected. Please check your connection. Bitkit will try to reconnect every few seconds."; "other__high_balance__nav_title" = "High Balance"; "other__high_balance__title" = "High\nBalance"; "other__high_balance__text" = "Your wallet balance exceeds $500.\nFor your security, consider moving some of your savings to an offline wallet."; diff --git a/Bitkit/Views/Sheets/ForceTransferSheet.swift b/Bitkit/Views/Sheets/ForceTransferSheet.swift index 9fba2250d..4745fb0b5 100644 --- a/Bitkit/Views/Sheets/ForceTransferSheet.swift +++ b/Bitkit/Views/Sheets/ForceTransferSheet.swift @@ -7,6 +7,7 @@ struct ForceTransferSheetItem: SheetItem { struct ForceTransferSheet: View { @EnvironmentObject private var app: AppViewModel + @EnvironmentObject private var network: NetworkMonitor @EnvironmentObject private var sheets: SheetViewModel @EnvironmentObject private var transfer: TransferViewModel let config: ForceTransferSheetItem @@ -29,6 +30,7 @@ struct ForceTransferSheet: View { onContinue: onForceTransfer ) } + .connectionIssuesOverlay(title: t("lightning__force_nav_title"), isOffline: !network.isConnected) } private func onCancel() { diff --git a/Bitkit/Views/Transfer/SavingsConfirmView.swift b/Bitkit/Views/Transfer/SavingsConfirmView.swift index 7c7f7681b..31f2dc2f0 100644 --- a/Bitkit/Views/Transfer/SavingsConfirmView.swift +++ b/Bitkit/Views/Transfer/SavingsConfirmView.swift @@ -5,6 +5,7 @@ struct SavingsConfirmView: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var currency: CurrencyViewModel @EnvironmentObject var navigation: NavigationViewModel + @EnvironmentObject var network: NetworkMonitor @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -100,6 +101,7 @@ struct SavingsConfirmView: View { .navigationBarHidden(true) .padding(.horizontal, 16) .bottomSafeAreaPadding() + .connectionIssuesOverlay(title: t("lightning__transfer__nav_title"), isOffline: !network.isConnected) } } diff --git a/Bitkit/Views/Transfer/SpendingAmount.swift b/Bitkit/Views/Transfer/SpendingAmount.swift index 750832191..414ce2865 100644 --- a/Bitkit/Views/Transfer/SpendingAmount.swift +++ b/Bitkit/Views/Transfer/SpendingAmount.swift @@ -6,6 +6,7 @@ struct SpendingAmount: View { @EnvironmentObject var currency: CurrencyViewModel @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel + @EnvironmentObject var network: NetworkMonitor @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -85,6 +86,7 @@ struct SpendingAmount: View { await calculateMaxTransferAmount() } } + .connectionIssuesOverlay(title: t("lightning__transfer__nav_title"), isOffline: !network.isConnected) } private var actionButtons: some View { diff --git a/Bitkit/Views/Transfer/SpendingConfirm.swift b/Bitkit/Views/Transfer/SpendingConfirm.swift index 9501d9a39..90a3ba0c4 100644 --- a/Bitkit/Views/Transfer/SpendingConfirm.swift +++ b/Bitkit/Views/Transfer/SpendingConfirm.swift @@ -8,6 +8,7 @@ struct SpendingConfirm: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel + @EnvironmentObject var network: NetworkMonitor @EnvironmentObject var settings: SettingsViewModel @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -134,6 +135,7 @@ struct SpendingConfirm: View { .task { await calculateTransactionFee() } + .connectionIssuesOverlay(title: t("lightning__transfer__nav_title"), isOffline: !network.isConnected) } private func onConfirm() async { diff --git a/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift b/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift index 9cf6a790d..d8717eb33 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift @@ -32,6 +32,7 @@ struct ReceiveSheetItem: SheetItem { struct ReceiveSheet: View { let config: ReceiveSheetItem @State private var navigationPath: [ReceiveRoute] = [] + @EnvironmentObject private var network: NetworkMonitor @EnvironmentObject private var wallet: WalletViewModel @EnvironmentObject private var tagManager: TagManager @@ -44,6 +45,7 @@ struct ReceiveSheet: View { } } } + .connectionIssuesOverlay(title: t("wallet__receive_bitcoin"), isOffline: !network.isConnected) .onAppear { wallet.invoiceAmountSats = 0 wallet.invoiceNote = "" diff --git a/Bitkit/Views/Wallets/Send/SendSheet.swift b/Bitkit/Views/Wallets/Send/SendSheet.swift index 3d3883ca7..f3eca432e 100644 --- a/Bitkit/Views/Wallets/Send/SendSheet.swift +++ b/Bitkit/Views/Wallets/Send/SendSheet.swift @@ -41,6 +41,7 @@ struct SendSheetItem: SheetItem { struct SendSheet: View { @EnvironmentObject private var app: AppViewModel + @EnvironmentObject private var network: NetworkMonitor @EnvironmentObject private var settings: SettingsViewModel @EnvironmentObject private var sheets: SheetViewModel @EnvironmentObject private var tagManager: TagManager @@ -95,6 +96,7 @@ struct SendSheet: View { } } .animation(.easeInOut(duration: 0.3), value: shouldShowSyncOverlay) + .connectionIssuesOverlay(title: t("wallet__send_bitcoin"), isOffline: !network.isConnected) .onAppear { tagManager.clearSelectedTags() wallet.resetSendState(speed: settings.defaultTransactionSpeed) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c424818e..0d07ee550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Connection issues overlay on Send, Receive, Transfer, and Force Transfer flows + ### Fixed - Fix keyboard and UI issues in the calculator widget #513 - Preserve msat precision for LNURL pay, withdraw callbacks and bolt11 #512 From e9207c47faed6d26709fab80c47705a81743754c Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 9 Apr 2026 13:34:40 -0300 Subject: [PATCH 2/5] doc: add pr number to changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d07ee550..7004e4e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Connection issues overlay on Send, Receive, Transfer, and Force Transfer flows +- Connection issues overlay on Send, Receive, Transfer, and Force Transfer flows #524 ### Fixed - Fix keyboard and UI issues in the calculator widget #513 From 5fc5147bf4d40c7741ec494df437b238d8ba69f4 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 10 Apr 2026 07:53:21 -0300 Subject: [PATCH 3/5] fix: add logs and inject network monitor directly in ConnectionIssuesOverlayModifier --- Bitkit/Components/ConnectionIssuesView.swift | 23 +++++++++++++------ Bitkit/Managers/NetworkMonitor.swift | 13 ++++++++++- Bitkit/Views/Sheets/ForceTransferSheet.swift | 3 +-- .../Views/Transfer/SavingsConfirmView.swift | 3 +-- Bitkit/Views/Transfer/SpendingAmount.swift | 3 +-- Bitkit/Views/Transfer/SpendingConfirm.swift | 3 +-- .../Views/Wallets/Receive/ReceiveSheet.swift | 3 +-- Bitkit/Views/Wallets/Send/SendSheet.swift | 3 +-- 8 files changed, 34 insertions(+), 20 deletions(-) diff --git a/Bitkit/Components/ConnectionIssuesView.swift b/Bitkit/Components/ConnectionIssuesView.swift index 4fcec9d44..ee2c79d96 100644 --- a/Bitkit/Components/ConnectionIssuesView.swift +++ b/Bitkit/Components/ConnectionIssuesView.swift @@ -88,19 +88,28 @@ private struct DashedRingsLayer: View { // MARK: - View Modifier -extension View { - /// Overlays a `ConnectionIssuesView` when the device is offline. - /// The underlying content remains mounted so navigation state and inputs are preserved. - func connectionIssuesOverlay(title: String, isOffline: Bool) -> some View { +private struct ConnectionIssuesOverlayModifier: ViewModifier { + let title: String + @EnvironmentObject private var network: NetworkMonitor + + func body(content: Content) -> some View { ZStack { - self + content - if isOffline { + if !network.isConnected { ConnectionIssuesView(title: title) .transition(.opacity) } } - .animation(.easeInOut(duration: 0.3), value: isOffline) + .animation(.easeInOut(duration: 0.3), value: network.isConnected) + } +} + +extension View { + /// Overlays a `ConnectionIssuesView` when the device is offline. + /// The underlying content remains mounted so navigation state and inputs are preserved. + func connectionIssuesOverlay(title: String) -> some View { + modifier(ConnectionIssuesOverlayModifier(title: title)) } } diff --git a/Bitkit/Managers/NetworkMonitor.swift b/Bitkit/Managers/NetworkMonitor.swift index 49b6c716e..2c0b65acd 100644 --- a/Bitkit/Managers/NetworkMonitor.swift +++ b/Bitkit/Managers/NetworkMonitor.swift @@ -25,8 +25,11 @@ final class NetworkMonitor: ObservableObject { // Set the pathUpdateHandler monitor.pathUpdateHandler = { [weak self] path in DispatchQueue.main.async { + let wasConnected = self?.isConnected + let isNowConnected = path.status == .satisfied + // Check if the device is connected to the internet - self?.isConnected = path.status == .satisfied + self?.isConnected = isNowConnected // Check if the network is expensive (e.g. cellular data) self?.isExpensive = path.isExpensive @@ -36,6 +39,14 @@ final class NetworkMonitor: ObservableObject { // Update the network path self?.nwPath = path + + if wasConnected != isNowConnected { + let interfaceType = path.availableInterfaces.first?.type + Logger + .debug( + "Network connectivity changed: \(isNowConnected ? "connected" : "disconnected") (interface: \(String(describing: interfaceType)), status: \(path.status))" + ) + } } } diff --git a/Bitkit/Views/Sheets/ForceTransferSheet.swift b/Bitkit/Views/Sheets/ForceTransferSheet.swift index 4745fb0b5..9fd120882 100644 --- a/Bitkit/Views/Sheets/ForceTransferSheet.swift +++ b/Bitkit/Views/Sheets/ForceTransferSheet.swift @@ -7,7 +7,6 @@ struct ForceTransferSheetItem: SheetItem { struct ForceTransferSheet: View { @EnvironmentObject private var app: AppViewModel - @EnvironmentObject private var network: NetworkMonitor @EnvironmentObject private var sheets: SheetViewModel @EnvironmentObject private var transfer: TransferViewModel let config: ForceTransferSheetItem @@ -30,7 +29,7 @@ struct ForceTransferSheet: View { onContinue: onForceTransfer ) } - .connectionIssuesOverlay(title: t("lightning__force_nav_title"), isOffline: !network.isConnected) + .connectionIssuesOverlay(title: t("lightning__force_nav_title")) } private func onCancel() { diff --git a/Bitkit/Views/Transfer/SavingsConfirmView.swift b/Bitkit/Views/Transfer/SavingsConfirmView.swift index 31f2dc2f0..8fa8dcef5 100644 --- a/Bitkit/Views/Transfer/SavingsConfirmView.swift +++ b/Bitkit/Views/Transfer/SavingsConfirmView.swift @@ -5,7 +5,6 @@ struct SavingsConfirmView: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var currency: CurrencyViewModel @EnvironmentObject var navigation: NavigationViewModel - @EnvironmentObject var network: NetworkMonitor @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -101,7 +100,7 @@ struct SavingsConfirmView: View { .navigationBarHidden(true) .padding(.horizontal, 16) .bottomSafeAreaPadding() - .connectionIssuesOverlay(title: t("lightning__transfer__nav_title"), isOffline: !network.isConnected) + .connectionIssuesOverlay(title: t("lightning__transfer__nav_title")) } } diff --git a/Bitkit/Views/Transfer/SpendingAmount.swift b/Bitkit/Views/Transfer/SpendingAmount.swift index 414ce2865..3918060e2 100644 --- a/Bitkit/Views/Transfer/SpendingAmount.swift +++ b/Bitkit/Views/Transfer/SpendingAmount.swift @@ -6,7 +6,6 @@ struct SpendingAmount: View { @EnvironmentObject var currency: CurrencyViewModel @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel - @EnvironmentObject var network: NetworkMonitor @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -86,7 +85,7 @@ struct SpendingAmount: View { await calculateMaxTransferAmount() } } - .connectionIssuesOverlay(title: t("lightning__transfer__nav_title"), isOffline: !network.isConnected) + .connectionIssuesOverlay(title: t("lightning__transfer__nav_title")) } private var actionButtons: some View { diff --git a/Bitkit/Views/Transfer/SpendingConfirm.swift b/Bitkit/Views/Transfer/SpendingConfirm.swift index 90a3ba0c4..7bca45408 100644 --- a/Bitkit/Views/Transfer/SpendingConfirm.swift +++ b/Bitkit/Views/Transfer/SpendingConfirm.swift @@ -8,7 +8,6 @@ struct SpendingConfirm: View { @EnvironmentObject var app: AppViewModel @EnvironmentObject var feeEstimatesManager: FeeEstimatesManager @EnvironmentObject var navigation: NavigationViewModel - @EnvironmentObject var network: NetworkMonitor @EnvironmentObject var settings: SettingsViewModel @EnvironmentObject var transfer: TransferViewModel @EnvironmentObject var wallet: WalletViewModel @@ -135,7 +134,7 @@ struct SpendingConfirm: View { .task { await calculateTransactionFee() } - .connectionIssuesOverlay(title: t("lightning__transfer__nav_title"), isOffline: !network.isConnected) + .connectionIssuesOverlay(title: t("lightning__transfer__nav_title")) } private func onConfirm() async { diff --git a/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift b/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift index d8717eb33..f393af867 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveSheet.swift @@ -32,7 +32,6 @@ struct ReceiveSheetItem: SheetItem { struct ReceiveSheet: View { let config: ReceiveSheetItem @State private var navigationPath: [ReceiveRoute] = [] - @EnvironmentObject private var network: NetworkMonitor @EnvironmentObject private var wallet: WalletViewModel @EnvironmentObject private var tagManager: TagManager @@ -45,7 +44,7 @@ struct ReceiveSheet: View { } } } - .connectionIssuesOverlay(title: t("wallet__receive_bitcoin"), isOffline: !network.isConnected) + .connectionIssuesOverlay(title: t("wallet__receive_bitcoin")) .onAppear { wallet.invoiceAmountSats = 0 wallet.invoiceNote = "" diff --git a/Bitkit/Views/Wallets/Send/SendSheet.swift b/Bitkit/Views/Wallets/Send/SendSheet.swift index f3eca432e..c09bfb0a3 100644 --- a/Bitkit/Views/Wallets/Send/SendSheet.swift +++ b/Bitkit/Views/Wallets/Send/SendSheet.swift @@ -41,7 +41,6 @@ struct SendSheetItem: SheetItem { struct SendSheet: View { @EnvironmentObject private var app: AppViewModel - @EnvironmentObject private var network: NetworkMonitor @EnvironmentObject private var settings: SettingsViewModel @EnvironmentObject private var sheets: SheetViewModel @EnvironmentObject private var tagManager: TagManager @@ -96,7 +95,7 @@ struct SendSheet: View { } } .animation(.easeInOut(duration: 0.3), value: shouldShowSyncOverlay) - .connectionIssuesOverlay(title: t("wallet__send_bitcoin"), isOffline: !network.isConnected) + .connectionIssuesOverlay(title: t("wallet__send_bitcoin")) .onAppear { tagManager.clearSelectedTags() wallet.resetSendState(speed: settings.defaultTransactionSpeed) From 143e315a40ff58b471a2611a647e5066d2aa88f9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 10 Apr 2026 09:09:31 -0300 Subject: [PATCH 4/5] feat: implement connectionIssuesOverlay in scan sheet and screen --- Bitkit/Views/Scanner/ScannerScreen.swift | 1 + Bitkit/Views/Scanner/ScannerSheet.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Bitkit/Views/Scanner/ScannerScreen.swift b/Bitkit/Views/Scanner/ScannerScreen.swift index 1a6609050..4edf1deae 100644 --- a/Bitkit/Views/Scanner/ScannerScreen.swift +++ b/Bitkit/Views/Scanner/ScannerScreen.swift @@ -58,6 +58,7 @@ struct ScannerScreen: View { .navigationBarHidden(true) .padding(.horizontal, 16) .bottomSafeAreaPadding() + .connectionIssuesOverlay(title: t("other__qr_scan")) .onAppear { scanner.configure( app: app, diff --git a/Bitkit/Views/Scanner/ScannerSheet.swift b/Bitkit/Views/Scanner/ScannerSheet.swift index 511287b8f..2aa6a003a 100644 --- a/Bitkit/Views/Scanner/ScannerSheet.swift +++ b/Bitkit/Views/Scanner/ScannerSheet.swift @@ -85,6 +85,7 @@ struct ScannerSheet: View { .presentationDragIndicator(.visible) } } + .connectionIssuesOverlay(title: t("other__qr_scan")) } private func handleManualEntrySubmit() async { From c2ffef3676480cd481173ce232870f33a439ab23 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 10 Apr 2026 09:33:17 -0300 Subject: [PATCH 5/5] feat: create syncNodeOverlay and implement on scan sheet and screen --- Bitkit/Components/SyncNodeView.swift | 32 ++++++++++++++++++++++++ Bitkit/Views/Scanner/ScannerScreen.swift | 1 + Bitkit/Views/Scanner/ScannerSheet.swift | 1 + 3 files changed, 34 insertions(+) diff --git a/Bitkit/Components/SyncNodeView.swift b/Bitkit/Components/SyncNodeView.swift index 1112293b3..d8e10083f 100644 --- a/Bitkit/Components/SyncNodeView.swift +++ b/Bitkit/Components/SyncNodeView.swift @@ -87,3 +87,35 @@ struct SyncNodeView: View { } } } + +// MARK: - View Modifier + +private struct SyncNodeOverlayModifier: ViewModifier { + @EnvironmentObject private var wallet: WalletViewModel + + private var shouldShowSyncOverlay: Bool { + guard wallet.nodeLifecycleState == .running else { return true } + let hasAnyChannels = (wallet.channels?.isEmpty == false) || wallet.channelCount > 0 + guard hasAnyChannels else { return false } + return !wallet.hasUsableChannels + } + + func body(content: Content) -> some View { + ZStack { + content + + if shouldShowSyncOverlay { + SyncNodeView() + .transition(.opacity) + } + } + .animation(.easeInOut(duration: 0.3), value: shouldShowSyncOverlay) + } +} + +extension View { + /// Overlays a `SyncNodeView` when the node is not running or channels aren't usable yet. + func syncNodeOverlay() -> some View { + modifier(SyncNodeOverlayModifier()) + } +} diff --git a/Bitkit/Views/Scanner/ScannerScreen.swift b/Bitkit/Views/Scanner/ScannerScreen.swift index 4edf1deae..a446c61d5 100644 --- a/Bitkit/Views/Scanner/ScannerScreen.swift +++ b/Bitkit/Views/Scanner/ScannerScreen.swift @@ -58,6 +58,7 @@ struct ScannerScreen: View { .navigationBarHidden(true) .padding(.horizontal, 16) .bottomSafeAreaPadding() + .syncNodeOverlay() .connectionIssuesOverlay(title: t("other__qr_scan")) .onAppear { scanner.configure( diff --git a/Bitkit/Views/Scanner/ScannerSheet.swift b/Bitkit/Views/Scanner/ScannerSheet.swift index 2aa6a003a..f6badb694 100644 --- a/Bitkit/Views/Scanner/ScannerSheet.swift +++ b/Bitkit/Views/Scanner/ScannerSheet.swift @@ -85,6 +85,7 @@ struct ScannerSheet: View { .presentationDragIndicator(.visible) } } + .syncNodeOverlay() .connectionIssuesOverlay(title: t("other__qr_scan")) }