From c5c62d96932214967e3743af89bf704824ab4385 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 11:13:59 +0700 Subject: [PATCH 1/9] Update text on ConnectView --- WooCommerce/Woo Watch App/ConnectView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Woo Watch App/ConnectView.swift b/WooCommerce/Woo Watch App/ConnectView.swift index aa12d0c581e..3414d6cb2f6 100644 --- a/WooCommerce/Woo Watch App/ConnectView.swift +++ b/WooCommerce/Woo Watch App/ConnectView.swift @@ -44,9 +44,10 @@ 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", - comment: "Info message when connecting your watch to the phone for the first time." + "watch.connect.messageWithWorkaround", + value: "Open Woo on your iPhone, log into your store, and hold your Watch nearby. " + + "If the issue persists, relaunch the app.", + comment: "Info message when connecting your watch to the phone for the first time with a workaround option." ) static let itsNotWorking = AppLocalizedString( "watch.connect.notworking.title", From 891bee8953ba969466e2d8ce5220f6c0ca5b7765 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 11:27:51 +0700 Subject: [PATCH 2/9] Add new tracking event for when syncing fails --- .../Classes/Analytics/WooAnalyticsStat.swift | 1 + .../WatchDependenciesSynchronizer.swift | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 7cbaa64acec..937fd161afb 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -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" diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index 229dc0d2458..2f65f43d5b2 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -10,6 +10,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() @@ -42,8 +44,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { /// @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 @@ -81,6 +86,7 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { let (enablesCrashReports, account) = configuration guard let storeID, let storeName, let credentials else { + analytics.track(.watchSyncingFailed, withError: SyncError.missingStoreDetailsOrCredentials) return nil } @@ -99,12 +105,16 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { .sink { [watchSession] dependencies, isSessionActive, 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 isSessionActive, watchSession.isPaired, watchSession.isWatchAppInstalled else { + analytics.track(.watchSyncingFailed, withError: SyncError.watchSessionInactiveOrNotPaired) + return + } do { // If dependencies is nil, send an empty dictionary. This is most likely a logged out state guard let dependencies else { + analytics.track(.watchSyncingFailed, withError: SyncError.noDependenciesFound) return try watchSession.updateApplicationContext([:]) } @@ -118,9 +128,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { try watchSession.updateApplicationContext(jsonObject) } else { + analytics.track(.watchSyncingFailed, withError: SyncError.encodingApplicationContextFailed) DDLogError("⛔️ Unable to encode watch dependencies for synchronization. Resulting object is not a dictionary") } } catch { + analytics.track(.watchSyncingFailed, withError: error) DDLogError("⛔️ Error synchronizing credentials into watch session: \(error)") } } @@ -177,3 +189,13 @@ extension WatchDependenciesSynchronizer { syncTrigger.toggle() } } + +extension WatchDependenciesSynchronizer { + enum SyncError: Error { + case missingStoreDetailsOrCredentials + case watchSessionInactiveOrNotPaired + case noDependenciesFound + case invalidApplicationContext + case encodingApplicationContextFailed + } +} From d80df4dfd38585eaa97b88794bd92af9a5a7db29 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 11:55:44 +0700 Subject: [PATCH 3/9] Import analytics protocol to WatchDependenciesSynchronizer --- .../System/WatchDependenciesSynchronizer.swift | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index 2f65f43d5b2..fdf778e2013 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -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. @@ -80,13 +81,13 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { let configurationDependencies = Publishers.CombineLatest($enablesCrashReports, $account) let watchDependencies = Publishers.CombineLatest(requiredDependencies, configurationDependencies) - .map { (required, configuration) -> WatchDependencies? in + .map { [weak self] (required, configuration) -> WatchDependencies? in let (storeID, storeName, credentials, currencySettings) = required let (enablesCrashReports, account) = configuration guard let storeID, let storeName, let credentials else { - analytics.track(.watchSyncingFailed, withError: SyncError.missingStoreDetailsOrCredentials) + self?.analytics.track(.watchSyncingFailed, withError: SyncError.missingStoreDetailsOrCredentials) return nil } @@ -102,11 +103,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { // Syncs the dependencies to the paired counterpart when the session becomes available. Publishers.CombineLatest3(watchDependencies, $isSessionActive, $syncTrigger) - .sink { [watchSession] dependencies, isSessionActive, forceSync in + .sink { [weak self, watchSession] dependencies, isSessionActive, 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 { - analytics.track(.watchSyncingFailed, withError: SyncError.watchSessionInactiveOrNotPaired) + self?.analytics.track(.watchSyncingFailed, withError: SyncError.watchSessionInactiveOrNotPaired) return } @@ -114,7 +115,7 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { // If dependencies is nil, send an empty dictionary. This is most likely a logged out state guard let dependencies else { - analytics.track(.watchSyncingFailed, withError: SyncError.noDependenciesFound) + self?.analytics.track(.watchSyncingFailed, withError: SyncError.noDependenciesFound) return try watchSession.updateApplicationContext([:]) } @@ -128,11 +129,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { try watchSession.updateApplicationContext(jsonObject) } else { - analytics.track(.watchSyncingFailed, withError: SyncError.encodingApplicationContextFailed) + self?.analytics.track(.watchSyncingFailed, withError: SyncError.encodingApplicationContextFailed) DDLogError("⛔️ Unable to encode watch dependencies for synchronization. Resulting object is not a dictionary") } } catch { - analytics.track(.watchSyncingFailed, withError: error) + self?.analytics.track(.watchSyncingFailed, withError: error) DDLogError("⛔️ Error synchronizing credentials into watch session: \(error)") } } @@ -167,7 +168,7 @@ extension WatchDependenciesSynchronizer { let analyticEvent = WooAnalyticsStat(rawValue: rawEvent) else { return DDLogError("⛔️ Unsupported watch tracks event: \(userInfo)") } - ServiceLocator.analytics.track(analyticEvent) + analytics.track(analyticEvent) } } From b19d79c8e35700ff89c215dbe33b4daf0624a4ce Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 12:45:41 +0700 Subject: [PATCH 4/9] Update tracking for when syncing fails due to inactive session --- .../WatchDependenciesSynchronizer.swift | 32 ++++++++++--------- WooCommerce/Classes/System/WooConstants.swift | 4 +++ WooCommerce/Woo Watch App/ConnectView.swift | 16 +++++++--- .../PhoneDependenciesSynchronizer.swift | 4 +++ 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index fdf778e2013..89efa884e15 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -81,15 +81,12 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { let configurationDependencies = Publishers.CombineLatest($enablesCrashReports, $account) let watchDependencies = Publishers.CombineLatest(requiredDependencies, configurationDependencies) - .map { [weak self] (required, configuration) -> WatchDependencies? in + .map { (required, configuration) -> WatchDependencies? in let (storeID, storeName, credentials, currencySettings) = required let (enablesCrashReports, account) = configuration - guard let storeID, let storeName, let credentials else { - self?.analytics.track(.watchSyncingFailed, withError: SyncError.missingStoreDetailsOrCredentials) - return nil - } + guard let storeID, let storeName, let credentials else { return nil } return .init(storeID: storeID, storeName: storeName, @@ -107,7 +104,15 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { // 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 { - self?.analytics.track(.watchSyncingFailed, withError: SyncError.watchSessionInactiveOrNotPaired) + self?.analytics.track( + .watchSyncingFailed, + properties: [ + "session_active": isSessionActive, + "session_paired": watchSession.isPaired, + "watch_app_installed": watchSession.isWatchAppInstalled + ], + error: SyncError.watchSessionInactiveOrNotPaired + ) return } @@ -140,11 +145,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { .store(in: &subscriptions) } - func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - DDLogInfo("🔵 WatchSession activated \(activationState)") - self.isSessionActive = activationState == .activated - } - func sessionDidBecomeInactive(_ session: WCSession) { // No op } @@ -162,7 +162,6 @@ 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 { @@ -183,11 +182,14 @@ extension WatchDependenciesSynchronizer { /// When one is identified we should try to re-sync credentials. /// func session(_ session: WCSession, didReceiveMessage message: [String: Any]) { - guard message[WooConstants.watchSyncKey] as? Bool == true else { + if message[WooConstants.watchSyncKey] as? Bool == true { + syncTrigger.toggle() + } else if message[WooConstants.watchSessionActivatedKey] as? Bool == true { + self.isSessionActive = true + DDLogInfo("🔵 WatchSession activated)") + } else { return DDLogError("⛔️ Unsupported sync request message: \(message)") } - - syncTrigger.toggle() } } diff --git a/WooCommerce/Classes/System/WooConstants.swift b/WooCommerce/Classes/System/WooConstants.swift index 88c5fd8031d..b28b00d3ffe 100644 --- a/WooCommerce/Classes/System/WooConstants.swift +++ b/WooCommerce/Classes/System/WooConstants.swift @@ -85,6 +85,10 @@ public enum WooConstants { /// static let watchTracksKey = "watch-tracks-event" + /// Key used to identify when session is activated on the watch. + /// + static let watchSessionActivatedKey = "watch_session_activated" + /// Key used to identify sync request attempt from the watch. /// static let watchSyncKey = "watch-sync-event" diff --git a/WooCommerce/Woo Watch App/ConnectView.swift b/WooCommerce/Woo Watch App/ConnectView.swift index 3414d6cb2f6..eaba8223d98 100644 --- a/WooCommerce/Woo Watch App/ConnectView.swift +++ b/WooCommerce/Woo Watch App/ConnectView.swift @@ -18,6 +18,10 @@ struct ConnectView: View { .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) + Text(Localization.workaroundTip) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + Image(systemName: "bolt.fill") .renderingMode(.original) .resizable() @@ -44,10 +48,14 @@ extension ConnectView { private enum Localization { static let connectMessage = AppLocalizedString( - "watch.connect.messageWithWorkaround", - value: "Open Woo on your iPhone, log into your store, and hold your Watch nearby. " + - "If the issue persists, relaunch the app.", - comment: "Info message when connecting your watch to the phone for the first time with a workaround option." + "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", diff --git a/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift b/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift index 1e73ec9a4c6..f9c8ebc3d8f 100644 --- a/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift +++ b/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift @@ -42,6 +42,10 @@ final class PhoneDependenciesSynchronizer: NSObject, ObservableObject, WCSession /// Get the latest application context when the session activates /// func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + if error == nil { + WCSession.default.sendMessage([WooConstants.watchSessionActivatedKey: true], replyHandler: nil) + } + DispatchQueue.main.async { self.storeDependencies(appContext: session.receivedApplicationContext) self.reloadDependencies() From 328b451c56a85901ad9be1a349481c3e223dae76 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 13:02:34 +0700 Subject: [PATCH 5/9] Restore activationDidCompleteWith method --- .../Classes/System/WatchDependenciesSynchronizer.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index 89efa884e15..142549e3ae1 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -145,6 +145,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { .store(in: &subscriptions) } + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + DDLogInfo("🔵 WatchSession activated \(activationState)") + self.isSessionActive = activationState == .activated + } + func sessionDidBecomeInactive(_ session: WCSession) { // No op } From 5d576857660e532f2ff97a9ce4f651dd97826128 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 13:32:20 +0700 Subject: [PATCH 6/9] Add tracking for activation state --- .../Classes/Analytics/WooAnalyticsStat.swift | 1 + .../System/WatchDependenciesSynchronizer.swift | 13 +++++++------ WooCommerce/Classes/System/WooConstants.swift | 4 ---- .../PhoneDependenciesSynchronizer.swift | 4 ---- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 937fd161afb..53671ccc3b3 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -1250,6 +1250,7 @@ enum WooAnalyticsStat: String { case watchConnectingOpened = "watch_connecting_opened" case watchSyncingFailed = "watch_syncing_failed" case watchMyStoreOpened = "watch_my_store_opened" + case watchActivationCompleted = "watch_activation_completed" case watchOrdersListOpened = "watch_orders_list_opened" case watchPushNotificationTapped = "watch_push_notification_tapped" case watchOrderDetailOpened = "watch_order_detail_opened" diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index 142549e3ae1..5f5db21e254 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -147,6 +147,11 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { DDLogInfo("🔵 WatchSession activated \(activationState)") + analytics.track( + .watchActivationCompleted, + properties: ["state": activationState.rawValue], + error: error + ) self.isSessionActive = activationState == .activated } @@ -187,14 +192,10 @@ extension WatchDependenciesSynchronizer { /// When one is identified we should try to re-sync credentials. /// func session(_ session: WCSession, didReceiveMessage message: [String: Any]) { - if message[WooConstants.watchSyncKey] as? Bool == true { - syncTrigger.toggle() - } else if message[WooConstants.watchSessionActivatedKey] as? Bool == true { - self.isSessionActive = true - DDLogInfo("🔵 WatchSession activated)") - } else { + guard message[WooConstants.watchSyncKey] as? Bool == true else { return DDLogError("⛔️ Unsupported sync request message: \(message)") } + syncTrigger.toggle() } } diff --git a/WooCommerce/Classes/System/WooConstants.swift b/WooCommerce/Classes/System/WooConstants.swift index b28b00d3ffe..88c5fd8031d 100644 --- a/WooCommerce/Classes/System/WooConstants.swift +++ b/WooCommerce/Classes/System/WooConstants.swift @@ -85,10 +85,6 @@ public enum WooConstants { /// static let watchTracksKey = "watch-tracks-event" - /// Key used to identify when session is activated on the watch. - /// - static let watchSessionActivatedKey = "watch_session_activated" - /// Key used to identify sync request attempt from the watch. /// static let watchSyncKey = "watch-sync-event" diff --git a/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift b/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift index f9c8ebc3d8f..1e73ec9a4c6 100644 --- a/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift +++ b/WooCommerce/Woo Watch App/Dependencies/PhoneDependenciesSynchronizer.swift @@ -42,10 +42,6 @@ final class PhoneDependenciesSynchronizer: NSObject, ObservableObject, WCSession /// Get the latest application context when the session activates /// func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - if error == nil { - WCSession.default.sendMessage([WooConstants.watchSessionActivatedKey: true], replyHandler: nil) - } - DispatchQueue.main.async { self.storeDependencies(appContext: session.receivedApplicationContext) self.reloadDependencies() From 57476c14aca284226f4db99353c298bc1aa092c8 Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 15:13:01 +0700 Subject: [PATCH 7/9] Check for activationState in watch session --- .../Classes/Analytics/WooAnalyticsStat.swift | 1 - .../WatchDependenciesSynchronizer.swift | 23 +++++-------------- WooCommerce/Woo Watch App/ConnectView.swift | 11 ++++++--- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index 53671ccc3b3..937fd161afb 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -1250,7 +1250,6 @@ enum WooAnalyticsStat: String { case watchConnectingOpened = "watch_connecting_opened" case watchSyncingFailed = "watch_syncing_failed" case watchMyStoreOpened = "watch_my_store_opened" - case watchActivationCompleted = "watch_activation_completed" case watchOrdersListOpened = "watch_orders_list_opened" case watchPushNotificationTapped = "watch_push_notification_tapped" case watchOrderDetailOpened = "watch_order_detail_opened" diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index 5f5db21e254..d54d67a3104 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -37,10 +37,6 @@ 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 @@ -99,15 +95,17 @@ 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 { [weak self, 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 { + guard watchSession.activationState == .activated, + watchSession.isPaired, + watchSession.isWatchAppInstalled else { self?.analytics.track( .watchSyncingFailed, properties: [ - "session_active": isSessionActive, + "session_active": watchSession.activationState == .activated, "session_paired": watchSession.isPaired, "watch_app_installed": watchSession.isWatchAppInstalled ], @@ -147,12 +145,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { DDLogInfo("🔵 WatchSession activated \(activationState)") - analytics.track( - .watchActivationCompleted, - properties: ["state": activationState.rawValue], - error: error - ) - self.isSessionActive = activationState == .activated } func sessionDidBecomeInactive(_ session: WCSession) { @@ -161,7 +153,6 @@ final class WatchDependenciesSynchronizer: NSObject, WCSessionDelegate { func sessionDidDeactivate(_ session: WCSession) { // Try to guarantee an active session - self.isSessionActive = false watchSession.activate() } } @@ -201,10 +192,8 @@ extension WatchDependenciesSynchronizer { extension WatchDependenciesSynchronizer { enum SyncError: Error { - case missingStoreDetailsOrCredentials case watchSessionInactiveOrNotPaired case noDependenciesFound - case invalidApplicationContext case encodingApplicationContextFailed } } diff --git a/WooCommerce/Woo Watch App/ConnectView.swift b/WooCommerce/Woo Watch App/ConnectView.swift index eaba8223d98..8d106affbca 100644 --- a/WooCommerce/Woo Watch App/ConnectView.swift +++ b/WooCommerce/Woo Watch App/ConnectView.swift @@ -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 @@ -18,9 +20,11 @@ struct ConnectView: View { .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) - Text(Localization.workaroundTip) - .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) @@ -30,6 +34,7 @@ struct ConnectView: View { Button(Localization.itsNotWorking) { synchronizer.requestCredentialSync() + didRequestSyncing = true } } } From 0939fee464db708b791c3d05d0f10749d5da881b Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 16:10:01 +0700 Subject: [PATCH 8/9] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index ba93be880e6..18b91b367bd 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -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: Fix connection issue upon fresh install [https://github.com/woocommerce/woocommerce-ios/pull/15867] 22.7 ----- From c79e386f2abfa596dfde2d3d130e1a82451ceb5d Mon Sep 17 00:00:00 2001 From: Huong Do Date: Fri, 4 Jul 2025 16:15:17 +0700 Subject: [PATCH 9/9] Fix typo --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 18b91b367bd..4c8c0ae15d6 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -7,7 +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: Fix connection issue upon fresh install [https://github.com/woocommerce/woocommerce-ios/pull/15867] +- [*] Watch app: Fixed connection issue upon fresh install [https://github.com/woocommerce/woocommerce-ios/pull/15867] 22.7 -----