Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.

Commit f4144aa

Browse files
committed
PasswordViewController: add a bottom button stack view with a new secondary button for magic link login.
1 parent 86a3f4e commit f4144aa

File tree

2 files changed

+116
-65
lines changed

2 files changed

+116
-65
lines changed
Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="aQT-Gx-U3x">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="aQT-Gx-U3x">
33
<device id="retina4_7" orientation="portrait" appearance="light"/>
44
<dependencies>
55
<deployment identifier="iOS"/>
6-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
6+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21169.4"/>
77
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+
<capability name="System colors in document resources" minToolsVersion="11.0"/>
89
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
910
</dependencies>
1011
<scenes>
@@ -20,50 +21,64 @@
2021
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
2122
<subviews>
2223
<tableView clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" bounces="NO" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="KLl-Uz-wEP">
23-
<rect key="frame" x="0.0" y="0.0" width="375" height="591"/>
24+
<rect key="frame" x="0.0" y="0.0" width="375" height="561"/>
2425
<sections/>
2526
<connections>
2627
<outlet property="dataSource" destination="aQT-Gx-U3x" id="Sct-0G-HTk"/>
2728
<outlet property="delegate" destination="aQT-Gx-U3x" id="2xB-Wr-Hdh"/>
2829
</connections>
2930
</tableView>
3031
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xwA-rd-6jO" userLabel="Button background view">
31-
<rect key="frame" x="0.0" y="591" width="375" height="76"/>
32+
<rect key="frame" x="0.0" y="561" width="375" height="106"/>
3233
<subviews>
33-
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ClH-Cn-49d" userLabel="Primary Button" customClass="NUXButton" customModule="WordPressAuthenticatorResources" customModuleProvider="target">
34-
<rect key="frame" x="16" y="16" width="343" height="44"/>
35-
<constraints>
36-
<constraint firstAttribute="height" constant="44" id="iBk-Pi-8cv"/>
37-
</constraints>
38-
<state key="normal" title="Button"/>
39-
<userDefinedRuntimeAttributes>
40-
<userDefinedRuntimeAttribute type="boolean" keyPath="isPrimary" value="YES"/>
41-
</userDefinedRuntimeAttributes>
42-
<connections>
43-
<action selector="handleContinueButtonTapped:" destination="aQT-Gx-U3x" eventType="touchUpInside" id="Yeh-8i-cow"/>
44-
</connections>
45-
</button>
34+
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="RjB-bg-t6D">
35+
<rect key="frame" x="16" y="8" width="343" height="90"/>
36+
<subviews>
37+
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ClH-Cn-49d" userLabel="Primary Button" customClass="NUXButton" customModule="WordPressAuthenticatorResources" customModuleProvider="target">
38+
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
39+
<constraints>
40+
<constraint firstAttribute="height" constant="44" id="iBk-Pi-8cv"/>
41+
</constraints>
42+
<state key="normal" title="Button"/>
43+
<userDefinedRuntimeAttributes>
44+
<userDefinedRuntimeAttribute type="boolean" keyPath="isPrimary" value="YES"/>
45+
</userDefinedRuntimeAttributes>
46+
<connections>
47+
<action selector="handleContinueButtonTapped:" destination="aQT-Gx-U3x" eventType="touchUpInside" id="Yeh-8i-cow"/>
48+
</connections>
49+
</button>
50+
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IAk-wS-Gex" customClass="NUXButton" customModule="WordPressAuthenticatorResources" customModuleProvider="target">
51+
<rect key="frame" x="0.0" y="60" width="343" height="30"/>
52+
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
53+
<state key="normal" title="Button"/>
54+
<userDefinedRuntimeAttributes>
55+
<userDefinedRuntimeAttribute type="boolean" keyPath="isPrimary" value="NO"/>
56+
</userDefinedRuntimeAttributes>
57+
</button>
58+
</subviews>
59+
</stackView>
4660
</subviews>
47-
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
61+
<viewLayoutGuide key="safeArea" id="VfW-kE-aWC"/>
62+
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
4863
<constraints>
49-
<constraint firstAttribute="bottomMargin" secondItem="ClH-Cn-49d" secondAttribute="bottom" constant="8" id="3Ba-yg-JKx"/>
50-
<constraint firstItem="ClH-Cn-49d" firstAttribute="top" secondItem="xwA-rd-6jO" secondAttribute="topMargin" constant="8" id="GgD-0x-Aud"/>
64+
<constraint firstItem="RjB-bg-t6D" firstAttribute="top" secondItem="xwA-rd-6jO" secondAttribute="top" constant="8" id="oia-aR-q8U"/>
65+
<constraint firstAttribute="bottom" secondItem="RjB-bg-t6D" secondAttribute="bottom" constant="8" id="rCm-Sg-bhf"/>
5166
</constraints>
52-
<viewLayoutGuide key="safeArea" id="VfW-kE-aWC"/>
5367
</view>
5468
</subviews>
55-
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
69+
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
5670
<constraints>
5771
<constraint firstItem="xwA-rd-6jO" firstAttribute="bottom" secondItem="dFS-Ic-byk" secondAttribute="bottomMargin" constant="8" id="85d-XY-Mr8"/>
5872
<constraint firstItem="xwA-rd-6jO" firstAttribute="trailing" secondItem="dFS-Ic-byk" secondAttribute="trailing" id="Bkw-QJ-Tbe"/>
59-
<constraint firstItem="KLl-Uz-wEP" firstAttribute="trailing" secondItem="ClH-Cn-49d" secondAttribute="trailing" constant="16" id="Bpv-qx-bHc"/>
60-
<constraint firstItem="ClH-Cn-49d" firstAttribute="leading" secondItem="KLl-Uz-wEP" secondAttribute="leading" constant="16" id="Rnp-SF-SGh"/>
6173
<constraint firstItem="xwA-rd-6jO" firstAttribute="top" secondItem="KLl-Uz-wEP" secondAttribute="bottom" id="gkZ-OV-HMi"/>
74+
<constraint firstItem="RjB-bg-t6D" firstAttribute="leading" secondItem="KLl-Uz-wEP" secondAttribute="leading" constant="16" id="kXv-Ig-Ty3"/>
6275
<constraint firstItem="xwA-rd-6jO" firstAttribute="leading" secondItem="dFS-Ic-byk" secondAttribute="leading" id="wBE-xi-42q"/>
76+
<constraint firstItem="KLl-Uz-wEP" firstAttribute="trailing" secondItem="RjB-bg-t6D" secondAttribute="trailing" constant="16" id="wPg-N4-vkn"/>
6377
</constraints>
6478
</view>
6579
</subviews>
66-
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
80+
<viewLayoutGuide key="safeArea" id="ihD-pY-rg9"/>
81+
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
6782
<constraints>
6883
<constraint firstItem="KLl-Uz-wEP" firstAttribute="leading" secondItem="ihD-pY-rg9" secondAttribute="leading" id="7Fn-Eh-Xx9"/>
6984
<constraint firstItem="ihD-pY-rg9" firstAttribute="trailing" secondItem="KLl-Uz-wEP" secondAttribute="trailing" id="7MD-ux-8i0"/>
@@ -73,10 +88,10 @@
7388
<constraint firstItem="dFS-Ic-byk" firstAttribute="leading" secondItem="ljV-kF-TaY" secondAttribute="leading" id="msS-7X-Za9"/>
7489
<constraint firstItem="dFS-Ic-byk" firstAttribute="trailing" secondItem="ljV-kF-TaY" secondAttribute="trailing" id="zY1-Yz-kTf"/>
7590
</constraints>
76-
<viewLayoutGuide key="safeArea" id="ihD-pY-rg9"/>
7791
</view>
7892
<connections>
7993
<outlet property="bottomContentConstraint" destination="Dva-c1-u2U" id="Mq1-PI-MuN"/>
94+
<outlet property="secondaryButton" destination="IAk-wS-Gex" id="psV-zJ-3Yd"/>
8095
<outlet property="submitButton" destination="ClH-Cn-49d" id="kBa-QN-0oH"/>
8196
<outlet property="tableView" destination="KLl-Uz-wEP" id="MGk-sG-xGv"/>
8297
<outlet property="tableViewLeadingConstraint" destination="7Fn-Eh-Xx9" id="yKO-sE-7mh"/>
@@ -88,4 +103,9 @@
88103
<point key="canvasLocation" x="-162.40000000000001" y="20.239880059970016"/>
89104
</scene>
90105
</scenes>
106+
<resources>
107+
<systemColor name="systemBackgroundColor">
108+
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
109+
</systemColor>
110+
</resources>
91111
</document>

WordPressAuthenticator/Unified Auth/View Related/Password/PasswordViewController.swift

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ class PasswordViewController: LoginViewController {
99

1010
@IBOutlet private weak var tableView: UITableView!
1111
@IBOutlet var bottomContentConstraint: NSLayoutConstraint?
12+
@IBOutlet private weak var secondaryButton: NUXButton!
1213

1314
private weak var passwordField: UITextField?
1415
private var rows = [Row]()
1516
private var errorMessage: String?
1617
private var shouldChangeVoiceOverFocus: Bool = false
1718
private var loginLinkCell: TextLinkButtonTableViewCell?
1819

20+
private let isMagicLinkShownAsSecondaryAction: Bool = WordPressAuthenticator.shared.configuration.isWPComMagicLinkShownAsSecondaryActionOnPasswordScreen
21+
1922
/// Depending on where we're coming from, this screen needs to track a password challenge
2023
/// (if logging on with a Social account) or not (if logging in through WP.com).
2124
///
@@ -51,6 +54,7 @@ class PasswordViewController: LoginViewController {
5154
defaultTableViewMargin = tableViewLeadingConstraint?.constant ?? 0
5255
setTableViewMargins(forWidth: view.frame.width)
5356

57+
configureLoginWithMagicLinkButton()
5458
localizePrimaryButton()
5559
registerTableViewCells()
5660
loadRows()
@@ -174,7 +178,7 @@ class PasswordViewController: LoginViewController {
174178

175179
// Is everything filled out?
176180
if !loginFields.validateFieldsPopulatedForSignin() {
177-
let errorMsg = Constants.missingInfoError
181+
let errorMsg = Localization.missingInfoError
178182
displayError(message: errorMsg, moveVoiceOverFocus: true)
179183

180184
return
@@ -248,6 +252,50 @@ extension PasswordViewController: NUXKeyboardResponder {
248252

249253
}
250254

255+
// MARK: - Magic Link
256+
257+
private extension PasswordViewController {
258+
func configureLoginWithMagicLinkButton() {
259+
if isMagicLinkShownAsSecondaryAction {
260+
secondaryButton.setTitle(Localization.loginWithMagicLink, for: .normal)
261+
secondaryButton.accessibilityIdentifier = AccessibilityIdentifier.loginWithMagicLink
262+
secondaryButton.on(.touchUpInside) { [weak self] _ in
263+
Task { @MainActor [weak self] in
264+
guard let self = self else { return }
265+
self.secondaryButton.isEnabled = false
266+
await self.loginWithMagicLink()
267+
self.secondaryButton.isEnabled = true
268+
}
269+
}
270+
} else {
271+
secondaryButton.isHidden = true
272+
}
273+
}
274+
275+
func loginWithMagicLink() async {
276+
tracker.track(click: .requestMagicLink)
277+
loginFields.meta.emailMagicLinkSource = .login
278+
279+
configureViewLoading(true)
280+
let result = await MagicLinkRequester().requestMagicLink(email: loginFields.username, jetpackLogin: loginFields.meta.jetpackLogin)
281+
switch result {
282+
case .success:
283+
didRequestAuthenticationLink()
284+
case .failure(let error):
285+
switch error {
286+
case MagicLinkRequester.MagicLinkRequestError.invalidEmail:
287+
DDLogError("Attempted to request authentication link, but the email address did not appear valid.")
288+
let alert = buildInvalidEmailAlert()
289+
present(alert, animated: true, completion: nil)
290+
default:
291+
tracker.track(failure: error.localizedDescription)
292+
displayError(error as NSError, sourceTag: sourceTag)
293+
}
294+
}
295+
configureViewLoading(false)
296+
}
297+
}
298+
251299
// MARK: - Table Management
252300

253301
private extension PasswordViewController {
@@ -284,7 +332,10 @@ private extension PasswordViewController {
284332
}
285333

286334
rows.append(.forgotPassword)
287-
rows.append(.sendMagicLink)
335+
336+
if !isMagicLinkShownAsSecondaryAction {
337+
rows.append(.sendMagicLink)
338+
}
288339
}
289340

290341
/// Configure cells.
@@ -394,7 +445,7 @@ private extension PasswordViewController {
394445
cell.configureButton(text: WordPressAuthenticator.shared.displayStrings.getLoginLinkButtonTitle,
395446
accessibilityTrait: .link,
396447
showBorder: true)
397-
cell.accessibilityIdentifier = "Get Login Link Button"
448+
cell.accessibilityIdentifier = AccessibilityIdentifier.loginWithMagicLink
398449

399450
// Save reference to the login link cell so it can be enabled/disabled.
400451
loginLinkCell = cell
@@ -406,8 +457,9 @@ private extension PasswordViewController {
406457

407458
cell.enableButton(false)
408459

409-
self.tracker.track(click: .requestMagicLink)
410-
self.requestAuthenticationLink()
460+
Task { @MainActor [weak self] in
461+
await self?.loginWithMagicLink()
462+
}
411463
}
412464
}
413465

@@ -441,40 +493,11 @@ private extension PasswordViewController {
441493
submitButton as Any
442494
]
443495

444-
UIAccessibility.post(notification: .screenChanged, argument: passwordField)
445-
}
446-
447-
/// Makes the call to request a magic authentication link be emailed to the user.
448-
///
449-
func requestAuthenticationLink() {
450-
loginFields.meta.emailMagicLinkSource = .login
451-
452-
let email = loginFields.username
453-
guard email.isValidEmail() else {
454-
DDLogError("Attempted to request authentication link, but the email address did not appear valid.")
455-
let alert = buildInvalidEmailAlert()
456-
present(alert, animated: true, completion: nil)
457-
return
496+
if isMagicLinkShownAsSecondaryAction {
497+
view.accessibilityElements?.append(secondaryButton as Any)
458498
}
459499

460-
configureViewLoading(true)
461-
let service = WordPressComAccountService()
462-
service.requestAuthenticationLink(for: email,
463-
jetpackLogin: loginFields.meta.jetpackLogin,
464-
success: { [weak self] in
465-
self?.didRequestAuthenticationLink()
466-
self?.configureViewLoading(false)
467-
468-
}, failure: { [weak self] (error: Error) in
469-
guard let self = self else {
470-
return
471-
}
472-
473-
self.tracker.track(failure: error.localizedDescription)
474-
475-
self.displayError(error as NSError, sourceTag: self.sourceTag)
476-
self.configureViewLoading(false)
477-
})
500+
UIAccessibility.post(notification: .screenChanged, argument: passwordField)
478501
}
479502

480503
/// When a magic link successfully sends, navigate the user to the next step.
@@ -541,11 +564,19 @@ private extension PasswordViewController {
541564
}
542565
}
543566
}
567+
}
544568

545-
/// Constants
569+
private extension PasswordViewController {
570+
/// Localization constants
546571
///
547-
struct Constants {
572+
enum Localization {
548573
static let missingInfoError = NSLocalizedString("Please fill out all the fields",
549574
comment: "A short prompt asking the user to properly fill out all login fields.")
575+
static let loginWithMagicLink = NSLocalizedString("Or log in with magic link",
576+
comment: "The button title for a secondary call-to-action button on the password screen. When the user wants to try sending a magic link instead of entering a password.")
577+
}
578+
579+
enum AccessibilityIdentifier {
580+
static let loginWithMagicLink = "Get Login Link Button"
550581
}
551582
}

0 commit comments

Comments
 (0)