Skip to content

Commit 67b689b

Browse files
Merge pull request #17854 from wordpress-mobile/issue/6969_filter_for_email_followers
People: Add email followers filter to people page
2 parents 2d53260 + b7c15a2 commit 67b689b

File tree

8 files changed

+289
-162
lines changed

8 files changed

+289
-162
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
-----
33
* [**] Some of the screens of the app has a new, fresh and more modern visual, including the initial one: My Site. [#17812]
44
* [**] Notifications: added a button to mark all notifications in the selected filter as read. [#17840]
5+
* [**] People: you can now manage Email Followers on the People section! [#17854]
56
* [*] Stats: fix navigation between Stats tab. [#17856]
67
* [*] Quick Start: Fixed a bug where a user logging in via a self-hosted site not connected to Jetpack would see Quick Start when selecting "No thanks" on the Quick Start prompt. [#17855]
78

WordPress/Classes/Models/ManagedPerson.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ManagedPerson: NSManagedObject {
3030
return User(managedPerson: self)
3131
case PersonKind.viewer.rawValue:
3232
return Viewer(managedPerson: self)
33+
case PersonKind.emailFollower.rawValue:
34+
return EmailFollower(managedPerson: self)
3335
default:
3436
return Follower(managedPerson: self)
3537
}
@@ -97,3 +99,18 @@ extension Viewer {
9799
isSuperAdmin: managedPerson.isSuperAdmin)
98100
}
99101
}
102+
103+
extension EmailFollower {
104+
init(managedPerson: ManagedPerson) {
105+
self.init(ID: Int(managedPerson.userID),
106+
username: managedPerson.username,
107+
firstName: managedPerson.firstName,
108+
lastName: managedPerson.lastName,
109+
displayName: managedPerson.displayName,
110+
role: RemoteRole.follower.slug,
111+
siteID: Int(managedPerson.siteID),
112+
linkedUserID: Int(managedPerson.linkedUserID),
113+
avatarURL: managedPerson.avatarURL.flatMap { URL(string: $0) },
114+
isSuperAdmin: managedPerson.isSuperAdmin)
115+
}
116+
}

WordPress/Classes/Services/PeopleService.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,28 @@ struct PeopleService {
6868
})
6969
}
7070

71+
/// Loads a page of Email Followers associated to the current blog, starting at the specified offset.
72+
///
73+
/// - Parameters:
74+
/// - offset: Number of records to skip.
75+
/// - count: Number of records to retrieve. By default set to 20.
76+
/// - success: Closure to be executed on success with the number of followers retrieved and a bool indicating if more are available.
77+
/// - failure: Closure to be executed on failure.
78+
///
79+
func loadEmailFollowersPage(_ offset: Int = 0,
80+
count: Int = 20,
81+
success: @escaping ((_ retrieved: Int, _ shouldLoadMore: Bool) -> Void),
82+
failure: ((Error) -> Void)? = nil) {
83+
let page = (offset / count) + 1
84+
remote.getEmailFollowers(siteID, page: page, max: count, success: { followers, hasMore in
85+
self.mergePeople(followers)
86+
success(followers.count, hasMore)
87+
}, failure: { error in
88+
DDLogError(String(describing: error))
89+
failure?(error)
90+
})
91+
}
92+
7193
/// Loads a page of Viewers associated to the current blog, starting at the specified offset.
7294
///
7395
/// - Parameters:
@@ -182,6 +204,30 @@ struct PeopleService {
182204
context.delete(managedPerson)
183205
}
184206

207+
/// Deletes a given EmailFollower.
208+
///
209+
/// - Parameters:
210+
/// - person: The email follower that should be deleted
211+
/// - success: Closure to be executed in case of success.
212+
/// - failure: Closure to be executed on error
213+
///
214+
func deleteEmailFollower(_ person: EmailFollower, success: (() -> Void)? = nil, failure: ((Error) -> Void)? = nil) {
215+
guard let managedPerson = managedPersonFromPerson(person) else {
216+
return
217+
}
218+
219+
remote.deleteEmailFollower(siteID, userID: person.ID, success: {
220+
success?()
221+
}, failure: { error in
222+
DDLogError("Error while deleting follower \(person.ID) from blog \(self.siteID): \(error)")
223+
224+
self.createManagedPerson(person)
225+
failure?(error)
226+
})
227+
228+
context.delete(managedPerson)
229+
}
230+
185231
/// Deletes a given Viewer.
186232
///
187233
/// - Parameters:

WordPress/Classes/ViewRelated/People/People.storyboard

Lines changed: 101 additions & 91 deletions
Large diffs are not rendered by default.

WordPress/Classes/ViewRelated/People/PeopleCell.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import UIKit
22
import WordPressShared
33

44
class PeopleCell: WPTableViewCell {
5-
@IBOutlet var avatarImageView: CircularImageView!
6-
@IBOutlet var displayNameLabel: UILabel!
7-
@IBOutlet var usernameLabel: UILabel!
8-
@IBOutlet var roleBadge: PeopleRoleBadgeLabel!
9-
@IBOutlet var superAdminRoleBadge: PeopleRoleBadgeLabel!
5+
@IBOutlet private weak var avatarImageView: CircularImageView!
6+
@IBOutlet private weak var displayNameLabel: UILabel!
7+
@IBOutlet private weak var usernameLabel: UILabel!
8+
@IBOutlet private weak var roleBadge: PeopleRoleBadgeLabel!
9+
@IBOutlet private weak var superAdminRoleBadge: PeopleRoleBadgeLabel!
10+
@IBOutlet private weak var badgeStackView: UIStackView!
1011

1112
override func awakeFromNib() {
1213
WPStyleGuide.configureLabel(displayNameLabel, textStyle: .callout)
@@ -18,6 +19,7 @@ class PeopleCell: WPTableViewCell {
1819
displayNameLabel.text = viewModel.displayName
1920
displayNameLabel.textColor = viewModel.usernameColor
2021
usernameLabel.text = viewModel.usernameText
22+
usernameLabel.isHidden = viewModel.usernameHidden
2123
roleBadge.borderColor = viewModel.roleBorderColor
2224
roleBadge.backgroundColor = viewModel.roleBackgroundColor
2325
roleBadge.textColor = viewModel.roleTextColor
@@ -27,6 +29,7 @@ class PeopleCell: WPTableViewCell {
2729
superAdminRoleBadge.isHidden = viewModel.superAdminHidden
2830
superAdminRoleBadge.borderColor = viewModel.superAdminBorderColor
2931
superAdminRoleBadge.backgroundColor = viewModel.superAdminBackgroundColor
32+
badgeStackView.isHidden = viewModel.roleHidden && viewModel.superAdminHidden
3033
}
3134

3235
@objc func setAvatarURL(_ avatarURL: URL?) {

WordPress/Classes/ViewRelated/People/PeopleCellViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ struct PeopleCellViewModel {
2020
return "@" + username
2121
}
2222

23+
var usernameHidden: Bool {
24+
return username.isEmpty
25+
}
26+
2327
var usernameColor: UIColor {
2428
return .text
2529
}

WordPress/Classes/ViewRelated/People/PeopleViewController.swift

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class PeopleViewController: UITableViewController, UIViewControllerRestoration {
6969
// Followers must be sorted out by creationDate!
7070
//
7171
switch filter {
72-
case .followers:
72+
case .followers, .email:
7373
return [NSSortDescriptor(key: "creationDate", ascending: true, selector: #selector(NSDate.compare(_:)))]
7474
default:
7575
return [NSSortDescriptor(key: "displayName", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))]
@@ -182,22 +182,18 @@ class PeopleViewController: UITableViewController, UIViewControllerRestoration {
182182
tableView.reloadData()
183183
}
184184

185-
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
186-
if let personViewController = segue.destination as? PersonViewController,
187-
let selectedIndexPath = tableView.indexPathForSelectedRow {
188-
personViewController.context = viewContext
189-
personViewController.blog = blog
190-
personViewController.person = personAtIndexPath(selectedIndexPath)
191-
switch filter {
192-
case .followers:
193-
personViewController.screenMode = .Follower
194-
case .users:
195-
personViewController.screenMode = .User
196-
case .viewers:
197-
personViewController.screenMode = .Viewer
198-
}
185+
@IBSegueAction func createPersonViewController(_ coder: NSCoder) -> PersonViewController? {
186+
guard let selectedIndexPath = tableView.indexPathForSelectedRow, let blog = blog else { return nil }
187+
188+
return PersonViewController(coder: coder,
189+
blog: blog,
190+
context: viewContext,
191+
person: personAtIndexPath(selectedIndexPath),
192+
screenMode: filter.screenMode)
193+
}
199194

200-
} else if let navController = segue.destination as? UINavigationController,
195+
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
196+
if let navController = segue.destination as? UINavigationController,
201197
let inviteViewController = navController.topViewController as? InvitePersonViewController {
202198
inviteViewController.blog = blog
203199
}
@@ -274,10 +270,11 @@ private extension PeopleViewController {
274270

275271
case users = "users"
276272
case followers = "followers"
273+
case email = "email"
277274
case viewers = "viewers"
278275

279276
static var defaultFilters: [Filter] {
280-
return [.users, .followers]
277+
return [.users, .followers, .email]
281278
}
282279

283280
var title: String {
@@ -288,6 +285,8 @@ private extension PeopleViewController {
288285
return NSLocalizedString("Followers", comment: "Blog Followers")
289286
case .viewers:
290287
return NSLocalizedString("Viewers", comment: "Blog Viewers")
288+
case .email:
289+
return NSLocalizedString("Email Followers", comment: "Blog Email Followers")
291290
}
292291
}
293292

@@ -299,6 +298,21 @@ private extension PeopleViewController {
299298
return .follower
300299
case .viewers:
301300
return .viewer
301+
case .email:
302+
return .emailFollower
303+
}
304+
}
305+
306+
var screenMode: PersonViewController.ScreenMode {
307+
switch self {
308+
case .users:
309+
return .User
310+
case .followers:
311+
return .Follower
312+
case .viewers:
313+
return .Viewer
314+
case .email:
315+
return .Email
302316
}
303317
}
304318
}
@@ -395,6 +409,8 @@ private extension PeopleViewController {
395409
loadUsersPage(offset, success: success)
396410
case .viewers:
397411
service.loadViewersPage(offset, success: success)
412+
case .email:
413+
service.loadEmailFollowersPage(offset, success: success)
398414
}
399415
}
400416

@@ -449,32 +465,29 @@ private extension PeopleViewController {
449465
// MARK: No Results Helpers
450466

451467
func refreshNoResultsView() {
452-
noResultsViewController.removeFromView()
453-
454-
if isInitialLoad {
455-
displayNoResultsView(forLoading: true)
456-
return
457-
}
458-
459468
guard resultsController.fetchedObjects?.count == 0 else {
469+
noResultsViewController.removeFromView()
460470
return
461471
}
462472

463-
displayNoResultsView()
473+
displayNoResultsView(isLoading: isInitialLoad)
464474
}
465475

466-
func displayNoResultsView(forLoading: Bool = false) {
467-
let accessoryView = forLoading ? NoResultsViewController.loadingAccessoryView() : nil
476+
func displayNoResultsView(isLoading: Bool = false) {
477+
let accessoryView = isLoading ? NoResultsViewController.loadingAccessoryView() : nil
468478
noResultsViewController.configure(title: noResultsTitle(), accessoryView: accessoryView)
469479

470-
addChild(noResultsViewController)
471-
tableView.addSubview(withFadeAnimation: noResultsViewController.view)
472-
473480
// Set the NRV top as the filterBar bottom so the NRV
474481
// adjusts correctly when refreshControl is active.
475482
let filterBarBottom = filterBar.frame.origin.y + filterBar.frame.size.height
476483
noResultsViewController.view.frame.origin.y = filterBarBottom
477484

485+
guard noResultsViewController.parent == nil else {
486+
noResultsViewController.updateView()
487+
return
488+
}
489+
addChild(noResultsViewController)
490+
tableView.addSubview(withFadeAnimation: noResultsViewController.view)
478491
noResultsViewController.didMove(toParent: self)
479492
}
480493

0 commit comments

Comments
 (0)