Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ABW-3439] Card Carousel #1187

Merged
merged 36 commits into from
Jul 11, 2024
Merged

[ABW-3439] Card Carousel #1187

merged 36 commits into from
Jul 11, 2024

Conversation

kugel3
Copy link
Contributor

@kugel3 kugel3 commented Jun 28, 2024

Jira ticket: ABW-3439
Jira ticket: ABW-3443

Depends on radixdlt/sargon#180

Description

A carousel showing various action cards at the top of the home screen.


// MARK: - CarouselCard
public enum CarouselCard: Hashable, Sendable {
case threeSixtyDegrees
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan on letting the Cards be an enum? Is it because we need to know what to do when the card is pressed, and we dont wanna represent that with a closure? Especially not if this is to be moved into Sargon?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the repertoire of cards is very limited, and they are completely static, so it seems like a good model. The action will be known at compile time, not sure it makes sense to store it on the model, it can just be handled in the reducer, or possibly the client.


@CasePathable
public enum ViewAction: Equatable, Sendable {
case didAppear
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually this is a task


return .init(
handleEvent: { event in
switch event {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not 100% sure about this approach. Should AppEventsClient perform some actions on the events, or should it just propagate the events?
In the later case the, HomeCardsClients would listen for the events emitted by the AppEventsClient and perform the actions on its own.

wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked this idea a lot, so gave it a try here. However, there seems to be a race condition error since the .appStarted event is never received by the listener. I even added this handy method to be called by the AppEventsClient on its initialization, to make sure the HomeCardsClient subscribes to events before the first one is dispatched, but problem was still present.

We can work on some delays but even having the AppsEventsClient starting the HomeCardsClient (and whatever other listener client we would have in the future) goes against the whole reasoning behind this change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved with AsyncReplaySubject

Comment on lines 45 to 46
if deepLink.isDeferred, let value = deepLink.clickEvent["deep_link_value"] as? String {
appEventsClient.handleEvent(.deepLinkReceived(value))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have DeepLinkHandlerClient, could we extend it to handle the deferred deepLink?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mm I am not sure I prefer it this way, since DeepLinkHandlerClient is about non deferred deep links. The logic for deferred vs non deferred is totally different, so not sure if it wouldn't add more confusion to put it there just for the sake of being a deep link.

Comment on lines 5 to 7
public var walletStarted: WalletStarted
public var walletCreated: WalletCreated
public var deepLinkReceived: DeepLinkReceived
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should express some command, like handle..., or on.... similar to the remove above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignored as per changes to AppEventClient

@MainActor
private var coreView: some SwiftUI.View {
TabView(selection: $selectedCardIndex) {
ForEach(Array(store.cards.enumerated()), id: \.1) { index, card in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use \.element instead of \.1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of enumerating it and then use element as id, instead of just doing \id:.self? And stepping back, what was wrong with having the card be identifiable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the index to tag each card, which is necessary for the positionIndicator defined below. We can extend the card model (now defined in Sargon), and make it Identifiable by defining an id for each. Same result, just need some extra code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with that is that swiftui will not be able to handle insertion and removal animations properly if the the index is the id. For example if you have four cards and remove the third card, with id and index 2, the collection will still have a card with id 2, but it will be the one that used to have id 3. In short, using index as id with a dynamic collection is not supported in SwiftUI. That is also why I called my helper type that wraps up the functionality you use here, "ForEachStatic".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't using the index as the id, we are using the \.element to identify them.

We are using the index to tag them, and that works correctly on your example since after removing card with index: 2, the index (and tag) is properly updated with the new set of elements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, alright, so you use element for id, and index only for the tag, for the benefit of the tabview.

I would still consider using the same tag and id, and map it in the reducer, since in there you have access to both the elements and their offsets.


private var positionIndicator: some SwiftUI.View {
HStack(spacing: spacing) {
if store.cards.count > 1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: maybe it should be outside of positionIndicator, just to not add an empty HStack I guess

Comment on lines +92 to +95
.padding([.top, .leading], .medium2)
.padding(.trailing, trailingPadding)
.padding(.bottom, .small1)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is like a lot of padding + frame, is it really needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes 🫤

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it so that the frame grows as much as it can, instead of hugging the content. It's very similar to using say an HSTack with Spacer's.

Comment on lines 103 to 109
Button(action: closeAction) {
Image(asset: AssetResource.close)
.resizable()
.frame(width: .medium3, height: .medium3)
.tint(.app.gray1)
.padding(.small2)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already close button, surprised that this is needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The close button is intended to be used in a toolbar so it has a bigger padding, which makes the X overlap with the background/dapp image. That's why I changed it to this manual implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CloseButton does not seem to have any padding, but anyway would be nice to have a single definition of CloseButton if possible.

Copy link
Contributor

@matiasbzurovski matiasbzurovski Jul 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CloseButton defines a size of 44x44, meaning the asset (24x24) will have a 10 padding on each side.

In this case, we want the asset to be of 16x16 and to have a padding of 8 on top & trailing sides.

I can extend the CloseButton to adapt for each type.

}
}

private var trailingPadding: CGFloat {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite getting what this is for, I guess I need to play with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically how much space we must leave empty from the trailing side of the view. Depending on its content, we have more or less space that we can use without the text overlapping with an image.

Original implementation had a fixed value, but that would leave very little space for some views on smaller devices (as iPhone SE) and content would look trimmed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason it was fixed is that the image we want to avoid covering is fixed size, because the view has a fixed height. That in turn is because tabview can't handle flexible heights very well.

@kugel3
Copy link
Contributor Author

kugel3 commented Jul 7, 2024

I saw a question about the dummy views, though I can't find it anymore. Their purpose is to show the teased cards on the sides. The tabview itself does not reach the end of the screen, it is only as wide as the card, plus half the inter card spacing on each side.

Comment on lines 195 to 197
if state.config.shouldSendWalletCreatedEvent {
appEventsClient.handleEvent(.walletCreated)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This event should be added when the onboarding is completed in App+Reducer, as by definition the onboarding happens when user creates a new Wallet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in such case, it could be because the user restored a wallet

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, then it would be better to have this code in OnboardingCoordinator when the CreateAccount completes.

@GhenadieVP GhenadieVP merged commit 04c658e into main Jul 11, 2024
6 checks passed
@GhenadieVP GhenadieVP deleted the ABW-3439_Card-carousel branch July 11, 2024 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants