From 6c23569d159e8c7626e1ad794d164ff5f6767af1 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 19 Sep 2023 09:18:20 +0200 Subject: [PATCH] UserPreferences -> Observable --- IceCubesApp/App/AppRouter.swift | 2 +- IceCubesApp/App/IceCubesApp.swift | 4 +- IceCubesApp/App/SafariRouter.swift | 2 +- IceCubesApp/App/SideBarView.swift | 3 +- IceCubesApp/App/Tabs/ExploreTab.swift | 3 +- IceCubesApp/App/Tabs/NotificationTab.swift | 3 +- .../Tabs/Settings/ContentSettingsView.swift | 4 +- .../Tabs/Settings/DisplaySettingsView.swift | 5 +- .../Tabs/Settings/HapticSettingsView.swift | 4 +- .../App/Tabs/Settings/SettingsTab.swift | 5 +- .../Settings/SwipeActionsSettingsView.swift | 4 +- .../Settings/TranslationSettingsView.swift | 56 ++- .../Tabs/Timeline/AddRemoteTimelineView.swift | 3 +- .../App/Tabs/Timeline/EditTagGroupView.swift | 3 +- .../App/Tabs/Timeline/TimelineTab.swift | 3 +- .../ShareViewController.swift | 2 +- .../Account/AccountDetailContextMenu.swift | 2 +- .../Sources/Account/AccountDetailView.swift | 3 +- .../Account/Filters/FiltersListView.swift | 1 + .../Sources/AppAccount/AppAccountView.swift | 3 +- .../AppAccount/AppAccountsSelectorView.swift | 3 +- .../List/ConversationsListView.swift | 3 +- .../Views/StatusEditorToolbarItem.swift | 2 +- .../Env/Sources/Env/UserPreferences.swift | 366 ++++++++++++++---- .../StatusEditorAccessoryView.swift | 3 +- .../Status/Editor/StatusEditorView.swift | 2 +- .../Status/Media/VideoPlayerView.swift | 2 +- .../Row/Subviews/StatusRowActionsView.swift | 3 +- .../Row/Subviews/StatusRowContextMenu.swift | 4 +- .../Subviews/StatusRowMediaPreviewView.swift | 3 +- .../Row/Subviews/StatusRowSwipeView.swift | 3 +- .../Row/Subviews/StatusRowTranslateView.swift | 3 +- 32 files changed, 390 insertions(+), 122 deletions(-) diff --git a/IceCubesApp/App/AppRouter.swift b/IceCubesApp/App/AppRouter.swift index e1f54aa8..1ab36a82 100644 --- a/IceCubesApp/App/AppRouter.swift +++ b/IceCubesApp/App/AppRouter.swift @@ -113,7 +113,7 @@ extension View { func withEnvironments() -> some View { environment(CurrentAccount.shared) - .environmentObject(UserPreferences.shared) + .environment(UserPreferences.shared) .environment(CurrentInstance.shared) .environment(Theme.shared) .environment(AppAccountsManager.shared) diff --git a/IceCubesApp/App/IceCubesApp.swift b/IceCubesApp/App/IceCubesApp.swift index 0d33e41d..9563f303 100644 --- a/IceCubesApp/App/IceCubesApp.swift +++ b/IceCubesApp/App/IceCubesApp.swift @@ -18,7 +18,7 @@ struct IceCubesApp: App { @State private var appAccountsManager = AppAccountsManager.shared @State private var currentInstance = CurrentInstance.shared @State private var currentAccount = CurrentAccount.shared - @StateObject private var userPreferences = UserPreferences.shared + @State private var userPreferences = UserPreferences.shared @State private var pushNotificationsService = PushNotificationsService.shared @State private var watcher = StreamWatcher() @State private var quickLook = QuickLook() @@ -48,7 +48,7 @@ struct IceCubesApp: App { .environment(quickLook) .environment(currentAccount) .environment(currentInstance) - .environmentObject(userPreferences) + .environment(userPreferences) .environment(theme) .environment(watcher) .environment(pushNotificationsService) diff --git a/IceCubesApp/App/SafariRouter.swift b/IceCubesApp/App/SafariRouter.swift index 5d3feb52..128647c6 100644 --- a/IceCubesApp/App/SafariRouter.swift +++ b/IceCubesApp/App/SafariRouter.swift @@ -13,7 +13,7 @@ extension View { @MainActor private struct SafariRouter: ViewModifier { @Environment(Theme.self) private var theme - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(RouterPath.self) private var routerPath @State private var safariManager = InAppSafariManager() diff --git a/IceCubesApp/App/SideBarView.swift b/IceCubesApp/App/SideBarView.swift index 5bb1cf1f..01beb719 100644 --- a/IceCubesApp/App/SideBarView.swift +++ b/IceCubesApp/App/SideBarView.swift @@ -5,12 +5,13 @@ import Env import Models import SwiftUI +@MainActor struct SideBarView: View { @Environment(AppAccountsManager.self) private var appAccounts @Environment(CurrentAccount.self) private var currentAccount @Environment(Theme.self) private var theme @Environment(StreamWatcher.self) private var watcher - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences @Environment(RouterPath.self) private var routerPath @Binding var selectedTab: Tab diff --git a/IceCubesApp/App/Tabs/ExploreTab.swift b/IceCubesApp/App/Tabs/ExploreTab.swift index 9e959104..722884bb 100644 --- a/IceCubesApp/App/Tabs/ExploreTab.swift +++ b/IceCubesApp/App/Tabs/ExploreTab.swift @@ -7,9 +7,10 @@ import Network import Shimmer import SwiftUI +@MainActor struct ExploreTab: View { @Environment(Theme.self) private var theme - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(CurrentAccount.self) private var currentAccount @Environment(Client.self) private var client @State private var routerPath = RouterPath() diff --git a/IceCubesApp/App/Tabs/NotificationTab.swift b/IceCubesApp/App/Tabs/NotificationTab.swift index 2e953aee..ad939714 100644 --- a/IceCubesApp/App/Tabs/NotificationTab.swift +++ b/IceCubesApp/App/Tabs/NotificationTab.swift @@ -7,6 +7,7 @@ import Notifications import SwiftUI import Timeline +@MainActor struct NotificationsTab: View { @Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool @Environment(\.scenePhase) private var scenePhase @@ -16,7 +17,7 @@ struct NotificationsTab: View { @Environment(StreamWatcher.self) private var watcher @Environment(AppAccountsManager.self) private var appAccount @Environment(CurrentAccount.self) private var currentAccount - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences @Environment(PushNotificationsService.self) private var pushNotificationsService @State private var routerPath = RouterPath() @Binding var popToRootTab: Tab diff --git a/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift b/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift index 2a955a20..fe8a5749 100644 --- a/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift @@ -7,11 +7,13 @@ import NukeUI import SwiftUI import UserNotifications +@MainActor struct ContentSettingsView: View { - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences @Environment(Theme.self) private var theme var body: some View { + @Bindable var userPreferences = userPreferences Form { Section("settings.content.boosts") { Toggle(isOn: $userPreferences.suppressDupeReblogs) { diff --git a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift index 6d775691..5ecceaf5 100644 --- a/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/DisplaySettingsView.swift @@ -18,12 +18,13 @@ import SwiftUI init() {} } +@MainActor struct DisplaySettingsView: View { typealias FontState = Theme.FontState @Environment(\.colorScheme) private var colorScheme @Environment(Theme.self) private var theme - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences @State private var localValues = DisplaySettingsLocalValues() @@ -180,6 +181,7 @@ struct DisplaySettingsView: View { @ViewBuilder private var layoutSection: some View { @Bindable var theme = theme + @Bindable var userPreferences = userPreferences Section("settings.display.section.display") { Picker("settings.display.avatar.position", selection: $theme.avatarPosition) { ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in @@ -210,6 +212,7 @@ struct DisplaySettingsView: View { @ViewBuilder private var platformsSection: some View { + @Bindable var userPreferences = userPreferences if UIDevice.current.userInterfaceIdiom == .phone { Section("iPhone") { Toggle("settings.display.show-tab-label", isOn: $userPreferences.showiPhoneTabLabel) diff --git a/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift b/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift index fefe6e49..91b371eb 100644 --- a/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/HapticSettingsView.swift @@ -4,11 +4,13 @@ import Models import Status import SwiftUI +@MainActor struct HapticSettingsView: View { @Environment(Theme.self) private var theme - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences var body: some View { + @Bindable var userPreferences = userPreferences Form { Section { Toggle("settings.haptic.timeline", isOn: $userPreferences.hapticTimelineEnabled) diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index ec972421..fdd8de5e 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -9,11 +9,12 @@ import Nuke import SwiftUI import Timeline +@MainActor struct SettingsTabs: View { @Environment(\.dismiss) private var dismiss @Environment(PushNotificationsService.self) private var pushNotifications - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Client.self) private var client @Environment(CurrentInstance.self) private var currentInstance @Environment(AppAccountsManager.self) private var appAccountsManager @@ -158,7 +159,9 @@ struct SettingsTabs: View { .listRowBackground(theme.primaryBackgroundColor) } + @ViewBuilder private var otherSections: some View { + @Bindable var preferences = preferences Section("settings.section.other") { if !ProcessInfo.processInfo.isiOSAppOnMac { Picker(selection: $preferences.preferredBrowser) { diff --git a/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift b/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift index 35d64909..5ef7ec6b 100644 --- a/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/SwipeActionsSettingsView.swift @@ -2,11 +2,13 @@ import DesignSystem import Env import SwiftUI +@MainActor struct SwipeActionsSettingsView: View { @Environment(Theme.self) private var theme - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences var body: some View { + @Bindable var userPreferences = userPreferences Form { Section { Label("settings.swipeactions.status.leading", systemImage: "arrow.right") diff --git a/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift b/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift index f20eaaa8..ea13c4e2 100644 --- a/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift @@ -2,26 +2,19 @@ import DesignSystem import Env import SwiftUI +@MainActor struct TranslationSettingsView: View { - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @State private var apiKey: String = "" var body: some View { Form { - Toggle(isOn: preferences.$alwaysUseDeepl) { - Text("settings.translation.always-deepl") - } - .listRowBackground(theme.primaryBackgroundColor) - + deepLToggle if preferences.alwaysUseDeepl { Section("settings.translation.user-api-key") { - Picker("settings.translation.api-key-type", selection: $preferences.userDeeplAPIFree) { - Text("DeepL API Free").tag(true) - Text("DeepL API Pro").tag(false) - } - + deepLPicker SecureField("settings.translation.user-api-key", text: $apiKey) .textContentType(.password) } @@ -40,14 +33,7 @@ struct TranslationSettingsView: View { .listRowBackground(theme.primaryBackgroundColor) } } - - Section { - Toggle(isOn: preferences.$autoDetectPostLanguage) { - Text("settings.translation.auto-detect-post-language") - } - } footer: { - Text("settings.translation.auto-detect-post-language-footer") - } + autoDetectSection } .navigationTitle("settings.translation.navigation-title") .scrollContentBackground(.hidden) @@ -57,6 +43,36 @@ struct TranslationSettingsView: View { } .onAppear(perform: updatePrefs) } + + @ViewBuilder + private var deepLToggle: some View { + @Bindable var preferences = preferences + Toggle(isOn: $preferences.alwaysUseDeepl) { + Text("settings.translation.always-deepl") + } + .listRowBackground(theme.primaryBackgroundColor) + } + + @ViewBuilder + private var deepLPicker: some View { + @Bindable var preferences = preferences + Picker("settings.translation.api-key-type", selection: $preferences.userDeeplAPIFree) { + Text("DeepL API Free").tag(true) + Text("DeepL API Pro").tag(false) + } + } + + @ViewBuilder + private var autoDetectSection: some View { + @Bindable var preferences = preferences + Section { + Toggle(isOn: $preferences.autoDetectPostLanguage) { + Text("settings.translation.auto-detect-post-language") + } + } footer: { + Text("settings.translation.auto-detect-post-language-footer") + } + } private func writeNewValue() { writeNewValue(value: apiKey) @@ -82,6 +98,6 @@ struct TranslationSettingsView: View { struct TranslationSettingsView_Previews: PreviewProvider { static var previews: some View { TranslationSettingsView() - .environmentObject(UserPreferences.shared) + .environment(UserPreferences.shared) } } diff --git a/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift b/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift index 868195e9..c1161795 100644 --- a/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift +++ b/IceCubesApp/App/Tabs/Timeline/AddRemoteTimelineView.swift @@ -7,10 +7,11 @@ import NukeUI import Shimmer import SwiftUI +@MainActor struct AddRemoteTimelineView: View { @Environment(\.dismiss) private var dismiss - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @State private var instanceName: String = "" diff --git a/IceCubesApp/App/Tabs/Timeline/EditTagGroupView.swift b/IceCubesApp/App/Tabs/Timeline/EditTagGroupView.swift index 08824188..a00bca96 100644 --- a/IceCubesApp/App/Tabs/Timeline/EditTagGroupView.swift +++ b/IceCubesApp/App/Tabs/Timeline/EditTagGroupView.swift @@ -7,10 +7,11 @@ import NukeUI import Shimmer import SwiftUI +@MainActor struct EditTagGroupView: View { @Environment(\.dismiss) private var dismiss - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @State private var title: String = "" diff --git a/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift b/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift index 70308398..755b7bd8 100644 --- a/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift +++ b/IceCubesApp/App/Tabs/Timeline/TimelineTab.swift @@ -7,11 +7,12 @@ import Network import SwiftUI import Timeline +@MainActor struct TimelineTab: View { @Environment(AppAccountsManager.self) private var appAccount @Environment(Theme.self) private var theme @Environment(CurrentAccount.self) private var currentAccount - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Client.self) private var client @State private var routerPath = RouterPath() @Binding var popToRootTab: Tab diff --git a/IceCubesShareExtension/ShareViewController.swift b/IceCubesShareExtension/ShareViewController.swift index e66c77ec..95353983 100644 --- a/IceCubesShareExtension/ShareViewController.swift +++ b/IceCubesShareExtension/ShareViewController.swift @@ -27,7 +27,7 @@ class ShareViewController: UIViewController { if let item = extensionContext?.inputItems.first as? NSExtensionItem { if let attachments = item.attachments { let view = StatusEditorView(mode: .shareExtension(items: attachments)) - .environmentObject(UserPreferences.shared) + .environment(UserPreferences.shared) .environment(appAccountsManager) .environment(client) .environment(account) diff --git a/Packages/Account/Sources/Account/AccountDetailContextMenu.swift b/Packages/Account/Sources/Account/AccountDetailContextMenu.swift index ce11c899..8b77e366 100644 --- a/Packages/Account/Sources/Account/AccountDetailContextMenu.swift +++ b/Packages/Account/Sources/Account/AccountDetailContextMenu.swift @@ -6,7 +6,7 @@ public struct AccountDetailContextMenu: View { @Environment(Client.self) private var client @Environment(RouterPath.self) private var routerPath @Environment(CurrentInstance.self) private var currentInstance - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences var viewModel: AccountDetailViewModel diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index f161f6fe..105657f2 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -7,6 +7,7 @@ import Shimmer import Status import SwiftUI +@MainActor public struct AccountDetailView: View { @Environment(\.openURL) private var openURL @Environment(\.redactionReasons) private var reasons @@ -14,7 +15,7 @@ public struct AccountDetailView: View { @Environment(StreamWatcher.self) private var watcher @Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentInstance.self) private var currentInstance - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @Environment(Client.self) private var client @Environment(RouterPath.self) private var routerPath diff --git a/Packages/Account/Sources/Account/Filters/FiltersListView.swift b/Packages/Account/Sources/Account/Filters/FiltersListView.swift index 56bfcabd..1fe5d6d6 100644 --- a/Packages/Account/Sources/Account/Filters/FiltersListView.swift +++ b/Packages/Account/Sources/Account/Filters/FiltersListView.swift @@ -4,6 +4,7 @@ import Models import Network import SwiftUI +@MainActor public struct FiltersListView: View { @Environment(\.dismiss) private var dismiss diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccountView.swift b/Packages/AppAccount/Sources/AppAccount/AppAccountView.swift index 733f2e50..34f0989e 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccountView.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccountView.swift @@ -3,11 +3,12 @@ import EmojiText import Env import SwiftUI +@MainActor public struct AppAccountView: View { @Environment(Theme.self) private var theme @Environment(RouterPath.self) private var routerPath @Environment(AppAccountsManager.self) private var appAccounts - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @State var viewModel: AppAccountViewModel diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift index 192dd429..43da9193 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift @@ -2,8 +2,9 @@ import DesignSystem import Env import SwiftUI +@MainActor public struct AppAccountsSelectorView: View { - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(CurrentAccount.self) private var currentAccount @Environment(AppAccountsManager.self) private var appAccounts @Environment(Theme.self) private var theme diff --git a/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift b/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift index df09d28f..19d23da4 100644 --- a/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift +++ b/Packages/Conversations/Sources/Conversations/List/ConversationsListView.swift @@ -5,8 +5,9 @@ import Network import Shimmer import SwiftUI +@MainActor public struct ConversationsListView: View { - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(RouterPath.self) private var routerPath @Environment(StreamWatcher.self) private var watcher @Environment(Client.self) private var client diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift index d0d5fd54..52da42a8 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift @@ -55,7 +55,7 @@ public struct StatusEditorToolbarItem: ToolbarContent { @MainActor public struct SecondaryColumnToolbarItem: ToolbarContent { @Environment(\.isSecondaryColumn) private var isSecondaryColumn - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences public init() {} diff --git a/Packages/Env/Sources/Env/UserPreferences.swift b/Packages/Env/Sources/Env/UserPreferences.swift index 15252c55..ad4c18a6 100644 --- a/Packages/Env/Sources/Env/UserPreferences.swift +++ b/Packages/Env/Sources/Env/UserPreferences.swift @@ -5,63 +5,250 @@ import Network import SwiftUI @MainActor -public class UserPreferences: ObservableObject { +@Observable public class UserPreferences { + class Storage { + @AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = [] + @AppStorage("tag_groups") public var tagGroups: [TagGroup] = [] + @AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari + @AppStorage("draft_posts") public var draftsPosts: [String] = [] + @AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true + @AppStorage("is_open_ai_enabled") public var isOpenAIEnabled: Bool = true + + @AppStorage("recently_used_languages") public var recentlyUsedLanguages: [String] = [] + @AppStorage("social_keyboard_composer") public var isSocialKeyboardEnabled: Bool = true + + @AppStorage("use_instance_content_settings") public var useInstanceContentSettings: Bool = true + @AppStorage("app_auto_expand_spoilers") public var appAutoExpandSpoilers = false + @AppStorage("app_auto_expand_media") public var appAutoExpandMedia: ServerPreferences.AutoExpandMedia = .hideSensitive + @AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub + @AppStorage("app_default_reply_visibility") public var appDefaultReplyVisibility: Models.Visibility = .pub + @AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false + @AppStorage("autoplay_video") public var autoPlayVideo = true + @AppStorage("always_use_deepl") public var alwaysUseDeepl = false + @AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true + @AppStorage("auto_detect_post_language") public var autoDetectPostLanguage = true + + @AppStorage("suppress_dupe_reblogs") public var suppressDupeReblogs: Bool = false + + @AppStorage("inAppBrowserReaderView") public var inAppBrowserReaderView = false + + @AppStorage("haptic_tab") public var hapticTabSelectionEnabled = true + @AppStorage("haptic_timeline") public var hapticTimelineEnabled = true + @AppStorage("haptic_button_press") public var hapticButtonPressEnabled = true + @AppStorage("sound_effect_enabled") public var soundEffectEnabled = true + + @AppStorage("show_tab_label_iphone") public var showiPhoneTabLabel = true + @AppStorage("show_alt_text_for_media") public var showAltTextForMedia = true + + @AppStorage("show_second_column_ipad") public var showiPadSecondaryColumn = true + + @AppStorage("swipeactions-status-trailing-right") public var swipeActionsStatusTrailingRight = StatusAction.favorite + @AppStorage("swipeactions-status-trailing-left") public var swipeActionsStatusTrailingLeft = StatusAction.boost + @AppStorage("swipeactions-status-leading-left") public var swipeActionsStatusLeadingLeft = StatusAction.reply + @AppStorage("swipeactions-status-leading-right") public var swipeActionsStatusLeadingRight = StatusAction.none + @AppStorage("swipeactions-use-theme-color") public var swipeActionsUseThemeColor = false + @AppStorage("swipeactions-icon-style") public var swipeActionsIconStyle: SwipeActionsIconStyle = .iconWithText + + @AppStorage("requested_review") public var requestedReview = false + + @AppStorage("collapse-long-posts") public var collapseLongPosts = true + + @AppStorage("share-button-behavior") public var shareButtonBehavior: PreferredShareButtonBehavior = .linkAndText + + init() { } + } + public static let sharedDefault = UserDefaults(suiteName: "group.com.thomasricouard.IceCubesApp") public static let shared = UserPreferences() - + private let storage = Storage() + private var client: Client? - - @AppStorage("remote_local_timeline") public var remoteLocalTimelines: [String] = [] - @AppStorage("tag_groups") public var tagGroups: [TagGroup] = [] - @AppStorage("preferred_browser") public var preferredBrowser: PreferredBrowser = .inAppSafari - @AppStorage("draft_posts") public var draftsPosts: [String] = [] - @AppStorage("show_translate_button_inline") public var showTranslateButton: Bool = true - @AppStorage("is_open_ai_enabled") public var isOpenAIEnabled: Bool = true - - @AppStorage("recently_used_languages") public var recentlyUsedLanguages: [String] = [] - @AppStorage("social_keyboard_composer") public var isSocialKeyboardEnabled: Bool = true - - @AppStorage("use_instance_content_settings") public var useInstanceContentSettings: Bool = true - @AppStorage("app_auto_expand_spoilers") public var appAutoExpandSpoilers = false - @AppStorage("app_auto_expand_media") public var appAutoExpandMedia: ServerPreferences.AutoExpandMedia = .hideSensitive - @AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub - @AppStorage("app_default_reply_visibility") public var appDefaultReplyVisibility: Models.Visibility = .pub - @AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false - @AppStorage("autoplay_video") public var autoPlayVideo = true - @AppStorage("always_use_deepl") public var alwaysUseDeepl = false - @AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true - @AppStorage("auto_detect_post_language") public var autoDetectPostLanguage = true - - @AppStorage("suppress_dupe_reblogs") public var suppressDupeReblogs: Bool = false - - @AppStorage("inAppBrowserReaderView") public var inAppBrowserReaderView = false - - @AppStorage("haptic_tab") public var hapticTabSelectionEnabled = true - @AppStorage("haptic_timeline") public var hapticTimelineEnabled = true - @AppStorage("haptic_button_press") public var hapticButtonPressEnabled = true - @AppStorage("sound_effect_enabled") public var soundEffectEnabled = true - - @AppStorage("show_tab_label_iphone") public var showiPhoneTabLabel = true - @AppStorage("show_alt_text_for_media") public var showAltTextForMedia = true - - @AppStorage("show_second_column_ipad") public var showiPadSecondaryColumn = true - - @AppStorage("swipeactions-status-trailing-right") public var swipeActionsStatusTrailingRight = StatusAction.favorite - @AppStorage("swipeactions-status-trailing-left") public var swipeActionsStatusTrailingLeft = StatusAction.boost - @AppStorage("swipeactions-status-leading-left") public var swipeActionsStatusLeadingLeft = StatusAction.reply - @AppStorage("swipeactions-status-leading-right") public var swipeActionsStatusLeadingRight = StatusAction.none - @AppStorage("swipeactions-use-theme-color") public var swipeActionsUseThemeColor = false - @AppStorage("swipeactions-icon-style") public var swipeActionsIconStyle: SwipeActionsIconStyle = .iconWithText - - @AppStorage("requested_review") public var requestedReview = false - - @AppStorage("collapse-long-posts") public var collapseLongPosts = true - - @AppStorage("share-button-behavior") public var shareButtonBehavior: PreferredShareButtonBehavior = .linkAndText - + + public var remoteLocalTimelines: [String] { + didSet { + storage.remoteLocalTimelines = remoteLocalTimelines + } + } + public var tagGroups: [TagGroup] { + didSet { + storage.tagGroups = tagGroups + } + } + public var preferredBrowser: PreferredBrowser { + didSet { + storage.preferredBrowser = preferredBrowser + } + } + public var draftsPosts: [String] { + didSet { + storage.draftsPosts = draftsPosts + } + } + public var showTranslateButton: Bool { + didSet { + storage.showTranslateButton = showTranslateButton + } + } + public var isOpenAIEnabled: Bool { + didSet { + storage.isOpenAIEnabled = isOpenAIEnabled + } + } + public var recentlyUsedLanguages: [String] { + didSet { + storage.recentlyUsedLanguages = recentlyUsedLanguages + } + } + public var isSocialKeyboardEnabled: Bool { + didSet { + storage.isSocialKeyboardEnabled = isSocialKeyboardEnabled + } + } + public var useInstanceContentSettings: Bool { + didSet { + storage.useInstanceContentSettings = useInstanceContentSettings + } + } + public var appAutoExpandSpoilers: Bool { + didSet { + storage.appAutoExpandSpoilers = appAutoExpandSpoilers + } + } + public var appAutoExpandMedia: ServerPreferences.AutoExpandMedia { + didSet { + storage.appAutoExpandMedia = appAutoExpandMedia + } + } + public var appDefaultPostVisibility: Models.Visibility { + didSet { + storage.appDefaultPostVisibility = appDefaultPostVisibility + } + } + public var appDefaultReplyVisibility: Models.Visibility { + didSet { + storage.appDefaultReplyVisibility = appDefaultReplyVisibility + } + } + public var appDefaultPostsSensitive: Bool { + didSet { + storage.appDefaultPostsSensitive = appDefaultPostsSensitive + } + } + public var autoPlayVideo: Bool { + didSet { + storage.autoPlayVideo = autoPlayVideo + } + } + public var alwaysUseDeepl: Bool { + didSet { + storage.alwaysUseDeepl = alwaysUseDeepl + } + } + public var userDeeplAPIFree: Bool { + didSet { + storage.userDeeplAPIFree = userDeeplAPIFree + } + } + public var autoDetectPostLanguage: Bool { + didSet { + storage.autoDetectPostLanguage = autoDetectPostLanguage + } + } + public var suppressDupeReblogs: Bool { + didSet { + storage.suppressDupeReblogs = suppressDupeReblogs + } + } + public var inAppBrowserReaderView: Bool { + didSet { + storage.inAppBrowserReaderView = inAppBrowserReaderView + } + } + public var hapticTabSelectionEnabled: Bool { + didSet { + storage.hapticTabSelectionEnabled = hapticTabSelectionEnabled + } + } + public var hapticTimelineEnabled: Bool { + didSet { + storage.hapticTimelineEnabled = hapticTimelineEnabled + } + } + public var hapticButtonPressEnabled: Bool { + didSet { + storage.hapticButtonPressEnabled = hapticButtonPressEnabled + } + } + public var soundEffectEnabled: Bool { + didSet { + storage.soundEffectEnabled = soundEffectEnabled + } + } + public var showiPhoneTabLabel: Bool { + didSet { + storage.showiPhoneTabLabel = showiPhoneTabLabel + } + } + public var showAltTextForMedia: Bool { + didSet { + storage.showAltTextForMedia = showAltTextForMedia + } + } + public var showiPadSecondaryColumn: Bool { + didSet { + storage.showiPadSecondaryColumn = showiPadSecondaryColumn + } + } + public var swipeActionsStatusTrailingRight: StatusAction { + didSet { + storage.swipeActionsStatusTrailingRight = swipeActionsStatusTrailingRight + } + } + public var swipeActionsStatusTrailingLeft: StatusAction { + didSet { + storage.swipeActionsStatusTrailingLeft = swipeActionsStatusTrailingLeft + } + } + public var swipeActionsStatusLeadingLeft: StatusAction { + didSet { + storage.swipeActionsStatusLeadingLeft = swipeActionsStatusLeadingLeft + } + } + public var swipeActionsStatusLeadingRight: StatusAction { + didSet { + storage.swipeActionsStatusLeadingRight = swipeActionsStatusLeadingRight + } + } + public var swipeActionsUseThemeColor: Bool { + didSet { + storage.swipeActionsUseThemeColor = swipeActionsUseThemeColor + } + } + public var swipeActionsIconStyle: SwipeActionsIconStyle { + didSet { + storage.swipeActionsIconStyle = swipeActionsIconStyle + } + } + public var requestedReview: Bool { + didSet { + storage.requestedReview = requestedReview + } + } + public var collapseLongPosts: Bool { + didSet { + storage.collapseLongPosts = collapseLongPosts + } + } + public var shareButtonBehavior: PreferredShareButtonBehavior { + didSet { + storage.shareButtonBehavior = shareButtonBehavior + } + } + + public enum SwipeActionsIconStyle: String, CaseIterable { case iconWithText, iconOnly - + public var description: LocalizedStringKey { switch self { case .iconWithText: @@ -70,7 +257,7 @@ public class UserPreferences: ObservableObject { "enum.swipeactions.icon-only" } } - + // Have to implement this manually here due to compiler not implicitly // inserting `nonisolated`, which leads to a warning: // @@ -81,7 +268,7 @@ public class UserPreferences: ObservableObject { [.iconWithText, .iconOnly] } } - + public var postVisibility: Models.Visibility { if useInstanceContentSettings { serverPreferences?.postVisibility ?? .pub @@ -89,26 +276,26 @@ public class UserPreferences: ObservableObject { appDefaultPostVisibility } } - + public func conformReplyVisibilityConstraints() { appDefaultReplyVisibility = getReplyVisibility() } - + private func getReplyVisibility() -> Models.Visibility { getMinVisibility(postVisibility, appDefaultReplyVisibility) } - + public func getReplyVisibility(of status: Status) -> Models.Visibility { getMinVisibility(getReplyVisibility(), status.visibility) } - + private func getMinVisibility(_ vis1: Models.Visibility, _ vis2: Models.Visibility) -> Models.Visibility { let no1 = Self.getIntOfVisibility(vis1) let no2 = Self.getIntOfVisibility(vis2) - + return no1 < no2 ? vis1 : vis2 } - + public var postIsSensitive: Bool { if useInstanceContentSettings { serverPreferences?.postIsSensitive ?? false @@ -116,7 +303,7 @@ public class UserPreferences: ObservableObject { appDefaultPostsSensitive } } - + public var autoExpandSpoilers: Bool { if useInstanceContentSettings { serverPreferences?.autoExpandSpoilers ?? true @@ -124,7 +311,7 @@ public class UserPreferences: ObservableObject { appAutoExpandSpoilers } } - + public var autoExpandMedia: ServerPreferences.AutoExpandMedia { if useInstanceContentSettings { serverPreferences?.autoExpandMedia ?? .hideSensitive @@ -132,8 +319,8 @@ public class UserPreferences: ObservableObject { appAutoExpandMedia } } - - @Published public var notificationsCount: [OauthToken: Int] = [:] { + + public var notificationsCount: [OauthToken: Int] = [:] { didSet { for (key, value) in notificationsCount { Self.sharedDefault?.set(value, forKey: "push_notifications_count_\(key.createdAt)") @@ -152,22 +339,20 @@ public class UserPreferences: ObservableObject { } } - @Published public var serverPreferences: ServerPreferences? - - private init() {} - + public var serverPreferences: ServerPreferences? + public func setClient(client: Client) { self.client = client Task { await refreshServerPreferences() } } - + public func refreshServerPreferences() async { guard let client, client.isAuth else { return } serverPreferences = try? await client.get(endpoint: Accounts.preferences) } - + public func markLanguageAsSelected(isoCode: String) { var copy = recentlyUsedLanguages if let index = copy.firstIndex(of: isoCode) { @@ -176,7 +361,7 @@ public class UserPreferences: ObservableObject { copy.insert(isoCode, at: 0) recentlyUsedLanguages = Array(copy.prefix(3)) } - + public static func getIntOfVisibility(_ vis: Models.Visibility) -> Int { switch vis { case .direct: @@ -189,4 +374,43 @@ public class UserPreferences: ObservableObject { 3 } } + + private init() { + remoteLocalTimelines = storage.remoteLocalTimelines + tagGroups = storage.tagGroups + preferredBrowser = storage.preferredBrowser + draftsPosts = storage.draftsPosts + showTranslateButton = storage.showTranslateButton + isOpenAIEnabled = storage.isOpenAIEnabled + recentlyUsedLanguages = storage.recentlyUsedLanguages + isSocialKeyboardEnabled = storage.isSocialKeyboardEnabled + useInstanceContentSettings = storage.useInstanceContentSettings + appAutoExpandSpoilers = storage.appAutoExpandSpoilers + appAutoExpandMedia = storage.appAutoExpandMedia + appDefaultPostVisibility = storage.appDefaultPostVisibility + appDefaultReplyVisibility = storage.appDefaultReplyVisibility + appDefaultPostsSensitive = storage.appDefaultPostsSensitive + autoPlayVideo = storage.autoPlayVideo + alwaysUseDeepl = storage.alwaysUseDeepl + userDeeplAPIFree = storage.userDeeplAPIFree + autoDetectPostLanguage = storage.autoDetectPostLanguage + suppressDupeReblogs = storage.suppressDupeReblogs + inAppBrowserReaderView = storage.inAppBrowserReaderView + hapticTabSelectionEnabled = storage.hapticTabSelectionEnabled + hapticTimelineEnabled = storage.hapticTimelineEnabled + hapticButtonPressEnabled = storage.hapticButtonPressEnabled + soundEffectEnabled = storage.soundEffectEnabled + showiPhoneTabLabel = storage.showiPhoneTabLabel + showAltTextForMedia = storage.showAltTextForMedia + showiPadSecondaryColumn = storage.showiPadSecondaryColumn + swipeActionsStatusTrailingRight = storage.swipeActionsStatusTrailingRight + swipeActionsStatusTrailingLeft = storage.swipeActionsStatusTrailingLeft + swipeActionsStatusLeadingLeft = storage.swipeActionsStatusLeadingLeft + swipeActionsStatusLeadingRight = storage.swipeActionsStatusLeadingRight + swipeActionsUseThemeColor = storage.swipeActionsUseThemeColor + swipeActionsIconStyle = storage.swipeActionsIconStyle + requestedReview = storage.requestedReview + collapseLongPosts = storage.collapseLongPosts + shareButtonBehavior = storage.shareButtonBehavior + } } diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift index 3373d02c..473e7187 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorAccessoryView.swift @@ -5,8 +5,9 @@ import NukeUI import PhotosUI import SwiftUI +@MainActor struct StatusEditorAccessoryView: View { - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @Environment(CurrentInstance.self) private var currentInstance @Environment(\.colorScheme) private var colorScheme diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift index 535dea18..cfc938cf 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift @@ -14,7 +14,7 @@ import UIKit @MainActor public struct StatusEditorView: View { @Environment(AppAccountsManager.self) private var appAccounts - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @Environment(Client.self) private var client @Environment(CurrentAccount.self) private var currentAccount diff --git a/Packages/Status/Sources/Status/Media/VideoPlayerView.swift b/Packages/Status/Sources/Status/Media/VideoPlayerView.swift index 98e7e700..41e63f60 100644 --- a/Packages/Status/Sources/Status/Media/VideoPlayerView.swift +++ b/Packages/Status/Sources/Status/Media/VideoPlayerView.swift @@ -51,7 +51,7 @@ import SwiftUI struct VideoPlayerView: View { @Environment(\.scenePhase) private var scenePhase @Environment(\.isCompact) private var isCompact - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(Theme.self) private var theme @State var viewModel: VideoPlayerViewModel diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift index 4d8dd09a..8548213c 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift @@ -4,11 +4,12 @@ import Models import Network import SwiftUI +@MainActor struct StatusRowActionsView: View { @Environment(Theme.self) private var theme @Environment(CurrentAccount.self) private var currentAccount @Environment(StatusDataController.self) private var statusDataController - @EnvironmentObject private var userPreferences: UserPreferences + @Environment(UserPreferences.self) private var userPreferences @Environment(\.isStatusFocused) private var isFocused diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift index 4a2c5943..841a82b1 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowContextMenu.swift @@ -10,7 +10,7 @@ struct StatusRowContextMenu: View { @Environment(Client.self) private var client @Environment(SceneDelegate.self) private var sceneDelegate - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(CurrentAccount.self) private var account @Environment(CurrentInstance.self) private var currentInstance @Environment(StatusDataController.self) private var statusDataController @@ -87,7 +87,7 @@ struct StatusRowContextMenu: View { } .environment(\.isInCaptureMode, true) .environment(Theme.shared) - .environmentObject(preferences) + .environment(preferences) .environment(account) .environment(currentInstance) .environment(SceneDelegate()) diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift index 17cae19b..fdc3f236 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowMediaPreviewView.swift @@ -5,6 +5,7 @@ import Nuke import NukeUI import SwiftUI +@MainActor public struct StatusRowMediaPreviewView: View { @Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool @Environment(\.extraLeadingInset) private var extraLeadingInset: CGFloat @@ -12,7 +13,7 @@ public struct StatusRowMediaPreviewView: View { @Environment(\.isCompact) private var isCompact: Bool @Environment(SceneDelegate.self) private var sceneDelegate - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(QuickLook.self) private var quickLook @Environment(Theme.self) private var theme diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowSwipeView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowSwipeView.swift index 68578a4f..82eac7de 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowSwipeView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowSwipeView.swift @@ -3,9 +3,10 @@ import Env import Models import SwiftUI +@MainActor struct StatusRowSwipeView: View { @Environment(Theme.self) private var theme - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences @Environment(CurrentAccount.self) private var currentAccount @Environment(StatusDataController.self) private var statusDataController diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowTranslateView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowTranslateView.swift index b0f90e77..f2e071e3 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowTranslateView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowTranslateView.swift @@ -3,11 +3,12 @@ import Env import Models import SwiftUI +@MainActor struct StatusRowTranslateView: View { @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(\.isCompact) private var isCompact: Bool - @EnvironmentObject private var preferences: UserPreferences + @Environment(UserPreferences.self) private var preferences var viewModel: StatusRowViewModel