diff --git a/Sources/BraveUI/SwiftUI/Shimmer.swift b/Sources/BraveUI/SwiftUI/Shimmer.swift index e856d1f9ca1..97098cd7ee5 100644 --- a/Sources/BraveUI/SwiftUI/Shimmer.swift +++ b/Sources/BraveUI/SwiftUI/Shimmer.swift @@ -38,16 +38,10 @@ private struct ShimmerViewModifier: ViewModifier { endPoint: points.1 ) .onAppear { - if #available(iOS 16, *) { + DispatchQueue.main.async { [self] in // Need this due to a SwiftUI bug… withAnimation(animation) { points = (.trailing, UnitPoint(x: 2, y: 0.5)) } - } else { - DispatchQueue.main.async { [self] in // Need this due to a SwiftUI bug… - withAnimation(animation) { - points = (.trailing, UnitPoint(x: 2, y: 0.5)) - } - } } } } diff --git a/Sources/BraveWallet/Crypto/CryptoPagesView.swift b/Sources/BraveWallet/Crypto/CryptoPagesView.swift deleted file mode 100644 index 466cd15288a..00000000000 --- a/Sources/BraveWallet/Crypto/CryptoPagesView.swift +++ /dev/null @@ -1,263 +0,0 @@ -/* Copyright 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import Foundation -import UIKit -import SwiftUI -import BraveCore -import PanModal -import BraveUI -import Strings - -struct CryptoPagesView: View { - @ObservedObject var cryptoStore: CryptoStore - @ObservedObject var keyringStore: KeyringStore - - @State private var isShowingMainMenu: Bool = false - @State private var isShowingSettings: Bool = false - @State private var isShowingSearch: Bool = false - @State private var fetchedPendingRequestsThisSession: Bool = false - @State private var selectedPageIndex: Int = 0 - - private var isConfirmationButtonVisible: Bool { - if case .transactions(let txs) = cryptoStore.pendingRequest { - return !txs.isEmpty - } - return cryptoStore.pendingRequest != nil - } - - var body: some View { - _CryptoPagesView( - keyringStore: keyringStore, - cryptoStore: cryptoStore, - isShowingPendingRequest: $cryptoStore.isPresentingPendingRequest, - isConfirmationsButtonVisible: isConfirmationButtonVisible, - selectedIndexChanged: { selectedIndex in - selectedPageIndex = selectedIndex - } - ) - .onAppear { - // If a user chooses not to confirm/reject their requests we shouldn't - // do it again until they close and re-open wallet - if !fetchedPendingRequestsThisSession { - // Give the animation time - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - self.fetchedPendingRequestsThisSession = true - self.cryptoStore.prepare(isInitialOpen: true) - } - } - } - .ignoresSafeArea() - .navigationTitle(Strings.Wallet.cryptoTitle) - .navigationBarTitleDisplayMode(.inline) - .introspectViewController(customize: { vc in - vc.navigationItem.do { - let appearance: UINavigationBarAppearance = { - let appearance = UINavigationBarAppearance() - appearance.configureWithOpaqueBackground() - appearance.titleTextAttributes = [.foregroundColor: UIColor.braveLabel] - appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.braveLabel] - appearance.backgroundColor = .braveBackground - appearance.shadowColor = .clear - return appearance - }() - $0.standardAppearance = appearance - $0.compactAppearance = appearance - $0.scrollEdgeAppearance = appearance - } - }) - .background( - NavigationLink( - destination: Web3SettingsView( - settingsStore: cryptoStore.settingsStore, - networkStore: cryptoStore.networkStore, - keyringStore: keyringStore - ), - isActive: $isShowingSettings - ) { - Text(Strings.Wallet.settings) - } - .hidden() - ) - .background( - Color.clear - .sheet(isPresented: $cryptoStore.isPresentingAssetSearch) { - AssetSearchView( - keyringStore: keyringStore, - cryptoStore: cryptoStore, - userAssetsStore: cryptoStore.portfolioStore.userAssetsStore - ) - } - ) - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button(action: { - cryptoStore.isPresentingAssetSearch = true - }) { - Label(Strings.Wallet.searchTitle, systemImage: "magnifyingglass") - .labelStyle(.iconOnly) - .foregroundColor(Color(.braveBlurpleTint)) - } - Button(action: { self.isShowingMainMenu = true }) { - Label(Strings.Wallet.otherWalletActionsAccessibilityTitle, braveSystemImage: "leo.more.horizontal") - .labelStyle(.iconOnly) - .foregroundColor(Color(.braveBlurpleTint)) - } - .accessibilityLabel(Strings.Wallet.otherWalletActionsAccessibilityTitle) - } - } - .sheet(isPresented: $isShowingMainMenu) { - MainMenuView( - isFromPortfolio: selectedPageIndex == 0, - isShowingSettings: $isShowingSettings, - keyringStore: keyringStore - ) - } - } - - private struct _CryptoPagesView: UIViewControllerRepresentable { - var keyringStore: KeyringStore - var cryptoStore: CryptoStore - var isShowingPendingRequest: Binding - var isConfirmationsButtonVisible: Bool - var selectedIndexChanged: (Int) -> Void - - func makeUIViewController(context: Context) -> CryptoPagesViewController { - CryptoPagesViewController( - keyringStore: keyringStore, - cryptoStore: cryptoStore, - isShowingPendingRequest: isShowingPendingRequest, - selectedIndexChanged: selectedIndexChanged - ) - } - func updateUIViewController(_ uiViewController: CryptoPagesViewController, context: Context) { - uiViewController.pendingRequestsButton.isHidden = !isConfirmationsButtonVisible - } - } -} - -private class CryptoPagesViewController: TabbedPageViewController { - private let keyringStore: KeyringStore - private let cryptoStore: CryptoStore - let pendingRequestsButton = ConfirmationsButton() - let selectedIndexChanged: (Int) -> Void - - @Binding private var isShowingPendingRequest: Bool - - init( - keyringStore: KeyringStore, - cryptoStore: CryptoStore, - isShowingPendingRequest: Binding, - selectedIndexChanged: @escaping (Int) -> Void - ) { - self.keyringStore = keyringStore - self.cryptoStore = cryptoStore - self._isShowingPendingRequest = isShowingPendingRequest - self.selectedIndexChanged = selectedIndexChanged - super.init(selectedIndexChanged: selectedIndexChanged) - } - - @available(*, unavailable) - required init(coder: NSCoder) { - fatalError() - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = Strings.Wallet.cryptoTitle - navigationItem.largeTitleDisplayMode = .never - view.backgroundColor = .braveGroupedBackground - - pages = [ - UIHostingController( - rootView: PortfolioView( - cryptoStore: cryptoStore, - keyringStore: keyringStore, - networkStore: cryptoStore.networkStore, - portfolioStore: cryptoStore.portfolioStore - ) - ).then { - $0.title = Strings.Wallet.portfolioPageTitle - }, - UIHostingController( - rootView: TransactionsActivityView( - store: cryptoStore.transactionsActivityStore, - networkStore: cryptoStore.networkStore - ) - ).then { - $0.title = Strings.Wallet.activityPageTitle - }, - UIHostingController( - rootView: AccountsView( - cryptoStore: cryptoStore, - keyringStore: keyringStore - ) - ).then { - $0.title = Strings.Wallet.accountsPageTitle - }, - UIHostingController( - rootView: MarketView( - cryptoStore: cryptoStore, - keyringStore: keyringStore - ) - ).then { - $0.title = Strings.Wallet.marketPageTitle - }, - ] - - view.addSubview(pendingRequestsButton) - pendingRequestsButton.snp.makeConstraints { - $0.trailing.equalToSuperview().inset(16) - $0.bottom.equalTo(view.safeAreaLayoutGuide).priority(.high) - $0.bottom.lessThanOrEqualTo(view).inset(8) - } - pendingRequestsButton.addTarget(self, action: #selector(tappedPendingRequestsButton), for: .touchUpInside) - } - - @objc private func tappedPendingRequestsButton() { - isShowingPendingRequest = true - } -} - -private class ConfirmationsButton: SpringButton { - private let imageView = UIImageView( - image: UIImage(braveSystemNamed: "leo.notification.dot")! - .applyingSymbolConfiguration(.init(pointSize: 18)) - ).then { - $0.tintColor = .white - } - - override init(frame: CGRect) { - super.init(frame: frame) - - backgroundColor = .braveBlurpleTint - addSubview(imageView) - - imageView.snp.makeConstraints { - $0.center.equalToSuperview() - } - snp.makeConstraints { - $0.width.equalTo(snp.height) - } - - layer.shadowColor = UIColor.black.cgColor - layer.shadowOffset = .init(width: 0, height: 1) - layer.shadowRadius = 1 - layer.shadowOpacity = 0.3 - - accessibilityLabel = Strings.Wallet.confirmTransactionsTitle - } - - override func layoutSubviews() { - super.layoutSubviews() - layer.cornerRadius = bounds.height / 2.0 - layer.shadowPath = UIBezierPath(ovalIn: bounds).cgPath - } - - override var intrinsicContentSize: CGSize { - .init(width: 36, height: 36) - } -} diff --git a/Sources/BraveWallet/Crypto/CryptoTabsView.swift b/Sources/BraveWallet/Crypto/CryptoTabsView.swift new file mode 100644 index 00000000000..f0f5b3f643c --- /dev/null +++ b/Sources/BraveWallet/Crypto/CryptoTabsView.swift @@ -0,0 +1,265 @@ +/* Copyright 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import Foundation +import UIKit +import SwiftUI +import BraveUI +import Strings + +struct CryptoTabsView: View { + private enum Tab: Equatable, Hashable, CaseIterable { + case portfolio + case activity + case accounts + case market + + @ViewBuilder var tabLabel: some View { + switch self { + case .portfolio: + Label(Strings.Wallet.portfolioPageTitle, braveSystemImage: "leo.coins") + case .activity: + Label(Strings.Wallet.activityPageTitle, braveSystemImage: "leo.activity") + case .accounts: + Label(Strings.Wallet.accountsPageTitle, braveSystemImage: "leo.user.accounts") + case .market: + Label(Strings.Wallet.marketPageTitle, braveSystemImage: "leo.discover") + } + } + } + + @ObservedObject var cryptoStore: CryptoStore + @ObservedObject var keyringStore: KeyringStore + var toolbarDismissContent: DismissContent + + @State private var isShowingMainMenu: Bool = false + @State private var isTabShowingSettings: [Tab: Bool] = Tab.allCases.reduce(into: [Tab: Bool]()) { $0[$1] = false } + @State private var isShowingSearch: Bool = false + @State private var fetchedPendingRequestsThisSession: Bool = false + @State private var selectedTab: Tab = .portfolio + + private var isConfirmationButtonVisible: Bool { + if case .transactions(let txs) = cryptoStore.pendingRequest { + return !txs.isEmpty + } + return cryptoStore.pendingRequest != nil + } + + var body: some View { + TabView(selection: $selectedTab) { + NavigationView { + PortfolioView( + cryptoStore: cryptoStore, + keyringStore: keyringStore, + networkStore: cryptoStore.networkStore, + portfolioStore: cryptoStore.portfolioStore + ) + .navigationTitle(Strings.Wallet.wallet) + .navigationBarTitleDisplayMode(.inline) + .introspectViewController(customize: { vc in + vc.navigationItem.do { + // no shadow when content is at top. + let noShadowAppearance: UINavigationBarAppearance = { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.titleTextAttributes = [.foregroundColor: UIColor.braveLabel] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.braveLabel] + appearance.backgroundColor = UIColor(braveSystemName: .pageBackground) + appearance.shadowColor = .clear + return appearance + }() + $0.scrollEdgeAppearance = noShadowAppearance + $0.compactScrollEdgeAppearance = noShadowAppearance + // shadow when content is scrolled behind navigation bar. + let shadowAppearance: UINavigationBarAppearance = { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.titleTextAttributes = [.foregroundColor: UIColor.braveLabel] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.braveLabel] + appearance.backgroundColor = UIColor(braveSystemName: .pageBackground) + return appearance + }() + $0.standardAppearance = shadowAppearance + $0.compactAppearance = shadowAppearance + } + }) + .toolbar { sharedToolbarItems } + .background(settingsNavigationLink(for: .portfolio)) + } + .navigationViewStyle(.stack) + .tabItem { + Tab.portfolio.tabLabel + } + .tag(Tab.portfolio) + + NavigationView { + TransactionsActivityView( + store: cryptoStore.transactionsActivityStore, + networkStore: cryptoStore.networkStore + ) + .navigationTitle(Strings.Wallet.wallet) + .navigationBarTitleDisplayMode(.inline) + .applyRegularNavigationAppearance() + .toolbar { sharedToolbarItems } + .background(settingsNavigationLink(for: .activity)) + } + .navigationViewStyle(.stack) + .tabItem { + Tab.activity.tabLabel + } + .tag(Tab.activity) + + NavigationView { + AccountsView( + cryptoStore: cryptoStore, + keyringStore: keyringStore + ) + .navigationTitle(Strings.Wallet.wallet) + .navigationBarTitleDisplayMode(.inline) + .applyRegularNavigationAppearance() + .toolbar { sharedToolbarItems } + .background(settingsNavigationLink(for: .accounts)) + } + .navigationViewStyle(.stack) + .tabItem { + Tab.accounts.tabLabel + } + .tag(Tab.accounts) + + NavigationView { + MarketView( + cryptoStore: cryptoStore, + keyringStore: keyringStore + ) + .navigationTitle(Strings.Wallet.wallet) + .navigationBarTitleDisplayMode(.inline) + .applyRegularNavigationAppearance() + .toolbar { sharedToolbarItems } + .background(settingsNavigationLink(for: .market)) + } + .navigationViewStyle(.stack) + .tabItem { + Tab.market.tabLabel + } + .tag(Tab.market) + } + .introspectTabBarController(customize: { tabBarController in + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = UIColor(braveSystemName: .containerBackground) + tabBarController.tabBar.standardAppearance = appearance + tabBarController.tabBar.scrollEdgeAppearance = appearance + }) + .overlay(alignment: .bottomTrailing, content: { + if isConfirmationButtonVisible { + Button(action: { + cryptoStore.isPresentingPendingRequest = true + }) { + Image(braveSystemName: "leo.notification.dot") + .font(.system(size: 18)) + .foregroundColor(.white) + .frame(width: 36, height: 36) + .background( + Color(uiColor: .braveBlurpleTint) + .clipShape(Circle()) + ) + } + .accessibilityLabel(Text(Strings.Wallet.confirmTransactionsTitle)) + .padding(.trailing, 16) + .padding(.bottom, 100) + } + }) + .onAppear { + // If a user chooses not to confirm/reject their requests we shouldn't + // do it again until they close and re-open wallet + if !fetchedPendingRequestsThisSession { + // Give the animation time + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.fetchedPendingRequestsThisSession = true + self.cryptoStore.prepare(isInitialOpen: true) + } + } + } + .ignoresSafeArea() + .background( + Color.clear + .sheet(isPresented: $cryptoStore.isPresentingAssetSearch) { + AssetSearchView( + keyringStore: keyringStore, + cryptoStore: cryptoStore, + userAssetsStore: cryptoStore.portfolioStore.userAssetsStore + ) + } + ) + .sheet(isPresented: $isShowingMainMenu) { + MainMenuView( + isFromPortfolio: selectedTab == .portfolio, + isShowingSettings: Binding(get: { + self.isTabShowingSettings[selectedTab, default: false] + }, set: { isActive, _ in + self.isTabShowingSettings[selectedTab] = isActive + }), + keyringStore: keyringStore + ) + } + } + + @ToolbarContentBuilder private var sharedToolbarItems: some ToolbarContent { + ToolbarItemGroup(placement: .navigationBarTrailing) { + Button(action: { + cryptoStore.isPresentingAssetSearch = true + }) { + Label(Strings.Wallet.searchTitle, systemImage: "magnifyingglass") + .labelStyle(.iconOnly) + .foregroundColor(Color(.braveBlurpleTint)) + } + Button(action: { self.isShowingMainMenu = true }) { + Label(Strings.Wallet.otherWalletActionsAccessibilityTitle, braveSystemImage: "leo.more.horizontal") + .labelStyle(.iconOnly) + .foregroundColor(Color(.braveBlurpleTint)) + } + .accessibilityLabel(Strings.Wallet.otherWalletActionsAccessibilityTitle) + } + toolbarDismissContent + } + + private func settingsNavigationLink(for tab: Tab) -> some View { + NavigationLink( + destination: Web3SettingsView( + settingsStore: cryptoStore.settingsStore, + networkStore: cryptoStore.networkStore, + keyringStore: keyringStore + ), + isActive: Binding(get: { + self.isTabShowingSettings[tab, default: false] + }, set: { isActive, _ in + self.isTabShowingSettings[tab] = isActive + }) + ) { + Text(Strings.Wallet.settings) + } + .hidden() + } +} + +private extension View { + func applyRegularNavigationAppearance() -> some View { + introspectViewController(customize: { vc in + vc.navigationItem.do { + let appearance: UINavigationBarAppearance = { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.titleTextAttributes = [.foregroundColor: UIColor.braveLabel] + appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.braveLabel] + appearance.backgroundColor = .braveBackground + return appearance + }() + $0.standardAppearance = appearance + $0.compactAppearance = appearance + $0.scrollEdgeAppearance = appearance + } + }) + } +} diff --git a/Sources/BraveWallet/Crypto/CryptoView.swift b/Sources/BraveWallet/Crypto/CryptoView.swift index 12b0d0d928d..d6284c1ba58 100644 --- a/Sources/BraveWallet/Crypto/CryptoView.swift +++ b/Sources/BraveWallet/Crypto/CryptoView.swift @@ -312,12 +312,11 @@ private struct CryptoContainerView: View { } var body: some View { - UIKitNavigationView { - CryptoPagesView(cryptoStore: cryptoStore, keyringStore: keyringStore) - .toolbar { - toolbarDismissContent - } - } + CryptoTabsView( + cryptoStore: cryptoStore, + keyringStore: keyringStore, + toolbarDismissContent: toolbarDismissContent + ) .background( Color.clear .sheet(item: $cryptoStore.buySendSwapDestination) { action in @@ -366,7 +365,7 @@ private struct CryptoContainerView: View { ) .environment( \.buySendSwapDestination, - Binding( + Binding( get: { [weak cryptoStore] in cryptoStore?.buySendSwapDestination }, set: { [weak cryptoStore] destination in if cryptoStore?.isPresentingAssetSearch == true { diff --git a/Sources/DesignSystem/Icons/Symbols.xcassets/leo.activity.symbolset/Contents.json b/Sources/DesignSystem/Icons/Symbols.xcassets/leo.activity.symbolset/Contents.json new file mode 100644 index 00000000000..2f415ce6e4c --- /dev/null +++ b/Sources/DesignSystem/Icons/Symbols.xcassets/leo.activity.symbolset/Contents.json @@ -0,0 +1,11 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "idiom" : "universal" + } + ] +} diff --git a/Sources/DesignSystem/Icons/Symbols.xcassets/leo.coins.symbolset/Contents.json b/Sources/DesignSystem/Icons/Symbols.xcassets/leo.coins.symbolset/Contents.json new file mode 100644 index 00000000000..2f415ce6e4c --- /dev/null +++ b/Sources/DesignSystem/Icons/Symbols.xcassets/leo.coins.symbolset/Contents.json @@ -0,0 +1,11 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "idiom" : "universal" + } + ] +} diff --git a/Sources/DesignSystem/Icons/Symbols.xcassets/leo.discover.symbolset/Contents.json b/Sources/DesignSystem/Icons/Symbols.xcassets/leo.discover.symbolset/Contents.json new file mode 100644 index 00000000000..2f415ce6e4c --- /dev/null +++ b/Sources/DesignSystem/Icons/Symbols.xcassets/leo.discover.symbolset/Contents.json @@ -0,0 +1,11 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "idiom" : "universal" + } + ] +}