summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-04-01 13:59:52 +0000
committerFuwn <[email protected]>2026-04-01 13:59:52 +0000
commitddaa9cbfe0cc8d254d4901ad192918d91dd627d4 (patch)
treeb0d1cf6642ae97ce41ff570f2bcd4a06b359685e
parentFix iOS install recipe app path resolution (diff)
downloadsora-testing-ddaa9cbfe0cc8d254d4901ad192918d91dd627d4.tar.xz
sora-testing-ddaa9cbfe0cc8d254d4901ad192918d91dd627d4.zip
Migrate settings manager to Swift Observation
-rw-r--r--Sora/App/SoraApp.swift6
-rw-r--r--Sora/Data/Booru/Post/BooruPost.swift2
-rw-r--r--Sora/Data/Settings/SettingsManager.swift402
-rw-r--r--Sora/Views/BookmarkMenuButtonView.swift3
-rw-r--r--Sora/Views/BookmarksView.swift5
-rw-r--r--Sora/Views/FavoriteMenuButtonView.swift3
-rw-r--r--Sora/Views/FavoritePostThumbnailView.swift5
-rw-r--r--Sora/Views/FavoritesView.swift5
-rw-r--r--Sora/Views/Generic/GenericItemView.swift3
-rw-r--r--Sora/Views/Generic/GenericListView.swift3
-rw-r--r--Sora/Views/MainView.swift74
-rw-r--r--Sora/Views/Post/Details/Carousel/PostDetailsCarouselView.swift3
-rw-r--r--Sora/Views/Post/Details/PostDetailsImageView.swift3
-rw-r--r--Sora/Views/Post/Details/PostDetailsTagsView.swift5
-rw-r--r--Sora/Views/Post/Details/PostDetailsView.swift3
-rw-r--r--Sora/Views/Post/Grid/PostGridBookmarkButtonView.swift3
-rw-r--r--Sora/Views/Post/Grid/PostGridFavoriteButtonView.swift5
-rw-r--r--Sora/Views/Post/Grid/PostGridSearchHistoryView.swift5
-rw-r--r--Sora/Views/Post/Grid/PostGridThumbnailView.swift3
-rw-r--r--Sora/Views/Post/Grid/PostGridView.swift3
-rw-r--r--Sora/Views/Settings/Collections/SettingsCollectionsListView.swift3
-rw-r--r--Sora/Views/Settings/Collections/SettingsCollectionsView.swift3
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift5
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionDebugView.swift3
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionDetailsView.swift7
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionImportExportView.swift3
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionProviderView.swift11
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionSearchView.swift5
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionSettingsView.swift11
-rw-r--r--Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift7
-rw-r--r--Sora/Views/Settings/SettingsView.swift2
-rw-r--r--SoraTests/SettingsManagerSyncTests.swift6
32 files changed, 412 insertions, 198 deletions
diff --git a/Sora/App/SoraApp.swift b/Sora/App/SoraApp.swift
index 8fdfb4d..77a50c7 100644
--- a/Sora/App/SoraApp.swift
+++ b/Sora/App/SoraApp.swift
@@ -12,18 +12,18 @@ func debugPrint(
@main
struct SoraApp: App {
- @StateObject private var settings = SettingsManager()
+ @State private var settings = SettingsManager()
@ViewBuilder
private func settingsContent() -> some View {
SettingsView()
- .environmentObject(settings)
+ .environment(settings)
}
var body: some Scene {
WindowGroup {
MainView()
- .environmentObject(settings)
+ .environment(settings)
}
#if os(macOS)
diff --git a/Sora/Data/Booru/Post/BooruPost.swift b/Sora/Data/Booru/Post/BooruPost.swift
index 458faa6..fb8ab08 100644
--- a/Sora/Data/Booru/Post/BooruPost.swift
+++ b/Sora/Data/Booru/Post/BooruPost.swift
@@ -1,6 +1,6 @@
import Foundation
-struct BooruPost: Identifiable, Hashable {
+struct BooruPost: Identifiable, Hashable, Sendable {
let id: String
let height: Int
let score: String
diff --git a/Sora/Data/Settings/SettingsManager.swift b/Sora/Data/Settings/SettingsManager.swift
index f59db1a..32a1b86 100644
--- a/Sora/Data/Settings/SettingsManager.swift
+++ b/Sora/Data/Settings/SettingsManager.swift
@@ -1,95 +1,158 @@
// swiftlint:disable file_length
+import Observation
@preconcurrency import SwiftUI
@MainActor
-class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_length
+@Observable
+class SettingsManager { // swiftlint:disable:this type_body_length
+ private enum StorageKey {
+ static let detailViewQuality = "detailViewQuality"
+ static let thumbnailQuality = "thumbnailQuality"
+ static let searchSuggestionsMode = "searchSuggestionsMode"
+ static let thumbnailGridColumns = "thumbnailGridColumns"
+ static let enableShareShortcut = "enableShareShortcut"
+ static let displayDetailsInformationBar = "displayDetailsInformationBar"
+ static let preloadedCarouselImages = "preloadedCarouselImages"
+ static let enableSync = "enableSync"
+ static let alternativeThumbnailGrid = "alternativeThumbnailGrid"
+ static let uniformThumbnailGrid = "uniformThumbnailGrid"
+ static let showHeldMoebooruPosts = "showHeldMoebooruPosts"
+ static let sendBooruUserAgent = "sendBooruUserAgent"
+ static let customBooruUserAgent = "customBooruUserAgent"
+ static let saveTagsToFile = "saveTagsToFile"
+ static let bookmarks = "bookmarks"
+ static let favorites = "favorites"
+ static let displayRatings = "displayRatings"
+ static let blurRatings = "blurRatings"
+ static let searchHistory = "searchHistory"
+ static let preferredBooru = "preferredBooru"
+ static let customProviders = "customProviders"
+ static let folders = "folders"
+ static let providerCredentials = "providerCredentials"
+ }
+
+ @ObservationIgnored private let userDefaults: UserDefaults
+
// MARK: - Stored Properties
- @AppStorage("detailViewQuality")
- var detailViewQuality: BooruPostFileType = .original
+ var detailViewQuality: BooruPostFileType {
+ didSet { userDefaults.set(detailViewQuality.rawValue, forKey: StorageKey.detailViewQuality) }
+ }
- @AppStorage("thumbnailQuality")
- private var _thumbnailQuality: BooruPostFileType = .preview
+ var thumbnailQuality: BooruPostFileType {
+ didSet { userDefaults.set(thumbnailQuality.rawValue, forKey: StorageKey.thumbnailQuality) }
+ }
- @AppStorage("searchSuggestionsMode")
- var searchSuggestionsMode: SettingsSearchSuggestionsMode = .disabled
+ var searchSuggestionsMode: SettingsSearchSuggestionsMode {
+ didSet {
+ userDefaults.set(
+ searchSuggestionsMode.rawValue,
+ forKey: StorageKey.searchSuggestionsMode
+ )
+ }
+ }
- @AppStorage("thumbnailGridColumns")
- var thumbnailGridColumns = 2
+ var thumbnailGridColumns: Int {
+ didSet { userDefaults.set(thumbnailGridColumns, forKey: StorageKey.thumbnailGridColumns) }
+ }
- @AppStorage("enableShareShortcut")
- var enableShareShortcut = false
+ var enableShareShortcut: Bool {
+ didSet { userDefaults.set(enableShareShortcut, forKey: StorageKey.enableShareShortcut) }
+ }
- @AppStorage("displayDetailsInformationBar")
- var displayDetailsInformationBar = true
+ var displayDetailsInformationBar: Bool {
+ didSet {
+ userDefaults.set(
+ displayDetailsInformationBar,
+ forKey: StorageKey.displayDetailsInformationBar
+ )
+ }
+ }
- @AppStorage("preloadedCarouselImages")
- var preloadedCarouselImages = 3
+ var preloadedCarouselImages: Int {
+ didSet { userDefaults.set(preloadedCarouselImages, forKey: StorageKey.preloadedCarouselImages) }
+ }
- @AppStorage("enableSync")
- var enableSync: Bool = false
+ var enableSync: Bool {
+ didSet { userDefaults.set(enableSync, forKey: StorageKey.enableSync) }
+ }
- @AppStorage("alternativeThumbnailGrid")
- var alternativeThumbnailGrid = false
+ var alternativeThumbnailGrid: Bool {
+ didSet {
+ userDefaults.set(alternativeThumbnailGrid, forKey: StorageKey.alternativeThumbnailGrid)
+ }
+ }
- @AppStorage("uniformThumbnailGrid")
- private var _uniformThumbnailGrid: Bool = false
+ var uniformThumbnailGrid: Bool {
+ didSet { userDefaults.set(uniformThumbnailGrid, forKey: StorageKey.uniformThumbnailGrid) }
+ }
- @AppStorage("showHeldMoebooruPosts")
- var showHeldMoebooruPosts = false
+ var showHeldMoebooruPosts: Bool {
+ didSet { userDefaults.set(showHeldMoebooruPosts, forKey: StorageKey.showHeldMoebooruPosts) }
+ }
- @AppStorage("sendBooruUserAgent")
- var sendBooruUserAgent = true
+ var sendBooruUserAgent: Bool {
+ didSet { userDefaults.set(sendBooruUserAgent, forKey: StorageKey.sendBooruUserAgent) }
+ }
- @AppStorage("customBooruUserAgent")
- var customBooruUserAgent = ""
+ var customBooruUserAgent: String {
+ didSet { userDefaults.set(customBooruUserAgent, forKey: StorageKey.customBooruUserAgent) }
+ }
- private var syncObservation: NSObjectProtocol?
+ @ObservationIgnored private var syncObservation: NSObjectProtocol?
#if os(macOS)
- @AppStorage("saveTagsToFile")
- var saveTagsToFile = false
+ var saveTagsToFile: Bool {
+ didSet { userDefaults.set(saveTagsToFile, forKey: StorageKey.saveTagsToFile) }
+ }
#endif
// MARK: - Private Properties
- private var bookmarksCache: [SettingsBookmark] = []
- private var favoritesCache: [SettingsFavoritePost] = []
- private var searchHistoryCache: [BooruSearchQuery] = []
- private var blurRatingsCache: [BooruRating] = []
- private var displayRatingsCache: [BooruRating] = []
- private var uniformThumbnailGridCache: Bool = false
- private var thumbnailQualityCache: BooruPostFileType = .preview
- private var isUpdatingCache = false
- private var pendingSyncKeys: Set<SettingsSyncKey> = []
- private let syncCoordinator = SettingsSyncCoordinator()
+ private var bookmarksCache: [SettingsBookmark]
+ private var favoritesCache: [SettingsFavoritePost]
+ private var searchHistoryCache: [BooruSearchQuery]
+ private var blurRatingsCache: [BooruRating]
+ private var displayRatingsCache: [BooruRating]
+ @ObservationIgnored private var isUpdatingCache = false
+ @ObservationIgnored private var pendingSyncKeys: Set<SettingsSyncKey> = []
+ @ObservationIgnored private let syncCoordinator = SettingsSyncCoordinator()
// MARK: - Codable Properties
- @AppStorage("bookmarks")
- private var bookmarksData = Data()
+ private var bookmarksData: Data {
+ didSet { userDefaults.set(bookmarksData, forKey: StorageKey.bookmarks) }
+ }
- @AppStorage("favorites")
- private var favoritesData = Data()
+ private var favoritesData: Data {
+ didSet { userDefaults.set(favoritesData, forKey: StorageKey.favorites) }
+ }
- @AppStorage("displayRatings")
- private var displayRatingsData = SettingsManager.encode(BooruRating.allCases) ?? Data()
+ private var displayRatingsData: Data {
+ didSet { userDefaults.set(displayRatingsData, forKey: StorageKey.displayRatings) }
+ }
- @AppStorage("blurRatings")
- private var blurRatingsData = SettingsManager.encode([.explicit as BooruRating]) ?? Data()
+ private var blurRatingsData: Data {
+ didSet { userDefaults.set(blurRatingsData, forKey: StorageKey.blurRatings) }
+ }
- @AppStorage("searchHistory")
- private var searchHistoryData = Data()
+ private var searchHistoryData: Data {
+ didSet { userDefaults.set(searchHistoryData, forKey: StorageKey.searchHistory) }
+ }
- @AppStorage("preferredBooru")
- private var preferredBooruData = Data()
+ private var preferredBooruData: Data {
+ didSet { userDefaults.set(preferredBooruData, forKey: StorageKey.preferredBooru) }
+ }
- @AppStorage("customProviders")
- private var customProvidersData = Data()
+ private var customProvidersData: Data {
+ didSet { userDefaults.set(customProvidersData, forKey: StorageKey.customProviders) }
+ }
- @AppStorage("folders")
- private var foldersData = Data()
+ private var foldersData: Data {
+ didSet { userDefaults.set(foldersData, forKey: StorageKey.folders) }
+ }
- @AppStorage("providerCredentials")
- private var providerCredentialsData = Data()
+ private var providerCredentialsData: Data {
+ didSet { userDefaults.set(providerCredentialsData, forKey: StorageKey.providerCredentials) }
+ }
// MARK: - Computed Properties
var bookmarks: [SettingsBookmark] {
@@ -107,7 +170,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "bookmarks",
- localData: $bookmarksData,
+ localData: storageBinding(for: \.bookmarksData),
newValue: sortedBookmarks,
encodedData: payload?.encodedData
) { $0 }
@@ -134,7 +197,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "favorites",
- localData: $favoritesData,
+ localData: storageBinding(for: \.favoritesData),
newValue: sortedFavorites,
encodedData: payload?.encodedData
) { $0 }
@@ -146,24 +209,6 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
}
}
- var uniformThumbnailGrid: Bool {
- get { uniformThumbnailGridCache }
-
- set {
- _uniformThumbnailGrid = newValue
- uniformThumbnailGridCache = newValue
- }
- }
-
- var thumbnailQuality: BooruPostFileType {
- get { thumbnailQualityCache }
-
- set {
- _thumbnailQuality = newValue
- thumbnailQualityCache = newValue
- }
- }
-
var displayRatings: [BooruRating] {
get { displayRatingsCache }
@@ -209,7 +254,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "searchHistory",
- localData: $searchHistoryData,
+ localData: storageBinding(for: \.searchHistoryData),
newValue: sortedSearchHistory,
encodedData: payload?.encodedData
) { $0 }
@@ -248,7 +293,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "customProviders",
- localData: $customProvidersData,
+ localData: storageBinding(for: \.customProvidersData),
newValue: newValue,
) { $0 }
pendingSyncKeys.insert(.customProviders)
@@ -275,7 +320,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "folders",
- localData: $foldersData,
+ localData: storageBinding(for: \.foldersData),
newValue: newValue,
) { $0 }
}
@@ -315,7 +360,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "providerAPIKeys",
- localData: $providerCredentialsData,
+ localData: storageBinding(for: \.providerCredentialsData),
newValue: mergedCredentials,
) { $0 }
}
@@ -361,7 +406,97 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
}
// MARK: - Initialisation
- init() {
+ init(userDefaults: UserDefaults = .standard) {
+ self.userDefaults = userDefaults
+ detailViewQuality = Self.stringValue(
+ forKey: StorageKey.detailViewQuality,
+ defaultValue: .original,
+ userDefaults: userDefaults
+ )
+ thumbnailQuality = Self.stringValue(
+ forKey: StorageKey.thumbnailQuality,
+ defaultValue: .preview,
+ userDefaults: userDefaults
+ )
+ searchSuggestionsMode = Self.stringValue(
+ forKey: StorageKey.searchSuggestionsMode,
+ defaultValue: .disabled,
+ userDefaults: userDefaults
+ )
+ thumbnailGridColumns = Self.integerValue(
+ forKey: StorageKey.thumbnailGridColumns,
+ defaultValue: 2,
+ userDefaults: userDefaults
+ )
+ enableShareShortcut = Self.boolValue(
+ forKey: StorageKey.enableShareShortcut,
+ defaultValue: false,
+ userDefaults: userDefaults
+ )
+ displayDetailsInformationBar = Self.boolValue(
+ forKey: StorageKey.displayDetailsInformationBar,
+ defaultValue: true,
+ userDefaults: userDefaults
+ )
+ preloadedCarouselImages = Self.integerValue(
+ forKey: StorageKey.preloadedCarouselImages,
+ defaultValue: 3,
+ userDefaults: userDefaults
+ )
+ enableSync = Self.boolValue(
+ forKey: StorageKey.enableSync,
+ defaultValue: false,
+ userDefaults: userDefaults
+ )
+ alternativeThumbnailGrid = Self.boolValue(
+ forKey: StorageKey.alternativeThumbnailGrid,
+ defaultValue: false,
+ userDefaults: userDefaults
+ )
+ uniformThumbnailGrid = Self.boolValue(
+ forKey: StorageKey.uniformThumbnailGrid,
+ defaultValue: false,
+ userDefaults: userDefaults
+ )
+ showHeldMoebooruPosts = Self.boolValue(
+ forKey: StorageKey.showHeldMoebooruPosts,
+ defaultValue: false,
+ userDefaults: userDefaults
+ )
+ sendBooruUserAgent = Self.boolValue(
+ forKey: StorageKey.sendBooruUserAgent,
+ defaultValue: true,
+ userDefaults: userDefaults
+ )
+ customBooruUserAgent = userDefaults.string(forKey: StorageKey.customBooruUserAgent) ?? ""
+ bookmarksCache = []
+ favoritesCache = []
+ searchHistoryCache = []
+ blurRatingsCache = []
+ displayRatingsCache = []
+ bookmarksData = userDefaults.data(forKey: StorageKey.bookmarks) ?? Data()
+ favoritesData = userDefaults.data(forKey: StorageKey.favorites) ?? Data()
+ displayRatingsData =
+ userDefaults.data(forKey: StorageKey.displayRatings)
+ ?? Self.encode(BooruRating.allCases)
+ ?? Data()
+ blurRatingsData =
+ userDefaults.data(forKey: StorageKey.blurRatings)
+ ?? Self.encode([.explicit as BooruRating])
+ ?? Data()
+ searchHistoryData = userDefaults.data(forKey: StorageKey.searchHistory) ?? Data()
+ preferredBooruData = userDefaults.data(forKey: StorageKey.preferredBooru) ?? Data()
+ customProvidersData = userDefaults.data(forKey: StorageKey.customProviders) ?? Data()
+ foldersData = userDefaults.data(forKey: StorageKey.folders) ?? Data()
+ providerCredentialsData = userDefaults.data(forKey: StorageKey.providerCredentials) ?? Data()
+ #if os(macOS)
+ saveTagsToFile = Self.boolValue(
+ forKey: StorageKey.saveTagsToFile,
+ defaultValue: false,
+ userDefaults: userDefaults
+ )
+ #endif
+
syncObservation = NotificationCenter.default.addObserver(
forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: NSUbiquitousKeyValueStore.default,
@@ -377,9 +512,6 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
loadSearchHistoryCache()
loadDisplayRatingsCache()
loadBlurRatingsCache()
-
- uniformThumbnailGridCache = _uniformThumbnailGrid
- thumbnailQualityCache = _thumbnailQuality
}
func updateBookmarks(_ newValue: [SettingsBookmark], encodedData: Data? = nil) async {
@@ -394,7 +526,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "bookmarks",
- localData: $bookmarksData,
+ localData: storageBinding(for: \.bookmarksData),
newValue: sortedBookmarks,
encodedData: resolvedEncodedData
) { $0 }
@@ -418,7 +550,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "favorites",
- localData: $favoritesData,
+ localData: storageBinding(for: \.favoritesData),
newValue: sortedFavorites,
encodedData: resolvedEncodedData
) { $0 }
@@ -442,7 +574,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "searchHistory",
- localData: $searchHistoryData,
+ localData: storageBinding(for: \.searchHistoryData),
newValue: sortedSearchHistory,
encodedData: payload?.encodedData
) { $0 }
@@ -498,20 +630,62 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "providerAPIKeys",
- localData: $providerCredentialsData,
+ localData: storageBinding(for: \.providerCredentialsData),
newValue: mergedCredentials,
) { $0 }
}
// MARK: - Private Helpers
+ @MainActor
private static func encode<T: Encodable>(_ value: T) -> Data? {
SettingsCodec.encode(value)
}
+ @MainActor
private static func decode<T: Decodable>(_ type: T.Type, from data: Data) -> T? {
SettingsCodec.decode(type, from: data)
}
+ @MainActor
+ private static func boolValue(
+ forKey key: String,
+ defaultValue: Bool,
+ userDefaults: UserDefaults
+ ) -> Bool {
+ (userDefaults.object(forKey: key) as? Bool) ?? defaultValue
+ }
+
+ @MainActor
+ private static func integerValue(
+ forKey key: String,
+ defaultValue: Int,
+ userDefaults: UserDefaults
+ ) -> Int {
+ (userDefaults.object(forKey: key) as? Int) ?? defaultValue
+ }
+
+ @MainActor
+ private static func stringValue<Value: RawRepresentable>(
+ forKey key: String,
+ defaultValue: Value,
+ userDefaults: UserDefaults
+ ) -> Value where Value.RawValue == String {
+ guard let rawValue = userDefaults.string(forKey: key) else {
+ return defaultValue
+ }
+
+ return Value(rawValue: rawValue) ?? defaultValue
+ }
+
+ private func storageBinding(for keyPath: ReferenceWritableKeyPath<SettingsManager, Data>)
+ -> Binding<Data>
+ {
+ Binding(
+ get: { self[keyPath: keyPath] },
+ set: { self[keyPath: keyPath] = $0 }
+ )
+ }
+
private func syncableData<T: Codable>(
key: String,
localData: Data,
@@ -584,18 +758,14 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
}
private func performBatchedSync(for keys: Set<SettingsSyncKey>) {
- var didChange = false
-
for key in keys {
- didChange = triggerSyncIfNeeded(for: key) || didChange
- }
-
- if didChange {
- objectWillChange.send()
+ _ = triggerSyncIfNeeded(for: key)
}
}
private func backupBookmarks() async {
+ let backupData = Self.encode(bookmarksCache)
+
await Task.detached {
guard
let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
@@ -610,7 +780,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
let timestamp = Int(Date().timeIntervalSince1970)
let backupFile = backupDirectory.appendingPathComponent("bookmarks_backup_\(timestamp).json")
- if let data = await Self.encode(self.bookmarksCache) {
+ if let data = backupData {
try? data.write(to: backupFile)
}
@@ -645,6 +815,8 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
}
private func backupFavorites() async {
+ let backupData = Self.encode(favoritesCache)
+
await Task.detached {
guard
let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
@@ -659,7 +831,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
let timestamp = Int(Date().timeIntervalSince1970)
let backupFile = backupDirectory.appendingPathComponent("favorites_backup_\(timestamp).json")
- if let data = await Self.encode(self.favoritesCache) {
+ if let data = backupData {
try? data.write(to: backupFile)
}
@@ -929,47 +1101,33 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
}
func syncFromCloud() {
- if self.enableSync {
- Task.detached { [weak self] in
+ if enableSync {
+ Task { [weak self] in
guard let self else { return }
if let data = NSUbiquitousKeyValueStore.default.data(forKey: "bookmarks") {
- await MainActor.run {
- self.bookmarksData = data
- }
+ bookmarksData = data
}
if let data = NSUbiquitousKeyValueStore.default.data(forKey: "searchHistory") {
- await MainActor.run {
- self.searchHistoryData = data
- }
+ searchHistoryData = data
}
if let data = NSUbiquitousKeyValueStore.default.data(forKey: "customProviders") {
- await MainActor.run {
- self.customProvidersData = data
- }
+ customProvidersData = data
}
- await MainActor.run {
- self.loadBookmarksCache()
- self.loadSearchHistoryCache()
- self.objectWillChange.send()
- }
+ loadBookmarksCache()
+ loadSearchHistoryCache()
}
}
}
func triggerSyncIfNeededForAll() {
let keysToSync: [SettingsSyncKey] = [.bookmarks, .favorites, .searchHistory, .customProviders]
- var didChange = false
for keyToSync in keysToSync {
- didChange = triggerSyncIfNeeded(for: keyToSync) || didChange
- }
-
- if didChange {
- objectWillChange.send()
+ _ = triggerSyncIfNeeded(for: keyToSync)
}
}
@@ -1238,7 +1396,7 @@ class SettingsManager: ObservableObject { // swiftlint:disable:this type_body_l
syncableData(
key: "folders",
- localData: $foldersData,
+ localData: storageBinding(for: \.foldersData),
newValue: updated,
) { $0 }
}
diff --git a/Sora/Views/BookmarkMenuButtonView.swift b/Sora/Views/BookmarkMenuButtonView.swift
index 8446539..6cd6812 100644
--- a/Sora/Views/BookmarkMenuButtonView.swift
+++ b/Sora/Views/BookmarkMenuButtonView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct BookmarkMenuButtonView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let tags: [String]
let provider: BooruProvider
var disableNewCollection = false
diff --git a/Sora/Views/BookmarksView.swift b/Sora/Views/BookmarksView.swift
index a20d9f0..bda4136 100644
--- a/Sora/Views/BookmarksView.swift
+++ b/Sora/Views/BookmarksView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct BookmarksView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@Binding var selectedTab: Int
var body: some View {
@@ -26,6 +27,6 @@ struct BookmarksView: View {
#Preview {
BookmarksView(selectedTab: .constant(1))
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
.environmentObject(BooruManager(.yandere))
}
diff --git a/Sora/Views/FavoriteMenuButtonView.swift b/Sora/Views/FavoriteMenuButtonView.swift
index a29b2b0..d1c78fe 100644
--- a/Sora/Views/FavoriteMenuButtonView.swift
+++ b/Sora/Views/FavoriteMenuButtonView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct FavoriteMenuButtonView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@EnvironmentObject var manager: BooruManager
let post: BooruPost
var disableNewCollection = false
diff --git a/Sora/Views/FavoritePostThumbnailView.swift b/Sora/Views/FavoritePostThumbnailView.swift
index 41113b9..b1f727b 100644
--- a/Sora/Views/FavoritePostThumbnailView.swift
+++ b/Sora/Views/FavoritePostThumbnailView.swift
@@ -2,7 +2,8 @@ import NetworkImage
import SwiftUI
struct FavoritePostThumbnailView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let favorite: SettingsFavoritePost
let onRemove: () -> Void
private var networkImageLoader: BooruNetworkImageLoader {
@@ -95,5 +96,5 @@ struct FavoritePostThumbnailView: View {
)
FavoritePostThumbnailView(favorite: sampleFavorite) { () }
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
}
diff --git a/Sora/Views/FavoritesView.swift b/Sora/Views/FavoritesView.swift
index 007500b..50de8ed 100644
--- a/Sora/Views/FavoritesView.swift
+++ b/Sora/Views/FavoritesView.swift
@@ -3,7 +3,8 @@
import SwiftUI
struct FavoritesView: View { // swiftlint:disable:this type_body_length
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@EnvironmentObject var manager: BooruManager
@Binding var selectedTab: Int
@State private var searchText: String = ""
@@ -701,6 +702,6 @@ extension SettingsFavoritePost {
#Preview {
FavoritesView(selectedTab: .constant(1), isPresented: .constant(false))
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
.environmentObject(BooruManager(.yandere))
}
diff --git a/Sora/Views/Generic/GenericItemView.swift b/Sora/Views/Generic/GenericItemView.swift
index 99c4f0a..a22d6c1 100644
--- a/Sora/Views/Generic/GenericItemView.swift
+++ b/Sora/Views/Generic/GenericItemView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct GenericItemView<T: GenericItem>: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let item: T
let removeAction: (UUID) -> Void
diff --git a/Sora/Views/Generic/GenericListView.swift b/Sora/Views/Generic/GenericListView.swift
index d1c1436..58ae099 100644
--- a/Sora/Views/Generic/GenericListView.swift
+++ b/Sora/Views/Generic/GenericListView.swift
@@ -4,7 +4,8 @@ import SwiftUI
// swiftlint:disable:next type_body_length
struct GenericListView<T: Identifiable & Hashable & GenericItem>: View {
- @EnvironmentObject private var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@EnvironmentObject private var manager: BooruManager
@Binding var selectedTab: Int
@State private var searchText: String = ""
diff --git a/Sora/Views/MainView.swift b/Sora/Views/MainView.swift
index 06e067b..834b192 100644
--- a/Sora/Views/MainView.swift
+++ b/Sora/Views/MainView.swift
@@ -1,6 +1,14 @@
import SwiftUI
struct MainView: View {
+ private struct ManagerConfiguration: Equatable {
+ let provider: BooruProvider
+ let credentials: BooruProviderCredentials?
+ let sendUserAgent: Bool
+ let customUserAgent: String
+ let showHeldMoebooruPosts: Bool
+ }
+
private enum MainSidebarSection: Int, CaseIterable, Hashable {
case posts = 0
case bookmarks = 1
@@ -33,28 +41,34 @@ struct MainView: View {
}
}
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@State private var selectedTab: Int = 0
@State private var selectedSidebarSection: MainSidebarSection? = .posts
@State private var manager = BooruManager(.yandere)
+ private var managerConfiguration: ManagerConfiguration {
+ ManagerConfiguration(
+ provider: settings.preferredBooru,
+ credentials: settings.providerCredentials
+ .first { $0.provider == settings.preferredBooru },
+ sendUserAgent: settings.sendBooruUserAgent,
+ customUserAgent: settings.customBooruUserAgent,
+ showHeldMoebooruPosts: settings.showHeldMoebooruPosts
+ )
+ }
+
var body: some View {
platformSpecificContent
- .environmentObject(settings)
+ .environment(settings)
.environmentObject(manager)
- .onChange(of: settings.preferredBooru) { _, newState in
- updateManager(newState)
+ .onChange(of: managerConfiguration) { _, newConfiguration in
+ updateManager(using: newConfiguration)
}
.onChange(of: manager.historyIndex) { _, _ in
- if manager.isNavigatingHistory {
- manager.selectedPost = nil
- }
+ handleHistoryIndexChange()
}
.onAppear(perform: initializeManager)
- .onChange(of: settings.providerCredentials) { updateManager(settings.preferredBooru) }
- .onChange(of: settings.showHeldMoebooruPosts) { updateManager(settings.preferredBooru) }
- .onChange(of: settings.sendBooruUserAgent) { updateManager(settings.preferredBooru) }
- .onChange(of: settings.customBooruUserAgent) { updateManager(settings.preferredBooru) }
#if os(macOS)
.onChange(of: selectedSidebarSection) { _, newValue in
guard let newValue else { return }
@@ -159,17 +173,26 @@ struct MainView: View {
#endif
}
- private func updateManager(_ provider: BooruProvider) {
- let previousSearchText = manager.searchText
+ private func handleHistoryIndexChange() {
+ if manager.isNavigatingHistory {
+ manager.selectedPost = nil
+ }
+ }
- manager = BooruManager(
- provider,
- credentials: settings.providerCredentials
- .first { $0.provider == settings.preferredBooru },
- sendUserAgent: settings.sendBooruUserAgent,
- customUserAgent: settings.customBooruUserAgent,
- showHeldMoebooruPosts: settings.showHeldMoebooruPosts
+ private func makeManager(from configuration: ManagerConfiguration) -> BooruManager {
+ BooruManager(
+ configuration.provider,
+ credentials: configuration.credentials,
+ sendUserAgent: configuration.sendUserAgent,
+ customUserAgent: configuration.customUserAgent,
+ showHeldMoebooruPosts: configuration.showHeldMoebooruPosts
)
+ }
+
+ private func updateManager(using configuration: ManagerConfiguration) {
+ let previousSearchText = manager.searchText
+
+ manager = makeManager(from: configuration)
manager.searchText = previousSearchText
Task(priority: .userInitiated) {
@@ -182,14 +205,7 @@ struct MainView: View {
}
private func initializeManager() {
- manager = BooruManager(
- settings.preferredBooru,
- credentials: settings.providerCredentials
- .first { $0.provider == settings.preferredBooru },
- sendUserAgent: settings.sendBooruUserAgent,
- customUserAgent: settings.customBooruUserAgent,
- showHeldMoebooruPosts: settings.showHeldMoebooruPosts
- )
+ manager = makeManager(from: managerConfiguration)
Task(priority: .userInitiated) {
if manager.posts.isEmpty {
@@ -207,7 +223,7 @@ struct MainView: View {
#endif
MainView()
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
#if os(macOS)
.frame(
width: screenSize.width / widthCoefficient,
diff --git a/Sora/Views/Post/Details/Carousel/PostDetailsCarouselView.swift b/Sora/Views/Post/Details/Carousel/PostDetailsCarouselView.swift
index e43be47..55d6243 100644
--- a/Sora/Views/Post/Details/Carousel/PostDetailsCarouselView.swift
+++ b/Sora/Views/Post/Details/Carousel/PostDetailsCarouselView.swift
@@ -2,7 +2,8 @@ import SwiftUI
struct PostDetailsCarouselView: View {
@EnvironmentObject var manager: BooruManager
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let posts: [BooruPost]
let focusedPost: BooruPost?
let onFocusedPostChange: (BooruPost) -> Void
diff --git a/Sora/Views/Post/Details/PostDetailsImageView.swift b/Sora/Views/Post/Details/PostDetailsImageView.swift
index 5b954a6..009dbed 100644
--- a/Sora/Views/Post/Details/PostDetailsImageView.swift
+++ b/Sora/Views/Post/Details/PostDetailsImageView.swift
@@ -3,7 +3,8 @@ import SwiftUI
import UserNotifications
struct PostDetailsImageView<Placeholder: View>: View { // swiftlint:disable:this type_body_length
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@EnvironmentObject var manager: BooruManager
var url: URL?
@Binding var loadingState: BooruPostLoadingState
diff --git a/Sora/Views/Post/Details/PostDetailsTagsView.swift b/Sora/Views/Post/Details/PostDetailsTagsView.swift
index 72ba757..f843b6c 100644
--- a/Sora/Views/Post/Details/PostDetailsTagsView.swift
+++ b/Sora/Views/Post/Details/PostDetailsTagsView.swift
@@ -2,7 +2,8 @@ import SwiftUI
struct PostDetailsTagsView: View {
@EnvironmentObject var manager: BooruManager
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@Binding var isPresented: Bool
@Binding var navigationPath: NavigationPath
var tags: [String]
@@ -101,5 +102,5 @@ struct PostDetailsTagsView: View {
tags: ["hololive", "absurdres", "nekomimi"]
)
.environmentObject(BooruManager(.danbooru))
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
}
diff --git a/Sora/Views/Post/Details/PostDetailsView.swift b/Sora/Views/Post/Details/PostDetailsView.swift
index bb58c49..d8a77c6 100644
--- a/Sora/Views/Post/Details/PostDetailsView.swift
+++ b/Sora/Views/Post/Details/PostDetailsView.swift
@@ -2,7 +2,8 @@ import SwiftUI
struct PostDetailsView: View {
@EnvironmentObject var manager: BooruManager
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let post: BooruPost
@Binding var navigationPath: NavigationPath
@State private var loadingStage: BooruPostLoadingState = .loadingPreview
diff --git a/Sora/Views/Post/Grid/PostGridBookmarkButtonView.swift b/Sora/Views/Post/Grid/PostGridBookmarkButtonView.swift
index 2dadfc3..020e0ce 100644
--- a/Sora/Views/Post/Grid/PostGridBookmarkButtonView.swift
+++ b/Sora/Views/Post/Grid/PostGridBookmarkButtonView.swift
@@ -2,7 +2,8 @@ import SwiftUI
struct PostGridBookmarkButtonView: View {
@EnvironmentObject private var manager: BooruManager
- @EnvironmentObject private var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let tags: [String]
let provider: BooruProvider
diff --git a/Sora/Views/Post/Grid/PostGridFavoriteButtonView.swift b/Sora/Views/Post/Grid/PostGridFavoriteButtonView.swift
index bd6b6f9..c6ecd7f 100644
--- a/Sora/Views/Post/Grid/PostGridFavoriteButtonView.swift
+++ b/Sora/Views/Post/Grid/PostGridFavoriteButtonView.swift
@@ -2,7 +2,8 @@ import SwiftUI
struct PostGridFavoriteButtonView: View {
@EnvironmentObject private var manager: BooruManager
- @EnvironmentObject private var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
let post: BooruPost
var isFavorited: Bool {
@@ -40,6 +41,6 @@ struct PostGridFavoriteButtonView: View {
)
PostGridFavoriteButtonView(post: samplePost)
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
.environmentObject(BooruManager(.yandere))
}
diff --git a/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift b/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift
index bc7c52a..fcadea1 100644
--- a/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift
+++ b/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct PostGridSearchHistoryView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@Binding var selectedTab: Int
@Binding var isPresented: Bool
@@ -30,6 +31,6 @@ struct PostGridSearchHistoryView: View {
selectedTab: .constant(0),
isPresented: .constant(true)
)
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
.environmentObject(BooruManager(.safebooru))
}
diff --git a/Sora/Views/Post/Grid/PostGridThumbnailView.swift b/Sora/Views/Post/Grid/PostGridThumbnailView.swift
index 5f7829e..fade59b 100644
--- a/Sora/Views/Post/Grid/PostGridThumbnailView.swift
+++ b/Sora/Views/Post/Grid/PostGridThumbnailView.swift
@@ -2,7 +2,8 @@ import NetworkImage
import SwiftUI
struct PostGridThumbnailView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@EnvironmentObject var manager: BooruManager
let post: BooruPost
let posts: [BooruPost]
diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift
index 330e294..77e96aa 100644
--- a/Sora/Views/Post/Grid/PostGridView.swift
+++ b/Sora/Views/Post/Grid/PostGridView.swift
@@ -3,7 +3,8 @@
import SwiftUI
struct PostGridView: View { // swiftlint:disable:this type_body_length
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@EnvironmentObject var manager: BooruManager
@State private var isSearchHistoryPresented = false
@State private var isSettingsPresented = false
diff --git a/Sora/Views/Settings/Collections/SettingsCollectionsListView.swift b/Sora/Views/Settings/Collections/SettingsCollectionsListView.swift
index c474b79..1cd9121 100644
--- a/Sora/Views/Settings/Collections/SettingsCollectionsListView.swift
+++ b/Sora/Views/Settings/Collections/SettingsCollectionsListView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct SettingsCollectionsListView: View {
- @EnvironmentObject private var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
var body: some View {
List {
diff --git a/Sora/Views/Settings/Collections/SettingsCollectionsView.swift b/Sora/Views/Settings/Collections/SettingsCollectionsView.swift
index a7ce5a7..f74fef9 100644
--- a/Sora/Views/Settings/Collections/SettingsCollectionsView.swift
+++ b/Sora/Views/Settings/Collections/SettingsCollectionsView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct SettingsCollectionsView: View {
- @EnvironmentObject private var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@State private var isDeleteConfirmationPresented = false
@State private var isRenameAlertPresented = false
diff --git a/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift b/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift
index 40a8a7e..353824a 100644
--- a/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct SettingsSectionContentRatingsView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
var body: some View {
List {
@@ -70,5 +71,5 @@ struct SettingsSectionContentRatingsView: View {
#Preview {
SettingsSectionContentRatingsView()
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
}
diff --git a/Sora/Views/Settings/Section/SettingsSectionDebugView.swift b/Sora/Views/Settings/Section/SettingsSectionDebugView.swift
index 83acb81..c97b545 100644
--- a/Sora/Views/Settings/Section/SettingsSectionDebugView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionDebugView.swift
@@ -1,7 +1,8 @@
import SwiftUI
struct SettingsSectionDebugView: View {
- @EnvironmentObject private var settingsManager: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settingsManager
var body: some View {
Button(action: {
diff --git a/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift b/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift
index 8db6002..c386634 100644
--- a/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift
@@ -1,9 +1,12 @@
import SwiftUI
struct SettingsSectionDetailsView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
var body: some View {
+ @Bindable var settings = settings
+
Form {
Section("Image Quality") {
Picker("Image Quality", selection: $settings.detailViewQuality) {
@@ -58,6 +61,6 @@ struct SettingsSectionDetailsView: View {
#Preview {
NavigationStack {
SettingsSectionDetailsView()
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
}
}
diff --git a/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift b/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift
index 381b6a4..846e9e5 100644
--- a/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift
@@ -2,7 +2,8 @@ import SwiftUI
import UniformTypeIdentifiers
struct SettingsSectionImportExportView: View {
- @EnvironmentObject private var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@State private var isFileExporterPresented = false
@State private var isFileImporterPresented = false
@State private var bookmarksExportDocument: JSONFileDocument?
diff --git a/Sora/Views/Settings/Section/SettingsSectionProviderView.swift b/Sora/Views/Settings/Section/SettingsSectionProviderView.swift
index b9a7900..02a8be6 100644
--- a/Sora/Views/Settings/Section/SettingsSectionProviderView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionProviderView.swift
@@ -1,13 +1,16 @@
import SwiftUI
struct SettingsSectionProviderView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@State private var showingCustomBooruSheet = false
@State private var newDomain: String = ""
@State private var newFlavor: BooruProviderFlavor = .danbooru
@State private var domainError: String?
var body: some View {
+ @Bindable var settings = settings
+
Form {
Section("Source") {
Picker("Website", selection: $settings.preferredBooru) {
@@ -173,7 +176,9 @@ struct SettingsSectionProviderView: View {
) {
Button("OK", role: .cancel) { () }
} message: {
- Text(domainError ?? "An unknown error occurred while validating the domain.")
+ if let domainError {
+ Text(domainError)
+ }
}
}
@@ -271,6 +276,6 @@ struct SettingsSectionProviderView: View {
#Preview {
NavigationStack {
SettingsSectionProviderView()
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
}
}
diff --git a/Sora/Views/Settings/Section/SettingsSectionSearchView.swift b/Sora/Views/Settings/Section/SettingsSectionSearchView.swift
index b702cd4..e82f872 100644
--- a/Sora/Views/Settings/Section/SettingsSectionSearchView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionSearchView.swift
@@ -1,9 +1,12 @@
import SwiftUI
struct SettingsSectionSearchView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
var body: some View {
+ @Bindable var settings = settings
+
Picker("Suggestions", selection: $settings.searchSuggestionsMode) {
ForEach(SettingsSearchSuggestionsMode.allCases, id: \.self) { type in
Text(type.rawValue.capitalized).tag(type)
diff --git a/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift b/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift
index 784ee2a..ad7fe83 100644
--- a/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift
@@ -1,17 +1,22 @@
import SwiftUI
struct SettingsSectionSettingsView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
var body: some View {
+ @Bindable var settings = settings
+
Toggle(isOn: $settings.enableSync) {
Text("Sync with iCloud")
Text("Keep bookmarks, collections, search history, and sources in sync across your devices.")
.font(.caption)
}
- .onChange(of: settings.enableSync) { _, _ in
- settings.triggerSyncIfNeededForAll()
+ .onChange(of: settings.enableSync) { _, isEnabled in
+ if isEnabled {
+ settings.triggerSyncIfNeededForAll()
+ }
}
Button("Reset Settings") {
diff --git a/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift b/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift
index f8dffc8..dc9d87e 100644
--- a/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift
+++ b/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift
@@ -1,10 +1,13 @@
import SwiftUI
struct SettingsSectionThumbnailsView: View {
- @EnvironmentObject var settings: SettingsManager
+ @Environment(SettingsManager.self)
+ private var settings
@State private var isShowingContentFiltering = false
var body: some View {
+ @Bindable var settings = settings
+
Form {
Section("Image Quality") {
Picker("Thumbnail Quality", selection: $settings.thumbnailQuality) {
@@ -62,6 +65,6 @@ struct SettingsSectionThumbnailsView: View {
#Preview {
NavigationStack {
SettingsSectionThumbnailsView()
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
}
}
diff --git a/Sora/Views/Settings/SettingsView.swift b/Sora/Views/Settings/SettingsView.swift
index 4ea60b8..ae5a620 100644
--- a/Sora/Views/Settings/SettingsView.swift
+++ b/Sora/Views/Settings/SettingsView.swift
@@ -56,6 +56,6 @@ struct SettingsView: View {
#Preview {
SettingsView()
- .environmentObject(SettingsManager())
+ .environment(SettingsManager())
.environmentObject(BooruManager(.yandere))
}
diff --git a/SoraTests/SettingsManagerSyncTests.swift b/SoraTests/SettingsManagerSyncTests.swift
index 8ed9d83..0ca99a2 100644
--- a/SoraTests/SettingsManagerSyncTests.swift
+++ b/SoraTests/SettingsManagerSyncTests.swift
@@ -247,7 +247,7 @@ final class SettingsManagerSyncTests: XCTestCase {
)
}
- func testManualFullSyncAggregatesChangeNotification() throws {
+ func testManualFullSyncAvoidsLegacyChangePublisher() throws {
let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift")
let fullSyncSection = try extractFunction(
named: "func triggerSyncIfNeededForAll()",
@@ -271,10 +271,10 @@ final class SettingsManagerSyncTests: XCTestCase {
"Full sync should evaluate all supported sync keys."
)
// swiftlint:disable:next prefer_nimble
- XCTAssertGreaterThan(
+ XCTAssertEqual(
objectWillChangeCount,
0,
- "Full sync should emit a consolidated objectWillChange notification when merged state changes."
+ "Observation-based sync should not depend on the legacy objectWillChange publisher."
)
}
}