diff --git a/Bitkit/Components/Activity/ActivityBanner.swift b/Bitkit/Components/Activity/ActivityBanner.swift index 87a5a1611..a8843bc30 100644 --- a/Bitkit/Components/Activity/ActivityBanner.swift +++ b/Bitkit/Components/Activity/ActivityBanner.swift @@ -43,6 +43,7 @@ struct ActivityBanner: View { Text(bannerText) .font(Fonts.black(size: 20)) .foregroundColor(.textPrimary) + .textCase(.uppercase) .kerning(0) .lineLimit(1) } diff --git a/Bitkit/Components/Activity/ActivityList.swift b/Bitkit/Components/Activity/ActivityList.swift index 00bcf0405..a3a570fb9 100644 --- a/Bitkit/Components/Activity/ActivityList.swift +++ b/Bitkit/Components/Activity/ActivityList.swift @@ -24,7 +24,7 @@ struct ActivityList: View { switch groupItem { case let .header(title): CaptionMText(title) - .padding(.top, 16) + .frame(height: 34, alignment: .bottom) case let .activity(item): NavigationLink(value: Route.activityDetail(item)) { @@ -43,12 +43,9 @@ struct ActivityList: View { private func getActivities() -> [Activity] { switch viewType { - case .all: - return activity.filteredActivities ?? [] - case .lightning: - return activity.lightningActivities ?? [] - case .onchain: - return activity.onchainActivities ?? [] + case .all: return activity.filteredActivities ?? [] + case .lightning: return activity.lightningActivities ?? [] + case .onchain: return activity.onchainActivities ?? [] } } } diff --git a/Bitkit/Components/BlurView.swift b/Bitkit/Components/BlurView.swift new file mode 100644 index 000000000..5d6bc4591 --- /dev/null +++ b/Bitkit/Components/BlurView.swift @@ -0,0 +1,18 @@ +import SwiftUI +import UIKit + +struct BlurView: UIViewRepresentable { + func makeUIView(context: Context) -> UIVisualEffectView { + UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterialLight)) + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + DispatchQueue.main.async { + if let backdropLayer = uiView.layer.sublayers?.first { + backdropLayer.filters?.removeAll(where: { filter in + String(describing: filter) != "gaussianBlur" + }) + } + } + } +} diff --git a/Bitkit/Components/Button/SecondaryButtonView.swift b/Bitkit/Components/Button/SecondaryButtonView.swift index 7ee1b3212..3e15fc5d1 100644 --- a/Bitkit/Components/Button/SecondaryButtonView.swift +++ b/Bitkit/Components/Button/SecondaryButtonView.swift @@ -22,37 +22,32 @@ struct SecondaryButtonView: View { .frame(maxWidth: size == .large ? .infinity : nil) .frame(height: buttonHeight) .padding(.horizontal, 16) - // TODO: Add background blur - .background( - RoundedRectangle(cornerRadius: 64) - .fill(isPressed ? Color.white10 : Color.white01) - .overlay( - RoundedRectangle(cornerRadius: 64) - .strokeBorder(borderColor, lineWidth: strokeWidth) - ) - ) + .background(isPressed ? Color.white10 : Color.clear) + .background(BlurView()) + .overlay(RoundedRectangle(cornerRadius: 64).strokeBorder(borderColor, lineWidth: strokeWidth)) + .cornerRadius(64) .contentShape(Rectangle()) } private var textColor: Color { - return isDisabled ? .white32 : .white80 + isDisabled ? .white32 : .white80 } private var borderColor: Color { - return isDisabled ? .clear : Color(hex: 0x3A3A3A) + isDisabled ? .clear : .gray4 } private var buttonHeight: CGFloat { switch size { - case .small: return 37 - case .large: return 56 + case .small: 37 + case .large: 56 } } private var strokeWidth: CGFloat { switch size { - case .small: return 1 - case .large: return 2 + case .small: 1 + case .large: 2 } } } diff --git a/Bitkit/Components/NavigationBar.swift b/Bitkit/Components/NavigationBar.swift index 7d3683ad4..36fb13dbb 100644 --- a/Bitkit/Components/NavigationBar.swift +++ b/Bitkit/Components/NavigationBar.swift @@ -89,5 +89,11 @@ struct NavigationBar: View { } } .frame(height: 48) + .background(LinearGradient( + colors: [.black, .black.opacity(0)], + startPoint: .top, + endPoint: .bottom + )) + .zIndex(.infinity) } } diff --git a/Bitkit/Components/SegmentedControl.swift b/Bitkit/Components/SegmentedControl.swift index fcf4c67a9..ecce8f382 100644 --- a/Bitkit/Components/SegmentedControl.swift +++ b/Bitkit/Components/SegmentedControl.swift @@ -32,7 +32,7 @@ struct SegmentedControl: View { HStack(spacing: 8) { ForEach(tabItems, id: \.tab) { tabItem in Button(action: { - withAnimation(.spring(response: 0.35, dampingFraction: 0.8)) { + withAnimation(.easeInOut(duration: 0.2)) { selectedTab = tabItem.tab } }) { @@ -43,6 +43,7 @@ struct SegmentedControl: View { Rectangle() .frame(height: 2) .foregroundColor(Color.white64) + if selectedTab == tabItem.tab { Rectangle() .frame(height: 2) @@ -51,7 +52,7 @@ struct SegmentedControl: View { } } } - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) .contentShape(Rectangle()) } .buttonStyle(PlainButtonStyle()) diff --git a/Bitkit/Components/ShopWebView.swift b/Bitkit/Components/ShopWebView.swift index 3dfdf43a3..6e8d590eb 100644 --- a/Bitkit/Components/ShopWebView.swift +++ b/Bitkit/Components/ShopWebView.swift @@ -31,7 +31,7 @@ struct ShopWebView: UIViewRepresentable { wkWebView.isOpaque = false wkWebView.backgroundColor = UIColor(red: 0x14 / 255.0, green: 0x17 / 255.0, blue: 0x16 / 255.0, alpha: 1.0) // #141716 wkWebView.scrollView.backgroundColor = wkWebView.backgroundColor - wkWebView.layer.cornerRadius = 8 + wkWebView.layer.cornerRadius = 18 wkWebView.clipsToBounds = true webView?.wrappedValue = wkWebView diff --git a/Bitkit/Extensions/View+SafeArea.swift b/Bitkit/Extensions/View+SafeArea.swift index 2c143e7a5..b9a724462 100644 --- a/Bitkit/Extensions/View+SafeArea.swift +++ b/Bitkit/Extensions/View+SafeArea.swift @@ -1,6 +1,30 @@ import SwiftUI import UIKit +// MARK: - Layout constants (header, tab bar, spacing) used for content margins across wallet/home screens + +enum ScreenLayout { + static let headerHeight: CGFloat = 48 + static let headerSpacing: CGFloat = 16 + static let tabBarHeight: CGFloat = 64 + static let bottomSpacing: CGFloat = 32 + + /// Safe area top + header + spacing (e.g. HomeScreen, HomeWalletView, HomeWidgetsView) + static var topPaddingWithSafeArea: CGFloat { + windowSafeAreaInsets.top + headerHeight + headerSpacing + } + + /// Header + spacing only, when view is already inside safe area (e.g. SavingsWalletScreen, SpendingWalletScreen) + static var topPaddingWithoutSafeArea: CGFloat { + headerHeight + headerSpacing + } + + /// Safe area bottom + tab bar + spacing + static var bottomPaddingWithSafeArea: CGFloat { + windowSafeAreaInsets.bottom + tabBarHeight + bottomSpacing + } +} + private var hasBottomSafeArea: Bool { windowSafeAreaInsets.bottom > 0 } diff --git a/Bitkit/MainNavView.swift b/Bitkit/MainNavView.swift index 1829d883a..731d599d1 100644 --- a/Bitkit/MainNavView.swift +++ b/Bitkit/MainNavView.swift @@ -165,6 +165,7 @@ struct MainNavView: View { .accentColor(.white) .overlay { TabBar() + .ignoresSafeArea(.keyboard) DrawerView() } .onChange(of: scenePhase) { _, newPhase in @@ -276,8 +277,8 @@ struct MainNavView: View { case let .activityDetail(activity): ActivityItemView(item: activity) case let .activityExplorer(activity): ActivityExplorerView(item: activity) case .buyBitcoin: BuyBitcoinView() - case .savingsWallet: SavingsWalletView() - case .spendingWallet: SpendingWalletView() + case .savingsWallet: SavingsWalletScreen() + case .spendingWallet: SpendingWalletScreen() case .scanner: ScannerScreen() // Transfer @@ -311,7 +312,6 @@ struct MainNavView: View { case .shopIntro: ShopIntro() case .shopDiscover: ShopDiscover() case let .shopMain(page): ShopMain(page: page) - case .shopMap: ShopMap() // Widgets case .widgetsIntro: WidgetsIntroView() diff --git a/Bitkit/ViewModels/NavigationViewModel.swift b/Bitkit/ViewModels/NavigationViewModel.swift index b43a79bc0..d8ff73db5 100644 --- a/Bitkit/ViewModels/NavigationViewModel.swift +++ b/Bitkit/ViewModels/NavigationViewModel.swift @@ -38,7 +38,6 @@ enum Route: Hashable { case shopIntro case shopDiscover case shopMain(page: String) - case shopMap // Widgets case widgetsIntro diff --git a/Bitkit/ViewModels/WidgetsViewModel.swift b/Bitkit/ViewModels/WidgetsViewModel.swift index 86d713bbd..cd954ff90 100644 --- a/Bitkit/ViewModels/WidgetsViewModel.swift +++ b/Bitkit/ViewModels/WidgetsViewModel.swift @@ -145,13 +145,13 @@ struct PlaceholderWidget: View { // MARK: - Widget Types enum WidgetType: String, CaseIterable, Codable { - case suggestions case price case news case blocks case facts - case calculator case weather + case calculator + case suggestions } // MARK: - WidgetsViewModel diff --git a/Bitkit/Views/Home/HomeWalletView.swift b/Bitkit/Views/Home/HomeWalletView.swift index fec79d7d5..603ff6d29 100644 --- a/Bitkit/Views/Home/HomeWalletView.swift +++ b/Bitkit/Views/Home/HomeWalletView.swift @@ -10,16 +10,6 @@ struct HomeWalletView: View { return activity.latestActivities?.isEmpty == false } - /// Safe area + header + spacing - private var topPadding: CGFloat { - windowSafeAreaInsets.top + 48 + 16 - } - - /// Safe area + tab bar + spacing - private var bottomPadding: CGFloat { - windowSafeAreaInsets.bottom + 64 + 32 - } - var body: some View { VStack(spacing: 0) { MoneyStack( @@ -66,8 +56,8 @@ struct HomeWalletView: View { WalletOnboardingView(type: .home) } } - .padding(.top, topPadding) - .padding(.bottom, bottomPadding) + .padding(.top, ScreenLayout.topPaddingWithSafeArea) + .padding(.bottom, ScreenLayout.bottomPaddingWithSafeArea) .padding(.horizontal) .animation(.spring(response: 0.3), value: hasActivity) } diff --git a/Bitkit/Views/Home/HomeWidgetsView.swift b/Bitkit/Views/Home/HomeWidgetsView.swift index 88d7b55a9..3f6b4f694 100644 --- a/Bitkit/Views/Home/HomeWidgetsView.swift +++ b/Bitkit/Views/Home/HomeWidgetsView.swift @@ -18,16 +18,6 @@ struct HomeWidgetsView: View { } } - /// Safe area + header + spacing - private var topPadding: CGFloat { - windowSafeAreaInsets.top + 48 + 16 - } - - /// Safe area + tab bar + spacing - private var bottomPadding: CGFloat { - windowSafeAreaInsets.bottom + 64 + 32 - } - var body: some View { ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 0) { @@ -55,8 +45,8 @@ struct HomeWidgetsView: View { .accessibilityIdentifier("WidgetsAdd") } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.top, topPadding) - .padding(.bottom, bottomPadding) + .padding(.top, ScreenLayout.topPaddingWithSafeArea) + .padding(.bottom, ScreenLayout.bottomPaddingWithSafeArea) .padding(.horizontal) } } diff --git a/Bitkit/Views/HomeScreen.swift b/Bitkit/Views/HomeScreen.swift index 0762f937c..6b382d06c 100644 --- a/Bitkit/Views/HomeScreen.swift +++ b/Bitkit/Views/HomeScreen.swift @@ -17,16 +17,6 @@ struct HomeScreen: View { scrollPosition ?? 0 } - /// Safe area + header + spacing - private var topPadding: CGFloat { - windowSafeAreaInsets.top + 48 + 16 - } - - /// Safe area + tab bar + spacing - private var bottomPadding: CGFloat { - windowSafeAreaInsets.bottom + 64 + 32 - } - var body: some View { ZStack(alignment: .top) { Header(showWidgetEditButton: currentPage == 1, isEditingWidgets: $isEditingWidgets) @@ -74,7 +64,7 @@ struct HomeScreen: View { startPoint: .top, endPoint: .bottom ) - .frame(height: topPadding) + .frame(height: ScreenLayout.topPaddingWithSafeArea) Spacer() @@ -83,7 +73,7 @@ struct HomeScreen: View { startPoint: .top, endPoint: .bottom ) - .frame(height: bottomPadding) + .frame(height: ScreenLayout.bottomPaddingWithSafeArea) } .ignoresSafeArea() .allowsHitTesting(false) diff --git a/Bitkit/Views/Shop/ShopDiscover.swift b/Bitkit/Views/Shop/ShopDiscover.swift index 41ef8d61f..d54a0b4b1 100644 --- a/Bitkit/Views/Shop/ShopDiscover.swift +++ b/Bitkit/Views/Shop/ShopDiscover.swift @@ -99,9 +99,7 @@ struct ShopDiscover: View { NavigationBar(title: t("other__shop__discover__nav_title")) .padding(.horizontal, 16) - SegmentedControl(selectedTab: $selectedTab, tabs: ShopTab.allCases) - .padding(.top, 16) - .padding(.bottom, 16) + SegmentedControl(selectedTab: $selectedTab, tabs: ShopTab.allCases, activeColor: .yellowAccent) .padding(.horizontal, 16) Group { @@ -111,6 +109,7 @@ struct ShopDiscover: View { case .map: ShopWebView(url: Env.btcMapUrl) .padding(.top, 16) + .padding(.horizontal, 16) } } .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/Bitkit/Views/Shop/ShopMain.swift b/Bitkit/Views/Shop/ShopMain.swift index 7f58cf63e..e8ef5cdb3 100644 --- a/Bitkit/Views/Shop/ShopMain.swift +++ b/Bitkit/Views/Shop/ShopMain.swift @@ -19,16 +19,10 @@ struct ShopMain: View { var body: some View { VStack(spacing: 0) { - NavigationBar( - title: t("other__shop__main__nav_title"), - showMenuButton: false - ) + NavigationBar(title: t("other__shop__main__nav_title")) - ShopWebView( - url: uri, - onMessage: handleMessage - ) - .padding(.top, 16) + ShopWebView(url: uri, onMessage: handleMessage) + .padding(.top, 16) } .navigationBarHidden(true) .padding(.horizontal, 16) diff --git a/Bitkit/Views/Shop/ShopMap.swift b/Bitkit/Views/Shop/ShopMap.swift deleted file mode 100644 index 1c367ff81..000000000 --- a/Bitkit/Views/Shop/ShopMap.swift +++ /dev/null @@ -1,25 +0,0 @@ -import SwiftUI -import WebKit - -struct ShopMap: View { - var body: some View { - VStack(spacing: 0) { - NavigationBar( - title: t("other__shop__discover__nav_title"), - showMenuButton: false - ) - - ShopWebView(url: Env.btcMapUrl) - .padding(.top, 16) - } - .navigationBarHidden(true) - .padding(.horizontal, 16) - } -} - -#Preview { - NavigationStack { - ShopMap() - } - .preferredColorScheme(.dark) -} diff --git a/Bitkit/Views/Wallets/Activity/AllActivityView.swift b/Bitkit/Views/Wallets/Activity/AllActivityView.swift index 4dced49b4..b49c4e000 100644 --- a/Bitkit/Views/Wallets/Activity/AllActivityView.swift +++ b/Bitkit/Views/Wallets/Activity/AllActivityView.swift @@ -5,9 +5,6 @@ struct AllActivityView: View { @EnvironmentObject private var app: AppViewModel @EnvironmentObject private var wallet: WalletViewModel - @State private var isHorizontalSwipe = false - @State private var dragOffset: CGFloat = 0 - var body: some View { ZStack(alignment: .top) { VStack(spacing: 0) { @@ -18,25 +15,13 @@ struct AllActivityView: View { .padding(.bottom, 16) SegmentedControl(selectedTab: $activity.selectedTab, tabs: ActivityTab.allCases) - .padding(.bottom, 8) ScrollView(showsIndicators: false) { ActivityList(viewType: .all) - // Leave some space for TabBar - .padding(.bottom, 130) .scrollDismissesKeyboard(.interactively) .highPriorityGesture( - // TODO: rewrite this probably + // TODO: rewrite or remove, causing UI freezes DragGesture(minimumDistance: 20, coordinateSpace: .local) - .onChanged { value in - let horizontalAmount = value.translation.width - let verticalAmount = value.translation.height - - if abs(horizontalAmount) > abs(verticalAmount) { - isHorizontalSwipe = true - dragOffset = horizontalAmount - } - } .onEnded { value in let horizontalAmount = value.translation.width let verticalAmount = value.translation.height @@ -62,12 +47,10 @@ struct AllActivityView: View { } } } - - isHorizontalSwipe = false - dragOffset = 0 } ) } + .contentMargins(.bottom, ScreenLayout.bottomPaddingWithSafeArea) .scrollDismissesKeyboard(.interactively) .refreshable { do { @@ -89,13 +72,12 @@ struct AllActivityView: View { startPoint: .top, endPoint: .bottom ) - .frame(height: 140) + .frame(height: ScreenLayout.bottomPaddingWithSafeArea) } - .ignoresSafeArea() + .ignoresSafeArea(edges: .bottom) .allowsHitTesting(false) } .navigationBarHidden(true) - .bottomSafeAreaPadding() .onAppear { activity.resetFilters() } diff --git a/Bitkit/Views/Wallets/SavingsWalletView.swift b/Bitkit/Views/Wallets/SavingsWalletScreen.swift similarity index 54% rename from Bitkit/Views/Wallets/SavingsWalletView.swift rename to Bitkit/Views/Wallets/SavingsWalletScreen.swift index 21d2499f9..e3cdce22b 100644 --- a/Bitkit/Views/Wallets/SavingsWalletView.swift +++ b/Bitkit/Views/Wallets/SavingsWalletScreen.swift @@ -1,20 +1,14 @@ import SwiftUI -struct SavingsWalletView: View { +struct SavingsWalletScreen: View { @EnvironmentObject var activity: ActivityListViewModel @EnvironmentObject var app: AppViewModel @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var wallet: WalletViewModel - /// Whether there are any onchain activities to display - private var hasOnchainActivities: Bool { - guard let activities = activity.onchainActivities else { return false } - return !activities.isEmpty - } - - /// tab bar + spacing - private var bottomPadding: CGFloat { - 64 + 32 + private var shouldShowOnboarding: Bool { + let hasOnchainActivities = activity.onchainActivities?.isEmpty == false + return wallet.totalOnchainSats == 0 && !hasOnchainActivities } /// Calculate remaining duration for force close transfers @@ -31,68 +25,76 @@ struct SavingsWalletView: View { var body: some View { ZStack(alignment: .top) { - VStack(spacing: 0) { - NavigationBar(title: t("wallet__savings__title"), icon: "btc") - .padding(.bottom, 16) + NavigationBar(title: t("wallet__savings__title"), icon: "btc") + .padding(.horizontal, 16) - MoneyStack( - sats: wallet.totalOnchainSats, - showSymbol: true, - showEyeIcon: false, - enableSwipeGesture: true, - enableHide: true, - testIdPrefix: "TotalBalance" - ) - - if wallet.balanceInTransferToSavings > 0 { - IncomingTransfer( - amount: UInt64(wallet.balanceInTransferToSavings), - remainingDuration: forceCloseRemainingDuration + VStack(spacing: 0) { + ScrollView(showsIndicators: false) { + MoneyStack( + sats: wallet.totalOnchainSats, + showSymbol: true, + showEyeIcon: false, + enableSwipeGesture: true, + enableHide: true, + testIdPrefix: "TotalBalance" ) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 16) - } - if wallet.totalOnchainSats > 0 || hasOnchainActivities { - if wallet.totalOnchainSats > 0, !GeoService.shared.isGeoBlocked { - transferButton - .transition(.move(edge: .leading).combined(with: .opacity)) - .padding(.top, 32) + if wallet.balanceInTransferToSavings > 0 { + IncomingTransfer( + amount: UInt64(wallet.balanceInTransferToSavings), + remainingDuration: forceCloseRemainingDuration + ) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 16) } - ScrollView(showsIndicators: false) { + if !shouldShowOnboarding { + if !GeoService.shared.isGeoBlocked { + transferButton + .transition(.move(edge: .leading).combined(with: .opacity)) + .padding(.top, 28) + } + ActivityList(viewType: .onchain) CustomButton(title: t("common__show_all"), variant: .tertiary) { navigation.navigate(.activityList) } - .padding(.bottom, bottomPadding) } - .accessibilityIdentifier("HomeScrollView") - .refreshable { - do { - try await wallet.sync() - try await activity.syncLdkNodePayments() - } catch { - app.toast(error) - } + } + .contentMargins(.top, ScreenLayout.topPaddingWithoutSafeArea) + .contentMargins(.bottom, ScreenLayout.bottomPaddingWithSafeArea) + .accessibilityIdentifier("HomeScrollView") + .refreshable { + do { + try await wallet.sync() + try await activity.syncLdkNodePayments() + } catch { + app.toast(error) } - .frame(maxWidth: .infinity, minHeight: 400) - .transition(.move(edge: .leading).combined(with: .opacity)) - } else { - Spacer() - WalletOnboardingView(type: .savings) - .padding(.bottom, bottomPadding) - .transition(.move(edge: .trailing).combined(with: .opacity)) } + .frame(maxWidth: .infinity, minHeight: 400) + .transition(.move(edge: .leading).combined(with: .opacity)) } .padding(.horizontal) .background(alignment: .topTrailing) { Image("piggybank") .resizable() .frame(width: 256, height: 256) - .offset(x: 110) + .offset(x: 118) + } + + VStack { + Spacer() + + if shouldShowOnboarding { + WalletOnboardingView(type: .savings) + .transition(.move(edge: .trailing).combined(with: .opacity)) + .padding(.bottom, ScreenLayout.bottomPaddingWithSafeArea) + .padding(.horizontal, 16) + } } + .ignoresSafeArea(edges: .bottom) // Bottom gradient: black 0% to black 100% VStack { @@ -102,9 +104,9 @@ struct SavingsWalletView: View { startPoint: .top, endPoint: .bottom ) - .frame(height: 140) + .frame(height: ScreenLayout.bottomPaddingWithSafeArea) } - .ignoresSafeArea() + .ignoresSafeArea(edges: .bottom) .allowsHitTesting(false) } .navigationBarHidden(true) @@ -133,7 +135,7 @@ struct SavingsWalletView: View { #Preview { NavigationStack { - SavingsWalletView() + SavingsWalletScreen() } .environmentObject(WalletViewModel()) .environmentObject(AppViewModel()) diff --git a/Bitkit/Views/Wallets/SpendingWalletView.swift b/Bitkit/Views/Wallets/SpendingWalletScreen.swift similarity index 50% rename from Bitkit/Views/Wallets/SpendingWalletView.swift rename to Bitkit/Views/Wallets/SpendingWalletScreen.swift index 448f5ac26..3ced09338 100644 --- a/Bitkit/Views/Wallets/SpendingWalletView.swift +++ b/Bitkit/Views/Wallets/SpendingWalletScreen.swift @@ -1,75 +1,65 @@ import SwiftUI -struct SpendingWalletView: View { +struct SpendingWalletScreen: View { @EnvironmentObject var activity: ActivityListViewModel @EnvironmentObject var app: AppViewModel @EnvironmentObject var navigation: NavigationViewModel @EnvironmentObject var wallet: WalletViewModel - /// Whether there are any lightning activities to display - private var hasLightningActivities: Bool { - guard let activities = activity.lightningActivities else { return false } - return !activities.isEmpty - } - - /// tab bar + spacing - private var bottomPadding: CGFloat { - 64 + 32 + private var shouldShowOnboarding: Bool { + let hasLightningActivities = activity.lightningActivities?.isEmpty == false + return wallet.totalLightningSats == 0 && !hasLightningActivities } var body: some View { ZStack(alignment: .top) { - VStack(spacing: 0) { - NavigationBar(title: t("wallet__spending__title"), icon: "ln") + NavigationBar(title: t("wallet__spending__title"), icon: "ln") + .padding(.horizontal, 16) - MoneyStack( - sats: wallet.totalLightningSats, - showSymbol: true, - showEyeIcon: false, - enableSwipeGesture: true, - enableHide: true, - testIdPrefix: "TotalBalance" - ) - .padding(.top) - - if wallet.balanceInTransferToSpending > 0 { - IncomingTransfer(amount: UInt64(wallet.balanceInTransferToSpending)) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 16) - } + VStack(spacing: 0) { + ScrollView(showsIndicators: false) { + MoneyStack( + sats: wallet.totalLightningSats, + showSymbol: true, + showEyeIcon: false, + enableSwipeGesture: true, + enableHide: true, + testIdPrefix: "TotalBalance" + ) - if wallet.totalLightningSats > 0 || hasLightningActivities { - if wallet.totalLightningSats > 0, let channels = wallet.channels, !channels.isEmpty { - transferButton - .transition(.move(edge: .leading).combined(with: .opacity)) - .padding(.top, 32) + if wallet.balanceInTransferToSpending > 0 { + IncomingTransfer(amount: UInt64(wallet.balanceInTransferToSpending)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 16) } - ScrollView(showsIndicators: false) { + if !shouldShowOnboarding { + if let channels = wallet.channels, !channels.isEmpty { + transferButton + .transition(.move(edge: .leading).combined(with: .opacity)) + .padding(.top, 28) + } + ActivityList(viewType: .lightning) CustomButton(title: t("common__show_all"), variant: .tertiary) { navigation.navigate(.activityList) } - .padding(.bottom, bottomPadding) } - .accessibilityIdentifier("HomeScrollView") - .refreshable { - do { - try await wallet.sync() - try await activity.syncLdkNodePayments() - } catch { - app.toast(error) - } + } + .contentMargins(.top, ScreenLayout.topPaddingWithoutSafeArea) + .contentMargins(.bottom, ScreenLayout.bottomPaddingWithSafeArea) + .accessibilityIdentifier("HomeScrollView") + .refreshable { + do { + try await wallet.sync() + try await activity.syncLdkNodePayments() + } catch { + app.toast(error) } - .frame(maxWidth: .infinity, minHeight: 400) - .transition(.move(edge: .leading).combined(with: .opacity)) - } else { - Spacer() - WalletOnboardingView(type: .spending) - .padding(.bottom, bottomPadding) - .transition(.move(edge: .trailing).combined(with: .opacity)) } + .frame(maxWidth: .infinity, minHeight: 400) + .transition(.move(edge: .leading).combined(with: .opacity)) } .padding(.horizontal) .background(alignment: .topTrailing) { @@ -79,6 +69,18 @@ struct SpendingWalletView: View { .offset(x: 128) } + VStack { + Spacer() + + if shouldShowOnboarding { + WalletOnboardingView(type: .spending) + .transition(.move(edge: .trailing).combined(with: .opacity)) + .padding(.bottom, ScreenLayout.bottomPaddingWithSafeArea) + .padding(.horizontal, 16) + } + } + .ignoresSafeArea(edges: .bottom) + // Bottom gradient: black 0% to black 100% VStack { Spacer() @@ -87,9 +89,9 @@ struct SpendingWalletView: View { startPoint: .top, endPoint: .bottom ) - .frame(height: 140) + .frame(height: ScreenLayout.bottomPaddingWithSafeArea) } - .ignoresSafeArea() + .ignoresSafeArea(edges: .bottom) .allowsHitTesting(false) } .navigationBarHidden(true) @@ -118,7 +120,7 @@ struct SpendingWalletView: View { #Preview { NavigationStack { - SpendingWalletView() + SpendingWalletScreen() } .environmentObject(WalletViewModel()) .environmentObject(AppViewModel())