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

[MBL-703] Prelaunch Page Watch Count Update #1813

Merged
merged 3 commits into from
Apr 17, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Kickstarter-iOS/SharedViews/PledgeCTAContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ final class PledgeCTAContainerView: UIView {

self.viewModel.outputs.prelaunchCTASaved
.observeForUI()
.observeValues { [weak self] isPrelaunch, saved in
.observeValues { [weak self] isPrelaunch, saved, _ in
guard isPrelaunch else { return }

_ = self?.pledgeCTAButton
Expand Down
32 changes: 28 additions & 4 deletions KsApi/GraphAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4440,6 +4440,7 @@ public enum GraphAPI {
__typename
id
isWatched
watchesCount
}
}
}
Expand Down Expand Up @@ -4542,6 +4543,7 @@ public enum GraphAPI {
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("id", type: .nonNull(.scalar(GraphQLID.self))),
GraphQLField("isWatched", type: .nonNull(.scalar(Bool.self))),
GraphQLField("watchesCount", type: .scalar(Int.self)),
]
}

Expand All @@ -4551,8 +4553,8 @@ public enum GraphAPI {
self.resultMap = unsafeResultMap
}

public init(id: GraphQLID, isWatched: Bool) {
self.init(unsafeResultMap: ["__typename": "Project", "id": id, "isWatched": isWatched])
public init(id: GraphQLID, isWatched: Bool, watchesCount: Int? = nil) {
self.init(unsafeResultMap: ["__typename": "Project", "id": id, "isWatched": isWatched, "watchesCount": watchesCount])
}

public var __typename: String {
Expand Down Expand Up @@ -4582,6 +4584,16 @@ public enum GraphAPI {
resultMap.updateValue(newValue, forKey: "isWatched")
}
}

/// Number of watchers a project has.
public var watchesCount: Int? {
get {
return resultMap["watchesCount"] as? Int
}
set {
resultMap.updateValue(newValue, forKey: "watchesCount")
}
}
}
}
}
Expand Down Expand Up @@ -5039,6 +5051,7 @@ public enum GraphAPI {
__typename
id
isWatched
watchesCount
}
}
}
Expand Down Expand Up @@ -5141,6 +5154,7 @@ public enum GraphAPI {
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("id", type: .nonNull(.scalar(GraphQLID.self))),
GraphQLField("isWatched", type: .nonNull(.scalar(Bool.self))),
GraphQLField("watchesCount", type: .scalar(Int.self)),
]
}

Expand All @@ -5150,8 +5164,8 @@ public enum GraphAPI {
self.resultMap = unsafeResultMap
}

public init(id: GraphQLID, isWatched: Bool) {
self.init(unsafeResultMap: ["__typename": "Project", "id": id, "isWatched": isWatched])
public init(id: GraphQLID, isWatched: Bool, watchesCount: Int? = nil) {
self.init(unsafeResultMap: ["__typename": "Project", "id": id, "isWatched": isWatched, "watchesCount": watchesCount])
}

public var __typename: String {
Expand Down Expand Up @@ -5181,6 +5195,16 @@ public enum GraphAPI {
resultMap.updateValue(newValue, forKey: "isWatched")
}
}

/// Number of watchers a project has.
public var watchesCount: Int? {
get {
return resultMap["watchesCount"] as? Int
}
set {
resultMap.updateValue(newValue, forKey: "watchesCount")
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions KsApi/models/graphql/WatchProjectResponseEnvelope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public struct WatchProjectResponseEnvelope: Decodable {
public struct Project: Decodable {
public var id: String
public var isWatched: Bool
public var watchesCount: Int
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ extension WatchProjectResponseEnvelope {
guard let projectFromData = data.watchProject?.project else {
return nil
}

let project = WatchProject.Project(
id: projectFromData.id,
isWatched: projectFromData.isWatched
isWatched: projectFromData.isWatched,
watchesCount: projectFromData.watchesCount ?? 0
)

return WatchProjectResponseEnvelope(watchProject: WatchProject(project: project))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ final class WatchProjectResponseEnvelope_UnwatchProjectMutationTests: XCTestCase

XCTAssertEqual(envelope?.watchProject.project.id, "id")
XCTAssertEqual(envelope?.watchProject.project.isWatched, false)
XCTAssertEqual(envelope?.watchProject.project.watchesCount, 100)
}

func test_envelopeFrom_ReturnsNil() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ extension WatchProjectResponseEnvelope {
}
let project = WatchProject.Project(
id: projectFromData.id,
isWatched: projectFromData.isWatched
isWatched: projectFromData.isWatched,
watchesCount: projectFromData.watchesCount ?? 0
)

return WatchProjectResponseEnvelope(watchProject: WatchProject(project: project))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ final class WatchProjectResponseEnvelope_WatchProjectMutationTests: XCTestCase {

XCTAssertEqual(envelope?.watchProject.project.id, "id")
XCTAssertEqual(envelope?.watchProject.project.isWatched, true)
XCTAssertEqual(envelope?.watchProject.project.watchesCount, 100)
}

func test_envelopeFrom_ReturnsNil() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import Foundation
extension WatchProjectResponseEnvelope {
internal static let watchTemplate = WatchProjectResponseEnvelope(
watchProject: .init(
project: .init(id: "UHJvamVjdC0xMzEzNzE3MDgy", isWatched: true)
project: .init(id: "UHJvamVjdC0xMzEzNzE3MDgy", isWatched: true, watchesCount: 10)
)
)

internal static let unwatchTemplate = WatchProjectResponseEnvelope(
watchProject: .init(
project: .init(id: "UHJvamVjdC0xMzEzNzE3MDgy", isWatched: true)
project: .init(id: "UHJvamVjdC0xMzEzNzE3MDgy", isWatched: true, watchesCount: 9)
)
)
}
1 change: 1 addition & 0 deletions KsApi/mutations/UnwatchProject.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mutation unwatchProject($input: UnwatchProjectInput!) {
project {
id
isWatched
watchesCount
}
}
}
1 change: 1 addition & 0 deletions KsApi/mutations/WatchProject.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mutation watchProject($input: WatchProjectInput!) {
project {
id
isWatched
watchesCount
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public enum WatchProjectResponseMutationTemplate {
"clientMutationId": nil,
"project": [
"id": "id",
"isWatched": watched
"isWatched": watched,
"watchesCount": 100
]
]
]
Expand All @@ -47,7 +48,8 @@ public enum WatchProjectResponseMutationTemplate {
"clientMutationId": nil,
"project": [
"id": "id",
"isWatched": watched
"isWatched": watched,
"watchesCount": 100
]
]
]
Expand Down
6 changes: 3 additions & 3 deletions Library/PledgeStateCTAType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public enum PledgeStateCTAType: Equatable {
case viewBacking
case viewRewards
case viewYourRewards
case prelaunch(saved: Bool)
case prelaunch(saved: Bool, watchCount: Int)

public var buttonTitle: String {
switch self {
Expand All @@ -23,7 +23,7 @@ public enum PledgeStateCTAType: Equatable {
return Strings.View_rewards()
case .viewYourRewards:
return Strings.View_your_rewards()
case let .prelaunch(saved):
case let .prelaunch(saved, _):
return saved ? Strings.Saved() : Strings.Notify_me_on_launch()
}
}
Expand All @@ -38,7 +38,7 @@ public enum PledgeStateCTAType: Equatable {
return .blue
case .viewBacking, .viewRewards, .viewYourRewards:
return .black
case let .prelaunch(saved):
case let .prelaunch(saved, _):
return saved ? .none : .black
}
}
Expand Down
22 changes: 16 additions & 6 deletions Library/ViewModels/PledgeCTAContainerViewViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public enum PledgeCTAContainerViewContext {

public typealias PledgeCTAPrelaunchState = (
prelaunch: Bool,
saved: Bool
saved: Bool,
watchesCount: Int
)

public typealias PledgeCTAContainerViewData = (
Expand Down Expand Up @@ -110,13 +111,14 @@ public final class PledgeCTAContainerViewViewModel: PledgeCTAContainerViewViewMo

self.prelaunchState <~ self.pledgeState.signal.skipNil().map { state -> PledgeCTAPrelaunchState in
switch state {
case let .prelaunch(saved):
case let .prelaunch(saved, watchCount):
return PledgeCTAPrelaunchState(
prelaunch: true,
saved: saved
saved: saved,
watchesCount: watchCount
)
default:
return PledgeCTAPrelaunchState(prelaunch: false, saved: false)
return PledgeCTAPrelaunchState(prelaunch: false, saved: false, watchesCount: 0)
}
}

Expand All @@ -125,13 +127,21 @@ public final class PledgeCTAContainerViewViewModel: PledgeCTAContainerViewViewMo
self.watchesLabelIsHidden = self.prelaunchState.signal.skipNil()
.map { !$0.prelaunch }

let updatedWatchCountProject = project
.takePairWhen(self.prelaunchState.signal.skipNil())
.map { project, prelaunchStateValue -> Project in
let updatedProjectWithWatchesCount = project |> \.watchesCount .~ prelaunchStateValue.watchesCount

return updatedProjectWithWatchesCount
}

self.buttonStyleType = self.pledgeState.signal.skipNil().map { $0.buttonStyle }
self.buttonTitleText = self.pledgeState.signal.skipNil().map { $0.buttonTitle }
let stackViewAndSpacerAreHidden = self.pledgeState.signal.skipNil().map { $0.stackViewAndSpacerAreHidden }
self.spacerIsHidden = stackViewAndSpacerAreHidden
self.stackViewIsHidden = stackViewAndSpacerAreHidden
self.titleText = self.pledgeState.signal.skipNil().map { $0.titleLabel }.skipNil()
self.watchesCountText = project
self.watchesCountText = Signal.merge(project, updatedWatchCountProject)
.map { project in
let watchesCountText = project.watchesCount ?? 0

Expand Down Expand Up @@ -200,7 +210,7 @@ private func pledgeCTA(project: Project, backing: Backing?) -> PledgeStateCTATyp
guard project.displayPrelaunch != .some(true) else {
let projectIsSaved = project.personalization.isStarred ?? false

return .prelaunch(saved: projectIsSaved)
return .prelaunch(saved: projectIsSaved, watchCount: project.watchesCount ?? 0)
}

guard let projectBacking = backing, project.personalization.isBacking == .some(true) else {
Expand Down
14 changes: 7 additions & 7 deletions Library/ViewModels/PledgeCTAContainerViewViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,17 +332,17 @@ internal final class PledgeCTAContainerViewViewModelTests: TestCase {
func testPledgeCTA_PrelaunchGoesFromUnsavedToSavedViaNotification_Success() {
let unsavedProject = Project.template
|> \.displayPrelaunch .~ true
|> \.watchesCount .~ 102
|> \.watchesCount .~ 99
|> \.personalization.isStarred .~ false

let prelaunchCTAUnsaved = PledgeCTAPrelaunchState(prelaunch: true, saved: false)
let prelaunchCTASaved = PledgeCTAPrelaunchState(prelaunch: true, saved: true)
let prelaunchCTAUnsaved = PledgeCTAPrelaunchState(prelaunch: true, saved: false, watchesCount: 99)
let prelaunchCTASaved = PledgeCTAPrelaunchState(prelaunch: true, saved: true, watchesCount: 100)

self.vm.inputs.configureWith(value: (.left((unsavedProject, nil)), false, .projectPamphlet))

self.buttonStyleType.assertValues([.black])
self.buttonTitleText.assertValues(["Notify me on launch"])
self.watchesCountText.assertValues(["102 followers"])
self.watchesCountText.assertValues(["99 followers"])
self.watchesLabelHidden.assertValues([false])
XCTAssertEqual(self.prelaunchCTASaved.values.count, 1)
XCTAssertEqual(self.prelaunchCTASaved.values.first!.prelaunch, prelaunchCTAUnsaved.prelaunch)
Expand All @@ -351,7 +351,7 @@ internal final class PledgeCTAContainerViewViewModelTests: TestCase {

let savedProject = Project.template
|> \.displayPrelaunch .~ true
|> \.watchesCount .~ 102
|> \.watchesCount .~ 100
|> \.personalization.isStarred .~ true

self.vm.inputs.savedProjectFromNotification(project: savedProject)
Expand All @@ -360,7 +360,7 @@ internal final class PledgeCTAContainerViewViewModelTests: TestCase {

self.buttonStyleType.assertValues([.black, .none])
self.buttonTitleText.assertValues(["Notify me on launch", "Saved"])
self.watchesCountText.assertValues(["102 followers"])
self.watchesCountText.assertValues(["99 followers", "100 followers"])
self.watchesLabelHidden.assertValues([false, false])
XCTAssertEqual(self.prelaunchCTASaved.values.count, 2)
XCTAssertEqual(self.prelaunchCTASaved.values.last!.prelaunch, prelaunchCTASaved.prelaunch)
Expand All @@ -383,6 +383,6 @@ internal final class PledgeCTAContainerViewViewModelTests: TestCase {
self.scheduler.advance(by: .seconds(1))

XCTAssertEqual(self.notifyDelegateCTATapped.values.count, 1)
XCTAssertEqual(self.notifyDelegateCTATapped.values.last!, .prelaunch(saved: true))
XCTAssertEqual(self.notifyDelegateCTATapped.values.last!, .prelaunch(saved: true, watchCount: 102))
}
}
4 changes: 2 additions & 2 deletions Library/ViewModels/ProjectPageViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ public final class ProjectPageViewModel: ProjectPageViewModelType, ProjectPageVi
self.updateWatchProjectWithPrelaunchProjectState = shouldUpdateWatchProjectOnPrelaunch
.map { pledgeCTAType -> PledgeCTAPrelaunchState? in
switch pledgeCTAType {
case let .prelaunch(saved):
return PledgeCTAPrelaunchState(prelaunch: true, saved: saved)
case let .prelaunch(saved, watchesCount):
return PledgeCTAPrelaunchState(prelaunch: true, saved: saved, watchesCount: watchesCount)
default:
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion Library/ViewModels/ProjectPageViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -811,11 +811,12 @@ final class ProjectPageViewModelTests: TestCase {

self.updateWatchProjectWithPrelaunchProjectState.assertDidNotEmitValue()

self.vm.inputs.pledgeCTAButtonTapped(with: .prelaunch(saved: true))
self.vm.inputs.pledgeCTAButtonTapped(with: .prelaunch(saved: true, watchCount: 10))

XCTAssertEqual(self.updateWatchProjectWithPrelaunchProjectState.values.count, 1)
XCTAssertEqual(self.updateWatchProjectWithPrelaunchProjectState.values.last!.prelaunch, true)
XCTAssertEqual(self.updateWatchProjectWithPrelaunchProjectState.values.last!.saved, true)
XCTAssertEqual(self.updateWatchProjectWithPrelaunchProjectState.values.last!.watchesCount, 10)
}
}

Expand Down
13 changes: 12 additions & 1 deletion Library/ViewModels/WatchProjectViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,18 @@ public final class WatchProjectViewModel: WatchProjectViewModelType,
.switchMap { project, shouldWatch in
watchProjectProducer(with: project, shouldWatch: shouldWatch)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.map { _ in (project, project.personalization.isStarred ?? false, success: true) }
.map { watchProjectEnvelope in
let updatedWatchCount = watchProjectEnvelope.watchProject.project.watchesCount

let updatedProjectWithWatchCount = project
|> \.watchesCount .~ updatedWatchCount

return (
updatedProjectWithWatchCount,
updatedProjectWithWatchCount.personalization.isStarred ?? false,
success: true
)
}
.flatMapError { _ in .init(value: (project, !shouldWatch, success: false)) }
.take(until: saveButtonTapped.ignoreValues())
}
Expand Down