1
1
import UIKit
2
2
import Gridicons
3
3
4
+ // This exists in addition to `SiteStatsInsightsDelegate` because `[StatSection]`
5
+ // can't be represented in an Obj-C protocol.
6
+ protocol StatsInsightsManagementDelegate : AnyObject {
7
+ func userUpdatedActiveInsights( _ insights: [ StatSection ] )
8
+ }
9
+
4
10
class AddInsightTableViewController : UITableViewController {
5
11
6
12
// MARK: - Properties
7
-
13
+ private weak var insightsManagementDelegate : StatsInsightsManagementDelegate ?
8
14
private weak var insightsDelegate : SiteStatsInsightsDelegate ?
9
- private var insightsShown = [ StatSection] ( )
15
+
16
+ /// Stored so that we can check if the user has made any changes.
17
+ private var originalInsightsShown = [ StatSection] ( )
18
+ private var insightsShown = [ StatSection] ( ) {
19
+ didSet {
20
+ updateSaveButton ( )
21
+ }
22
+ }
23
+
24
+ private var hasChanges : Bool {
25
+ return insightsShown != originalInsightsShown
26
+ }
27
+
10
28
private var selectedStat : StatSection ?
11
29
30
+ private lazy var saveButton = UIBarButtonItem ( barButtonSystemItem: . save, target: self , action: #selector( saveTapped) )
31
+
12
32
private lazy var tableHandler : ImmuTableViewHandler = {
13
33
return ImmuTableViewHandler ( takeOver: self )
14
34
} ( )
@@ -17,17 +37,20 @@ class AddInsightTableViewController: UITableViewController {
17
37
18
38
override init ( style: UITableView . Style ) {
19
39
super. init ( style: style)
20
- navigationItem. title = NSLocalizedString ( " Add New Stats Card " , comment: " Add New Stats Card view title " )
40
+
41
+ navigationItem. title = TextContent . title
21
42
}
22
43
23
44
required init ? ( coder aDecoder: NSCoder ) {
24
45
fatalError ( " init(coder:) has not been implemented " )
25
46
}
26
47
27
- convenience init ( insightsDelegate: SiteStatsInsightsDelegate , insightsShown: [ StatSection ] ) {
48
+ convenience init ( insightsDelegate: SiteStatsInsightsDelegate , insightsManagementDelegate : StatsInsightsManagementDelegate ? = nil , insightsShown: [ StatSection ] ) {
28
49
self . init ( style: . grouped)
29
50
self . insightsDelegate = insightsDelegate
51
+ self . insightsManagementDelegate = insightsManagementDelegate
30
52
self . insightsShown = insightsShown
53
+ self . originalInsightsShown = insightsShown
31
54
}
32
55
33
56
// MARK: - View
@@ -39,19 +62,26 @@ class AddInsightTableViewController: UITableViewController {
39
62
reloadViewModel ( )
40
63
WPStyleGuide . configureColors ( view: view, tableView: tableView)
41
64
WPStyleGuide . configureAutomaticHeightRows ( for: tableView)
42
- tableView. accessibilityIdentifier = " Add Insight Table "
65
+ tableView. accessibilityIdentifier = TextContent . title
66
+
67
+ if FeatureFlag . statsNewAppearance. enabled {
68
+ tableView. isEditing = true
69
+ tableView. allowsSelectionDuringEditing = true
70
+ }
43
71
44
72
navigationItem. leftBarButtonItem = UIBarButtonItem ( image: . gridicon( . cross) , style: . plain, target: self , action: #selector( doneTapped) )
45
73
}
46
74
47
- override func viewWillDisappear ( _ animated : Bool ) {
48
- super . viewWillDisappear ( animated )
49
-
50
- if selectedStat == nil {
51
- insightsDelegate ? . addInsightDismissed ? ( )
75
+ func handleDismissViaGesture ( from presenter : UIViewController ) {
76
+ if FeatureFlag . statsNewAppearance . enabled && hasChanges {
77
+ promptToSave ( from : presenter )
78
+ } else {
79
+ trackDismiss ( )
52
80
}
53
81
}
54
82
83
+ // MARK: TableView Data Source / Delegate Overrides
84
+
55
85
override func tableView( _ tableView: UITableView , heightForHeaderInSection section: Int ) -> CGFloat {
56
86
return 38
57
87
}
@@ -60,10 +90,120 @@ class AddInsightTableViewController: UITableViewController {
60
90
return 0
61
91
}
62
92
93
+ override func tableView( _ tableView: UITableView , canMoveRowAt indexPath: IndexPath ) -> Bool {
94
+ guard FeatureFlag . statsNewAppearance. enabled else {
95
+ return false
96
+ }
97
+
98
+ return isActiveCardsSection ( indexPath. section)
99
+ }
100
+
101
+ override func tableView( _ tableView: UITableView , moveRowAt sourceIndexPath: IndexPath , to destinationIndexPath: IndexPath ) {
102
+ guard FeatureFlag . statsNewAppearance. enabled else {
103
+ return
104
+ }
105
+
106
+ if isActiveCardsSection ( sourceIndexPath. section) && isActiveCardsSection ( destinationIndexPath. section) {
107
+ let item = insightsShown. remove ( at: sourceIndexPath. row)
108
+ insightsShown. insert ( item, at: destinationIndexPath. row)
109
+ reloadViewModel ( )
110
+ }
111
+ }
112
+
113
+ override func tableView( _ tableView: UITableView , canEditRowAt indexPath: IndexPath ) -> Bool {
114
+ guard FeatureFlag . statsNewAppearance. enabled else {
115
+ return false
116
+ }
117
+
118
+ return insightsShown. count > 0 && isActiveCardsSection ( indexPath. section)
119
+ }
120
+
121
+ override func tableView( _ tableView: UITableView , targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath , toProposedIndexPath proposedDestinationIndexPath: IndexPath ) -> IndexPath {
122
+ if isActiveCardsSection ( proposedDestinationIndexPath. section) {
123
+ return proposedDestinationIndexPath
124
+ }
125
+
126
+ return IndexPath ( row: insightsShown. count - 1 , section: 0 )
127
+ }
128
+
129
+ override func tableView( _ tableView: UITableView , shouldIndentWhileEditingRowAt indexPath: IndexPath ) -> Bool {
130
+ return false
131
+ }
132
+
133
+ private func isActiveCardsSection( _ sectionIndex: Int ) -> Bool {
134
+ return sectionIndex == 0
135
+ }
136
+
137
+ // MARK: - Actions
138
+
139
+ private func updateSaveButton( ) {
140
+ guard FeatureFlag . statsNewAppearance. enabled else {
141
+ return
142
+ }
143
+
144
+ if hasChanges {
145
+ navigationItem. rightBarButtonItem = saveButton
146
+ } else {
147
+ navigationItem. rightBarButtonItem = nil
148
+ }
149
+ }
150
+
63
151
@objc private func doneTapped( ) {
64
- WPAnalytics . trackEvent ( . statsInsightsManagementDismissed)
152
+ if FeatureFlag . statsNewAppearance. enabled && hasChanges {
153
+ promptToSave ( from: self )
154
+ } else {
155
+ dismiss ( )
156
+ }
157
+ }
158
+
159
+ @objc func saveTapped( ) {
160
+ saveChanges ( )
161
+
65
162
dismiss ( animated: true , completion: nil )
66
163
}
164
+
165
+ private func dismiss( ) {
166
+ trackDismiss ( )
167
+
168
+ dismiss ( animated: true , completion: nil )
169
+ }
170
+
171
+ private func trackDismiss( ) {
172
+ WPAnalytics . trackEvent ( . statsInsightsManagementDismissed)
173
+ insightsDelegate? . addInsightDismissed ? ( )
174
+ }
175
+
176
+ private func saveChanges( ) {
177
+ insightsManagementDelegate? . userUpdatedActiveInsights ( insightsShown)
178
+
179
+ WPAnalytics . track ( . statsInsightsManagementSaved, properties: [ " types " : [ insightsShown. map ( { $0. title} ) ] ] )
180
+
181
+ // Update original to stop us detecting changes on dismiss
182
+ originalInsightsShown = insightsShown
183
+ }
184
+
185
+ private func promptToSave( from viewController: UIViewController ? ) {
186
+ let alert = UIAlertController ( title: nil , message: TextContent . savePromptMessage, preferredStyle: . actionSheet)
187
+ alert. addAction ( UIAlertAction ( title: TextContent . savePromptSaveButton, style: . default, handler: { _ in
188
+ self . saveTapped ( )
189
+ } ) )
190
+ alert. addAction ( UIAlertAction ( title: TextContent . savePromptDiscardButton, style: . destructive, handler: { _ in
191
+ self . dismiss ( )
192
+ } ) )
193
+ alert. addCancelActionWithTitle ( TextContent . savePromptCancelButton, handler: nil )
194
+ viewController? . present ( alert, animated: true , completion: nil )
195
+ }
196
+
197
+ fileprivate enum TextContent {
198
+ static let title = NSLocalizedString ( " stats.insights.management.title " , value: " Manage Stats Cards " , comment: " Title of the Stats Insights Management screen " )
199
+ static let activeCardsHeader = NSLocalizedString ( " stats.insights.management.activeCards " , value: " Active Cards " , comment: " Header title indicating which Stats Insights cards the user currently has set to active. " )
200
+ static let placeholderRowTitle = NSLocalizedString ( " stats.insights.management.selectCardsPrompt " , value: " Select cards from the list below " , comment: " Prompt displayed on the Stats Insights management screen telling the user to tap a row to add it to their list of active cards. " )
201
+
202
+ static let savePromptMessage = NSLocalizedString ( " stats.insights.management.savePrompt.message " , value: " You've made changes to your active Insights cards. " , comment: " Title of alert in Stats Insights management, prompting the user to save changes to their list of active Stats cards. " )
203
+ static let savePromptSaveButton = NSLocalizedString ( " stats.insights.management.savePrompt.saveButton " , value: " Save Changes " , comment: " Title of button in Stats Insights management, prompting the user to save changes to their list of active Stats cards. " )
204
+ static let savePromptDiscardButton = NSLocalizedString ( " stats.insights.management.savePrompt.discardButton " , value: " Discard Changes " , comment: " Title of button in Stats Insights management, prompting the user to discard changes to their list of active Stats cards. " )
205
+ static let savePromptCancelButton = NSLocalizedString ( " stats.insights.management.savePrompt.cancelButton " , value: " Cancel " , comment: " Title of button to cancel an alert and take no action. " )
206
+ }
67
207
}
68
208
69
209
private extension AddInsightTableViewController {
@@ -75,33 +215,84 @@ private extension AddInsightTableViewController {
75
215
}
76
216
77
217
func tableViewModel( ) -> ImmuTable {
78
- return ImmuTable ( sections: [ sectionForCategory ( . general) ,
218
+ return ImmuTable ( sections: [ selectedStatsSection ( ) ,
219
+ sectionForCategory ( . general) ,
79
220
sectionForCategory ( . postsAndPages) ,
80
- sectionForCategory ( . activity) ]
221
+ sectionForCategory ( . activity) ] . compactMap ( { $0 } )
81
222
)
82
223
}
83
224
84
225
// MARK: - Table Sections
85
226
86
- func sectionForCategory( _ category: InsightsCategories ) -> ImmuTableSection {
87
- return ImmuTableSection ( headerText: category. title,
88
- rows: category. insights. map {
89
- let enabled = !insightsShown. contains ( $0)
227
+ func selectedStatsSection( ) -> ImmuTableSection ? {
228
+ guard FeatureFlag . statsNewAppearance. enabled else {
229
+ return nil
230
+ }
231
+
232
+ guard insightsShown. count > 0 else {
233
+ return ImmuTableSection ( headerText: TextContent . activeCardsHeader, rows: [ placeholderRow] )
234
+ }
235
+
236
+ return ImmuTableSection ( headerText: TextContent . activeCardsHeader,
237
+ rows: insightsShown. map {
238
+ return AddInsightStatRow ( title: $0. insightManagementTitle,
239
+ enabled: true ,
240
+ action: rowActionFor ( $0) ) }
241
+ )
242
+ }
243
+
244
+ func sectionForCategory( _ category: InsightsCategories ) -> ImmuTableSection ? {
245
+ guard FeatureFlag . statsNewAppearance. enabled else {
246
+ return ImmuTableSection ( headerText: category. title,
247
+ rows: category. insights. map {
248
+ let enabled = !insightsShown. contains ( $0)
249
+ return AddInsightStatRow ( title: $0. insightManagementTitle,
250
+ enabled: enabled,
251
+ action: enabled ? rowActionFor ( $0) : nil ) }
252
+ )
253
+ }
90
254
255
+ let rows = category. insights. filter ( { !self . insightsShown. contains ( $0) } )
256
+ guard rows. count > 0 else {
257
+ return nil
258
+ }
259
+
260
+ return ImmuTableSection ( headerText: category. title,
261
+ rows: rows. map {
91
262
return AddInsightStatRow ( title: $0. insightManagementTitle,
92
- enabled: enabled ,
93
- action: enabled ? rowActionFor ( $0) : nil ) }
263
+ enabled: false ,
264
+ action: rowActionFor ( $0) ) }
94
265
)
95
266
}
96
267
97
268
func rowActionFor( _ statSection: StatSection ) -> ImmuTableAction {
98
269
return { [ unowned self] row in
99
- self . selectedStat = statSection
100
- self . insightsDelegate? . addInsightSelected ? ( statSection)
270
+ if FeatureFlag . statsNewAppearance. enabled {
271
+ toggleRow ( for: statSection)
272
+ } else {
273
+ self . selectedStat = statSection
274
+ self . insightsDelegate? . addInsightSelected ? ( statSection)
275
+
276
+ WPAnalytics . track ( . statsInsightsManagementSaved, properties: [ " types " : [ statSection. title] ] )
277
+ self . dismiss ( animated: true , completion: nil )
278
+ }
279
+ }
280
+ }
101
281
102
- WPAnalytics . track ( . statsInsightsManagementSaved, properties: [ " types " : [ statSection. title] ] )
103
- self . dismiss ( animated: true , completion: nil )
282
+ func toggleRow( for statSection: StatSection ) {
283
+ if let index = insightsShown. firstIndex ( of: statSection) {
284
+ insightsShown. remove ( at: index)
285
+ } else {
286
+ insightsShown. append ( statSection)
104
287
}
288
+
289
+ reloadViewModel ( )
290
+ }
291
+
292
+ var placeholderRow : ImmuTableRow {
293
+ return AddInsightStatRow ( title: TextContent . placeholderRowTitle,
294
+ enabled: false ,
295
+ action: nil )
105
296
}
106
297
107
298
// MARK: - Insights Categories
0 commit comments