diff --git a/Projects/App/iOS/Sources/Application/AppDelegate.swift b/Projects/App/iOS/Sources/Application/AppDelegate.swift index 325644f6..7ddd56a4 100644 --- a/Projects/App/iOS/Sources/Application/AppDelegate.swift +++ b/Projects/App/iOS/Sources/Application/AppDelegate.swift @@ -16,6 +16,8 @@ import UserDefaultsClient import WatchConnectivity import WidgetKit +private let watchSyncNotification = Notification.Name("TodayWhatWatchSyncDidRequest") + final class AppDelegate: UIResponder, UIApplicationDelegate { @Dependency(\.userDefaultsClient) var userDefaultsClient @Dependency(\.localDatabaseClient) var localDatabaseClient @@ -44,6 +46,12 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { session.delegate = self session.activate() } + NotificationCenter.default.addObserver( + self, + selector: #selector(handleWatchSyncNotification(_:)), + name: watchSyncNotification, + object: nil + ) TWLog.setUserProperty(property: .activeWatch, value: WCSession.default.isWatchAppInstalled ? true : false) if let schoolTypeRawString = self.userDefaultsClient.getValue(.schoolType) as? String, @@ -104,33 +112,7 @@ extension AppDelegate: WCSessionDelegate { activationDidCompleteWith activationState: WCSessionActivationState, error: Error? ) { - guard - let type = userDefaultsClient.getValue(.schoolType) as? String, - let code = userDefaultsClient.getValue(.schoolCode) as? String, - let orgCode = userDefaultsClient.getValue(.orgCode) as? String, - let grade = userDefaultsClient.getValue(.grade) as? Int, - let `class` = userDefaultsClient.getValue(.class) as? Int - else { - return - } - let isOnModifiedTimeTable = userDefaultsClient.getValue(.isOnModifiedTimeTable) as? Bool ?? false - let timeTables = try? localDatabaseClient.readRecords(as: ModifiedTimeTableLocalEntity.self) - var dict: [String: Any] = [ - "type": type, - "code": code, - "orgCode": orgCode, - "grade": grade, - "class": `class`, - "isOnModifiedTimeTable": isOnModifiedTimeTable, - "timeTables": encodeTimeTables(timeTables: timeTables ?? []) - ] - if let major = userDefaultsClient.getValue(.major) as? String { - dict["major"] = major - } - - session.sendMessage(dict, replyHandler: nil) { error in - TWLog.error(error.localizedDescription) - } + pushCurrentWatchData() } func session( @@ -138,30 +120,7 @@ extension AppDelegate: WCSessionDelegate { didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void ) { - guard - let type = userDefaultsClient.getValue(.schoolType) as? String, - let code = userDefaultsClient.getValue(.schoolCode) as? String, - let orgCode = userDefaultsClient.getValue(.orgCode) as? String, - let grade = userDefaultsClient.getValue(.grade) as? Int, - let `class` = userDefaultsClient.getValue(.class) as? Int - else { - return - } - let isOnModifiedTimeTable = userDefaultsClient.getValue(.isOnModifiedTimeTable) as? Bool ?? false - let timeTables = try? localDatabaseClient.readRecords(as: ModifiedTimeTableLocalEntity.self) - var reply: [String: Any] = [ - "type": type, - "code": code, - "orgCode": orgCode, - "grade": grade, - "class": `class`, - "isOnModifiedTimeTable": isOnModifiedTimeTable, - "timeTables": encodeTimeTables(timeTables: timeTables ?? []) - ] - if let major = userDefaultsClient.getValue(.major) as? String { - reply["major"] = major - } - + guard let reply = buildWatchPayload() else { return } replyHandler(reply) } @@ -191,6 +150,57 @@ extension AppDelegate: WCSessionDelegate { // swiftlint: enable force_try return data } + + private func buildWatchPayload() -> [String: Any]? { + guard + let type = userDefaultsClient.getValue(.schoolType) as? String, + let code = userDefaultsClient.getValue(.schoolCode) as? String, + let orgCode = userDefaultsClient.getValue(.orgCode) as? String, + let grade = userDefaultsClient.getValue(.grade) as? Int, + let `class` = userDefaultsClient.getValue(.class) as? Int + else { + return nil + } + + let isOnModifiedTimeTable = userDefaultsClient.getValue(.isOnModifiedTimeTable) as? Bool ?? false + let timeTables = try? localDatabaseClient.readRecords(as: ModifiedTimeTableLocalEntity.self) + var payload: [String: Any] = [ + "type": type, + "code": code, + "orgCode": orgCode, + "grade": grade, + "class": `class`, + "isOnModifiedTimeTable": isOnModifiedTimeTable, + "timeTables": encodeTimeTables(timeTables: timeTables ?? []) + ] + if let major = userDefaultsClient.getValue(.major) as? String { + payload["major"] = major + } + return payload + } + + @objc + private func handleWatchSyncNotification(_ notification: Notification) { + pushCurrentWatchData() + } + + private func pushCurrentWatchData() { + guard let payload = buildWatchPayload() else { return } + + do { + try session.updateApplicationContext(payload) + } catch { + TWLog.error(error) + } + + guard session.activationState == .activated, session.isWatchAppInstalled, session.isReachable else { + return + } + + session.sendMessage(payload, replyHandler: nil) { error in + TWLog.error(error.localizedDescription) + } + } } private extension AppDelegate { diff --git a/Projects/App/watchOS/Sources/Manager/WatchSessionManager.swift b/Projects/App/watchOS/Sources/Manager/WatchSessionManager.swift index 99bc3038..e6da39e7 100644 --- a/Projects/App/watchOS/Sources/Manager/WatchSessionManager.swift +++ b/Projects/App/watchOS/Sources/Manager/WatchSessionManager.swift @@ -7,6 +7,7 @@ import WatchConnectivity final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { @Dependency(\.userDefaultsClient) var userDefaultsClient @Dependency(\.localDatabaseClient) var localDatabaseClient + @Published private(set) var syncVersion: Int = 0 var isReachable: Bool { session.isReachable @@ -24,34 +25,7 @@ final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { error: Error? ) { sendMessage(message: [:]) { [weak self] items in - guard let self else { return } - guard - let code = items["code"] as? String, - let orgCode = items["orgCode"] as? String, - let grade = items["grade"] as? Int, - let `class` = items["class"] as? Int, - let type = items["type"] as? String, - let isOnModifiedTimeTable = items["isOnModifiedTimeTable"] as? Bool, - let timeTablesData = items["timeTables"] as? Data - else { - return - } - let timeTables = self.decodeTimeTables(data: timeTablesData) - let dict: [UserDefaultsKeys: Any] = [ - .grade: grade, - .class: `class`, - .schoolType: type, - .orgCode: orgCode, - .schoolCode: code, - .isOnModifiedTimeTable: isOnModifiedTimeTable - ] - dict.forEach { key, value in - self.userDefaultsClient.setValue(key, value) - } - if let major = items["major"] as? String { - self.userDefaultsClient.setValue(.major, major) - } - try? self.localDatabaseClient.save(records: timeTables) + self?.applyIncomingItems(items) } } @@ -78,33 +52,15 @@ final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { replyHandler: @escaping ([String: Any]) -> Void ) { print("RECEIVE : \(message)") - guard - let code = message["code"] as? String, - let orgCode = message["orgCode"] as? String, - let grade = message["grade"] as? Int, - let `class` = message["class"] as? Int, - let type = message["type"] as? String, - let isOnModifiedTimeTable = message["isOnModifiedTimeTable"] as? Bool, - let timeTablesData = message["timeTables"] as? Data - else { - return - } - let timeTables = decodeTimeTables(data: timeTablesData) - let dict: [UserDefaultsKeys: Any] = [ - .grade: grade, - .class: `class`, - .schoolType: type, - .orgCode: orgCode, - .schoolCode: code, - .isOnModifiedTimeTable: isOnModifiedTimeTable - ] - dict.forEach { key, value in - self.userDefaultsClient.setValue(key, value) - } - if let major = message["major"] as? String { - self.userDefaultsClient.setValue(.major, major) - } - try? self.localDatabaseClient.save(records: timeTables) + applyIncomingItems(message) + } + + func session( + _ session: WCSession, + didReceiveMessage message: [String: Any] + ) { + print("RECEIVE : \(message)") + applyIncomingItems(message) } func sendMessage( @@ -123,10 +79,52 @@ final class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject { session.sendMessage(message, replyHandler: reply, errorHandler: error) } + func session( + _ session: WCSession, + didReceiveApplicationContext applicationContext: [String: Any] + ) { + applyIncomingItems(applicationContext) + } + // swiftlint: disable force_try private func decodeTimeTables(data: Data) -> [ModifiedTimeTableLocalEntity] { let entities = try! JSONDecoder().decode([ModifiedTimeTableLocalEntity].self, from: data) return entities } + + private func applyIncomingItems(_ items: [String: Any]) { + guard + let code = items["code"] as? String, + let orgCode = items["orgCode"] as? String, + let grade = items["grade"] as? Int, + let `class` = items["class"] as? Int, + let type = items["type"] as? String, + let isOnModifiedTimeTable = items["isOnModifiedTimeTable"] as? Bool, + let timeTablesData = items["timeTables"] as? Data + else { + return + } + + let timeTables = decodeTimeTables(data: timeTablesData) + let dict: [UserDefaultsKeys: Any] = [ + .grade: grade, + .class: `class`, + .schoolType: type, + .orgCode: orgCode, + .schoolCode: code, + .isOnModifiedTimeTable: isOnModifiedTimeTable + ] + dict.forEach { key, value in + self.userDefaultsClient.setValue(key, value) + } + if let major = items["major"] as? String { + self.userDefaultsClient.setValue(.major, major) + } + try? self.localDatabaseClient.deleteAll(record: ModifiedTimeTableLocalEntity.self) + try? self.localDatabaseClient.save(records: timeTables) + DispatchQueue.main.async { + self.syncVersion += 1 + } + } // swiftlint: enable force_try } diff --git a/Projects/App/watchOS/Sources/Scenes/Main/MainView.swift b/Projects/App/watchOS/Sources/Scenes/Main/MainView.swift index a76a1569..b852bf15 100644 --- a/Projects/App/watchOS/Sources/Scenes/Main/MainView.swift +++ b/Projects/App/watchOS/Sources/Scenes/Main/MainView.swift @@ -3,6 +3,7 @@ import SwiftUI struct MainView: View { @StateObject var viewModel = MainViewModel() + @StateObject var watchSessionManager = WatchSessionManager.shared @State var isPresentedOption = false private let mainColor: Color = Color("Main") private let subColor: Color = Color("Sub") @@ -104,6 +105,11 @@ struct MainView: View { .task { await viewModel.loadData() } + .onChange(of: watchSessionManager.syncVersion) { _ in + Task { + await viewModel.loadData() + } + } .refreshable { Task { await viewModel.loadData() diff --git a/Projects/Feature/ModifyTimeTableFeature/Sources/ModifyTimeTableCore.swift b/Projects/Feature/ModifyTimeTableFeature/Sources/ModifyTimeTableCore.swift index 6b65ddd2..873fa634 100644 --- a/Projects/Feature/ModifyTimeTableFeature/Sources/ModifyTimeTableCore.swift +++ b/Projects/Feature/ModifyTimeTableFeature/Sources/ModifyTimeTableCore.swift @@ -137,6 +137,10 @@ public struct ModifyTimeTableCore: Reducer { WidgetCenter.shared.reloadTimelines(ofKind: "TodayWhatTimeTableWidget") state.isShowingSuccessToast = true userDefaultsClient.setValue(.isOnModifiedTimeTable, true) + NotificationCenter.default.post( + name: Notification.Name("TodayWhatWatchSyncDidRequest"), + object: nil + ) case let .toastDismissed(dismissed): state.isShowingSuccessToast = dismissed diff --git a/Projects/Feature/SettingsFeature/Sources/SettingsCore.swift b/Projects/Feature/SettingsFeature/Sources/SettingsCore.swift index de6d4e12..413ca89d 100644 --- a/Projects/Feature/SettingsFeature/Sources/SettingsCore.swift +++ b/Projects/Feature/SettingsFeature/Sources/SettingsCore.swift @@ -3,6 +3,7 @@ import AllergySettingFeature import BaseFeature import ComposableArchitecture import DeviceClient +import Foundation import ITunesClient import ModifyTimeTableFeature import SchoolSettingFeature @@ -100,6 +101,10 @@ public struct SettingsCore: Reducer { case let .isOnModifiedTimeTableChagned(isOnModifiedTimeTable): state.isOnModifiedTimeTable = isOnModifiedTimeTable userDefaultsClient.setValue(.isOnModifiedTimeTable, isOnModifiedTimeTable) + NotificationCenter.default.post( + name: Notification.Name("TodayWhatWatchSyncDidRequest"), + object: nil + ) let log = IsOnModifiedTimeTableToggledEventLog(isOnModifiedTimeTable: isOnModifiedTimeTable) TWLog.event(log)