-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathDashboardViewModel.swift
292 lines (238 loc) · 10.7 KB
/
DashboardViewModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import KsApi
import Prelude
import ReactiveSwift
import ReactiveExtensions
import Result
public enum DrawerState {
case open
case closed
public var toggled: DrawerState {
return self == .open ? .closed : .open
}
}
public struct DashboardTitleViewData {
public let drawerState: DrawerState
public let isArrowHidden: Bool
public let currentProjectIndex: Int
}
public struct ProjectsDrawerData {
public let project: Project
public let indexNum: Int
public let isChecked: Bool
}
public protocol DashboardViewModelInputs {
/// Call to switch display to another project from the drawer.
func `switch`(toProject param: Param)
/// Call when the projects drawer has animated out.
func dashboardProjectsDrawerDidAnimateOut()
/// Call when the project context cell is tapped.
func projectContextCellTapped()
/// Call when to show or hide the projects drawer.
func showHideProjectsDrawer()
/// Call when the view will appear.
func viewWillAppear(animated: Bool)
}
public protocol DashboardViewModelOutputs {
/// Emits when should animate out projects drawer.
var animateOutProjectsDrawer: Signal<(), NoError> { get }
/// Emits when should dismiss projects drawer.
var dismissProjectsDrawer: Signal<(), NoError> { get }
/// Emits when to focus the screen reader on the titleView.
var focusScreenReaderOnTitleView: Signal<(), NoError> { get }
/// Emits the funding stats and project to be displayed in the funding cell.
var fundingData: Signal<(funding: [ProjectStatsEnvelope.FundingDateStats],
project: Project), NoError> { get }
/// Emits when to go to the project page.
var goToProject: Signal<(Project, RefTag), NoError> { get }
/// Emits when should present projects drawer with data to populate it.
var presentProjectsDrawer: Signal<[ProjectsDrawerData], NoError> { get }
/// Emits the currently selected project to display in the context and action cells.
var project: Signal<Project, NoError> { get }
/// Emits the cumulative, project, and referreral distribution data to display in the referrers cell.
var referrerData: Signal<(cumulative: ProjectStatsEnvelope.CumulativeStats, project: Project,
stats: [ProjectStatsEnvelope.ReferrerStats]), NoError> { get }
/// Emits the project, reward stats, and cumulative pledges to display in the rewards cell.
var rewardData: Signal<(stats: [ProjectStatsEnvelope.RewardStats], project: Project), NoError> { get }
/// Emits the video stats to display in the video cell.
var videoStats: Signal<ProjectStatsEnvelope.VideoStats, NoError> { get }
/// Emits data for the title view.
var updateTitleViewData: Signal<DashboardTitleViewData, NoError> { get }
}
public protocol DashboardViewModelType {
var inputs: DashboardViewModelInputs { get }
var outputs: DashboardViewModelOutputs { get }
}
public final class DashboardViewModel: DashboardViewModelInputs, DashboardViewModelOutputs,
DashboardViewModelType {
public init() {
let projects = self.viewWillAppearAnimatedProperty.signal.filter(isFalse).ignoreValues()
.switchMap {
AppEnvironment.current.apiService.fetchProjects(member: true)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.demoteErrors()
.map { $0.projects }
}
let projectsAndSelected = projects
.switchMap { [switchToProject = self.switchToProjectProperty.producer] projects in
switchToProject
.map { param in
find(projectForParam: param, in: projects) ?? projects.first
}
.skipNil()
.map { (projects, $0) }
}
self.project = projectsAndSelected.map(second)
let selectedProjectAndStatsEvent = self.project
.switchMap { project in
AppEnvironment.current.apiService.fetchProjectStats(projectId: project.id)
.ksr_delay(AppEnvironment.current.apiDelayInterval, on: AppEnvironment.current.scheduler)
.map { (project, $0) }
.materialize()
}
let selectedProjectAndStats = selectedProjectAndStatsEvent.values()
self.fundingData = selectedProjectAndStats
.map { project, stats in
(funding: stats.fundingDistribution, project: project)
}
self.referrerData = selectedProjectAndStats
.map { project, stats in
(cumulative: stats.cumulativeStats, project: project, stats: stats.referralDistribution)
}
self.videoStats = selectedProjectAndStats.map { _, stats in stats.videoStats }.skipNil()
self.rewardData = selectedProjectAndStats
.map { project, stats in
(stats: stats.rewardDistribution, project: project)
}
let drawerStateProjectsAndSelectedProject = Signal.merge(
projectsAndSelected.map { ($0, $1, false) },
projectsAndSelected.takeWhen(self.showHideProjectsDrawerProperty.signal).map { ($0, $1, true) }
)
.scan(nil) { (data, projectsProjectToggle) -> (DrawerState, [Project], Project)? in
let (projects, project, toggle) = projectsProjectToggle
return (
toggle ? (data?.0.toggled ?? DrawerState.closed) : DrawerState.closed,
projects,
project
)
}
.skipNil()
self.updateTitleViewData = drawerStateProjectsAndSelectedProject
.map { drawerState, projects, selectedProject in
DashboardTitleViewData(
drawerState: drawerState,
isArrowHidden: projects.count <= 1,
currentProjectIndex: projects.index(of: selectedProject) ?? 0
)
}
let updateDrawerStateToOpen = self.updateTitleViewData
.map { $0.drawerState == .open }
.skip(first: 1)
self.presentProjectsDrawer = drawerStateProjectsAndSelectedProject
.filter { drawerState, _, _ in drawerState == .open }
.map { _, projects, selectedProject in
projects.map { project in
ProjectsDrawerData(
project: project,
indexNum: projects.index(of: project) ?? 0,
isChecked: project == selectedProject
)
}
}
self.animateOutProjectsDrawer = updateDrawerStateToOpen
.filter(isFalse)
.ignoreValues()
self.dismissProjectsDrawer = self.projectsDrawerDidAnimateOutProperty.signal
self.goToProject = self.project
.takeWhen(self.projectContextCellTappedProperty.signal)
.map { ($0, RefTag.dashboard) }
self.focusScreenReaderOnTitleView = self.viewWillAppearAnimatedProperty.signal.ignoreValues()
let projectForTrackingViews = Signal.merge(
projects.map { $0.first }.skipNil().take(first: 1),
self.project
.takeWhen(self.viewWillAppearAnimatedProperty.signal.filter(isFalse))
)
projectForTrackingViews
.observeValues { AppEnvironment.current.koala.trackDashboardView(project: $0) }
self.project
.takeWhen(self.presentProjectsDrawer)
.observeValues { AppEnvironment.current.koala.trackDashboardShowProjectSwitcher(onProject: $0) }
let drawerHasClosedAndShouldTrack = Signal.merge(
self.showHideProjectsDrawerProperty.signal.map { (drawerState: true, shouldTrack: true) },
self.switchToProjectProperty.signal.map { _ in (drawerState: true, shouldTrack: false) }
)
.scan(nil) { (data, toggledStateAndShouldTrack) -> (DrawerState, Bool)? in
let (drawerState, shouldTrack) = toggledStateAndShouldTrack
return drawerState
? ((data?.0.toggled ?? DrawerState.closed), shouldTrack)
: (DrawerState.closed, shouldTrack)
}
.skipNil()
.filter { drawerState, _ in drawerState == .open }
.map { _, shouldTrack in shouldTrack }
self.project
.takePairWhen(drawerHasClosedAndShouldTrack)
.filter { _, shouldTrack in shouldTrack }
.observeValues { project, _ in
AppEnvironment.current.koala.trackDashboardClosedProjectSwitcher(onProject: project)
}
projects
.takePairWhen(self.switchToProjectProperty.signal)
.map { projects, param in find(projectForParam: param, in: projects) }
.skipNil()
.observeValues { AppEnvironment.current.koala.trackDashboardSwitchProject($0) }
}
// swiftlint:enable function_body_length
fileprivate let showHideProjectsDrawerProperty = MutableProperty()
public func showHideProjectsDrawer() {
self.showHideProjectsDrawerProperty.value = ()
}
fileprivate let projectContextCellTappedProperty = MutableProperty()
public func projectContextCellTapped() {
self.projectContextCellTappedProperty.value = ()
}
fileprivate let switchToProjectProperty = MutableProperty<Param?>(nil)
public func `switch`(toProject param: Param) {
self.switchToProjectProperty.value = param
}
fileprivate let projectsDrawerDidAnimateOutProperty = MutableProperty()
public func dashboardProjectsDrawerDidAnimateOut() {
self.projectsDrawerDidAnimateOutProperty.value = ()
}
fileprivate let viewWillAppearAnimatedProperty = MutableProperty(false)
public func viewWillAppear(animated: Bool) {
self.viewWillAppearAnimatedProperty.value = animated
}
public let animateOutProjectsDrawer: Signal<(), NoError>
public let dismissProjectsDrawer: Signal<(), NoError>
public let focusScreenReaderOnTitleView: Signal<(), NoError>
public let fundingData: Signal<(funding: [ProjectStatsEnvelope.FundingDateStats],
project: Project), NoError>
public let goToProject: Signal<(Project, RefTag), NoError>
public let project: Signal<Project, NoError>
public let presentProjectsDrawer: Signal<[ProjectsDrawerData], NoError>
public let referrerData: Signal<(cumulative: ProjectStatsEnvelope.CumulativeStats, project: Project,
stats: [ProjectStatsEnvelope.ReferrerStats]), NoError>
public let rewardData: Signal<(stats: [ProjectStatsEnvelope.RewardStats], project: Project), NoError>
public let videoStats: Signal<ProjectStatsEnvelope.VideoStats, NoError>
public let updateTitleViewData: Signal<DashboardTitleViewData, NoError>
public var inputs: DashboardViewModelInputs { return self }
public var outputs: DashboardViewModelOutputs { return self }
}
extension ProjectsDrawerData: Equatable {}
public func == (lhs: ProjectsDrawerData, rhs: ProjectsDrawerData) -> Bool {
return lhs.project.id == rhs.project.id
}
extension DashboardTitleViewData: Equatable {}
public func == (lhs: DashboardTitleViewData, rhs: DashboardTitleViewData) -> Bool {
return lhs.drawerState == rhs.drawerState &&
lhs.currentProjectIndex == rhs.currentProjectIndex &&
lhs.isArrowHidden == rhs.isArrowHidden
}
private func find(projectForParam param: Param?, in projects: [Project]) -> Project? {
guard let param = param else { return nil }
return projects.filter { project in
if case .id(project.id) = param { return true }
if case .slug(project.slug) = param { return true }
return false
}.first
}