// // TransactionsManagerView.swift // Zashi // // Created by Lukáš Korba on 01-22-2025. // import SwiftUI import ComposableArchitecture import Generated import UIComponents import Models import ZcashLightClientKit import AddressBook public struct TransactionsManagerView: View { @Environment(\.colorScheme) private var colorScheme @State var filtersSheetHeight: CGFloat = .zero @Perception.Bindable var store: StoreOf let tokenName: String @Shared(.appStorage(.sensitiveContent)) var isSensitiveContentHidden = false @Shared(.inMemory(.walletStatus)) public var walletStatus: WalletStatus = .none public init(store: StoreOf, tokenName: String) { self.store = store self.tokenName = tokenName } public var body: some View { WithPerceptionTracking { VStack(spacing: 0) { HStack(spacing: 0) { ZashiTextField( text: $store.searchTerm, placeholder: L10n.Filter.search, eraseAction: { store.send(.eraseSearchTermTapped) }, accessoryView: !store.searchTerm.isEmpty ? Asset.Assets.Icons.xClose.image .zImage(size: 16, style: Design.Btns.Tertiary.fg) : nil, prefixView: Asset.Assets.Icons.search.image .zImage(size: 20, style: Design.Dropdowns.Default.text) ) .padding(.trailing, 8) Button { store.send(.filterTapped) } label: { ZStack { Asset.Assets.Icons.filter.image .zImage(size: 24, style: Design.Text.primary) .padding(10) .background { RoundedRectangle(cornerRadius: Design.Radius._xl) .fill(Design.Btns.Secondary.bg.color(colorScheme)) .overlay { RoundedRectangle(cornerRadius: Design.Radius._xl) .stroke(store.activeFilters.count > 0 ? Design.Utility.Gray._900.color(colorScheme) : Design.Utility.Gray._100.color(colorScheme), style: StrokeStyle(lineWidth: store.activeFilters.count > 0 ? 2.0 : 1.0) ) } } if store.activeFilters.count > 0 { Text("\(store.activeFilters.count)") .zFont(.medium, size: 12, style: Design.Text.opposite) .background { Circle() .fill(Design.Utility.Gray._900.color(colorScheme)) .frame(width: 20, height: 20) .background { Circle() .fill(Asset.Colors.background.color) .frame(width: 24, height: 24) } } .offset(x: 22, y: -22) } } } } .screenHorizontalPadding() .padding(.vertical, 12) .padding(.top, walletStatus == .restoring ? 24 : 0) if store.transactionSections.isEmpty && !store.isInvalidated { noTransactionsView() Spacer() } else { ScrollViewReader { scrollViewProxy in List { if store.isInvalidated { VStack(alignment: .leading, spacing: 0) { ForEach(0..<15) { _ in NoTransactionPlaceholder(true) } Spacer() } .frame(maxWidth: .infinity) .listRowInsets(EdgeInsets()) .listRowBackground(Asset.Colors.background.color) .listRowSeparator(.hidden) } else { ForEach(store.transactionSections) { section in WithPerceptionTracking { Section { ForEach(section.transactions) { transaction in WithPerceptionTracking { Button { store.send(.transactionTapped(transaction.id)) } label: { TransactionRowView( transaction: transaction, isUnread: TransactionsManager.isUnread(transaction), divider: section.latestTransactionId != transaction.id ) } .listRowInsets(EdgeInsets()) } } .listRowBackground(Asset.Colors.background.color) .listRowSeparator(.hidden) } header: { Text(section.id) .zFont(.medium, size: 16, style: Design.Text.tertiary) .screenHorizontalPadding() .listRowInsets(EdgeInsets()) .listRowBackground(Asset.Colors.background.color) .listRowSeparator(.hidden) .id(section.id) } } } } } .onChange(of: store.transactionSections) { _ in scrollViewProxy.scrollTo(store.transactionSections.first?.id, anchor: .top) } } } } .disabled(store.transactions.isEmpty) .applyScreenBackground() .listStyle(.plain) .onAppear { store.send(.onAppear) } .navigationBarItems(trailing: hideBalancesButton()) .sheet(isPresented: $store.filtersRequest) { filtersContent() .applyScreenBackground() } } .navigationBarTitleDisplayMode(.inline) .zashiBack() { store.send(.dismissRequired) } .screenTitle(L10n.TransactionHistory.title.uppercased()) } @ViewBuilder func hideBalancesButton() -> some View { Button { $isSensitiveContentHidden.withLock { $0.toggle() } } label: { let image = isSensitiveContentHidden ? Asset.Assets.eyeOff.image : Asset.Assets.eyeOn.image image .zImage(size: 24, color: Asset.Colors.primary.color) .padding(8) } } @ViewBuilder func noTransactionsView() -> some View { WithPerceptionTracking { ZStack { VStack(spacing: 0) { ForEach(0..<5) { _ in NoTransactionPlaceholder() } Spacer() } .overlay { LinearGradient( stops: [ Gradient.Stop(color: .clear, location: 0.0), Gradient.Stop(color: Asset.Colors.background.color, location: 0.3) ], startPoint: .top, endPoint: .bottom ) } VStack(spacing: 0) { Asset.Assets.Illustrations.emptyState.image .resizable() .frame(width: 164, height: 164) .padding(.bottom, 20) Text(L10n.Filter.noResults) .zFont(.semiBold, size: 20, style: Design.Text.primary) .padding(.bottom, 8) Text(L10n.Filter.weTried) .zFont(size: 14, style: Design.Text.tertiary) .padding(.bottom, 20) } .padding(.top, 40) } } } } // MARK: - Previews #Preview { TransactionsManagerView(store: TransactionsManager.initial, tokenName: "ZEC") } // MARK: - Store extension TransactionsManager { public static var initial = StoreOf( initialState: .initial ) { TransactionsManager() } } // MARK: - Placeholders extension TransactionsManager.State { public static let initial = TransactionsManager.State() }