Skip to content

Commit

Permalink
[ABW-3439] Card Carousel (#1187)
Browse files Browse the repository at this point in the history
Co-authored-by: Matias Bzurovski <matias.bzurovski@rdx.works>
Co-authored-by: matiasbzurovski <164921079+matiasbzurovski@users.noreply.github.com>
  • Loading branch information
3 people committed Jul 11, 2024
1 parent 2cf28b0 commit 04c658e
Show file tree
Hide file tree
Showing 43 changed files with 742 additions and 240 deletions.
78 changes: 77 additions & 1 deletion RadixWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/radixdlt/sargon",
"state" : {
"revision" : "eb02bd76b1261527938aac2cd982b8e1b505acf2",
"version" : "1.0.23"
"revision" : "f04e79c75c0124218f5f3f2d5d6968bbb2ae73b4",
"version" : "1.0.25"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// MARK: - AppEventsClient
public struct AppEventsClient: Sendable {
public var handleEvent: HandleEvent
public var events: Events

init(handleEvent: @escaping HandleEvent, events: @escaping Events) {
self.handleEvent = handleEvent
self.events = events
}
}

// MARK: AppEventsClient.HandleEvent
extension AppEventsClient {
public typealias HandleEvent = @Sendable (AppEvent) -> Void
public typealias Events = @Sendable () -> AnyAsyncSequence<AppEvent>
}

extension DependencyValues {
public var appEventsClient: AppEventsClient {
get { self[AppEventsClient.self] }
set { self[AppEventsClient.self] = newValue }
}
}

// MARK: - AppEvent
public enum AppEvent: Sendable, Hashable {
case appStarted
case walletCreated
case deferredDeepLinkReceived(String)
}
14 changes: 14 additions & 0 deletions RadixWallet/Clients/AppEventsClient/AppEventsClient+Live.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
extension AppEventsClient: DependencyKey {
public static let liveValue: AppEventsClient = {
let eventsSubject = AsyncReplaySubject<AppEvent>(bufferSize: 10)

return .init(
handleEvent: { event in
eventsSubject.send(event)
},
events: {
eventsSubject.eraseToAnyAsyncSequence()
}
)
}()
}
16 changes: 16 additions & 0 deletions RadixWallet/Clients/AppEventsClient/AppEventsClient+Test.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// MARK: - AppEventsClient + TestDependencyKey
extension AppEventsClient: TestDependencyKey {
public static let previewValue = Self.noop

public static let testValue = Self(
handleEvent: unimplemented("\(Self.self).handleEvent"),
events: unimplemented("\(Self.self).events")
)
}

extension AppEventsClient {
public static let noop = Self(
handleEvent: { _ in },
events: { AsyncLazySequence([]).eraseToAnyAsyncSequence() }
)
}
11 changes: 4 additions & 7 deletions RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Live.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,13 @@ extension AppsFlyerClient: DependencyKey {
}

private class Delegate: NSObject, DeepLinkDelegate, @unchecked Sendable {
@Dependency(\.appEventsClient) var appEventsClient

func didResolveDeepLink(_ result: DeepLinkResult) {
if let deepLink = result.deepLink {
loggerGlobal.info("did resolve deep link. Is deferred: \(deepLink.isDeferred). Click events: \(deepLink.clickEvent)")
if deepLink.isDeferred {
let message = if let deepLinkValue = deepLink.clickEvent["deep_link_value"] as? String {
"Resolved deferred DL with value \(deepLinkValue)"
} else {
"Resolved deferred DL without value"
}
AppsFlyerLib.shared().logEvent(message, withValues: deepLink.clickEvent)
if deepLink.isDeferred, let value = deepLink.clickEvent["deep_link_value"] as? String {
appEventsClient.handleEvent(.deferredDeepLinkReceived(value))
}
} else if let error = result.error {
loggerGlobal.info("failed to resolve deep link. Status: \(result.status), Error: \(error.localizedDescription)")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// MARK: - BootstrapClient
struct BootstrapClient: Sendable {
var bootstrap: Bootstrap
}

// MARK: BootstrapClient.Bootstrap
extension BootstrapClient {
typealias Bootstrap = @Sendable () -> Void
}

extension DependencyValues {
var bootstrapClient: BootstrapClient {
get { self[BootstrapClient.self] }
set { self[BootstrapClient.self] = newValue }
}
}
13 changes: 13 additions & 0 deletions RadixWallet/Clients/BootstrapClient/BootstrapClient+Live.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
extension BootstrapClient: DependencyKey {
static var liveValue: BootstrapClient {
@Dependency(\.appsFlyerClient) var appsFlyerClient
@Dependency(\.homeCardsClient) var homeCardsClient

return .init(
bootstrap: {
appsFlyerClient.start()
homeCardsClient.bootstrap()
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// MARK: - HomeCardsClient
public struct HomeCardsClient: Sendable {
public var cards: Cards
public var removeCard: RemoveCard

init(
cards: @escaping Cards,
removeCard: @escaping RemoveCard
) {
self.cards = cards
self.removeCard = removeCard
}
}

extension HomeCardsClient {
public typealias Cards = @Sendable () -> AnyAsyncSequence<[HomeCard]>
public typealias RemoveCard = @Sendable (HomeCard) -> Void
}

extension DependencyValues {
public var homeCardsClient: HomeCardsClient {
get { self[HomeCardsClient.self] }
set { self[HomeCardsClient.self] = newValue }
}
}

extension HomeCardsClient {
/// An empty method to be called when the app starts, so that the client gets initialized before being used for the first time.
/// This is necessary to monitor events that need to be delivered to Sargon before the client is used from its respective UI.
/// Should be removed once SargonOS is integrated.
func bootstrap() {}
}
70 changes: 70 additions & 0 deletions RadixWallet/Clients/HomeCardsClient/HomeCardsClient+Live.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import ComposableArchitecture

// MARK: - HomeCardsClient + DependencyKey
extension HomeCardsClient: DependencyKey {
public static let liveValue: Self = {
@Dependency(\.appEventsClient) var appEventsClient

let observer = HomeCardsObserver()

// We are hardcoding to `.mainnet` because the cards are currently gateway agnostic. In the future, when Profile is integrated into Sargon, it will be Sargon
// observing the current gateway and defining the networkId to use.
let manager = HomeCardsManager(networkAntenna: URLSession.shared, networkId: .mainnet, cardsStorage: HomeCardsStorage(), observer: observer)

Task {
for try await event in appEventsClient.events() {
guard !Task.isCancelled else { return }
await handle(event: event)
}
}

@Sendable
func handle(event: AppEvent) async {
switch event {
case .appStarted:
try? await manager.bootstrap()
case .walletCreated:
try? await manager.walletCreated()
case let .deferredDeepLinkReceived(value):
try? await manager.deferredDeepLinkReceived(encodedValue: value)
}
}

return Self(
cards: {
observer.subject.eraseToAnyAsyncSequence()
},
removeCard: { card in
Task {
try? await manager.cardDismissed(card: card)
}
}
)
}()
}

// MARK: - HomeCardsManager + Sendable
extension HomeCardsManager: @unchecked Sendable {}

// MARK: - HomeCardsStorage
private final class HomeCardsStorage: Sargon.HomeCardsStorage {
private let userDefaults = UserDefaults.Dependency.radix

func saveCards(encodedCards: Data) async throws {
userDefaults.setHomeCards(encodedCards)
}

func loadCards() async throws -> Data? {
userDefaults.getHomeCards()
}
}

// MARK: - HomeCardsObserver
private final class HomeCardsObserver: Sargon.HomeCardsObserver, Sendable {
let subject: AsyncCurrentValueSubject<[HomeCard]> = .init([])

func handleCardsUpdate(cards: [HomeCard]) {
print("M- Received cards from Sargon \(cards)")
subject.send(cards)
}
}
16 changes: 16 additions & 0 deletions RadixWallet/Clients/HomeCardsClient/HomeCardsClient+Test.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// MARK: - HomeCardsClient + TestDependencyKey
extension HomeCardsClient: TestDependencyKey {
public static let previewValue = Self.noop

public static let testValue = Self(
cards: unimplemented("\(Self.self).cards"),
removeCard: unimplemented("\(Self.self).removeCard")
)
}

extension HomeCardsClient {
public static let noop = Self(
cards: { AsyncLazySequence([]).eraseToAnyAsyncSequence() },
removeCard: { _ in }
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Sargon
// MARK: - UserDefaultsKey
public enum UserDefaultsKey: String, Sendable, Hashable, CaseIterable {
case hideMigrateOlympiaButton
case showRadixBanner
case epochForWhenLastUsedByAccountAddress
case transactionsCompletedCounter
case dateOfLastSubmittedNPSSurvey
Expand All @@ -16,6 +15,7 @@ public enum UserDefaultsKey: String, Sendable, Hashable, CaseIterable {
case lastSyncedAccountsWithCE
case showRelinkConnectorsAfterUpdate
case showRelinkConnectorsAfterProfileRestore
case homeCards

/// DO NOT CHANGE THIS KEY
case activeProfileID
Expand Down Expand Up @@ -97,14 +97,6 @@ extension UserDefaults.Dependency {
set(value, forKey: Key.hideMigrateOlympiaButton.rawValue)
}

public var showRadixBanner: Bool {
bool(key: .showRadixBanner)
}

public func setShowRadixBanner(_ value: Bool) {
set(value, forKey: Key.showRadixBanner.rawValue)
}

public func getActiveProfileID() -> ProfileID? {
string(forKey: Key.activeProfileID.rawValue).flatMap(UUID.init(uuidString:))
}
Expand Down Expand Up @@ -233,6 +225,14 @@ extension UserDefaults.Dependency {
public func setShowRelinkConnectorsAfterProfileRestore(_ value: Bool) {
set(value, forKey: Key.showRelinkConnectorsAfterProfileRestore.rawValue)
}

public func getHomeCards() -> Data? {
data(key: .homeCards)
}

public func setHomeCards(_ value: Data) {
set(data: value, key: .homeCards)
}
}

// MARK: - BackupResult
Expand Down
50 changes: 44 additions & 6 deletions RadixWallet/Core/DesignSystem/Components/CloseButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,59 @@ public struct CloseButtonBar: View {

// MARK: - CloseButton
public struct CloseButton: View {
let kind: Kind
let action: () -> Void

public init(action: @escaping () -> Void) {
public init(kind: Kind = .toolbar, action: @escaping () -> Void) {
self.kind = kind
self.action = action
}
}

extension CloseButton {
public var body: some View {
Button(action: action) {
Image(.close)
.foregroundColor(.app.gray1)
.tint(.app.gray1)
.resizable()
.frame(kind.size)
.tint(kind.tint)
.padding(kind.padding)
}
.frame(.small, alignment: kind.alignment)
}
}

// MARK: CloseButton.Kind
extension CloseButton {
public enum Kind {
case toolbar
case homeCard

var size: CGFloat {
switch self {
case .toolbar: .medium1
case .homeCard: .medium3
}
}

var tint: Color {
switch self {
case .toolbar: .app.gray1
case .homeCard: .app.gray2
}
}

var padding: CGFloat {
switch self {
case .toolbar: .zero
case .homeCard: .small2
}
}

var alignment: Alignment {
switch self {
case .toolbar: .center
case .homeCard: .topTrailing
}
}
.frame(.small)
}
}

Expand Down
11 changes: 11 additions & 0 deletions RadixWallet/Core/DesignSystem/Components/GeometryExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

extension CGRect {
public subscript(unitPoint: UnitPoint) -> CGPoint {
.init(x: minX + unitPoint.x * width, y: minY + unitPoint.y * height)
}

public var center: CGPoint {
.init(x: midX, y: midY)
}
}
6 changes: 0 additions & 6 deletions RadixWallet/Core/DesignSystem/Components/JaggedEdge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,3 @@ struct JaggedEdgeShape: Shape {
}
}
}

extension CGRect {
subscript(unitPoint: UnitPoint) -> CGPoint {
.init(x: minX + unitPoint.x * width, y: minY + unitPoint.y * height)
}
}
Loading

0 comments on commit 04c658e

Please sign in to comment.