Skip to content

Commit 2c72b18

Browse files
Merge branch 'trunk' into feat/7298-site-cred-login
2 parents 129d74f + 714f4af commit 2c72b18

34 files changed

+488
-70
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
4747
return true
4848
case .loginPrologueOnboarding:
4949
return true
50+
case .loginErrorNotifications:
51+
return buildConfig == .localDeveloper || buildConfig == .alpha
5052
default:
5153
return true
5254
}

Experiments/Experiments/FeatureFlag.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,8 @@ public enum FeatureFlag: Int {
9797
/// Onboarding experiment on the login prologue screen
9898
///
9999
case loginPrologueOnboarding
100+
101+
/// Local notifications scheduled 24 hours after certain login errors
102+
///
103+
case loginErrorNotifications
100104
}

Gemfile.lock

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
GIT
22
remote: git@github.com:wordpress-mobile/release-toolkit
3-
revision: a4d04a43125d84288e46ea38b893d001cd660a3b
3+
revision: 57e9ad1c7311b076d7c273cd176073748299fdca
44
branch: trunk
55
specs:
6-
fastlane-plugin-wpmreleasetoolkit (4.2.0)
6+
fastlane-plugin-wpmreleasetoolkit (5.1.0)
77
activesupport (~> 5)
88
bigdecimal (~> 1.4)
99
buildkit (~> 1.5)
1010
chroma (= 0.2.0)
1111
diffy (~> 3.3)
1212
git (~> 1.3)
13+
google-cloud-storage (~> 1.31)
1314
jsonlint (~> 0.3)
1415
nokogiri (~> 1.11)
1516
octokit (~> 4.18)
1617
parallel (~> 1.14)
18+
plist (~> 3.1)
1719
progress_bar (~> 1.3)
1820
rake (>= 12.3, < 14.0)
1921
rake-compiler (~> 1.0)
@@ -23,7 +25,7 @@ GEM
2325
specs:
2426
CFPropertyList (3.0.5)
2527
rexml
26-
activesupport (5.2.8)
28+
activesupport (5.2.8.1)
2729
concurrent-ruby (~> 1.0, >= 1.0.2)
2830
i18n (>= 0.7, < 2)
2931
minitest (~> 5.1)
@@ -104,7 +106,7 @@ GEM
104106
highline (~> 2.0.0)
105107
concurrent-ruby (1.1.10)
106108
declarative (0.0.20)
107-
diffy (3.4.0)
109+
diffy (3.4.2)
108110
digest-crc (0.6.4)
109111
rake (>= 12.0.0, < 14.0.0)
110112
domain_name (0.5.20190701)
@@ -134,8 +136,8 @@ GEM
134136
faraday-em_synchrony (1.0.0)
135137
faraday-excon (1.1.0)
136138
faraday-httpclient (1.0.1)
137-
faraday-multipart (1.0.3)
138-
multipart-post (>= 1.2, < 3)
139+
faraday-multipart (1.0.4)
140+
multipart-post (~> 2)
139141
faraday-net_http (1.0.1)
140142
faraday-net_http_persistent (1.2.0)
141143
faraday-patron (1.0.0)
@@ -233,7 +235,7 @@ GEM
233235
http-cookie (1.0.4)
234236
domain_name (~> 0.5)
235237
httpclient (2.8.3)
236-
i18n (1.10.0)
238+
i18n (1.12.0)
237239
concurrent-ruby (~> 1.0)
238240
jmespath (1.6.1)
239241
json (2.6.1)
@@ -245,23 +247,23 @@ GEM
245247
mini_magick (4.11.0)
246248
mini_mime (1.1.2)
247249
mini_portile2 (2.8.0)
248-
minitest (5.15.0)
250+
minitest (5.16.2)
249251
molinillo (0.8.0)
250252
multi_json (1.15.0)
251253
multipart-post (2.0.0)
252254
nanaimo (0.3.0)
253255
nap (1.1.0)
254256
naturally (2.2.1)
255257
netrc (0.11.0)
256-
nokogiri (1.13.6)
258+
nokogiri (1.13.8)
257259
mini_portile2 (~> 2.8.0)
258260
racc (~> 1.4)
259-
nokogiri (1.13.6-x86_64-darwin)
261+
nokogiri (1.13.8-x86_64-darwin)
260262
racc (~> 1.4)
261-
octokit (4.23.0)
263+
octokit (4.25.1)
262264
faraday (>= 1, < 3)
263265
sawyer (~> 0.9)
264-
oj (3.13.13)
266+
oj (3.13.17)
265267
optimist (3.0.1)
266268
options (2.3.2)
267269
optparse (0.1.1)
@@ -306,7 +308,7 @@ GEM
306308
ruby-progressbar (1.11.0)
307309
ruby2_keywords (0.0.5)
308310
rubyzip (2.3.2)
309-
sawyer (0.9.1)
311+
sawyer (0.9.2)
310312
addressable (>= 2.3.5)
311313
faraday (>= 0.17.3, < 3)
312314
security (0.1.3)
@@ -328,7 +330,7 @@ GEM
328330
tty-cursor (~> 0.7)
329331
typhoeus (1.4.0)
330332
ethon (>= 0.9.0)
331-
tzinfo (1.2.9)
333+
tzinfo (1.2.10)
332334
thread_safe (~> 0.1)
333335
uber (0.1.0)
334336
unf (0.1.4)

RELEASE-NOTES.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
*** PLEASE FOLLOW THIS FORMAT: [<priority indicator, more stars = higher priority>] <description> [<PR URL>]
22

33
9.8
4-
----
4+
-----
55
- [***] Login: Introduce a way to sign in using store credentials. [https://github.com/woocommerce/woocommerce-ios/pull/7320]
6+
- [*] In-Person Payments: The purchase card reader information card appears also in the Orders list screen. [https://github.com/woocommerce/woocommerce-ios/pull/7326]
67

78
9.7
8-
----
9+
-----
910
- [***] Orders: Orders can now be edited within the app. [https://github.com/woocommerce/woocommerce-ios/pull/7300]
11+
- [**] Orders: You can now view the Custom Fields for an order in the Order Details screen. [https://github.com/woocommerce/woocommerce-ios/pull/7310]
1012
- [*] In-Person Payments: Card Reader Manuals now appear based on country availability, consolidated into an unique view [https://github.com/woocommerce/woocommerce-ios/pull/7178]
1113
- [*] Login: Jetpack setup flow is now accessible from the Login with Store Address flow. [https://github.com/woocommerce/woocommerce-ios/pull/7294]
1214
- [*] In-Person Payments: The purchase card reader information card can be dismissed [https://github.com/woocommerce/woocommerce-ios/pull/7260]
1315
- [*] In-Person Payments: When dismissing the purchase card reader information card, the user can choose to be reminded in 14 days. [https://github.com/woocommerce/woocommerce-ios/pull/7271]
1416
- [*] In-Person Payments: The purchase card reader information card appears also in the App Settings screen. [https://github.com/woocommerce/woocommerce-ios/pull/7308]
1517
- [*] Refund lines in the Order details screen now appear ordered from oldest to newest [https://github.com/woocommerce/woocommerce-ios/pull/7287]
1618
- [*] Login: when the app is in logged out state, an onboarding screen is shown before the prologue screen if the user hasn't finished or skipped it. [https://github.com/woocommerce/woocommerce-ios/pull/7324]
17-
- [**] Orders: You can now view the Custom Fields for an order in the Order Details screen. [https://github.com/woocommerce/woocommerce-ios/pull/7310]
1819
- [*] Orders: When a store has no orders yet, there is an updated message with a link to learn more on the Orders tab. [https://github.com/woocommerce/woocommerce-ios/pull/7328]
1920

2021
9.6

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public enum WooAnalyticsStat: String {
3838
case loginEmailFormViewed = "login_email_form_viewed"
3939
case loginJetpackRequiredScreenViewed = "login_jetpack_required_screen_viewed"
4040
case loginJetpackRequiredViewInstructionsButtonTapped = "login_jetpack_required_view_instructions_button_tapped"
41+
case loginLocalNotificationTapped = "login_local_notification_tapped"
4142
case loginWhatIsJetpackHelpScreenViewed = "login_what_is_jetpack_help_screen_viewed"
4243
case loginWhatIsJetpackHelpScreenOkButtonTapped = "login_what_is_jetpack_help_screen_ok_button_tapped"
4344
case loginWhatIsJetpackHelpScreenLearnMoreButtonTapped = "login_what_is_jetpack_help_screen_learn_more_button_tapped"

WooCommerce/Classes/AppDelegate.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
6363
setupKeyboardStateProvider()
6464
handleLaunchArguments()
6565
appleIDCredentialChecker.observeLoggedInStateForAppleIDObservations()
66+
setupUserNotificationCenter()
6667

6768
// Components that require prior Auth
6869
setupZendesk()
@@ -138,7 +139,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
138139
)
139140

140141
// show this notification seconds from now
141-
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1.5, repeats: false)
142+
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
142143

143144
// choose a random identifier
144145
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
@@ -288,6 +289,9 @@ private extension AppDelegate {
288289
///
289290
func setupPushNotificationsManagerIfPossible() {
290291
guard ServiceLocator.stores.isAuthenticated, ServiceLocator.stores.needsDefaultStore == false else {
292+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.loginErrorNotifications) {
293+
ServiceLocator.pushNotesManager.ensureAuthorizationIsRequested(includesProvisionalAuth: true, onCompletion: nil)
294+
}
291295
return
292296
}
293297

@@ -296,10 +300,17 @@ private extension AppDelegate {
296300
#else
297301
let pushNotesManager = ServiceLocator.pushNotesManager
298302
pushNotesManager.registerForRemoteNotifications()
299-
pushNotesManager.ensureAuthorizationIsRequested(onCompletion: nil)
303+
pushNotesManager.ensureAuthorizationIsRequested(includesProvisionalAuth: false, onCompletion: nil)
300304
#endif
301305
}
302306

307+
func setupUserNotificationCenter() {
308+
guard ServiceLocator.featureFlagService.isFeatureFlagEnabled(.loginErrorNotifications) else {
309+
return
310+
}
311+
UNUserNotificationCenter.current().delegate = self
312+
}
313+
303314
/// Set up app review prompt
304315
///
305316
func setupAppRatingManager() {
@@ -403,3 +414,32 @@ extension AppDelegate {
403414
RequirementsChecker.checkMinimumWooVersionForDefaultStore()
404415
}
405416
}
417+
418+
// MARK: - UNUserNotificationCenterDelegate
419+
420+
extension AppDelegate: UNUserNotificationCenterDelegate {
421+
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
422+
switch response.actionIdentifier {
423+
case LocalNotification.Action.contactSupport.rawValue:
424+
guard let viewController = window?.rootViewController else {
425+
return
426+
}
427+
ZendeskProvider.shared.showNewRequestIfPossible(from: viewController, with: nil)
428+
ServiceLocator.analytics.track(.loginLocalNotificationTapped, withProperties: [
429+
"action": "contact_support",
430+
"type": response.notification.request.identifier
431+
])
432+
default:
433+
// Triggered when the user taps on the notification itself instead of one of the actions.
434+
switch response.notification.request.identifier {
435+
case LocalNotification.Scenario.loginSiteAddressError.rawValue:
436+
ServiceLocator.analytics.track(.loginLocalNotificationTapped, withProperties: [
437+
"action": "default",
438+
"type": response.notification.request.identifier
439+
])
440+
default:
441+
return
442+
}
443+
}
444+
}
445+
}

WooCommerce/Classes/Authentication/AuthenticationManager.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
212212
}
213213

214214
func handleError(_ error: Error, onCompletion: @escaping (UIViewController) -> Void) {
215+
requestLocalNotificationIfApplicable(error: error)
216+
215217
guard let errorViewModel = viewModel(error) else {
216218
return
217219
}
@@ -276,6 +278,10 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
276278
return
277279
}
278280

281+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.loginErrorNotifications) {
282+
ServiceLocator.pushNotesManager.cancelLocalNotification(scenarios: [.loginSiteAddressError])
283+
}
284+
279285
guard matcher.match(originalURL: wpcomLogin.siteURL) else {
280286
DDLogWarn("⚠️ Present account mismatch error for site: \(String(describing: credentials.wpcom?.siteURL))")
281287
let viewModel = WrongAccountErrorViewModel(siteURL: credentials.wpcom?.siteURL)
@@ -407,6 +413,28 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {
407413
}
408414
}
409415

416+
// MARK: - Local notifications
417+
418+
private extension AuthenticationManager {
419+
func requestLocalNotificationIfApplicable(error: Error) {
420+
guard ServiceLocator.featureFlagService.isFeatureFlagEnabled(.loginErrorNotifications) else {
421+
return
422+
}
423+
424+
let wooAuthError = AuthenticationError.make(with: error)
425+
switch wooAuthError {
426+
case .notWPSite, .notValidAddress:
427+
let notification = LocalNotification(scenario: .loginSiteAddressError)
428+
ServiceLocator.pushNotesManager.cancelLocalNotification(scenarios: [notification.scenario])
429+
ServiceLocator.pushNotesManager.requestLocalNotification(notification,
430+
// 24 hours from now.
431+
trigger: UNTimeIntervalNotificationTrigger(timeInterval: 86400, repeats: false))
432+
default:
433+
break
434+
}
435+
}
436+
}
437+
410438
// MARK: - Private helpers
411439
private extension AuthenticationManager {
412440
func isJetpackValidForSelfHostedSite(url: String) -> Bool {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import Foundation
2+
3+
/// Content for a local notification to be converted to `UNNotificationContent`.
4+
struct LocalNotification {
5+
let title: String
6+
let body: String
7+
let scenario: Scenario
8+
let actions: CategoryActions?
9+
10+
/// A category of actions in a notification.
11+
struct CategoryActions {
12+
let category: Category
13+
let actions: [Action]
14+
}
15+
16+
/// The scenario for the local notification.
17+
/// Its raw value is used for the identifier of a local notification and also the event property for analytics.
18+
enum Scenario: String {
19+
case loginSiteAddressError = "site_address_error"
20+
}
21+
22+
/// The category of actions for a local notification.
23+
enum Category: String {
24+
case loginError
25+
}
26+
27+
/// The action type in a local notification.
28+
enum Action: String {
29+
case contactSupport
30+
31+
/// The title of the action in a local notification.
32+
var title: String {
33+
switch self {
34+
case .contactSupport:
35+
return NSLocalizedString("Contact support", comment: "Local notification action to contact support.")
36+
}
37+
}
38+
}
39+
}
40+
41+
extension LocalNotification {
42+
init(scenario: Scenario) {
43+
switch scenario {
44+
case .loginSiteAddressError:
45+
self.init(title: Localization.errorLoggingInTitle,
46+
body: Localization.errorLoggingInBody,
47+
scenario: .loginSiteAddressError,
48+
actions: .init(category: .loginError, actions: [.contactSupport]))
49+
}
50+
}
51+
}
52+
53+
private extension LocalNotification {
54+
enum Localization {
55+
static let errorLoggingInTitle = NSLocalizedString("Problems with logging in?",
56+
comment: "Local notification title when the user encounters an error logging in " +
57+
"with site address.")
58+
static let errorLoggingInBody = NSLocalizedString("Get some help!",
59+
comment: "Local notification body when the user encounters an error logging in " +
60+
"with site address.")
61+
}
62+
}

0 commit comments

Comments
 (0)