diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents index 1d517412..98ed7f35 100644 --- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents +++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents @@ -191,6 +191,7 @@ + @@ -282,7 +283,7 @@ - + diff --git a/CoreDataStack/Entity/Setting.swift b/CoreDataStack/Entity/Setting.swift index 88768eda..0be80f97 100644 --- a/CoreDataStack/Entity/Setting.swift +++ b/CoreDataStack/Entity/Setting.swift @@ -16,6 +16,7 @@ public final class Setting: NSManagedObject { @NSManaged public var appearanceRaw: String @NSManaged public var preferredTrueBlackDarkMode: Bool @NSManaged public var preferredStaticAvatar: Bool + @NSManaged public var preferredUsingDefaultBrowser: Bool @NSManaged public private(set) var createdAt: Date @NSManaged public private(set) var updatedAt: Date @@ -62,6 +63,12 @@ extension Setting { self.preferredStaticAvatar = preferredStaticAvatar didUpdate(at: Date()) } + + public func update(preferredUsingDefaultBrowser: Bool) { + guard preferredUsingDefaultBrowser != self.preferredUsingDefaultBrowser else { return } + self.preferredUsingDefaultBrowser = preferredUsingDefaultBrowser + didUpdate(at: Date()) + } public func didUpdate(at networkDate: Date) { self.updatedAt = networkDate diff --git a/Localization/app.json b/Localization/app.json index 5c8dd5cc..50abb54b 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -495,12 +495,8 @@ "dark": "Always Dark" }, "appearance_settings": { - "dark_mode": { - "title": "True black Dark Mode" - }, - "avatar_animation": { - "title": "Disable avatar animation" - } + "true_black_dark_mode": "True black Dark Mode", + "disable_avatar_animation": "Disable avatar animation" }, "notifications": { "title": "Notifications", @@ -516,6 +512,10 @@ "title": "Notify me when" } }, + "preference": { + "title": "Preference", + "using_default_browser": "Using default browser open link" + }, "boringzone": { "title": "The Boring zone", "terms": "Terms of Service", diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index b6c0cf11..26e91f96 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -416,6 +416,7 @@ DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */; }; DBA1DB80268F84F80052DB59 /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA1DB7F268F84F80052DB59 /* NotificationType.swift */; }; DBA465932696B495002B41DB /* APIService+WebFinger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA465922696B495002B41DB /* APIService+WebFinger.swift */; }; + DBA465952696E387002B41DB /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA465942696E387002B41DB /* AppPreference.swift */; }; DBA5E7A3263AD0A3004598BB /* PhotoLibraryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */; }; DBA5E7A5263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */; }; DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */; }; @@ -1046,6 +1047,7 @@ DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = ""; }; DBA1DB7F268F84F80052DB59 /* NotificationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = ""; }; DBA465922696B495002B41DB /* APIService+WebFinger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+WebFinger.swift"; sourceTree = ""; }; + DBA465942696E387002B41DB /* AppPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreference.swift; sourceTree = ""; }; DBA5E7A2263AD0A3004598BB /* PhotoLibraryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryService.swift; sourceTree = ""; }; DBA5E7A4263BD28C004598BB /* ContextMenuImagePreviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewModel.swift; sourceTree = ""; }; DBA5E7A8263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuImagePreviewViewController.swift; sourceTree = ""; }; @@ -1987,6 +1989,7 @@ isa = PBXGroup; children = ( DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */, + DBA465942696E387002B41DB /* AppPreference.swift */, DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */, DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */, DB1D842F26566512000346B3 /* KeyboardPreference.swift */, @@ -3228,6 +3231,7 @@ DBAC649B267DF8C8007FE9FD /* ActivityIndicatorNode.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, 2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */, + DBA465952696E387002B41DB /* AppPreference.swift in Sources */, DB87D4572609DD5300D12C0D /* DeleteBackwardResponseTextField.swift in Sources */, 2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */, DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index b918e181..f1f288fa 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -192,8 +192,12 @@ extension SceneCoordinator { sender?.navigationController?.pushViewController(viewController, animated: true) case .safariPresent(let animated, let completion): - viewController.modalPresentationCapturesStatusBarAppearance = true - presentingViewController.present(viewController, animated: animated, completion: completion) + if UserDefaults.shared.preferredUsingDefaultBrowser, case let .safari(url) = scene { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else { + viewController.modalPresentationCapturesStatusBarAppearance = true + presentingViewController.present(viewController, animated: animated, completion: completion) + } case .alertController(let animated, let completion): viewController.modalPresentationCapturesStatusBarAppearance = true diff --git a/Mastodon/Diffiable/Item/SettingsItem.swift b/Mastodon/Diffiable/Item/SettingsItem.swift index aca02474..7320e9fc 100644 --- a/Mastodon/Diffiable/Item/SettingsItem.swift +++ b/Mastodon/Diffiable/Item/SettingsItem.swift @@ -13,6 +13,7 @@ enum SettingsItem: Hashable { case appearanceDarkMode(settingObjectID: NSManagedObjectID) case appearanceDisableAvatarAnimation(settingObjectID: NSManagedObjectID) case notification(settingObjectID: NSManagedObjectID, switchMode: NotificationSwitchMode) + case preferenceUsingDefaultBrowser(settingObjectID: NSManagedObjectID) case boringZone(item: Link) case spicyZone(item: Link) } diff --git a/Mastodon/Diffiable/Section/SettingsSection.swift b/Mastodon/Diffiable/Section/SettingsSection.swift index 9a248dca..46ac82a1 100644 --- a/Mastodon/Diffiable/Section/SettingsSection.swift +++ b/Mastodon/Diffiable/Section/SettingsSection.swift @@ -11,6 +11,7 @@ enum SettingsSection: Hashable { case appearance case appearanceSettings case notifications + case preference case boringZone case spicyZone @@ -19,6 +20,7 @@ enum SettingsSection: Hashable { case .appearance: return L10n.Scene.Settings.Section.Appearance.title case .appearanceSettings: return "" case .notifications: return L10n.Scene.Settings.Section.Notifications.title + case .preference: return L10n.Scene.Settings.Section.Preference.title case .boringZone: return L10n.Scene.Settings.Section.Boringzone.title case .spicyZone: return L10n.Scene.Settings.Section.Spicyzone.title } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index ab2b75f4..6ea2504a 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -934,14 +934,10 @@ internal enum L10n { internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Appearance.Title") } internal enum AppearanceSettings { - internal enum AvatarAnimation { - /// Disable avatar animation - internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.AvatarAnimation.Title") - } - internal enum DarkMode { - /// True black Dark Mode - internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.DarkMode.Title") - } + /// Disable avatar animation + internal static let disableAvatarAnimation = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation") + /// True black Dark Mode + internal static let trueBlackDarkMode = L10n.tr("Localizable", "Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode") } internal enum Boringzone { /// Privacy Policy @@ -975,6 +971,12 @@ internal enum L10n { internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Trigger.Title") } } + internal enum Preference { + /// Preference + internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Preference.Title") + /// Using default browser open link + internal static let usingDefaultBrowser = L10n.tr("Localizable", "Scene.Settings.Section.Preference.UsingDefaultBrowser") + } internal enum Spicyzone { /// Clear Media Cache internal static let clear = L10n.tr("Localizable", "Scene.Settings.Section.Spicyzone.Clear") diff --git a/Mastodon/Preference/AppPreference.swift b/Mastodon/Preference/AppPreference.swift new file mode 100644 index 00000000..4ede61cf --- /dev/null +++ b/Mastodon/Preference/AppPreference.swift @@ -0,0 +1,20 @@ +// +// AppPreference.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-7-8. +// + +import UIKit + +extension UserDefaults { + + @objc dynamic var preferredUsingDefaultBrowser: Bool { + get { + register(defaults: [#function: false]) + return bool(forKey: #function) + } + set { self[#function] = newValue } + } + +} diff --git a/Mastodon/Resources/ar.lproj/Localizable.strings b/Mastodon/Resources/ar.lproj/Localizable.strings index f6211e83..82663ba2 100644 --- a/Mastodon/Resources/ar.lproj/Localizable.strings +++ b/Mastodon/Resources/ar.lproj/Localizable.strings @@ -317,8 +317,8 @@ any server."; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; "Scene.Settings.Section.Appearance.Light" = "Always Light"; "Scene.Settings.Section.Appearance.Title" = "Appearance"; -"Scene.Settings.Section.AppearanceSettings.AvatarAnimation.Title" = "Disable avatar animation"; -"Scene.Settings.Section.AppearanceSettings.DarkMode.Title" = "True black Dark Mode"; +"Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation" = "Disable avatar animation"; +"Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode" = "True black Dark Mode"; "Scene.Settings.Section.Boringzone.Privacy" = "Privacy Policy"; "Scene.Settings.Section.Boringzone.Terms" = "Terms of Service"; "Scene.Settings.Section.Boringzone.Title" = "The Boring zone"; @@ -332,6 +332,8 @@ any server."; "Scene.Settings.Section.Notifications.Trigger.Follower" = "a follower"; "Scene.Settings.Section.Notifications.Trigger.Noone" = "no one"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when"; +"Scene.Settings.Section.Preference.Title" = "Preference"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Using default browser open link"; "Scene.Settings.Section.Spicyzone.Clear" = "Clear Media Cache"; "Scene.Settings.Section.Spicyzone.Signout" = "Sign Out"; "Scene.Settings.Section.Spicyzone.Title" = "The spicy zone"; diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index f6211e83..82663ba2 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -317,8 +317,8 @@ any server."; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; "Scene.Settings.Section.Appearance.Light" = "Always Light"; "Scene.Settings.Section.Appearance.Title" = "Appearance"; -"Scene.Settings.Section.AppearanceSettings.AvatarAnimation.Title" = "Disable avatar animation"; -"Scene.Settings.Section.AppearanceSettings.DarkMode.Title" = "True black Dark Mode"; +"Scene.Settings.Section.AppearanceSettings.DisableAvatarAnimation" = "Disable avatar animation"; +"Scene.Settings.Section.AppearanceSettings.TrueBlackDarkMode" = "True black Dark Mode"; "Scene.Settings.Section.Boringzone.Privacy" = "Privacy Policy"; "Scene.Settings.Section.Boringzone.Terms" = "Terms of Service"; "Scene.Settings.Section.Boringzone.Title" = "The Boring zone"; @@ -332,6 +332,8 @@ any server."; "Scene.Settings.Section.Notifications.Trigger.Follower" = "a follower"; "Scene.Settings.Section.Notifications.Trigger.Noone" = "no one"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when"; +"Scene.Settings.Section.Preference.Title" = "Preference"; +"Scene.Settings.Section.Preference.UsingDefaultBrowser" = "Using default browser open link"; "Scene.Settings.Section.Spicyzone.Clear" = "Clear Media Cache"; "Scene.Settings.Section.Spicyzone.Signout" = "Sign Out"; "Scene.Settings.Section.Spicyzone.Title" = "The spicy zone"; diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 6a08bd45..aa2ff0cf 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -364,6 +364,9 @@ extension SettingsViewController: UITableViewDelegate { case .notification: // do nothing break + case .preferenceUsingDefaultBrowser: + // do nothing + break case .boringZone(let link), .spicyZone(let link): switch link { case .termsOfService, .privacyPolicy: @@ -501,7 +504,24 @@ extension SettingsViewController: SettingsToggleCellDelegate { // do nothing } .store(in: &disposeBag) + case .preferenceUsingDefaultBrowser(let settingObjectID): + let managedObjectContext = context.backgroundManagedObjectContext + managedObjectContext.performChanges { + let setting = managedObjectContext.object(with: settingObjectID) as! Setting + setting.update(preferredUsingDefaultBrowser: isOn) + } + .sink { result in + switch result { + case .success: + UserDefaults.shared.preferredUsingDefaultBrowser = isOn + case .failure(let error): + assertionFailure(error.localizedDescription) + break + } + } + .store(in: &disposeBag) default: + assertionFailure() break } } diff --git a/Mastodon/Scene/Settings/SettingsViewModel.swift b/Mastodon/Scene/Settings/SettingsViewModel.swift index 142de7dc..5d60f166 100644 --- a/Mastodon/Scene/Settings/SettingsViewModel.swift +++ b/Mastodon/Scene/Settings/SettingsViewModel.swift @@ -102,13 +102,18 @@ extension SettingsViewModel { ] snapshot.appendSections([.appearanceSettings]) snapshot.appendItems(appearanceSettingItems, toSection: .appearanceSettings) - + + // notification let notificationItems = SettingsItem.NotificationSwitchMode.allCases.map { mode in SettingsItem.notification(settingObjectID: setting.objectID, switchMode: mode) } snapshot.appendSections([.notifications]) snapshot.appendItems(notificationItems, toSection: .notifications) + // preference + snapshot.appendSections([.preference]) + snapshot.appendItems([.preferenceUsingDefaultBrowser(settingObjectID: setting.objectID)], toSection: .preference) + // boring zone let boringZoneSettingsItems: [SettingsItem] = { let links: [SettingsItem.Link] = [ @@ -170,7 +175,8 @@ extension SettingsViewModel { cell.delegate = settingsAppearanceTableViewCellDelegate return cell case .appearanceDarkMode(let objectID), - .appearanceDisableAvatarAnimation(let objectID): + .appearanceDisableAvatarAnimation(let objectID), + .preferenceUsingDefaultBrowser(let objectID): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell cell.delegate = settingsToggleCellDelegate self.context.managedObjectContext.performAndWait { @@ -231,11 +237,14 @@ extension SettingsViewModel { ) { switch item { case .appearanceDarkMode: - cell.textLabel?.text = L10n.Scene.Settings.Section.AppearanceSettings.DarkMode.title + cell.textLabel?.text = L10n.Scene.Settings.Section.AppearanceSettings.trueBlackDarkMode cell.switchButton.isOn = setting.preferredTrueBlackDarkMode case .appearanceDisableAvatarAnimation: - cell.textLabel?.text = L10n.Scene.Settings.Section.AppearanceSettings.AvatarAnimation.title + cell.textLabel?.text = L10n.Scene.Settings.Section.AppearanceSettings.disableAvatarAnimation cell.switchButton.isOn = setting.preferredStaticAvatar + case .preferenceUsingDefaultBrowser: + cell.textLabel?.text = L10n.Scene.Settings.Section.Preference.usingDefaultBrowser + cell.switchButton.isOn = setting.preferredUsingDefaultBrowser default: assertionFailure() } diff --git a/Mastodon/Service/AuthenticationService.swift b/Mastodon/Service/AuthenticationService.swift index 4a460b1d..a0bbca57 100644 --- a/Mastodon/Service/AuthenticationService.swift +++ b/Mastodon/Service/AuthenticationService.swift @@ -81,26 +81,6 @@ final class AuthenticationService: NSObject { .assign(to: \.value, on: activeMastodonAuthenticationBox) .store(in: &disposeBag) - activeMastodonAuthenticationBox - .receive(on: RunLoop.main) - .sink { [weak self] authenticationBox in - guard let _ = self else { return } - guard let authenticationBox = authenticationBox else { return } - let request = Setting.sortedFetchRequest - request.predicate = Setting.predicate(domain: authenticationBox.domain, userID: authenticationBox.userID) - guard let setting = managedObjectContext.safeFetch(request).first else { return } - - let themeName: ThemeName = setting.preferredTrueBlackDarkMode ? .system : .mastodon - if UserDefaults.shared.currentThemeNameRawValue != themeName.rawValue { - ThemeService.shared.set(themeName: themeName) - os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update theme style", ((#file as NSString).lastPathComponent), #line, #function) - } - if UserDefaults.shared.preferredStaticAvatar != setting.preferredStaticAvatar { - UserDefaults.shared.preferredStaticAvatar = setting.preferredStaticAvatar - } - } - .store(in: &disposeBag) - do { try mastodonAuthenticationFetchedResultsController.performFetch() mastodonAuthentications.value = mastodonAuthenticationFetchedResultsController.fetchedObjects ?? [] diff --git a/Mastodon/Service/SettingService.swift b/Mastodon/Service/SettingService.swift index 7a25d6c7..7da8c368 100644 --- a/Mastodon/Service/SettingService.swift +++ b/Mastodon/Service/SettingService.swift @@ -91,20 +91,16 @@ final class SettingService { self.currentSettingUpdateSubscription = nil return } - + + SettingService.updatePreference(setting: setting) self.currentSettingUpdateSubscription = ManagedObjectObserver.observe(object: setting) .sink(receiveCompletion: { _ in // do nothing }, receiveValue: { change in guard case .update(let object) = change.changeType, let setting = object as? Setting else { return } - - // observe apparance mode - switch setting.appearance { - case .automatic: UserDefaults.shared.customUserInterfaceStyle = .unspecified - case .light: UserDefaults.shared.customUserInterfaceStyle = .light - case .dark: UserDefaults.shared.customUserInterfaceStyle = .dark - } + + SettingService.updatePreference(setting: setting) }) } .store(in: &disposeBag) @@ -187,3 +183,37 @@ extension SettingService { } } + +extension SettingService { + + static func updatePreference(setting: Setting) { + // set appearance + let userInterfaceStyle: UIUserInterfaceStyle = { + switch setting.appearance { + case .automatic: return .unspecified + case .light: return .light + case .dark: return .dark + } + }() + if UserDefaults.shared.customUserInterfaceStyle != userInterfaceStyle { + UserDefaults.shared.customUserInterfaceStyle = userInterfaceStyle + } + + // set theme + let themeName: ThemeName = setting.preferredTrueBlackDarkMode ? .system : .mastodon + if UserDefaults.shared.currentThemeNameRawValue != themeName.rawValue { + ThemeService.shared.set(themeName: themeName) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update theme style", ((#file as NSString).lastPathComponent), #line, #function) + } + + // set avatar mode + if UserDefaults.shared.preferredStaticAvatar != setting.preferredStaticAvatar { + UserDefaults.shared.preferredStaticAvatar = setting.preferredStaticAvatar + } + + // set browser + if UserDefaults.shared.preferredUsingDefaultBrowser != setting.preferredUsingDefaultBrowser { + UserDefaults.shared.preferredUsingDefaultBrowser = setting.preferredUsingDefaultBrowser + } + } +}