diff --git a/RadixWallet.xcodeproj/project.pbxproj b/RadixWallet.xcodeproj/project.pbxproj index d8b2be4d07..39cae02b62 100644 --- a/RadixWallet.xcodeproj/project.pbxproj +++ b/RadixWallet.xcodeproj/project.pbxproj @@ -1068,6 +1068,9 @@ A4DBBEB32C20305000D0A59E /* CardCarousel+Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4DBBEB22C20305000D0A59E /* CardCarousel+Reducer.swift */; }; A4DBBEB52C20305700D0A59E /* CardCarousel+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4DBBEB42C20305700D0A59E /* CardCarousel+View.swift */; }; A4DCCC4B2C2DA08000438A7B /* GeometryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4DCCC4A2C2DA08000438A7B /* GeometryExtensions.swift */; }; + A4DCCC502C2DA32C00438A7B /* CardCarouselClient+Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4DCCC4D2C2DA26100438A7B /* CardCarouselClient+Interface.swift */; }; + A4DCCC512C2DA33100438A7B /* CardCarouselClient+Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4DCCC4E2C2DA26100438A7B /* CardCarouselClient+Live.swift */; }; + A4DCCC522C2DA33300438A7B /* CardCarouselClient+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4DCCC4F2C2DA26200438A7B /* CardCarouselClient+Test.swift */; }; A4EB37C82B6272F3003FE31D /* TrackedValidatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4EB37C72B6272F3003FE31D /* TrackedValidatorInteraction.swift */; }; A4EBB5D62BD0777C00D05FDE /* ConfigurationBackup+Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4EBB5D52BD0777C00D05FDE /* ConfigurationBackup+Reducer.swift */; }; A4EBB5D82BD0777F00D05FDE /* ConfigurationBackup+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4EBB5D72BD0777F00D05FDE /* ConfigurationBackup+View.swift */; }; @@ -2195,6 +2198,9 @@ A4DBBEB22C20305000D0A59E /* CardCarousel+Reducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardCarousel+Reducer.swift"; sourceTree = ""; }; A4DBBEB42C20305700D0A59E /* CardCarousel+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardCarousel+View.swift"; sourceTree = ""; }; A4DCCC4A2C2DA08000438A7B /* GeometryExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeometryExtensions.swift; sourceTree = ""; }; + A4DCCC4D2C2DA26100438A7B /* CardCarouselClient+Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardCarouselClient+Interface.swift"; sourceTree = ""; }; + A4DCCC4E2C2DA26100438A7B /* CardCarouselClient+Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardCarouselClient+Live.swift"; sourceTree = ""; }; + A4DCCC4F2C2DA26200438A7B /* CardCarouselClient+Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardCarouselClient+Test.swift"; sourceTree = ""; }; A4EB37C72B6272F3003FE31D /* TrackedValidatorInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackedValidatorInteraction.swift; sourceTree = ""; }; A4EBB5D52BD0777C00D05FDE /* ConfigurationBackup+Reducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationBackup+Reducer.swift"; sourceTree = ""; }; A4EBB5D72BD0777F00D05FDE /* ConfigurationBackup+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationBackup+View.swift"; sourceTree = ""; }; @@ -4242,6 +4248,7 @@ 48CFBF522ADC10D900E77A5C /* Clients */ = { isa = PBXGroup; children = ( + A4DCCC4C2C2DA23E00438A7B /* CardCarouselClient */, E7A5AC942C09F428006CB6EC /* ResetWalletClient */, A4ECE2782BEEB01800468BF6 /* CloudBackupClient */, A4ECE2712BEEAFFC00468BF6 /* SecurityCenterClient */, @@ -5838,6 +5845,16 @@ path = CardCarousel; sourceTree = ""; }; + A4DCCC4C2C2DA23E00438A7B /* CardCarouselClient */ = { + isa = PBXGroup; + children = ( + A4DCCC4D2C2DA26100438A7B /* CardCarouselClient+Interface.swift */, + A4DCCC4E2C2DA26100438A7B /* CardCarouselClient+Live.swift */, + A4DCCC4F2C2DA26200438A7B /* CardCarouselClient+Test.swift */, + ); + path = CardCarouselClient; + sourceTree = ""; + }; A4ECE2712BEEAFFC00468BF6 /* SecurityCenterClient */ = { isa = PBXGroup; children = ( @@ -6774,6 +6791,7 @@ 48CFC2872ADC10D900E77A5C /* MinimumPercentageStepper+View.swift in Sources */, E76645A42C23138300065D9A /* Throwable.swift in Sources */, 48CFC2A12ADC10D900E77A5C /* PersonaFeature.swift in Sources */, + A4DCCC502C2DA32C00438A7B /* CardCarouselClient+Interface.swift in Sources */, 48CFC4162ADC10DA00E77A5C /* PasteboardClient+Test.swift in Sources */, 48CFC34E2ADC10D900E77A5C /* Login+View.swift in Sources */, 48CFC3122ADC10D900E77A5C /* AccountsToImport.swift in Sources */, @@ -7055,6 +7073,7 @@ 48CFC5732ADC10DA00E77A5C /* Configuration.swift in Sources */, 48CFC5A42ADC10DA00E77A5C /* PrimaryTextButtonStyle.swift in Sources */, 83EE47862AF0EE3C00155F03 /* ProgrammaticScryptoSborValueMap.swift in Sources */, + A4DCCC522C2DA33300438A7B /* CardCarouselClient+Test.swift in Sources */, A462B57D2B83656900C26D20 /* MetadataNonFungibleGlobalIdArrayValueAllOfValues.swift in Sources */, 48CFC2A92ADC10D900E77A5C /* DappDetails+View.swift in Sources */, 48CFC52B2ADC10DA00E77A5C /* MetadataU64Value.swift in Sources */, @@ -7199,6 +7218,7 @@ 48CFC5562ADC10DA00E77A5C /* GatewayInfoResponseReleaseInfo.swift in Sources */, 48CFC47F2ADC10DA00E77A5C /* ROLAClient+Interface.swift in Sources */, 48CFC46B2ADC10DA00E77A5C /* ImportLegacyWalletClient+Interface.swift in Sources */, + A4DCCC512C2DA33100438A7B /* CardCarouselClient+Live.swift in Sources */, 48CFC4D82ADC10DA00E77A5C /* TransactionStatusResponseKnownPayloadItem.swift in Sources */, 48CFC4522ADC10DA00E77A5C /* NetworkSwitchingClient+Interface.swift in Sources */, 83AAAC6D2B483D1B00222B64 /* StakeSummaryView.swift in Sources */, diff --git a/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Interface.swift b/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Interface.swift new file mode 100644 index 0000000000..1cec289904 --- /dev/null +++ b/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Interface.swift @@ -0,0 +1,32 @@ +// MARK: - CardCarouselClient +public struct CardCarouselClient: Sendable { + public var cards: Cards + public var closeCard: CloseCard + + init( + cards: @escaping Cards, + closeCard: @escaping CloseCard + ) { + self.cards = cards + self.closeCard = closeCard + } +} + +// MARK: - CarouselCard +public enum CarouselCard: Hashable, Sendable { + case threeSixtyDegrees + case connect + case somethingElse +} + +extension CardCarouselClient { + public typealias Cards = @Sendable () -> AnyAsyncSequence<[CarouselCard]> + public typealias CloseCard = @Sendable (CarouselCard) -> Void +} + +extension DependencyValues { + public var cardCarouselClient: CardCarouselClient { + get { self[CardCarouselClient.self] } + set { self[CardCarouselClient.self] = newValue } + } +} diff --git a/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Live.swift b/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Live.swift new file mode 100644 index 0000000000..74439423b8 --- /dev/null +++ b/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Live.swift @@ -0,0 +1,23 @@ +import ComposableArchitecture + +extension CardCarouselClient: DependencyKey { + public static let liveValue: Self = { + let cardSubject = AsyncCurrentValueSubject<[CarouselCard]>([.threeSixtyDegrees, .connect, .somethingElse]) + + @Dependency(\.errorQueue) var errorQueue + @Dependency(\.appPreferencesClient) var appPreferencesClient + @Dependency(\.cacheClient) var cacheClient + @Dependency(\.radixConnectClient) var radixConnectClient + @Dependency(\.userDefaults) var userDefaults + + return Self( + cards: { + cardSubject.eraseToAnyAsyncSequence() + }, + closeCard: { card in + guard let index = cardSubject.value.firstIndex(of: card) else { return } + cardSubject.value.remove(at: index) + } + ) + }() +} diff --git a/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Test.swift b/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Test.swift new file mode 100644 index 0000000000..00a3af6900 --- /dev/null +++ b/RadixWallet/Clients/CardCarouselClient/CardCarouselClient+Test.swift @@ -0,0 +1,16 @@ +// MARK: - CardCarouselClient + TestDependencyKey +extension CardCarouselClient: TestDependencyKey { + public static let previewValue = Self.noop + + public static let testValue = Self( + cards: unimplemented("\(Self.self).cards"), + closeCard: unimplemented("\(Self.self).closeCard") + ) +} + +extension CardCarouselClient { + public static let noop = Self( + cards: { AsyncLazySequence([]).eraseToAnyAsyncSequence() }, + closeCard: { _ in } + ) +} diff --git a/RadixWallet/Features/CardCarousel/CardCarousel+Reducer.swift b/RadixWallet/Features/CardCarousel/CardCarousel+Reducer.swift index 72607d9757..71bb09e9a8 100644 --- a/RadixWallet/Features/CardCarousel/CardCarousel+Reducer.swift +++ b/RadixWallet/Features/CardCarousel/CardCarousel+Reducer.swift @@ -2,11 +2,10 @@ import ComposableArchitecture // MARK: - CardCarousel @Reducer -public struct CardCarousel: FeatureReducer { +public struct CardCarousel: FeatureReducer, Sendable { @ObservableState public struct State: Hashable, Sendable { public var cards: [CarouselCard] - public var taps: Int = 0 } public typealias Action = FeatureAction @@ -18,6 +17,13 @@ public struct CardCarousel: FeatureReducer { case closeTapped(CarouselCard) } + @CasePathable + public enum InternalAction: Equatable, Sendable { + case setCards([CarouselCard]) + } + + @Dependency(\.cardCarouselClient) var cardCarouselClient + public var body: some ReducerOf { Reduce(core) } @@ -25,23 +31,26 @@ public struct CardCarousel: FeatureReducer { public func reduce(into state: inout State, viewAction: ViewAction) -> Effect { switch viewAction { case .didAppear: - print("•• didAppear") - return .none + return .run { send in + do { + for try await cards in cardCarouselClient.cards() { + await send(.internal(.setCards(cards))) + } + } catch {} + } case let .cardTapped(card): - state.taps += 1 - print("•• didTap \(state.taps)") return .none case let .closeTapped(card): - guard let index = state.cards.firstIndex(where: { $0 == card }) else { return .none } - state.cards.remove(at: index) + cardCarouselClient.closeCard(card) return .none } } -} -// MARK: - CarouselCard -public enum CarouselCard: Hashable, Sendable { - case threeSixtyDegrees - case connect - case somethingElse + public func reduce(into state: inout State, internalAction: InternalAction) -> Effect { + switch internalAction { + case let .setCards(cards): + state.cards = cards + return .none + } + } } diff --git a/RadixWallet/Features/CardCarousel/CardCarousel+View.swift b/RadixWallet/Features/CardCarousel/CardCarousel+View.swift index db06f854d5..ae54e8c340 100644 --- a/RadixWallet/Features/CardCarousel/CardCarousel+View.swift +++ b/RadixWallet/Features/CardCarousel/CardCarousel+View.swift @@ -46,6 +46,7 @@ extension CardCarousel { } } .tabViewStyle(.page(indexDisplayMode: .never)) + .animation(.default, value: store.cards) } .coordinateSpace(name: Self.coordSpace) }