Draft of the new navigation system

This commit is contained in:
Lukas Korba 2025-02-27 15:17:25 +01:00
parent 8dfb90ff06
commit d123edf24b
17 changed files with 501 additions and 384 deletions

View File

@ -1,4 +1,4 @@
// swift-tools-version: 5.6
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
@ -7,7 +7,7 @@ let package = Package(
name: "modules",
defaultLocalization: "en",
platforms: [
.iOS(.v15)
.iOS(.v16)
],
products: [
.library(name: "About", targets: ["About"]),

View File

@ -87,6 +87,7 @@ public struct BalancesView: View {
.navigationBarTitleDisplayMode(.inline)
.padding(.vertical, 1)
.applyScreenBackground()
.zashiBack()
.alert(
store: store.scope(
state: \.$alert,

View File

@ -61,6 +61,12 @@ public struct Home {
case updateTransactionList([TransactionState])
case transactionList(TransactionList.Action)
case walletBalances(WalletBalances.Action)
// primary actions
case receiveTapped
case sendTapped
case scanTapped
case moreTapped
}
@Dependency(\.mainQueue) var mainQueue
@ -105,6 +111,9 @@ public struct Home {
.cancel(id: CancelStateId),
.cancel(id: CancelEventId)
)
case .receiveTapped, .sendTapped, .scanTapped, .moreTapped:
return .none
case .resolveReviewRequest:
if reviewRequest.canRequestReview() {

View File

@ -50,6 +50,40 @@ public struct HomeView: View {
.padding(.bottom, 20)
}
HStack(spacing: 8) {
button(
L10n.Tabs.receive,
icon: Asset.Assets.Icons.received.image
) {
store.send(.receiveTapped)
}
button(
L10n.Tabs.send,
icon: Asset.Assets.Icons.sent.image
) {
store.send(.sendTapped)
}
button(
"Scan",
icon: Asset.Assets.Icons.scan.image
) {
store.send(.scanTapped)
}
button(
"More",
icon: Asset.Assets.Icons.dotsMenu.image
) {
store.send(.moreTapped)
}
}
.zFont(.medium, size: 12, style: Design.Text.primary)
.padding(.top, 24)
.padding(.bottom, 32)
.screenHorizontalPadding()
VStack(spacing: 0) {
if store.transactionListState.transactions.isEmpty && !store.transactionListState.isInvalidated {
noTransactionsView()
@ -172,6 +206,27 @@ public struct HomeView: View {
}
}
}
private func button(_ title: String, icon: Image, action: @escaping () -> Void) -> some View {
Button {
action()
} label: {
VStack(spacing: 4) {
icon
.resizable()
.renderingMode(.template)
.frame(width: 24, height: 24)
Text(title)
}
.frame(maxWidth: .infinity)
.frame(height: 76)
.background {
RoundedRectangle(cornerRadius: 20)
.fill(Design.Surfaces.bgSecondary.color(colorScheme))
}
}
}
}
// MARK: - Previews

View File

@ -158,6 +158,7 @@ public struct ReceiveView: View {
}
.padding(.horizontal, 4)
.applyScreenBackground()
.zashiBack()
}
@ViewBuilder private func addressBlock(

View File

@ -16,9 +16,9 @@ import Pasteboard
/// In this file is a collection of helpers that control all state and action related operations
/// for the `Root` with a connection to the UI navigation.
extension Root {
public struct DebugState: Equatable { }
public struct DebugState { }
public indirect enum DebugAction: Equatable {
public indirect enum DebugAction {
case cancelRescan
case cantStartSync(ZcashError)
case copySeedToPasteboard

View File

@ -17,8 +17,8 @@ import SwiftUI
/// In this file is a collection of helpers that control all state and action related operations
/// for the `Root` with a connection to the UI navigation.
extension Root {
public struct DestinationState: Equatable {
public enum Destination: Equatable {
public struct DestinationState {
public enum Destination {
case deeplinkWarning
case notEnoughFreeSpace
case onboarding
@ -42,7 +42,7 @@ extension Root {
}
}
public enum DestinationAction: Equatable {
public enum DestinationAction {
case deeplink(URL)
case deeplinkHome
case deeplinkSend(Zatoshi, String, String)

View File

@ -22,7 +22,7 @@ extension Root {
static let noAuthenticationWithinXMinutes = 15
}
public enum InitializationAction: Equatable {
public enum InitializationAction {
case appDelegate(AppDelegateAction)
case checkBackupPhraseValidation
case checkRestoreWalletFlag(SyncStatus)

View File

@ -50,7 +50,7 @@ public struct Root {
let CancelFlexaId = UUID()
@ObservableState
public struct State: Equatable {
public struct State {
public var CancelEventId = UUID()
public var CancelStateId = UUID()
@ -128,7 +128,7 @@ public struct Root {
}
}
public enum Action: Equatable {
public enum Action {
public enum ConfirmationDialog: Equatable {
case fullRescan
case quickRescan

View File

@ -24,14 +24,14 @@ import AddressBookClient
@Reducer
public struct SendFlow {
public enum Confirmation: Equatable {
public enum Confirmation {
case requestPayment
case send
}
@ObservableState
public struct State: Equatable {
public enum Destination: Equatable {
public struct State {
public enum Destination {
case partialProposalError
case scanQR
}
@ -194,7 +194,7 @@ public struct SendFlow {
}
}
public enum Action: Equatable {
public enum Action {
case addNewContactTapped(RedactableString)
case addressBookTapped
case addressUpdated(RedactableString)
@ -248,7 +248,7 @@ public struct SendFlow {
case .onAppear:
state.scanState.checkers = [.zcashAddressScanChecker, .requestZecScanChecker]
state.memoState.charLimit = zcashSDKEnvironment.memoCharLimit
guard let account = state.zashiWalletAccount else {
guard let _ = state.zashiWalletAccount else {
return .send(.exchangeRateSetupChanged)
}
return .send(.exchangeRateSetupChanged)

View File

@ -214,6 +214,7 @@ public struct SendFlowView: View {
}
.padding(.vertical, 1)
.applyScreenBackground()
.zashiBack()
.alert(store: store.scope(
state: \.$alert,
action: \.alert

View File

@ -36,8 +36,15 @@ import UserMetadataProvider
@Reducer
public struct Tabs {
@Reducer
public enum Path {
case sendFlow(SendFlow)
}
@ObservableState
public struct State: Equatable {
public struct State {
var path = StackState<Path.State>()
public enum Destination: Equatable {
case addressDetails
case currencyConversionSetup
@ -84,19 +91,6 @@ public struct Tabs {
case send
case receive
case balances
public var title: String {
switch self {
case .account:
return L10n.Tabs.account
case .send:
return L10n.Tabs.send
case .receive:
return L10n.Tabs.receive
case .balances:
return L10n.Tabs.balances
}
}
}
public var accountSwitchRequest = false
@ -177,7 +171,7 @@ public struct Tabs {
}
}
public enum Action: BindableAction, Equatable {
public enum Action: BindableAction {
case accountSwitchTapped
case addKeystoneHWWalletTapped
case addKeystoneHWWallet(AddKeystoneHWWallet.Action)
@ -191,6 +185,7 @@ public struct Tabs {
case home(Home.Action)
case keystoneBannerTapped
case onAppear
case path(StackActionOf<Path>)
case presentKeystoneWeb
case rateTooltipTapped
case receive(Receive.Action)
@ -236,9 +231,9 @@ public struct Tabs {
Scan()
}
Scope(state: \.sendState, action: \.send) {
SendFlow()
}
// Scope(state: \.sendState, action: \.send) {
// SendFlow()
// }
Scope(state: \.sendConfirmationState, action: \.sendConfirmation) {
SendConfirmation()
@ -292,6 +287,9 @@ public struct Tabs {
state.isRateEducationEnabled = userStoredPreferences.exchangeRate() == nil
return .none
case .path:
return .none
case .accountSwitchTapped:
state.accountSwitchRequest.toggle()
return .none
@ -386,6 +384,18 @@ public struct Tabs {
}
return .send(.updateStackDestinationRequestPayment(.requestPaymentConfirmation))
case .home(.receiveTapped):
print("__LD \(state.sendState.address)")
state.selectedTab = .receive
return .none
case .home(.sendTapped):
var test = SendFlow.State()
test.address = "aaa"
state.path.append(.sendFlow(test))
state.selectedTab = .send
return .none
case .home(.seeAllTransactionsTapped):
return .send(.updateStackDestinationTransactions(.manager))
@ -716,5 +726,6 @@ public struct Tabs {
return .none
}
}
.forEach(\.path, action: \.path)
}
}

View File

@ -46,17 +46,363 @@ public struct TabsView: View {
public var body: some View {
WithPerceptionTracking {
ZStack {
TabView(selection: $store.selectedTab) {
HomeView(
store: self.store.scope(
state: \.homeState,
action: \.home
),
tokenName: tokenName
)
.tag(Tabs.State.Tab.account)
NavigationStack(path: $store.scope(state: \.path, action: \.path)) {
HomeView(
store: self.store.scope(
state: \.homeState,
action: \.home
),
tokenName: tokenName
)
} destination: { store in
switch store.case {
case let .sendFlow(store):
SendFlowView(store: store, tokenName: tokenName)
}
}
}
}
// public var body: some View {
// WithPerceptionTracking {
// accountView()
// .navigationBarTitleDisplayMode(.inline)
// .navigationBarItems(
// leading:
// walletAccountSwitcher()
// )
// .navigationBarItems(
// trailing:
// HStack(spacing: 0) {
// if store.selectedTab != .receive {
// hideBalancesButton()
// }
//
// settingsButton()
// }
// .animation(nil, value: store.selectedTab)
// )
// }
// }
// public var body2: some View {
// WithPerceptionTracking {
// accountView()
// .navigationLinkEmpty(
// isActive: store.bindingFor(.sendConfirmation),
// destination: {
// SendConfirmationView(
// store: store.sendConfirmationStore(),
// tokenName: tokenName
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingFor(.sendConfirmationKeystone),
// destination: {
// SignWithKeystoneView(store: store.sendConfirmationStore(), tokenName: tokenName)
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingFor(.currencyConversionSetup),
// destination: {
// CurrencyConversionSetupView(
// store: store.currencyConversionSetupStore()
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingFor(.addressDetails),
// destination: {
// AddressDetailsView(store: store.addressDetailsStore())
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackAddKeystoneKWWallet(.addKeystoneHWWallet),
// destination: {
// AddKeystoneHWWalletView(
// store: store.addKeystoneHWWalletStore()
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackAddKeystoneKWWallet(.scan),
// destination: {
// ScanView(
// store: store.scanStore()
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackAddKeystoneKWWallet(.accountSelection),
// destination: {
// AccountsSelectionView(
// store: store.addKeystoneHWWalletStore()
// )
// }
// )
// }
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackMaxPrivacy(.zecKeyboard),
// destination: {
// ZecKeyboardView(
// store: store.zecKeyboardStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackMaxPrivacy(.requestZec),
// destination: {
// RequestZecView(
// store: store.requestZecStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackMaxPrivacy(.requestZecSummary),
// destination: {
// RequestZecSummaryView(
// store: store.requestZecStore(),
// tokenName: tokenName
// )
// }
// )
// }
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackLowPrivacy(.zecKeyboard),
// destination: {
// ZecKeyboardView(
// store: store.zecKeyboardStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackLowPrivacy(.requestZecSummary),
// destination: {
// RequestZecSummaryView(
// store: store.requestZecStore(),
// tokenName: tokenName
// )
// }
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackRequestPayment(.requestPaymentConfirmation),
// destination: {
// RequestPaymentConfirmationView(
// store: store.sendConfirmationStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackRequestPayment(.addressBookNewContact),
// destination: {
// AddressBookContactView(store: store.addressBookStore())
// }
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackTransactions(.manager),
// destination: {
// TransactionsManagerView(
// store: store.transactionsManagerStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackTransactions(.details),
// destination: {
// TransactionDetailsView(
// store: store.transactionDetailsStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackTransactions(.addressBook),
// destination: {
// AddressBookContactView(store: store.addressBookStore())
// }
// )
// }
// )
// }
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackTransactionsHP(.details),
// destination: {
// TransactionDetailsView(
// store: store.transactionDetailsStore(),
// tokenName: tokenName
// )
// .navigationLinkEmpty(
// isActive: store.bindingForStackTransactionsHP(.addressBook),
// destination: {
// AddressBookContactView(store: store.addressBookStore())
// }
// )
// }
// )
// .navigationBarTitleDisplayMode(.inline)
// .navigationBarItems(
// leading:
// walletAccountSwitcher()
// )
// .navigationBarItems(
// trailing:
// HStack(spacing: 0) {
// if store.selectedTab != .receive {
// hideBalancesButton()
// }
//
// settingsButton()
// }
// .animation(nil, value: store.selectedTab)
// )
// .walletStatusPanel()
// .sheet(isPresented: $store.isInAppBrowserOn) {
// if let url = URL(string: store.inAppBrowserURL) {
// InAppBrowserView(url: url)
// }
// }
// .sheet(isPresented: $store.accountSwitchRequest) {
// accountSwitchContent()
// }
// .sheet(isPresented: $store.selectTextRequest) {
// VStack(alignment: .leading) {
// HStack {
// Spacer()
//
// Button {
// store.send(.dismissSelectTextEditor)
// } label: {
// Asset.Assets.buttonCloseX.image
// .zImage(size: 24, style: Design.Btns.Tertiary.fg)
// .padding(8)
// .background {
// RoundedRectangle(cornerRadius: 12)
// .fill(Design.Btns.Tertiary.bg.color(colorScheme))
// }
// }
// }
//
// TextEditor(text: $store.textToSelect)
// .colorBackground(Asset.Colors.background.color)
// .background(Asset.Colors.background.color)
// .zFont(size: 14, style: Design.Text.primary)
// }
// .padding()
// .applyScreenBackground()
// }
// .overlayPreferenceValue(ExchangeRateStaleTooltipPreferenceKey.self) { preferences in
// WithPerceptionTracking {
// if store.isRateTooltipEnabled {
// GeometryReader { geometry in
// preferences.map {
// Tooltip(
// title: L10n.Tooltip.ExchangeRate.title,
// desc: L10n.Tooltip.ExchangeRate.desc
// ) {
// store.send(.rateTooltipTapped)
// }
// .frame(width: geometry.size.width - 40)
// .offset(x: 20, y: geometry[$0].minY + geometry[$0].height)
// }
// }
// }
// }
// }
// .overlayPreferenceValue(ExchangeRateFeaturePreferenceKey.self) { preferences in
// WithPerceptionTracking {
// if store.isRateEducationEnabled && store.selectedTab != .receive {
// GeometryReader { geometry in
// preferences.map {
// VStack(alignment: .leading, spacing: 0) {
// HStack(alignment: .top, spacing: 0) {
// Asset.Assets.coinsSwap.image
// .zImage(size: 20, style: Design.Text.primary)
// .padding(10)
// .background {
// Circle()
// .fill(Design.Surfaces.bgTertiary.color(colorScheme))
// }
// .padding(.trailing, 16)
//
// VStack(alignment: .leading, spacing: 5) {
// Text(L10n.CurrencyConversion.cardTitle)
// .zFont(size: 14, style: Design.Text.tertiary)
//
// Text(L10n.CurrencyConversion.title)
// .zFont(.semiBold, size: 16, style: Design.Text.primary)
// .lineLimit(1)
// .minimumScaleFactor(0.5)
// }
// .padding(.trailing, 16)
//
// Spacer(minLength: 0)
//
// Button {
// store.send(.currencyConversionCloseTapped)
// } label: {
// Asset.Assets.buttonCloseX.image
// .zImage(size: 20, style: Design.HintTooltips.defaultFg)
// }
// .padding(20)
// .offset(x: 20, y: -20)
// }
//
// Button {
// store.send(.updateDestination(.currencyConversionSetup))
// } label: {
// Text(L10n.CurrencyConversion.cardButton)
// .zFont(.semiBold, size: 16, style: Design.Btns.Tertiary.fg)
// .frame(height: 24)
// .frame(maxWidth: .infinity)
// .padding(.vertical, 12)
// .background {
// RoundedRectangle(cornerRadius: 12)
// .fill(Design.Btns.Tertiary.bg.color(colorScheme))
// }
// }
// }
// .padding(24)
// .background {
// RoundedRectangle(cornerRadius: 12)
// .fill(Design.Surfaces.bgPrimary.color(colorScheme))
// .background {
// RoundedRectangle(cornerRadius: 12)
// .stroke(Design.Surfaces.strokeSecondary.color(colorScheme))
// }
// }
// .frame(width: geometry.size.width - 40)
// .offset(x: 20, y: geometry[$0].minY + geometry[$0].height)
// }
// }
// }
// }
// }
// }
// }
}
extension TabsView {
@ViewBuilder func accountView() -> some View {
WithPerceptionTracking {
// VStack {
// HStack {
// Button("send") { store.send(.selectedTabChanged(.send)) }
// Button("receive") { store.send(.selectedTabChanged(.receive)) }
// Button("balances") { store.send(.selectedTabChanged(.balances)) }
// }
HomeView(
store: self.store.scope(
state: \.homeState,
action: \.home
),
tokenName: tokenName
)
// }
.onAppear { store.send(.onAppear) }
.navigationLinkEmpty(
isActive: store.bindingTabFor(.send),
destination: {
SendFlowView(
store: self.store.scope(
state: \.sendState,
@ -64,8 +410,11 @@ public struct TabsView: View {
),
tokenName: tokenName
)
.tag(Tabs.State.Tab.send)
}
)
.navigationLinkEmpty(
isActive: store.bindingTabFor(.receive),
destination: {
ReceiveView(
store: self.store.scope(
state: \.receiveState,
@ -73,8 +422,11 @@ public struct TabsView: View {
),
networkType: networkType
)
.tag(Tabs.State.Tab.receive)
}
)
.navigationLinkEmpty(
isActive: store.bindingTabFor(.balances),
destination: {
BalancesView(
store: self.store.scope(
state: \.balanceBreakdownState,
@ -82,341 +434,8 @@ public struct TabsView: View {
),
tokenName: tokenName
)
.tag(Tabs.State.Tab.balances)
}
.onAppear { store.send(.onAppear) }
VStack(spacing: 0) {
Spacer()
HStack {
ForEach((Tabs.State.Tab.allCases), id: \.self) { item in
Button {
store.send(.selectedTabChanged(item), animation: .easeInOut)
} label: {
VStack {
WithPerceptionTracking {
if store.selectedTab == item {
Text("\(item.title)")
.font(.custom(FontFamily.Inter.black.name, size: 12))
.foregroundColor(Asset.Colors.primary.color)
Rectangle()
.frame(height: 2)
.zForegroundColor(Design.Surfaces.brandBg)
.matchedGeometryEffect(id: "Tabs", in: tabsID, properties: .frame)
} else {
Text("\(item.title)")
.font(.custom(FontFamily.Inter.regular.name, size: 12))
.foregroundColor(Asset.Colors.primary.color)
Rectangle()
.frame(height: 2)
.foregroundColor(.clear)
}
}
}
.frame(minHeight: 50)
}
if item.rawValue < Tabs.State.Tab.allCases.count-1 {
Spacer()
}
}
}
.padding(.horizontal, 40)
.background(Asset.Colors.background.color)
}
.ignoresSafeArea(.keyboard)
.navigationLinkEmpty(
isActive: store.bindingFor(.sendConfirmation),
destination: {
SendConfirmationView(
store: store.sendConfirmationStore(),
tokenName: tokenName
)
}
)
.navigationLinkEmpty(
isActive: store.bindingFor(.sendConfirmationKeystone),
destination: {
SignWithKeystoneView(store: store.sendConfirmationStore(), tokenName: tokenName)
}
)
.navigationLinkEmpty(
isActive: store.bindingFor(.currencyConversionSetup),
destination: {
CurrencyConversionSetupView(
store: store.currencyConversionSetupStore()
)
}
)
.navigationLinkEmpty(
isActive: store.bindingFor(.addressDetails),
destination: {
AddressDetailsView(store: store.addressDetailsStore())
}
)
.navigationLinkEmpty(
isActive: store.bindingForStackAddKeystoneKWWallet(.addKeystoneHWWallet),
destination: {
AddKeystoneHWWalletView(
store: store.addKeystoneHWWalletStore()
)
.navigationLinkEmpty(
isActive: store.bindingForStackAddKeystoneKWWallet(.scan),
destination: {
ScanView(
store: store.scanStore()
)
.navigationLinkEmpty(
isActive: store.bindingForStackAddKeystoneKWWallet(.accountSelection),
destination: {
AccountsSelectionView(
store: store.addKeystoneHWWalletStore()
)
}
)
}
)
}
)
.navigationLinkEmpty(
isActive: store.bindingForStackMaxPrivacy(.zecKeyboard),
destination: {
ZecKeyboardView(
store: store.zecKeyboardStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackMaxPrivacy(.requestZec),
destination: {
RequestZecView(
store: store.requestZecStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackMaxPrivacy(.requestZecSummary),
destination: {
RequestZecSummaryView(
store: store.requestZecStore(),
tokenName: tokenName
)
}
)
}
)
}
)
.navigationLinkEmpty(
isActive: store.bindingForStackLowPrivacy(.zecKeyboard),
destination: {
ZecKeyboardView(
store: store.zecKeyboardStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackLowPrivacy(.requestZecSummary),
destination: {
RequestZecSummaryView(
store: store.requestZecStore(),
tokenName: tokenName
)
}
)
}
)
.navigationLinkEmpty(
isActive: store.bindingForStackRequestPayment(.requestPaymentConfirmation),
destination: {
RequestPaymentConfirmationView(
store: store.sendConfirmationStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackRequestPayment(.addressBookNewContact),
destination: {
AddressBookContactView(store: store.addressBookStore())
}
)
}
)
.navigationLinkEmpty(
isActive: store.bindingForStackTransactions(.manager),
destination: {
TransactionsManagerView(
store: store.transactionsManagerStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackTransactions(.details),
destination: {
TransactionDetailsView(
store: store.transactionDetailsStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackTransactions(.addressBook),
destination: {
AddressBookContactView(store: store.addressBookStore())
}
)
}
)
}
)
.navigationLinkEmpty(
isActive: store.bindingForStackTransactionsHP(.details),
destination: {
TransactionDetailsView(
store: store.transactionDetailsStore(),
tokenName: tokenName
)
.navigationLinkEmpty(
isActive: store.bindingForStackTransactionsHP(.addressBook),
destination: {
AddressBookContactView(store: store.addressBookStore())
}
)
}
)
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(
leading:
walletAccountSwitcher()
)
.navigationBarItems(
trailing:
HStack(spacing: 0) {
if store.selectedTab != .receive {
hideBalancesButton()
}
settingsButton()
}
.animation(nil, value: store.selectedTab)
)
.walletStatusPanel()
.sheet(isPresented: $store.isInAppBrowserOn) {
if let url = URL(string: store.inAppBrowserURL) {
InAppBrowserView(url: url)
}
}
.sheet(isPresented: $store.accountSwitchRequest) {
accountSwitchContent()
}
.sheet(isPresented: $store.selectTextRequest) {
VStack(alignment: .leading) {
HStack {
Spacer()
Button {
store.send(.dismissSelectTextEditor)
} label: {
Asset.Assets.buttonCloseX.image
.zImage(size: 24, style: Design.Btns.Tertiary.fg)
.padding(8)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(Design.Btns.Tertiary.bg.color(colorScheme))
}
}
}
TextEditor(text: $store.textToSelect)
.colorBackground(Asset.Colors.background.color)
.background(Asset.Colors.background.color)
.zFont(size: 14, style: Design.Text.primary)
}
.padding()
.applyScreenBackground()
}
.overlayPreferenceValue(ExchangeRateStaleTooltipPreferenceKey.self) { preferences in
WithPerceptionTracking {
if store.isRateTooltipEnabled {
GeometryReader { geometry in
preferences.map {
Tooltip(
title: L10n.Tooltip.ExchangeRate.title,
desc: L10n.Tooltip.ExchangeRate.desc
) {
store.send(.rateTooltipTapped)
}
.frame(width: geometry.size.width - 40)
.offset(x: 20, y: geometry[$0].minY + geometry[$0].height)
}
}
}
}
}
.overlayPreferenceValue(ExchangeRateFeaturePreferenceKey.self) { preferences in
WithPerceptionTracking {
if store.isRateEducationEnabled && store.selectedTab != .receive {
GeometryReader { geometry in
preferences.map {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top, spacing: 0) {
Asset.Assets.coinsSwap.image
.zImage(size: 20, style: Design.Text.primary)
.padding(10)
.background {
Circle()
.fill(Design.Surfaces.bgTertiary.color(colorScheme))
}
.padding(.trailing, 16)
VStack(alignment: .leading, spacing: 5) {
Text(L10n.CurrencyConversion.cardTitle)
.zFont(size: 14, style: Design.Text.tertiary)
Text(L10n.CurrencyConversion.title)
.zFont(.semiBold, size: 16, style: Design.Text.primary)
.lineLimit(1)
.minimumScaleFactor(0.5)
}
.padding(.trailing, 16)
Spacer(minLength: 0)
Button {
store.send(.currencyConversionCloseTapped)
} label: {
Asset.Assets.buttonCloseX.image
.zImage(size: 20, style: Design.HintTooltips.defaultFg)
}
.padding(20)
.offset(x: 20, y: -20)
}
Button {
store.send(.updateDestination(.currencyConversionSetup))
} label: {
Text(L10n.CurrencyConversion.cardButton)
.zFont(.semiBold, size: 16, style: Design.Btns.Tertiary.fg)
.frame(height: 24)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(Design.Btns.Tertiary.bg.color(colorScheme))
}
}
}
.padding(24)
.background {
RoundedRectangle(cornerRadius: 12)
.fill(Design.Surfaces.bgPrimary.color(colorScheme))
.background {
RoundedRectangle(cornerRadius: 12)
.stroke(Design.Surfaces.strokeSecondary.color(colorScheme))
}
}
.frame(width: geometry.size.width - 40)
.offset(x: 20, y: geometry[$0].minY + geometry[$0].height)
}
}
}
}
}
}
}
}
@ -526,6 +545,13 @@ extension StoreOf<Tabs> {
)
}
func bindingTabFor(_ selectedTab: Tabs.State.Tab) -> Binding<Bool> {
Binding<Bool>(
get: { self.selectedTab == selectedTab },
set: { self.send(.selectedTabChanged($0 ? selectedTab : .account)) }
)
}
func bindingForStackAddKeystoneKWWallet(_ destination: Tabs.State.StackDestinationAddKeystoneHWWallet) -> Binding<Bool> {
Binding<Bool>(
get: {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "scan.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -104,6 +104,7 @@ public enum Asset {
public static let qr = ImageAsset(name: "qr")
public static let received = ImageAsset(name: "received")
public static let save = ImageAsset(name: "save")
public static let scan = ImageAsset(name: "scan")
public static let search = ImageAsset(name: "search")
public static let sent = ImageAsset(name: "sent")
public static let server = ImageAsset(name: "server")

View File

@ -2375,7 +2375,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = "zashi-internal-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -2405,7 +2405,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = "zashi-internal-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -2435,7 +2435,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = "zashi-internal-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",