Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Fakes/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,10 @@ extension Networking.JustInTimeMessage {
siteID: .fake(),
messageID: .fake(),
featureClass: .fake(),
ttl: .fake(),
content: .fake(),
cta: .fake(),
assets: .fake()
assets: .fake(),
template: .fake()
)
}
}
Expand Down
10 changes: 9 additions & 1 deletion Fakes/Fakes/Yosemite.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ extension Yosemite.JustInTimeMessage {
backgroundImageUrl: .fake(),
backgroundImageDarkUrl: .fake(),
badgeImageUrl: .fake(),
badgeImageDarkUrl: .fake()
badgeImageDarkUrl: .fake(),
template: .fake()
)
}
}
extension JustInTimeMessageTemplate {
/// Returns a "ready to use" type filled with fake values.
///
public static func fake() -> JustInTimeMessageTemplate {
.banner
}
}
Comment on lines +28 to +34
Copy link
Contributor

Choose a reason for hiding this comment

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

Much cleaner for tests 💯

extension Yosemite.ProductReviewFromNoteParcel {
/// Returns a "ready to use" type filled with fake values.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,27 +396,27 @@ extension Networking.JustInTimeMessage {
siteID: CopiableProp<Int64> = .copy,
messageID: CopiableProp<String> = .copy,
featureClass: CopiableProp<String> = .copy,
ttl: CopiableProp<Int64> = .copy,
content: CopiableProp<JustInTimeMessage.Content> = .copy,
cta: CopiableProp<JustInTimeMessage.CTA> = .copy,
assets: CopiableProp<[String: URL]> = .copy
assets: CopiableProp<[String: URL]> = .copy,
template: CopiableProp<String> = .copy
) -> Networking.JustInTimeMessage {
let siteID = siteID ?? self.siteID
let messageID = messageID ?? self.messageID
let featureClass = featureClass ?? self.featureClass
let ttl = ttl ?? self.ttl
let content = content ?? self.content
let cta = cta ?? self.cta
let assets = assets ?? self.assets
let template = template ?? self.template

return Networking.JustInTimeMessage(
siteID: siteID,
messageID: messageID,
featureClass: featureClass,
ttl: ttl,
content: content,
cta: cta,
assets: assets
assets: assets,
template: template
)
}
}
Expand Down
18 changes: 8 additions & 10 deletions Networking/Networking/Model/JustInTimeMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ public struct JustInTimeMessage: GeneratedCopiable, GeneratedFakeable, Equatable
///
public let featureClass: String

/// TTL, or Time To Live: validity of the JITM's client-side dismissal in seconds, only relevant after dismissal.
///
public let ttl: Int64

/// Content of the JITM: in particular, the title and description of the message
///
public let content: Content
Expand All @@ -34,31 +30,33 @@ public struct JustInTimeMessage: GeneratedCopiable, GeneratedFakeable, Equatable
///
public let assets: [String: URL]

public let template: String

public init(siteID: Int64,
messageID: String,
featureClass: String,
ttl: Int64,
content: JustInTimeMessage.Content,
cta: JustInTimeMessage.CTA,
assets: [String: URL]) {
assets: [String: URL],
template: String) {
self.siteID = siteID
self.messageID = messageID
self.featureClass = featureClass
self.ttl = ttl
self.content = content
self.cta = cta
self.assets = assets
self.template = template
}
}

extension JustInTimeMessage: Codable {
enum CodingKeys: String, CodingKey {
case messageID = "id"
case featureClass = "feature_class"
case ttl
case content
case cta = "CTA"
case assets
case template
}

public init(from decoder: Decoder) throws {
Expand All @@ -71,21 +69,21 @@ extension JustInTimeMessage: Codable {
self.siteID = siteID
self.messageID = try container.decode(String.self, forKey: JustInTimeMessage.CodingKeys.messageID)
self.featureClass = try container.decode(String.self, forKey: JustInTimeMessage.CodingKeys.featureClass)
self.ttl = try container.decode(Int64.self, forKey: JustInTimeMessage.CodingKeys.ttl)
self.content = try container.decode(JustInTimeMessage.Content.self, forKey: JustInTimeMessage.CodingKeys.content)
self.cta = try container.decode(JustInTimeMessage.CTA.self, forKey: JustInTimeMessage.CodingKeys.cta)
self.assets = try container.decodeIfPresent([String: URL].self, forKey: .assets) ?? [:]
self.template = try container.decodeIfPresent(String.self, forKey: .template) ?? "default"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: JustInTimeMessage.CodingKeys.self)

try container.encode(self.messageID, forKey: JustInTimeMessage.CodingKeys.messageID)
try container.encode(self.featureClass, forKey: JustInTimeMessage.CodingKeys.featureClass)
try container.encode(self.ttl, forKey: JustInTimeMessage.CodingKeys.ttl)
try container.encode(self.content, forKey: JustInTimeMessage.CodingKeys.content)
try container.encode(self.cta, forKey: JustInTimeMessage.CodingKeys.cta)
try container.encode(self.assets, forKey: .assets)
try container.encode(self.template, forKey: .template)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ final class JustInTimeMessageListMapperTests: XCTestCase {
siteID: dummySiteID,
messageID: "woomobile_ipp_barcode_users",
featureClass: "woomobile_ipp",
ttl: 300,
content: JustInTimeMessage.Content(
message: "In-person card payments",
description: "Sell anywhere, and take card payments using a mobile card reader."),
cta: JustInTimeMessage.CTA(
message: "Purchase Card Reader",
link: "https://woocommerce.com/products/hardware/US"),
assets: ["background_image_url": URL(string: "https://example.net/images/background-light@2x.png")!])
assets: ["background_image_url": URL(string: "https://example.net/images/background-light@2x.png")!],
template: "modal")
assertEqual(expectedJustInTimeMessage, justInTimeMessage)
}

Expand All @@ -55,14 +55,14 @@ final class JustInTimeMessageListMapperTests: XCTestCase {
let expectedJustInTimeMessage = JustInTimeMessage(siteID: dummySiteID,
messageID: "woomobile_ipp_barcode_users",
featureClass: "woomobile_ipp",
ttl: 300,
content: JustInTimeMessage.Content(
message: "In-person card payments",
description: "Sell anywhere, and take card payments using a mobile card reader."),
cta: JustInTimeMessage.CTA(
message: "Purchase Card Reader",
link: "https://woocommerce.com/products/hardware/US"),
assets: [:])
assets: [:],
template: "default")
assertEqual(expectedJustInTimeMessage, justInTimeMessage)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"primary": true,
"link": "https:\/\/woocommerce.com\/products\/hardware\/US"
},
"template": "default",
"template": "modal",
"ttl": 300,
"id": "woomobile_ipp_barcode_users",
"feature_class": "woomobile_ipp",
Expand Down
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

13.7
-----
- [*] JITMs: Added modal-style Just in Time Message support on the dashboard [https://github.com/woocommerce/woocommerce-ios/pull/9694]
- [*] Order Creation: Products can be searched by SKU when adding products to an order. [https://github.com/woocommerce/woocommerce-ios/pull/9711]
- [*] Orders: Fixes order details so separate order items are not combined just because they are the same product or variation. [https://github.com/woocommerce/woocommerce-ios/pull/9710]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class JustInTimeMessagesProvider {
self.analytics = analytics
}

func loadMessage(for screen: JustInTimeMessagesSourceScreen, siteID: Int64) async throws -> JustInTimeMessageAnnouncementCardViewModel? {
func loadMessage(for screen: JustInTimeMessagesSourceScreen, siteID: Int64) async throws -> JustInTimeMessageViewModel? {
guard let source = appScreenJitmSourceMapping[screen] else {
DDLogInfo("Could not load JITM for \(screen) because there is no mapping for the given screen")
return nil
Expand All @@ -39,7 +39,7 @@ final class JustInTimeMessagesProvider {
.JustInTimeMessage.fetchSuccess(source: source,
messageID: message.messageID,
count: Int64(messages.count)))
let viewModel = JustInTimeMessageAnnouncementCardViewModel(
let viewModel = JustInTimeMessageViewModel(
justInTimeMessage: message,
screenName: source,
siteID: siteID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import UIKit
import Yosemite
import Combine

final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewModelProtocol {
final class JustInTimeMessageViewModel {
private let siteID: Int64

private let analytics: Analytics
Expand All @@ -21,6 +21,8 @@ final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewMode
private let justInTimeMessage: JustInTimeMessage

// MARK: - Message properties
let template: JustInTimeMessageTemplate

let title: String

let message: String
Expand All @@ -31,6 +33,8 @@ final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewMode

let imageDarkUrl: URL?

let badgeType: BadgeView.BadgeType?

private let url: URL?

private let messageID: String
Expand Down Expand Up @@ -59,6 +63,7 @@ final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewMode
self.featureClass = justInTimeMessage.featureClass
self.justInTimeMessage = justInTimeMessage
self.screenName = screenName
self.template = justInTimeMessage.template
self.title = justInTimeMessage.title
self.message = justInTimeMessage.detail
self.buttonTitle = justInTimeMessage.buttonTitle
Expand All @@ -83,42 +88,18 @@ final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewMode
}.store(in: &cancellables)
}

// MARK: - default AnnouncementCardViewModelProtocol conformance
let showDividers: Bool = false

let badgeType: BadgeView.BadgeType?

let image: UIImage = .paymentsFeatureBannerImage

var showDismissConfirmation: Bool = false

let dismissAlertTitle: String = ""

let dismissAlertMessage: String = ""

// MARK: - AnnouncementCardViewModelProtocol methods
func onAppear() {
analytics.track(event: .JustInTimeMessage.messageDisplayed(source: screenName,
messageID: messageID,
featureClass: featureClass))
}

// MARK: - Actions
func ctaTapped() {
analytics.track(event: .JustInTimeMessage.callToActionTapped(source: screenName,
messageID: messageID,
featureClass: featureClass))

trackCtaTapped()
guard let url = url else {
return
}

urlRouter.handle(url: url)
}

func dontShowAgainTapped() {
analytics.track(event: .JustInTimeMessage.dismissTapped(source: screenName,
messageID: messageID,
featureClass: featureClass))
func dismissTapped() {
trackDismissTapped()
let action = JustInTimeMessageAction.dismissMessage(justInTimeMessage,
siteID: siteID,
completion: { result in
Expand All @@ -142,7 +123,65 @@ final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewMode
stores.dispatch(action)
}

// MARK: - Analytics
private func trackMessageDisplayed() {
analytics.track(event: .JustInTimeMessage.messageDisplayed(source: screenName,
messageID: messageID,
featureClass: featureClass))
}

private func trackCtaTapped() {
analytics.track(event: .JustInTimeMessage.callToActionTapped(source: screenName,
messageID: messageID,
featureClass: featureClass))
}

private func trackDismissTapped() {
analytics.track(event: .JustInTimeMessage.dismissTapped(source: screenName,
messageID: messageID,
featureClass: featureClass))
}
}


extension JustInTimeMessageViewModel: AnnouncementCardViewModelProtocol {
Copy link
Contributor

Choose a reason for hiding this comment

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

TIL that we cannot make this extension's access control private because is declaring protocol conformances, it makes sense, but I didn't realized until the compiler told me :D

// MARK: - default AnnouncementCardViewModelProtocol conformance
var showDividers: Bool { false }

var image: UIImage { .paymentsFeatureBannerImage }

var showDismissConfirmation: Bool { false }

var dismissAlertTitle: String {
switch template {
case .modal:
return Localization.maybeLaterButton
default:
return ""
}
}

var dismissAlertMessage: String { "" }

// MARK: - AnnouncementCardViewModelProtocol methods
func onAppear() {
trackMessageDisplayed()
}

func dontShowAgainTapped() {
dismissTapped()
}

func remindLaterTapped() {
// No-op
}
}

private extension JustInTimeMessageViewModel {
enum Localization {
static let maybeLaterButton = NSLocalizedString(
"Maybe Later",
comment: "Dismiss button title for modally presented Just in Time Messages"
)
}
}
Loading