Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [*] POS: icon button with confirmation step used for clearing the cart [https://github.com/woocommerce/woocommerce-ios/pull/15829]
- [*] Shipping Labels: Fixed a portion of layout issues caused by bigger accessibility content size categories. [https://github.com/woocommerce/woocommerce-ios/pull/15844]
- [*] Shipping Labels: Enable the confirm button on the payment method sheet even when there are no changes. [https://github.com/woocommerce/woocommerce-ios/pull/15856]
- [*] Watch app: Fixed connection issue upon fresh install [https://github.com/woocommerce/woocommerce-ios/pull/15867]

22.7
-----
Expand Down
1 change: 1 addition & 0 deletions WooCommerce/Classes/Analytics/WooAnalyticsStat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,7 @@ enum WooAnalyticsStat: String {
case watchAppOpened = "watch_app_opened"
case watchStoreDataSynced = "watch_store_data_synced"
case watchConnectingOpened = "watch_connecting_opened"
case watchSyncingFailed = "watch_syncing_failed"
case watchMyStoreOpened = "watch_my_store_opened"
case watchOrdersListOpened = "watch_orders_list_opened"
case watchPushNotificationTapped = "watch_push_notification_tapped"
Expand Down
52 changes: 36 additions & 16 deletions WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import WatchConnectivity
import Combine
import Networking
import protocol WooFoundation.Analytics
import class WooFoundation.CurrencySettings

/// Type that syncs the necessary dependencies to the watch session.
Expand All @@ -10,6 +11,8 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
/// Current WatchKit Session
private let watchSession: WCSession

private let analytics: Analytics

/// Subscriptions store for combine publishers
///
private var subscriptions = Set<AnyCancellable>()
Expand All @@ -34,16 +37,15 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
///
@Published var account: Account?

/// Tracks if the current watch session is active or not
///
@Published private var isSessionActive: Bool = false

/// Toggle this value to force a credentials sync.
///
@Published private var syncTrigger = false

init(watchSession: WCSession = WCSession.default, storedDependencies: WatchDependencies?) {
init(watchSession: WCSession = WCSession.default,
storedDependencies: WatchDependencies?,
analytics: Analytics = ServiceLocator.analytics) {
self.watchSession = watchSession
self.analytics = analytics
super.init()

self.storeID = storedDependencies?.storeID
Expand Down Expand Up @@ -80,9 +82,7 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
let (storeID, storeName, credentials, currencySettings) = required
let (enablesCrashReports, account) = configuration

guard let storeID, let storeName, let credentials else {
return nil
}
guard let storeID, let storeName, let credentials else { return nil }

return .init(storeID: storeID,
storeName: storeName,
Expand All @@ -95,16 +95,30 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {
.debounce(for: 0.5, scheduler: DispatchQueue.main)

// Syncs the dependencies to the paired counterpart when the session becomes available.
Publishers.CombineLatest3(watchDependencies, $isSessionActive, $syncTrigger)
.sink { [watchSession] dependencies, isSessionActive, forceSync in
watchDependencies.combineLatest($syncTrigger)
.sink { [weak self, watchSession] dependencies, forceSync in

// Do not update the context if the session is not active, the watch is not paired or the watch app is not installed.
guard isSessionActive, watchSession.isPaired, watchSession.isWatchAppInstalled else { return }
guard watchSession.activationState == .activated,
watchSession.isPaired,
watchSession.isWatchAppInstalled else {
self?.analytics.track(
.watchSyncingFailed,
properties: [
"session_active": watchSession.activationState == .activated,
"session_paired": watchSession.isPaired,
"watch_app_installed": watchSession.isWatchAppInstalled
],
error: SyncError.watchSessionInactiveOrNotPaired
)
return
}

do {

// If dependencies is nil, send an empty dictionary. This is most likely a logged out state
guard let dependencies else {
self?.analytics.track(.watchSyncingFailed, withError: SyncError.noDependenciesFound)
return try watchSession.updateApplicationContext([:])
}

Expand All @@ -118,9 +132,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {

try watchSession.updateApplicationContext(jsonObject)
} else {
self?.analytics.track(.watchSyncingFailed, withError: SyncError.encodingApplicationContextFailed)
DDLogError("⛔️ Unable to encode watch dependencies for synchronization. Resulting object is not a dictionary")
}
} catch {
self?.analytics.track(.watchSyncingFailed, withError: error)
DDLogError("⛔️ Error synchronizing credentials into watch session: \(error)")
}
}
Expand All @@ -129,7 +145,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
DDLogInfo("🔵 WatchSession activated \(activationState)")
self.isSessionActive = activationState == .activated
}

func sessionDidBecomeInactive(_ session: WCSession) {
Expand All @@ -138,7 +153,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate {

func sessionDidDeactivate(_ session: WCSession) {
// Try to guarantee an active session
self.isSessionActive = false
watchSession.activate()
}
}
Expand All @@ -149,13 +163,12 @@ extension WatchDependenciesSynchronizer {
/// This is in order to not duplicate tracks configuration which involve quite a lot of information to be transmitted to the watch.
///
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {

// The user info could contain a track event. Send it if we found one.
guard let rawEvent = userInfo[WooConstants.watchTracksKey] as? String,
let analyticEvent = WooAnalyticsStat(rawValue: rawEvent) else {
return DDLogError("⛔️ Unsupported watch tracks event: \(userInfo)")
}
ServiceLocator.analytics.track(analyticEvent)
analytics.track(analyticEvent)
}
}

Expand All @@ -173,7 +186,14 @@ extension WatchDependenciesSynchronizer {
guard message[WooConstants.watchSyncKey] as? Bool == true else {
return DDLogError("⛔️ Unsupported sync request message: \(message)")
}

syncTrigger.toggle()
}
}

extension WatchDependenciesSynchronizer {
enum SyncError: Error {
case watchSessionInactiveOrNotPaired
case noDependenciesFound
case encodingApplicationContextFailed
}
}
18 changes: 16 additions & 2 deletions WooCommerce/Woo Watch App/ConnectView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ struct ConnectView: View {

@EnvironmentObject private var tracksProvider: WatchTracksProvider

@State private var didRequestSyncing = false

let synchronizer: PhoneDependenciesSynchronizer

let message: String = Localization.connectMessage
Expand All @@ -18,6 +20,12 @@ struct ConnectView: View {
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)

if didRequestSyncing {
Text(Localization.workaroundTip)
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
}

Image(systemName: "bolt.fill")
.renderingMode(.original)
.resizable()
Expand All @@ -26,6 +34,7 @@ struct ConnectView: View {

Button(Localization.itsNotWorking) {
synchronizer.requestCredentialSync()
didRequestSyncing = true
}
}
}
Expand All @@ -44,10 +53,15 @@ extension ConnectView {

private enum Localization {
static let connectMessage = AppLocalizedString(
"watch.connect.message",
value: "Open Woo on your iPhone, connect your store, and hold your Watch nearby",
"watch.connect.messageUpdated",
value: "Open Woo on your iPhone, log into your store, and hold your Watch nearby.",
comment: "Info message when connecting your watch to the phone for the first time."
)
static let workaroundTip = AppLocalizedString(
"watch.connect.workaround",
value: "If the error persists, relaunch the app.",
comment: "Workaround when connecting the watch to the phone fails."
)
static let itsNotWorking = AppLocalizedString(
"watch.connect.notworking.title",
value: "It's not working",
Expand Down