diff --git a/Sora/Sora.entitlements b/Sora/Sora.entitlements index ee95ab7..b356c32 100644 --- a/Sora/Sora.entitlements +++ b/Sora/Sora.entitlements @@ -2,6 +2,10 @@ + com.apple.developer.icloud-container-identifiers + + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox com.apple.security.network.client diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index 3f5660a..795a3d1 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -13,6 +13,10 @@ struct SoraApp: App { @StateObject private var moduleManager = ModuleManager() @StateObject private var librarykManager = LibraryManager() + init() { + _ = iCloudSyncManager.shared + } + var body: some Scene { WindowGroup { ContentView() diff --git a/Sora/Utils/ContinueWatching/ContinueWatchingManager.swift b/Sora/Utils/ContinueWatching/ContinueWatchingManager.swift index f8b6452..99a7adb 100644 --- a/Sora/Utils/ContinueWatching/ContinueWatchingManager.swift +++ b/Sora/Utils/ContinueWatching/ContinueWatchingManager.swift @@ -11,7 +11,13 @@ class ContinueWatchingManager { static let shared = ContinueWatchingManager() private let storageKey = "continueWatchingItems" - private init() {} + private init() { + NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil) + } + + @objc private func handleiCloudSync() { + NotificationCenter.default.post(name: .ContinueWatchingDidUpdate, object: nil) + } func save(item: ContinueWatchingItem) { if item.progress >= 0.9 { diff --git a/Sora/Utils/DownloadManager/DownloadManager.swift b/Sora/Utils/DownloadManager/DownloadManager.swift index bb3d207..78d2b08 100644 --- a/Sora/Utils/DownloadManager/DownloadManager.swift +++ b/Sora/Utils/DownloadManager/DownloadManager.swift @@ -9,10 +9,6 @@ import Foundation import FFmpegSupport import UIKit -extension Notification.Name { - static let DownloadManagerStatusUpdate = Notification.Name("DownloadManagerStatusUpdate") -} - class DownloadManager { static let shared = DownloadManager() diff --git a/Sora/Utils/Extensions/Notification+Name.swift b/Sora/Utils/Extensions/Notification+Name.swift new file mode 100644 index 0000000..2cfb5e5 --- /dev/null +++ b/Sora/Utils/Extensions/Notification+Name.swift @@ -0,0 +1,14 @@ +// +// Notification+Name.swift +// Sulfur +// +// Created by Francesco on 17/04/25. +// + +import Foundation + +extension Notification.Name { + static let iCloudSyncDidComplete = Notification.Name("iCloudSyncDidComplete") + static let ContinueWatchingDidUpdate = Notification.Name("ContinueWatchingDidUpdate") + static let DownloadManagerStatusUpdate = Notification.Name("DownloadManagerStatusUpdate") +} diff --git a/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift b/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift new file mode 100644 index 0000000..9a9c3a1 --- /dev/null +++ b/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift @@ -0,0 +1,94 @@ +// +// iCloudSyncManager.swift +// Sulfur +// +// Created by Francesco on 17/04/25. +// + +import UIKit + +class iCloudSyncManager { + static let shared = iCloudSyncManager() + + private let defaultsToSync: [String] = [ + "externalPlayer", + "alwaysLandscape", + "rememberPlaySpeed", + "holdSpeedPlayer", + "skipIncrement", + "skipIncrementHold", + "holdForPauseEnabled", + "skip85Visible", + "doubleTapSeekEnabled", + "selectedModuleId", + "mediaColumnsPortrait", + "mediaColumnsLandscape", + "sendPushUpdates", + "sendTraktUpdates", + "bookmarkedItems", + "continueWatchingItems" + ] + + private init() { + setupSync() + + NotificationCenter.default.addObserver(self, selector: #selector(willEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) + } + + private func setupSync() { + NSUbiquitousKeyValueStore.default.synchronize() + + syncFromiCloud() + + NotificationCenter.default.addObserver(self, selector: #selector(iCloudDidChangeExternally), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default) + + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil) + } + + @objc private func willEnterBackground() { + syncToiCloud() + } + + private func syncFromiCloud() { + let iCloud = NSUbiquitousKeyValueStore.default + let defaults = UserDefaults.standard + + for key in defaultsToSync { + if let value = iCloud.object(forKey: key) { + defaults.set(value, forKey: key) + } + } + + defaults.synchronize() + NotificationCenter.default.post(name: .iCloudSyncDidComplete, object: nil) + } + + private func syncToiCloud() { + let iCloud = NSUbiquitousKeyValueStore.default + let defaults = UserDefaults.standard + + for key in defaultsToSync { + if let value = defaults.object(forKey: key) { + iCloud.set(value, forKey: key) + } + } + + iCloud.synchronize() + } + + @objc private func iCloudDidChangeExternally(_ notification: Notification) { + guard let userInfo = notification.userInfo, + let reason = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int else { + return + } + + if reason == NSUbiquitousKeyValueStoreServerChange || + reason == NSUbiquitousKeyValueStoreInitialSyncChange { + syncFromiCloud() + } + } + + @objc private func userDefaultsDidChange(_ notification: Notification) { + syncToiCloud() + } +} diff --git a/Sora/Views/LibraryView/LibraryManager.swift b/Sora/Views/LibraryView/LibraryManager.swift index 285524e..6e8e3d3 100644 --- a/Sora/Views/LibraryView/LibraryManager.swift +++ b/Sora/Views/LibraryView/LibraryManager.swift @@ -33,6 +33,14 @@ class LibraryManager: ObservableObject { init() { loadBookmarks() + + NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil) + } + + @objc private func handleiCloudSync() { + DispatchQueue.main.async { + self.loadBookmarks() + } } func removeBookmark(item: LibraryItem) { diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index d3bf7f7..8e19b85 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; }; 1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; }; 135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; }; + 136BBE7E2DB102D600906B5E /* iCloudSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136BBE7D2DB102D600906B5E /* iCloudSyncManager.swift */; }; + 136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136BBE7F2DB1038000906B5E /* Notification+Name.swift */; }; 138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; }; 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; }; 139935662D468C450065CEFF /* ModuleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 139935652D468C450065CEFF /* ModuleManager.swift */; }; @@ -96,6 +98,8 @@ 133F55BA2D33B55100E08EEA /* LibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = ""; }; 1359ED132D76F49900C13034 /* finTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = finTopView.swift; sourceTree = ""; }; 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewPlayer.swift; sourceTree = ""; }; + 136BBE7D2DB102D600906B5E /* iCloudSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iCloudSyncManager.swift; sourceTree = ""; }; + 136BBE7F2DB1038000906B5E /* Notification+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Name.swift"; sourceTree = ""; }; 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; 139935652D468C450065CEFF /* ModuleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleManager.swift; sourceTree = ""; }; @@ -257,6 +261,7 @@ 133D7C852D2BE2640075467E /* Utils */ = { isa = PBXGroup; children = ( + 136BBE7C2DB102BE00906B5E /* iCloudSyncManager */, 13DB7CEA2D7DED50004371D3 /* DownloadManager */, 13C0E5E82D5F85DD00E7F619 /* ContinueWatching */, 13103E8C2D58E037000F0673 /* SkeletonCells */, @@ -275,6 +280,7 @@ isa = PBXGroup; children = ( 73D164D42D8B5B340011A360 /* JavaScriptCore+Extensions.swift */, + 136BBE7F2DB1038000906B5E /* Notification+Name.swift */, 1327FBA82D758DEA00FC6689 /* UIDevice+Model.swift */, 133D7C872D2BE2640075467E /* URLSession.swift */, 1359ED132D76F49900C13034 /* finTopView.swift */, @@ -315,6 +321,14 @@ path = LibraryView; sourceTree = ""; }; + 136BBE7C2DB102BE00906B5E /* iCloudSyncManager */ = { + isa = PBXGroup; + children = ( + 136BBE7D2DB102D600906B5E /* iCloudSyncManager.swift */, + ); + path = iCloudSyncManager; + sourceTree = ""; + }; 1384DCDF2D89BE870094797A /* Helpers */ = { isa = PBXGroup; children = ( @@ -539,7 +553,9 @@ 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 132AF1252D9995F900A0140B /* JSController-Search.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, + 136BBE7E2DB102D600906B5E /* iCloudSyncManager.swift in Sources */, 1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */, + 136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */, 13DB46902D900A38008CBC03 /* URL.swift in Sources */, 130C6BFA2D53AB1F00DC1432 /* SettingsViewData.swift in Sources */, 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */,