-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathSignupEpilogueTableViewController.swift
299 lines (238 loc) · 11.2 KB
/
SignupEpilogueTableViewController.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
293
294
295
296
297
298
299
import UIKit
import WordPressAuthenticator
protocol SignupEpilogueTableViewControllerDelegate {
func displayNameUpdated(newDisplayName: String)
func displayNameAutoGenerated(newDisplayName: String)
func passwordUpdated(newPassword: String)
func usernameTapped(userInfo: LoginEpilogueUserInfo?)
}
/// Data source to get the temporary user info, not yet saved in the user account.
///
protocol SignupEpilogueTableViewControllerDataSource {
var customDisplayName: String? { get }
var password: String? { get }
var username: String? { get }
}
class SignupEpilogueTableViewController: UITableViewController, EpilogueUserInfoCellViewControllerProvider {
// MARK: - Properties
open var dataSource: SignupEpilogueTableViewControllerDataSource?
open var delegate: SignupEpilogueTableViewControllerDelegate?
open var credentials: AuthenticatorCredentials?
open var socialService: SocialService?
private var epilogueUserInfo: LoginEpilogueUserInfo?
private var userInfoCell: EpilogueUserInfoCell?
private var showPassword: Bool = true
private var reloaded: Bool = false
// MARK: - View
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .basicBackground
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getUserInfo()
configureTable()
if reloaded {
tableView.reloadData()
}
reloaded = true
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return Constants.numberOfSections
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard section != TableSections.userInfo else {
return Constants.userInfoRows
}
return showPassword ? Constants.allAccountRows : Constants.noPasswordRows
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// Don't show section header for User Info
guard section != TableSections.userInfo,
let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: CellIdentifiers.sectionHeaderFooter) as? EpilogueSectionHeaderFooter else {
return nil
}
cell.titleLabel?.text = NSLocalizedString("Account Details", comment: "Header for account details, shown after signing up.").localizedUppercase
cell.titleLabel?.accessibilityIdentifier = "New Account Header"
cell.accessibilityLabel = cell.titleLabel?.text
return cell
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard section != TableSections.userInfo,
showPassword,
let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: CellIdentifiers.sectionHeaderFooter) as? EpilogueSectionHeaderFooter else {
return nil
}
cell.titleLabel?.numberOfLines = 0
cell.topConstraint.constant = Constants.footerTopMargin
cell.titleLabel?.text = NSLocalizedString("You can always log in with a link like the one you just used, but you can also set up a password if you prefer.", comment: "Information shown below the optional password field after new account creation.")
cell.accessibilityLabel = cell.titleLabel?.text
return cell
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// User Info Row
if indexPath.section == TableSections.userInfo {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.epilogueUserInfoCell) as? EpilogueUserInfoCell else {
return UITableViewCell()
}
if let epilogueUserInfo = epilogueUserInfo {
cell.configure(userInfo: epilogueUserInfo, showEmail: true, allowGravatarUploads: true)
}
cell.viewControllerProvider = self
userInfoCell = cell
return cell
}
// Account Details Rows
guard let cellType = EpilogueCellType(rawValue: indexPath.row) else {
return UITableViewCell()
}
return getEpilogueCellFor(cellType: cellType)
}
override func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return Constants.headerFooterHeight
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == TableSections.userInfo ? 0 : UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
return Constants.headerFooterHeight
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
guard section != TableSections.userInfo, showPassword else {
return 0
}
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cellType = EpilogueCellType(rawValue: indexPath.row),
cellType == .username {
delegate?.usernameTapped(userInfo: epilogueUserInfo)
}
}
}
// MARK: - Private Extension
private extension SignupEpilogueTableViewController {
func configureTable() {
let headerFooterNib = UINib(nibName: CellNibNames.sectionHeaderFooter, bundle: nil)
tableView.register(headerFooterNib, forHeaderFooterViewReuseIdentifier: CellIdentifiers.sectionHeaderFooter)
let cellNib = UINib(nibName: CellNibNames.signupEpilogueCell, bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: CellIdentifiers.signupEpilogueCell)
let userInfoNib = UINib(nibName: CellNibNames.epilogueUserInfoCell, bundle: nil)
tableView.register(userInfoNib, forCellReuseIdentifier: CellIdentifiers.epilogueUserInfoCell)
WPStyleGuide.configureColors(view: view, tableView: tableView)
tableView.backgroundColor = .basicBackground
// remove empty cells
tableView.tableFooterView = UIView()
}
func getUserInfo() {
guard let account = try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext) else {
return
}
var userInfo = LoginEpilogueUserInfo(account: account)
if let socialservice = socialService {
showPassword = false
userInfo.update(with: socialservice)
} else {
if let customDisplayName = dataSource?.customDisplayName {
userInfo.fullName = customDisplayName
} else {
let autoDisplayName = Self.generateDisplayName(from: userInfo.email)
userInfo.fullName = autoDisplayName
delegate?.displayNameAutoGenerated(newDisplayName: autoDisplayName)
}
}
epilogueUserInfo = userInfo
}
func getEpilogueCellFor(cellType: EpilogueCellType) -> SignupEpilogueCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.signupEpilogueCell) as? SignupEpilogueCell else {
return SignupEpilogueCell()
}
switch cellType {
case .displayName:
cell.configureCell(forType: .displayName,
labelText: NSLocalizedString("Display Name", comment: "Display Name label text."),
fieldValue: dataSource?.customDisplayName ?? epilogueUserInfo?.fullName)
case .username:
cell.configureCell(forType: .username,
labelText: NSLocalizedString("Username", comment: "Username label text."),
fieldValue: dataSource?.username ?? epilogueUserInfo?.username)
case .password:
cell.configureCell(forType: .password,
fieldValue: dataSource?.password,
fieldPlaceholder: NSLocalizedString("Password (optional)", comment: "Password field placeholder text"))
}
cell.delegate = self
return cell
}
struct Constants {
static let numberOfSections = 2
static let userInfoRows = 1
static let noPasswordRows = 2
static let allAccountRows = 3
static let headerFooterHeight: CGFloat = 50
static let footerTrailingMargin: CGFloat = 16
static let footerTopMargin: CGFloat = 8
}
struct TableSections {
static let userInfo = 0
}
struct CellIdentifiers {
static let sectionHeaderFooter = "SectionHeaderFooter"
static let signupEpilogueCell = "SignupEpilogueCell"
static let epilogueUserInfoCell = "userInfo"
}
struct CellNibNames {
static let sectionHeaderFooter = "EpilogueSectionHeaderFooter"
static let signupEpilogueCell = "SignupEpilogueCell"
static let epilogueUserInfoCell = "EpilogueUserInfoCell"
}
}
// MARK: - SignupEpilogueCellDelegate
extension SignupEpilogueTableViewController: SignupEpilogueCellDelegate {
func updated(value: String, forType: EpilogueCellType) {
switch forType {
case .displayName:
delegate?.displayNameUpdated(newDisplayName: value)
case .password:
delegate?.passwordUpdated(newPassword: value)
default:
break
}
}
func changed(value: String, forType: EpilogueCellType) {
if forType == .displayName {
userInfoCell?.fullNameLabel?.text = value
delegate?.displayNameUpdated(newDisplayName: value)
} else if forType == .password {
delegate?.passwordUpdated(newPassword: value)
}
}
}
extension SignupEpilogueTableViewController {
// Notice that this duplicates almost one-to-one the logic from
// `ZendeskUtils.generateDisplayName(from:)` with the only difference being the method on
// `ZendeskUtils` returns `nil` if there is no "@" in the input.
//
// Later down the track, we might want to merge the two, ideally by updating this code to
// handle a `String?` value. Alternativetly, we could define an `Email` `String` wrapper and
// push the responsibility to validate the input as an email up the chain.
//
// At the time of writing, it was better to ensure the code didn't crash rather than
// restructuring the callsite.
//
// See https://github.com/wordpress-mobile/WordPressAuthenticator-iOS/issues/759
static func generateDisplayName(from rawEmail: String) -> String {
// step 1: lower case
let email = rawEmail.lowercased()
// step 2: remove the @ and everything after
let localPart = email.split(separator: "@")[0]
// step 3: remove all non-alpha characters
let localCleaned = localPart.replacingOccurrences(of: "[^A-Za-z/.]", with: "", options: .regularExpression)
// step 4: turn periods into spaces
let nameLowercased = localCleaned.replacingOccurrences(of: ".", with: " ")
// step 5: capitalize
let autoDisplayName = nameLowercased.capitalized
return autoDisplayName
}
}