Skip to content

Commit

Permalink
[MBL-1495] [MBL-1502] PPO UI implementation (#2186)
Browse files Browse the repository at this point in the history
* Connect PPO UI to PaginatedList

* Fix alert flag parsing caused by inverting keys for types and icons

* Cleanup and improvement

* Split up PPOView into separate view builders for different pieces

* Fix some blended layers

* Action routing

* Format fix

* Fix test

* Add local snapshot test image for tests

* Switch from raw image URL to a Kingfisher Source

* Sigh we'll just snapshot the placeholder

* Format

* Move some style stuff into PPOCardStyles and rename it to PPOStyles

* Format

* Hash pledge

* Remove showProject case

* Replace showProject with viewBackingDetails
  • Loading branch information
stevestreza-ksr authored Nov 1, 2024
1 parent 3394de2 commit 2534a52
Show file tree
Hide file tree
Showing 24 changed files with 516 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ struct PPOAddressSummary: View {
HStack(alignment: .firstTextBaseline) {
// TODO: Localize
Text("Shipping address")
.font(Font(PPOCardStyles.title.font))
.foregroundStyle(Color(PPOCardStyles.title.color))
.font(Font(PPOStyles.title.font))
.foregroundStyle(Color(PPOStyles.title.color))
.frame(width: self.leadingColumnWidth, alignment: Constants.textAlignment)

Text(self.address)
.font(Font(PPOCardStyles.body.font))
.foregroundStyle(Color(PPOCardStyles.body.color))
.font(Font(PPOStyles.body.font))
.foregroundStyle(Color(PPOStyles.body.color))
.frame(maxWidth: Constants.maxWidth, alignment: Constants.textAlignment)
}
.frame(maxWidth: Constants.maxWidth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct PPOAlertFlag: View {
Spacer()
.frame(width: Constants.spacerWidth)
Text(self.alert.message)
.font(Font(PPOCardStyles.title.font))
.font(Font(PPOStyles.title.font))
.foregroundStyle(self.foregroundColor)
}
.padding(Constants.padding)
Expand All @@ -27,29 +27,29 @@ struct PPOAlertFlag: View {
}

var image: Image {
switch self.alert.type {
switch self.alert.icon {
case .time:
Image(PPOCardStyles.timeImage)
Image(PPOStyles.timeImage)
case .alert:
Image(PPOCardStyles.alertImage)
Image(PPOStyles.alertImage)
}
}

var foregroundColor: Color {
switch self.alert.icon {
switch self.alert.type {
case .warning:
Color(uiColor: PPOCardStyles.warningColor.foreground)
Color(uiColor: PPOStyles.warningColor.foreground)
case .alert:
Color(uiColor: PPOCardStyles.alertColor.foreground)
Color(uiColor: PPOStyles.alertColor.foreground)
}
}

var backgroundColor: Color {
switch self.alert.icon {
switch self.alert.type {
case .warning:
Color(uiColor: PPOCardStyles.warningColor.background)
Color(uiColor: PPOStyles.warningColor.background)
case .alert:
Color(uiColor: PPOCardStyles.alertColor.background)
Color(uiColor: PPOStyles.alertColor.background)
}
}

Expand All @@ -64,10 +64,10 @@ struct PPOAlertFlag: View {

#Preview("Stack of flags") {
VStack(alignment: .leading, spacing: 8) {
PPOAlertFlag(alert: .init(type: .time, icon: .warning, message: "Address locks in 8 hours"))
PPOAlertFlag(alert: .init(type: .alert, icon: .warning, message: "Survey available"))
PPOAlertFlag(alert: .init(type: .warning, icon: .time, message: "Address locks in 8 hours"))
PPOAlertFlag(alert: .init(type: .warning, icon: .alert, message: "Survey available"))
PPOAlertFlag(alert: .init(type: .alert, icon: .alert, message: "Payment failed"))
PPOAlertFlag(alert: .init(type: .time, icon: .alert, message: "Pledge will be dropped in 6 days"))
PPOAlertFlag(alert: .init(type: .alert, icon: .time, message: "Pledge will be dropped in 6 days"))
PPOAlertFlag(alert: .init(type: .alert, icon: .alert, message: "Card needs authentication"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import SwiftUI

struct PPOProjectCard: View {
@StateObject var viewModel: PPOProjectCardViewModel
var parentSize: CGSize

var onViewBackingDetails: ((PPOProjectCardModel) -> Void)? = nil
var onSendMessage: ((PPOProjectCardModel) -> Void)? = nil
var onPerformAction: ((PPOProjectCardModel, PPOProjectCardModel.Action) -> Void)? = nil

var body: some View {
VStack(spacing: Constants.spacing) {
self.flagList
self.projectDetails(leadingColumnWidth: self.viewModel.parentSize.width * Constants.firstColumnWidth)
self.projectDetails(leadingColumnWidth: self.parentSize.width * Constants.firstColumnWidth)
self.divider
self.projectCreator
self.divider
self.addressDetails(leadingColumnWidth: self.viewModel.parentSize.width * Constants.firstColumnWidth)
self.addressDetails(leadingColumnWidth: self.parentSize.width * Constants.firstColumnWidth)
self.actionButtons
}
.padding(.vertical)
Expand All @@ -32,8 +37,16 @@ struct PPOProjectCard: View {
content: { self.badge.opacity(self.viewModel.card.isUnread ? 1 : 0) }
)

// insets
.padding(.horizontal, Constants.outerPadding)
// Handle actions
.onReceive(self.viewModel.viewBackingDetailsTapped) {
self.onViewBackingDetails?(self.viewModel.card)
}
.onReceive(self.viewModel.sendMessageTapped) {
self.onSendMessage?(self.viewModel.card)
}
.onReceive(self.viewModel.actionPerformed) { action in
self.onPerformAction?(self.viewModel.card, action)
}
}

@ViewBuilder
Expand All @@ -44,7 +57,7 @@ struct PPOProjectCard: View {
@ViewBuilder
private var badge: some View {
Circle()
.fill(Color(uiColor: PPOCardStyles.badgeColor))
.fill(Color(uiColor: PPOStyles.badgeColor))
.frame(width: Constants.badgeSize, height: Constants.badgeSize)
.offset(x: Constants.badgeSize / 2, y: -(Constants.badgeSize / 2))
}
Expand All @@ -67,7 +80,7 @@ struct PPOProjectCard: View {
@ViewBuilder
private func projectDetails(leadingColumnWidth: CGFloat) -> some View {
PPOProjectDetails(
imageUrl: self.viewModel.card.imageURL,
image: self.viewModel.card.image,
title: self.viewModel.card.title,
pledge: self.viewModel.card.pledge,
leadingColumnWidth: leadingColumnWidth
Expand All @@ -77,8 +90,13 @@ struct PPOProjectCard: View {

@ViewBuilder
private var projectCreator: some View {
PPOProjectCreator(creatorName: self.viewModel.card.creatorName)
.padding([.horizontal])
PPOProjectCreator(
creatorName: self.viewModel.card.creatorName,
onSendMessage: { [weak viewModel] () in
viewModel?.sendCreatorMessage()
}
)
.padding([.horizontal])
}

@ViewBuilder
Expand Down Expand Up @@ -146,7 +164,10 @@ struct PPOProjectCard: View {
ScrollView(.vertical) {
VStack(spacing: 16) {
ForEach(PPOProjectCardModel.previewTemplates) { template in
PPOProjectCard(viewModel: PPOProjectCardViewModel(card: template, parentSize: geometry.size))
PPOProjectCard(
viewModel: PPOProjectCardViewModel(card: template),
parentSize: geometry.size
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Foundation
import Kingfisher
import KsApi
import Library

public struct PPOProjectCardModel: Identifiable, Equatable {
public struct PPOProjectCardModel: Identifiable, Equatable, Hashable {
public let isUnread: Bool
public let alerts: [Alert]
public let imageURL: URL
public let image: Kingfisher.Source
public let title: String
public let pledge: GraphAPI.MoneyFragment
public let creatorName: String
Expand All @@ -15,6 +16,19 @@ public struct PPOProjectCardModel: Identifiable, Equatable {
public let backingDetailsUrl: String
public let projectAnalytics: GraphAPI.ProjectAnalyticsFragment

public func hash(into hasher: inout Hasher) {
hasher.combine(self.isUnread)
hasher.combine(self.alerts)
hasher.combine(self.image)
hasher.combine(self.title)
hasher.combine(self.pledge)
hasher.combine(self.creatorName)
hasher.combine(self.address)
hasher.combine(self.actions.0)
hasher.combine(self.actions.1)
hasher.combine(self.tierType)
}

// MARK: - Identifiable

public let id = UUID()
Expand All @@ -26,7 +40,7 @@ public struct PPOProjectCardModel: Identifiable, Equatable {
public static func == (lhs: PPOProjectCardModel, rhs: PPOProjectCardModel) -> Bool {
lhs.isUnread == rhs.isUnread &&
lhs.alerts == rhs.alerts &&
lhs.imageURL == rhs.imageURL &&
lhs.image == rhs.image &&
lhs.title == rhs.title &&
lhs.pledge == rhs.pledge &&
lhs.creatorName == rhs.creatorName &&
Expand All @@ -41,7 +55,7 @@ public struct PPOProjectCardModel: Identifiable, Equatable {
case confirmAddress
}

public enum Action: Identifiable, Equatable {
public enum Action: Identifiable, Equatable, Hashable {
case confirmAddress
case editAddress
case completeSurvey
Expand Down Expand Up @@ -98,7 +112,7 @@ public struct PPOProjectCardModel: Identifiable, Equatable {
}
}

public struct Alert: Identifiable, Equatable {
public struct Alert: Identifiable, Equatable, Hashable {
public let type: AlertType
public let icon: AlertIcon
public let message: String
Expand All @@ -113,7 +127,7 @@ public struct PPOProjectCardModel: Identifiable, Equatable {
"\(self.type)-\(self.icon)-\(self.message)"
}

public enum AlertType: Identifiable, Equatable {
public enum AlertIcon: Identifiable, Equatable {
case time
case alert

Expand All @@ -127,7 +141,7 @@ public struct PPOProjectCardModel: Identifiable, Equatable {
}
}

public enum AlertIcon: Identifiable, Equatable {
public enum AlertType: Identifiable, Equatable {
case warning
case alert

Expand All @@ -145,7 +159,7 @@ public struct PPOProjectCardModel: Identifiable, Equatable {

extension PPOProjectCardModel.Alert {
init?(flag: GraphAPI.PpoCardFragment.Flag) {
let alertType: PPOProjectCardModel.Alert.AlertType? = switch flag.type {
let alertIcon: PPOProjectCardModel.Alert.AlertIcon? = switch flag.icon {
case "alert":
.alert
case "time":
Expand All @@ -154,7 +168,7 @@ extension PPOProjectCardModel.Alert {
nil
}

let alertIcon: PPOProjectCardModel.Alert.AlertIcon? = switch flag.icon {
let alertType: PPOProjectCardModel.Alert.AlertType? = switch flag.type {
case "alert":
.alert
case "warning":
Expand All @@ -180,6 +194,14 @@ extension GraphAPI.MoneyFragment: Equatable {
}
}

extension GraphAPI.MoneyFragment: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.amount)
hasher.combine(self.currency)
hasher.combine(self.symbol)
}
}

extension PPOProjectCardModel {
#if targetEnvironment(simulator)
public static let previewTemplates: [PPOProjectCardModel] = [
Expand All @@ -194,9 +216,9 @@ extension PPOProjectCardModel {
internal static let confirmAddressTemplate = PPOProjectCardModel(
isUnread: true,
alerts: [
.init(type: .time, icon: .warning, message: "Address locks in 8 hours")
.init(type: .warning, icon: .time, message: "Address locks in 8 hours")
],
imageURL: URL(string: "http://localhost/")!,
image: .network(URL(string: "https:///")!),
title: "Sugardew Island - Your cozy farm shop let’s pretend this is a way way way longer title",
pledge: .init(amount: "50.00", currency: .usd, symbol: "$"),
creatorName: "rokaplay truncate if longer than",
Expand All @@ -215,10 +237,10 @@ extension PPOProjectCardModel {
internal static let addressLockTemplate = PPOProjectCardModel(
isUnread: true,
alerts: [
.init(type: .alert, icon: .warning, message: "Survey available"),
.init(type: .time, icon: .warning, message: "Address locks in 48 hours")
.init(type: .warning, icon: .alert, message: "Survey available"),
.init(type: .warning, icon: .time, message: "Address locks in 48 hours")
],
imageURL: URL(string: "http://localhost/")!,
image: .network(URL(string: "https:///")!),
title: "Sugardew Island - Your cozy farm shop let’s pretend this is a way way way longer title",
pledge: .init(amount: "50.00", currency: .usd, symbol: "$"),
creatorName: "rokaplay truncate if longer than",
Expand All @@ -234,12 +256,12 @@ extension PPOProjectCardModel {
alerts: [
.init(type: .alert, icon: .alert, message: "Payment failed"),
.init(
type: .time,
icon: .alert,
type: .alert,
icon: .time,
message: "Pledge will be dropped in 6 days"
)
],
imageURL: URL(string: "http://localhost/")!,
image: .network(URL(string: "https:///")!),
title: "Sugardew Island - Your cozy farm shop let’s pretend this is a way way way longer title",
pledge: .init(amount: "50.00", currency: .usd, symbol: "$"),
creatorName: "rokaplay truncate if longer than",
Expand All @@ -255,12 +277,12 @@ extension PPOProjectCardModel {
alerts: [
.init(type: .alert, icon: .alert, message: "Card needs authentication"),
.init(
type: .time,
icon: .alert,
type: .alert,
icon: .time,
message: "Pledge will be dropped in 6 days"
)
],
imageURL: URL(string: "http://localhost/")!,
image: .network(URL(string: "https:///")!),
title: "Sugardew Island - Your cozy farm shop let’s pretend this is a way way way longer title",
pledge: .init(amount: "50.00", currency: .usd, symbol: "$"),
creatorName: "rokaplay truncate if longer than",
Expand All @@ -274,9 +296,9 @@ extension PPOProjectCardModel {
internal static let completeSurveyTemplate = PPOProjectCardModel(
isUnread: true,
alerts: [
.init(type: .alert, icon: .warning, message: "Survey available")
.init(type: .warning, icon: .alert, message: "Survey available")
],
imageURL: URL(string: "http://localhost/")!,
image: .network(URL(string: "https:///")!),
title: "Sugardew Island - Your cozy farm shop let’s pretend this is a way way way longer title",
pledge: .init(amount: "50.00", currency: .usd, symbol: "$"),
creatorName: "rokaplay truncate if longer than",
Expand Down Expand Up @@ -330,8 +352,9 @@ extension PPOProjectCardModel {
let backing = card.backing?.fragments.ppoBackingFragment
let ppoProject = backing?.project?.fragments.ppoProjectFragment

let imageURL = ppoProject?.image?.url
let image = ppoProject?.image?.url
.flatMap { URL(string: $0) }
.map { Kingfisher.Source.network($0) }

let title = ppoProject?.name
let pledge = backing?.amount.fragments.moneyFragment
Expand Down Expand Up @@ -373,12 +396,11 @@ extension PPOProjectCardModel {

let projectAnalyticsFragment = backing?.project?.fragments.projectAnalyticsFragment

if let imageURL, let title, let pledge, let creatorName, let projectAnalyticsFragment,
let backingDetailsUrl {
if let image, let title, let pledge, let creatorName, let projectAnalyticsFragment, let backingDetailsUrl {
self.init(
isUnread: true,
alerts: alerts,
imageURL: imageURL,
image: image,
title: title,
pledge: pledge,
creatorName: creatorName,
Expand Down
Loading

0 comments on commit 2534a52

Please sign in to comment.