From d300037ed97458c9ff168b124fdc1bab28bcd04c Mon Sep 17 00:00:00 2001 From: kugel3 Date: Sun, 10 Mar 2024 00:18:20 +0100 Subject: [PATCH] collapsing --- .../TransactionHistoryClient+Live.swift | 47 +++++++- .../DesignSystem/Layouts/FlowLayout.swift | 5 +- .../Styles/BlueTextButtonStyle.swift | 2 +- .../TransactionFilters+View.swift | 110 +++++++++++++----- .../TransactionHistory+Reducer.swift | 2 +- .../TransactionHistory+View.swift | 37 ++++++ 6 files changed, 168 insertions(+), 35 deletions(-) diff --git a/RadixWallet/Clients/TransactionHistoryClient/TransactionHistoryClient+Live.swift b/RadixWallet/Clients/TransactionHistoryClient/TransactionHistoryClient+Live.swift index 4dde919b4b..82fb25ebd8 100644 --- a/RadixWallet/Clients/TransactionHistoryClient/TransactionHistoryClient+Live.swift +++ b/RadixWallet/Clients/TransactionHistoryClient/TransactionHistoryClient+Live.swift @@ -139,8 +139,6 @@ extension TransactionHistoryClient { items.append(transactionItem) } - let items_ = try await response.items.parallelMap(transaction(for:)) - // We filter out complex resources, i.e. Stake Claim NFTs, Pool Units and LSUs let simpleResources = keyedResources.filter { $0.metadata.validator == nil && $0.metadata.poolUnit == nil @@ -187,6 +185,51 @@ extension TransactionHistoryClient { } } +// MARK: - TransactionInfo +struct TransactionInfo: Sendable { + static let timestampFormatter: ISO8601DateFormatter = { + let dateformatter = ISO8601DateFormatter() + dateformatter.formatOptions.insert(.withFractionalSeconds) + return dateformatter + }() + + let time: Date + let message: String? + let manifestClass: GatewayAPI.ManifestClass? +// let fungibleBalanceChanges: String +// let nonFungibleBalanceChanges: String + let depositSettingsUpdated: Bool + let failed: Bool +} + +extension TransactionInfo { + init(info: GatewayAPI.CommittedTransactionInfo) throws { + guard let time = TransactionInfo.timestampFormatter.date(from: info.roundTimestamp) else { + struct CorruptTimestamp: Error { let roundTimestamd: String } + throw CorruptTimestamp(roundTimestamd: info.roundTimestamp) + } + + let message = info.message?.plaintext?.content.string + let manifestClass = info.manifestClasses?.first + guard info.receipt?.status == .committedSuccess else { + self.init(time: time, message: message, manifestClass: manifestClass, depositSettingsUpdated: false, failed: true) + return + } + + let changes = info.balanceChanges + + let depositSettingsUpdated = info.manifestClasses?.contains(.accountDepositSettingsUpdate) == true + + self.init( + time: time, + message: message, + manifestClass: manifestClass, + depositSettingsUpdated: depositSettingsUpdated, + failed: false + ) + } +} + extension TransactionHistoryRequest { var gatewayRequest: GatewayAPI.StreamTransactionsRequest { .init( diff --git a/RadixWallet/Core/DesignSystem/Layouts/FlowLayout.swift b/RadixWallet/Core/DesignSystem/Layouts/FlowLayout.swift index 448122495d..52f217eace 100644 --- a/RadixWallet/Core/DesignSystem/Layouts/FlowLayout.swift +++ b/RadixWallet/Core/DesignSystem/Layouts/FlowLayout.swift @@ -3,12 +3,12 @@ public struct FlowLayout: Layout { public let alignment: VerticalAlignment public let spacing: CGSize - public init(alignment: VerticalAlignment = .center, spacing: CGFloat = 10, rowLimit: Int? = nil) { + public init(alignment: VerticalAlignment = .center, spacing: CGFloat = 10) { self.alignment = alignment self.spacing = .init(width: spacing, height: spacing) } - public init(alignment: VerticalAlignment = .center, spacing: CGSize, rowLimit: Int? = nil) { + public init(alignment: VerticalAlignment = .center, spacing: CGSize) { self.alignment = alignment self.spacing = spacing } @@ -38,6 +38,7 @@ public struct FlowLayout: Layout { containerWidth: bounds.width, alignment: alignment ).offsets + for (offset, subview) in zip(offsets, subviews) { subview.place(at: CGPoint(x: offset.x + bounds.minX, y: offset.y + bounds.minY), proposal: .unspecified) } diff --git a/RadixWallet/Core/DesignSystem/Styles/BlueTextButtonStyle.swift b/RadixWallet/Core/DesignSystem/Styles/BlueTextButtonStyle.swift index d887fadcff..bad378b93f 100644 --- a/RadixWallet/Core/DesignSystem/Styles/BlueTextButtonStyle.swift +++ b/RadixWallet/Core/DesignSystem/Styles/BlueTextButtonStyle.swift @@ -12,6 +12,6 @@ public struct BlueTextButtonStyle: ButtonStyle { configuration.label .textStyle(.body1StandaloneLink) .foregroundColor(.app.blue2) - .opacity(configuration.isPressed ? 0.2 : 1) + .opacity(configuration.isPressed ? 0.5 : 1) } } diff --git a/RadixWallet/Features/AccountHistory/TransactionFilters+View.swift b/RadixWallet/Features/AccountHistory/TransactionFilters+View.swift index 97ecb73267..da25ea9a0f 100644 --- a/RadixWallet/Features/AccountHistory/TransactionFilters+View.swift +++ b/RadixWallet/Features/AccountHistory/TransactionFilters+View.swift @@ -18,17 +18,21 @@ extension TransactionHistoryFilters { ScrollView { WithViewStore(store, observe: \.filters, send: { .view($0) }) { viewStore in VStack(spacing: .medium3) { - SubSection(filters: viewStore.transferTypes, store: store) + HStack(spacing: .small1) { + FiltersView(filters: viewStore.transferTypes, store: store) + + Spacer(minLength: 0) + } Divider() if viewStore.showAssetsSection { Section("Type of asset") { // FIXME: Strings - SubSection("Tokens", filters: viewStore.fungibles, flexible: tokenLabels, store: store) + SubSection("Tokens", filters: viewStore.fungibles, labels: tokenLabels, store: store) Divider() - SubSection("NFTs", filters: viewStore.nonFungibles, flexible: nftLabels, store: store) + SubSection("NFTs", filters: viewStore.nonFungibles, labels: nftLabels, store: store) } Divider() @@ -67,11 +71,11 @@ extension TransactionHistoryFilters { } private var tokenLabels: SubSection.FlexibleLabels { - .init(showAll: "Show all tokens", showLess: "Show fewer tokens") + .init(showAll: "Show all tokens", showLess: "Show fewer tokens") // FIXME: Strings } private var nftLabels: SubSection.FlexibleLabels { - .init(showAll: "Show all NFTs", showLess: "Show fewer NFTs") + .init(showAll: "Show all NFTs", showLess: "Show fewer NFTs") // FIXME: Strings } struct Section: SwiftUI.View { @@ -86,19 +90,19 @@ extension TransactionHistoryFilters { var body: some SwiftUI.View { VStack(spacing: 0) { - HStack(spacing: .zero) { - Text(name) - .textStyle(.body1Header) - .foregroundStyle(.app.gray1) - .padding(.vertical, .small2) + Button { + withAnimation(.default) { + expanded.toggle() + } + } label: { + HStack(spacing: .zero) { + Text(name) + .textStyle(.body1Header) + .foregroundStyle(.app.gray1) + .padding(.vertical, .small2) - Spacer() + Spacer() - Button { - withAnimation(.default) { - expanded.toggle() - } - } label: { Image(expanded ? .chevronUp : .chevronDown) } } @@ -119,16 +123,30 @@ extension TransactionHistoryFilters { let showLess: String } - @SwiftUI.State private var showsAll: Bool = false + @SwiftUI.State private var rowHeight: CGFloat = .infinity + @SwiftUI.State private var totalHeight: CGFloat = .infinity + @SwiftUI.State private var isCollapsed: Bool = true + let heading: String? let filters: IdentifiedArrayOf - let flexible: FlexibleLabels? + let labels: FlexibleLabels? let store: StoreOf - init(_ heading: String? = nil, filters: IdentifiedArrayOf, flexible: FlexibleLabels? = nil, store: StoreOf) { + private var showCollapseButton: Bool { + labels != nil && totalHeight > collapsedHeight + } + + private var collapsedHeight: CGFloat { + CGFloat(collapsedRowLimit) * rowHeight + CGFloat(collapsedRowLimit - 1) * spacing + } + + private let collapsedRowLimit: Int = 2 + private let spacing: CGFloat = .small1 + + init(_ heading: String? = nil, filters: IdentifiedArrayOf, labels: FlexibleLabels? = nil, store: StoreOf) { self.heading = heading self.filters = filters - self.flexible = flexible + self.labels = labels self.store = store } @@ -144,25 +162,58 @@ extension TransactionHistoryFilters { } HStack(spacing: .zero) { - FlowLayout(spacing: .small1) { - ForEach(filters) { filter in - TransactionFilterView(filter: filter) { id in - store.send(.view(.filterTapped(id))) - } - } + FlowLayout(spacing: spacing) { + FiltersView(filters: filters, store: store) + } + .measureSize(flowLayoutID) + .overlay { + TransactionFilterView.Dummy() + .measureSize(flowDummyID) } Spacer(minLength: 0) } + .frame(maxHeight: isCollapsed ? collapsedHeight : .infinity, alignment: .top) + .clipped() + .onReadSizes(flowDummyID, flowLayoutID) { dummySize, flowSize in + rowHeight = dummySize.height + totalHeight = flowSize.height + } - if let flexible { - Button(showsAll ? "- \(flexible.showLess)" : "+ \(flexible.showAll)") { - showsAll.toggle() + if showCollapseButton, let labels { + Button { + withAnimation { + isCollapsed.toggle() + } + } label: { + ZStack { + Text("+ \(labels.showAll)") + .opacity(isCollapsed ? 1 : 0) + Text("- \(labels.showLess)") + .opacity(isCollapsed ? 0 : 1) + } } .buttonStyle(.blueText) .padding(.top, .medium3) } } + .animation(.default, value: isCollapsed) + } + } + + private let flowLayoutID = "FlowLayout" + private let flowDummyID = "FlowDummy" + } + + private struct FiltersView: SwiftUI.View { + let filters: IdentifiedArrayOf + let store: StoreOf + + var body: some SwiftUI.View { + ForEach(filters) { filter in + TransactionFilterView(filter: filter) { id in + store.send(.view(.filterTapped(id))) + } } } } @@ -227,6 +278,7 @@ struct TransactionFilterView: SwiftUI.View { var body: some SwiftUI.View { Text("ABC") .textStyle(.body1HighImportance) + .foregroundStyle(.clear) .padding(.vertical, .small2) } } diff --git a/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift b/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift index 09df75d0b9..49e3355442 100644 --- a/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift +++ b/RadixWallet/Features/AccountHistory/TransactionHistory+Reducer.swift @@ -152,7 +152,7 @@ public struct TransactionHistory: Sendable, FeatureReducer { period: range, filters: filters, allResources: allResources, - ascending: true, + ascending: false, cursor: nil ) let response = try await transactionHistoryClient.getTransactionHistory(request) diff --git a/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift b/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift index 62d421e934..bb68f4edaa 100644 --- a/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift +++ b/RadixWallet/Features/AccountHistory/TransactionHistory+View.swift @@ -540,3 +540,40 @@ extension TransactionHistory.State.TransactionSection { day.formatted(date: .abbreviated, time: .omitted) } } + +extension View { + public func measureSize(_ id: AnyHashable) -> some View { + background { + GeometryReader { proxy in + Color.clear + .preference(key: PositionsPreferenceKey.self, value: [id: proxy.frame(in: .local)]) + } + } + } + + public func readSize(_ id: AnyHashable, content: @escaping (CGSize) -> some View) -> some View { + overlayPreferenceValue(PositionsPreferenceKey.self, alignment: .top) { positions in + if let size = positions[id]?.size { + content(size) + } else { + EmptyView() + } + } + } + + public func onReadSize(_ id: AnyHashable, content: @escaping (CGSize) -> Void) -> some View { + onPreferenceChange(PositionsPreferenceKey.self) { positions in + if let size = positions[id]?.size { + content(size) + } + } + } + + public func onReadSizes(_ id1: AnyHashable, _ id2: AnyHashable, content: @escaping (CGSize, CGSize) -> Void) -> some View { + onPreferenceChange(PositionsPreferenceKey.self) { positions in + if let size1 = positions[id1]?.size, let size2 = positions[id2]?.size { + content(size1, size2) + } + } + } +}