Skip to content

Commit 3051894

Browse files
committed
Stats Insights management: Allow reordering and adding / removing multiple rows at once
1 parent c9ce689 commit 3051894

File tree

3 files changed

+203
-22
lines changed

3 files changed

+203
-22
lines changed

WordPress/Classes/ViewRelated/Stats/Insights/Insights Management/AddInsightTableViewController.swift

Lines changed: 173 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
import UIKit
22
import Gridicons
33

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+
func insightsManagementDismissed()
9+
}
10+
411
class AddInsightTableViewController: UITableViewController {
512

613
// MARK: - Properties
7-
14+
private weak var insightsManagementDelegate: StatsInsightsManagementDelegate?
815
private weak var insightsDelegate: SiteStatsInsightsDelegate?
9-
private var insightsShown = [StatSection]()
16+
17+
/// Stored so that we can check if the user has made any changes.
18+
private var originalInsightsShown = [StatSection]()
19+
private var insightsShown = [StatSection]() {
20+
didSet {
21+
updateSaveButton()
22+
}
23+
}
24+
1025
private var selectedStat: StatSection?
1126

27+
private lazy var saveButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveInsights))
28+
1229
private lazy var tableHandler: ImmuTableViewHandler = {
1330
return ImmuTableViewHandler(takeOver: self)
1431
}()
@@ -17,17 +34,20 @@ class AddInsightTableViewController: UITableViewController {
1734

1835
override init(style: UITableView.Style) {
1936
super.init(style: style)
20-
navigationItem.title = NSLocalizedString("Add New Stats Card", comment: "Add New Stats Card view title")
37+
38+
navigationItem.title = TextContent.title
2139
}
2240

2341
required init?(coder aDecoder: NSCoder) {
2442
fatalError("init(coder:) has not been implemented")
2543
}
2644

27-
convenience init(insightsDelegate: SiteStatsInsightsDelegate, insightsShown: [StatSection]) {
45+
convenience init(insightsDelegate: SiteStatsInsightsDelegate, insightsManagementDelegate: StatsInsightsManagementDelegate? = nil, insightsShown: [StatSection]) {
2846
self.init(style: .grouped)
2947
self.insightsDelegate = insightsDelegate
48+
self.insightsManagementDelegate = insightsManagementDelegate
3049
self.insightsShown = insightsShown
50+
self.originalInsightsShown = insightsShown
3151
}
3252

3353
// MARK: - View
@@ -39,19 +59,30 @@ class AddInsightTableViewController: UITableViewController {
3959
reloadViewModel()
4060
WPStyleGuide.configureColors(view: view, tableView: tableView)
4161
WPStyleGuide.configureAutomaticHeightRows(for: tableView)
42-
tableView.accessibilityIdentifier = "Add Insight Table"
62+
tableView.accessibilityIdentifier = TextContent.title
63+
64+
if FeatureFlag.statsNewAppearance.enabled {
65+
tableView.isEditing = true
66+
tableView.allowsSelectionDuringEditing = true
67+
}
4368

4469
navigationItem.leftBarButtonItem = UIBarButtonItem(image: .gridicon(.cross), style: .plain, target: self, action: #selector(doneTapped))
4570
}
4671

4772
override func viewWillDisappear(_ animated: Bool) {
4873
super.viewWillDisappear(animated)
4974

50-
if selectedStat == nil {
51-
insightsDelegate?.addInsightDismissed?()
75+
if FeatureFlag.statsNewAppearance.enabled {
76+
// TODO: Check for any changes, prompt user
77+
} else {
78+
if selectedStat == nil {
79+
insightsDelegate?.addInsightDismissed?()
80+
}
5281
}
5382
}
5483

84+
// MARK: TableView Data Source / Delegate Overrides
85+
5586
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
5687
return 38
5788
}
@@ -60,9 +91,81 @@ class AddInsightTableViewController: UITableViewController {
6091
return 0
6192
}
6293

94+
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
95+
guard FeatureFlag.statsNewAppearance.enabled else {
96+
return false
97+
}
98+
99+
return isActiveCardsSection(indexPath.section)
100+
}
101+
102+
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
103+
guard FeatureFlag.statsNewAppearance.enabled else {
104+
return
105+
}
106+
107+
if isActiveCardsSection(sourceIndexPath.section) && isActiveCardsSection(destinationIndexPath.section) {
108+
let item = insightsShown.remove(at: sourceIndexPath.row)
109+
insightsShown.insert(item, at: destinationIndexPath.row)
110+
reloadViewModel()
111+
}
112+
}
113+
114+
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
115+
guard FeatureFlag.statsNewAppearance.enabled else {
116+
return false
117+
}
118+
119+
return insightsShown.count > 0 && isActiveCardsSection(indexPath.section)
120+
}
121+
122+
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
123+
if isActiveCardsSection(proposedDestinationIndexPath.section) {
124+
return proposedDestinationIndexPath
125+
}
126+
127+
return IndexPath(row: insightsShown.count - 1, section: 0)
128+
}
129+
130+
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
131+
return false
132+
}
133+
134+
private func isActiveCardsSection(_ sectionIndex: Int) -> Bool {
135+
return sectionIndex == 0
136+
}
137+
138+
// MARK: - Actions
139+
140+
private func updateSaveButton() {
141+
guard FeatureFlag.statsNewAppearance.enabled else {
142+
return
143+
}
144+
145+
if insightsShown != originalInsightsShown {
146+
navigationItem.rightBarButtonItem = saveButton
147+
} else {
148+
navigationItem.rightBarButtonItem = nil
149+
}
150+
}
151+
152+
@objc func saveInsights() {
153+
insightsManagementDelegate?.userUpdatedActiveInsights(insightsShown)
154+
155+
dismiss(animated: true, completion: nil)
156+
}
157+
63158
@objc private func doneTapped() {
64159
WPAnalytics.trackEvent(.statsInsightsManagementDismissed)
65160
dismiss(animated: true, completion: nil)
161+
162+
// TODO: Prompt user if they have unsaved changes
163+
}
164+
165+
fileprivate enum TextContent {
166+
static let title = NSLocalizedString("stats.insights.management.title", value: "Manage Stats Cards", comment: "Title of the Stats Insights Management screen")
167+
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.")
168+
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.")
66169
}
67170
}
68171

@@ -75,33 +178,84 @@ private extension AddInsightTableViewController {
75178
}
76179

77180
func tableViewModel() -> ImmuTable {
78-
return ImmuTable(sections: [ sectionForCategory(.general),
181+
return ImmuTable(sections: [ selectedStatsSection(),
182+
sectionForCategory(.general),
79183
sectionForCategory(.postsAndPages),
80-
sectionForCategory(.activity) ]
184+
sectionForCategory(.activity) ].compactMap({$0})
81185
)
82186
}
83187

84188
// MARK: - Table Sections
85189

86-
func sectionForCategory(_ category: InsightsCategories) -> ImmuTableSection {
87-
return ImmuTableSection(headerText: category.title,
88-
rows: category.insights.map {
89-
let enabled = !insightsShown.contains($0)
190+
func selectedStatsSection() -> ImmuTableSection? {
191+
guard FeatureFlag.statsNewAppearance.enabled else {
192+
return nil
193+
}
194+
195+
guard insightsShown.count > 0 else {
196+
return ImmuTableSection(headerText: TextContent.activeCardsHeader, rows: [placeholderRow])
197+
}
90198

199+
return ImmuTableSection(headerText: TextContent.activeCardsHeader,
200+
rows: insightsShown.map {
91201
return AddInsightStatRow(title: $0.insightManagementTitle,
92-
enabled: enabled,
93-
action: enabled ? rowActionFor($0) : nil) }
202+
enabled: true,
203+
action: rowActionFor($0)) }
204+
)
205+
}
206+
207+
func sectionForCategory(_ category: InsightsCategories) -> ImmuTableSection? {
208+
guard FeatureFlag.statsNewAppearance.enabled else {
209+
return ImmuTableSection(headerText: category.title,
210+
rows: category.insights.map {
211+
let enabled = !insightsShown.contains($0)
212+
return AddInsightStatRow(title: $0.insightManagementTitle,
213+
enabled: enabled,
214+
action: enabled ? rowActionFor($0) : nil) }
215+
)
216+
}
217+
218+
let rows = category.insights.filter({ !self.insightsShown.contains($0) })
219+
guard rows.count > 0 else {
220+
return nil
221+
}
222+
223+
return ImmuTableSection(headerText: category.title,
224+
rows: rows.map {
225+
return AddInsightStatRow(title: $0.insightManagementTitle,
226+
enabled: false,
227+
action: rowActionFor($0)) }
94228
)
95229
}
96230

97231
func rowActionFor(_ statSection: StatSection) -> ImmuTableAction {
98232
return { [unowned self] row in
99-
self.selectedStat = statSection
100-
self.insightsDelegate?.addInsightSelected?(statSection)
233+
if FeatureFlag.statsNewAppearance.enabled {
234+
toggleRow(for: statSection)
235+
} else {
236+
self.selectedStat = statSection
237+
self.insightsDelegate?.addInsightSelected?(statSection)
238+
239+
WPAnalytics.track(.statsInsightsManagementSaved, properties: ["types": [statSection.title]])
240+
self.dismiss(animated: true, completion: nil)
241+
}
242+
}
243+
}
101244

102-
WPAnalytics.track(.statsInsightsManagementSaved, properties: ["types": [statSection.title]])
103-
self.dismiss(animated: true, completion: nil)
245+
func toggleRow(for statSection: StatSection) {
246+
if let index = insightsShown.firstIndex(of: statSection) {
247+
insightsShown.remove(at: index)
248+
} else {
249+
insightsShown.append(statSection)
104250
}
251+
252+
reloadViewModel()
253+
}
254+
255+
var placeholderRow: ImmuTableRow {
256+
return AddInsightStatRow(title: TextContent.placeholderRowTitle,
257+
enabled: false,
258+
action: nil)
105259
}
106260

107261
// MARK: - Insights Categories

WordPress/Classes/ViewRelated/Stats/Insights/SiteStatsInsightsTableViewController.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class SiteStatsInsightsTableViewController: UIViewController, StoryboardLoadable
104104
}
105105

106106
let controller = AddInsightTableViewController(insightsDelegate: self,
107-
insightsShown: insightsToShow.compactMap { $0.statSection })
107+
insightsManagementDelegate: self, insightsShown: insightsToShow.compactMap { $0.statSection })
108108
let navigationController = UINavigationController(rootViewController: controller)
109109
present(navigationController, animated: true, completion: nil)
110110
}
@@ -633,7 +633,27 @@ extension SiteStatsInsightsTableViewController: SiteStatsInsightsDelegate {
633633
alert.popoverPresentationController?.sourceView = fromButton
634634
present(alert, animated: true)
635635
}
636+
}
637+
638+
// MARK: - StatsInsightsManagementDelegate
639+
640+
extension SiteStatsInsightsTableViewController: StatsInsightsManagementDelegate {
641+
func userUpdatedActiveInsights(_ insights: [StatSection]) {
642+
let insightTypes = insights.compactMap({ $0.insightType })
643+
644+
guard insightTypes.count == insights.count else {
645+
return
646+
}
636647

648+
// TODO: Tracks events
649+
insightsToShow = insightTypes
650+
refreshInsights(forceRefresh: true)
651+
updateView()
652+
}
653+
654+
func insightsManagementDismissed() {
655+
656+
}
637657
}
638658

639659
// MARK: - SharingViewControllerDelegate

WordPress/Classes/ViewRelated/Stats/SiteStatsTableViewCells.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import UIKit
2+
import Gridicons
23

34
// MARK: - Shared Rows
45

@@ -380,8 +381,14 @@ struct AddInsightStatRow: ImmuTableRow {
380381

381382
cell.accessibilityLabel = title
382383
cell.isAccessibilityElement = true
383-
cell.accessibilityTraits = enabled ? .button : .notEnabled
384-
cell.accessibilityHint = enabled ? enabledHint : disabledHint
384+
385+
let canTap = FeatureFlag.statsNewAppearance.enabled ? action != nil : enabled
386+
cell.accessibilityTraits = canTap ? .button : .notEnabled
387+
cell.accessibilityHint = canTap && enabled ? disabledHint : enabledHint
388+
389+
if FeatureFlag.statsNewAppearance.enabled {
390+
cell.accessoryView = canTap ? UIImageView(image: .gridicon(.addOutline)) : nil
391+
}
385392
}
386393
}
387394

0 commit comments

Comments
 (0)