From aa06192fdb7b2d23182f3716d665817978a6dd1d Mon Sep 17 00:00:00 2001 From: Craig Reyenga <craig.reyenga@gmail.com> Date: Tue, 3 Dec 2024 15:10:08 -0500 Subject: [PATCH] Settings: refactor controller and cell Each settings item is now of a designated type; the cell simply displays each item as presented without any special logic for each. This new pattern also allows for the removal of all logic that relied on a given settings item's position for its behavior. Now, settings items can be added or removed at will, without affecting the others. There should be no user-facing changes in this branch; if there are any, they are regressions. --- .periphery.yml | 7 + Sources/Helpers/KeychainCoordinator.swift | 4 +- .../Controller/PasscodeLockController.swift | 2 +- .../Controller/SettingsController.swift | 362 ++--- Sources/Settings/Model/SettingsSection.swift | 1391 ++++++++--------- Sources/Settings/View/SettingsCell.swift | 175 ++- 6 files changed, 875 insertions(+), 1066 deletions(-) create mode 100644 .periphery.yml diff --git a/.periphery.yml b/.periphery.yml new file mode 100644 index 000000000..a933780ac --- /dev/null +++ b/.periphery.yml @@ -0,0 +1,7 @@ +retain_objc_accessible: true +retain_public: true +schemes: +- VLC-iOS +targets: +- VLC-iOS +workspace: VLC.xcworkspace diff --git a/Sources/Helpers/KeychainCoordinator.swift b/Sources/Helpers/KeychainCoordinator.swift index e2a7d20e2..2002b0093 100644 --- a/Sources/Helpers/KeychainCoordinator.swift +++ b/Sources/Helpers/KeychainCoordinator.swift @@ -52,8 +52,8 @@ class KeychainCoordinator: NSObject { private lazy var passcodeLockController: PasscodeLockController = { let passcodeController = PasscodeLockController(action: .enter) - passcodeController?.delegate = self - return passcodeController! + passcodeController.delegate = self + return passcodeController }() override init() { diff --git a/Sources/Settings/Controller/PasscodeLockController.swift b/Sources/Settings/Controller/PasscodeLockController.swift index 96ac64b49..0fe209135 100644 --- a/Sources/Settings/Controller/PasscodeLockController.swift +++ b/Sources/Settings/Controller/PasscodeLockController.swift @@ -134,7 +134,7 @@ class PasscodeLockController: UIViewController { // MARK: - Init - convenience init?(action: PasscodeAction) { + convenience init(action: PasscodeAction) { self.init() self.action = action setup() diff --git a/Sources/Settings/Controller/SettingsController.swift b/Sources/Settings/Controller/SettingsController.swift index 4f943378e..0965fd1c5 100644 --- a/Sources/Settings/Controller/SettingsController.swift +++ b/Sources/Settings/Controller/SettingsController.swift @@ -20,9 +20,12 @@ import LocalAuthentication extension Notification.Name { static let VLCDisableGroupingDidChangeNotification = Notification.Name("disableGroupingDidChangeNotfication") + static let VLCDidToggleSettingNotification = Notification.Name("didToggleSettingNotification") } class SettingsController: UITableViewController { + static let toggleNotificationKey = "toggleNotificationKey" + static let toggleNotificationValue = "toggleNotificationValue" private let cellReuseIdentifier = "settingsCell" private let sectionHeaderReuseIdentifier = "sectionHeaderReuseIdentifier" @@ -36,6 +39,13 @@ class SettingsController: UITableViewController { private var isBackingUp = false private let isLabActivated: Bool = true + /// the source of all data. + private var settingsSections: [SettingsSection] = [] { + didSet { + tableView.reloadData() + } + } + override var preferredStatusBarStyle: UIStatusBarStyle { return PresentationTheme.current.colors.statusBarStyle } @@ -62,6 +72,7 @@ class SettingsController: UITableViewController { registerTableViewClasses() setupBarButton() addObservers() + reloadSettingsSections() } // MARK: - Setup Functions @@ -94,6 +105,10 @@ class SettingsController: UITableViewController { selector: #selector(miniPlayerIsHidden), name: NSNotification.Name(rawValue: VLCPlayerDisplayControllerHideMiniPlayer), object: nil) + notificationCenter.addObserver(self, + selector: #selector(didToggleSettingNotification(note:)), + name: .VLCDidToggleSettingNotification, + object: nil) } private func registerTableViewClasses() { @@ -148,7 +163,7 @@ class SettingsController: UITableViewController { self.view.backgroundColor = PresentationTheme.current.colors.background setNavBarAppearance() self.setNeedsStatusBarAppearanceUpdate() - self.tableView.reloadData() // When theme changes hide the black theme section if needed + self.reloadSettingsSections() // When theme changes hide the black theme section if needed } @objc private func miniPlayerIsShown() { @@ -165,7 +180,40 @@ class SettingsController: UITableViewController { right: 0) } + @objc private func didToggleSettingNotification(note: Notification) { + guard let preferenceKey = note.userInfo?[Self.toggleNotificationKey] as? String else { return } + guard let isOn = note.userInfo?[Self.toggleNotificationValue] as? Bool else { return } + + userDefaults.set(isOn, forKey: preferenceKey) + + switch preferenceKey { + case kVLCSettingPasscodeOnKey: + passcodeLockSwitchOn(state: isOn) + case kVLCSettingHideLibraryInFilesApp: + medialibraryHidingLockSwitchOn(state: isOn) + case kVLCSettingBackupMediaLibrary: + mediaLibraryBackupActivateSwitchOn(state: isOn) + case kVLCSettingsDisableGrouping: + medialibraryDisableGroupingSwitchOn(state: isOn) + case kVLCSettingPlaybackTapSwipeEqual, kVLCSettingPlaybackForwardBackwardEqual: + reloadSettingsSections() + default: + break + } + } + // MARK: - Helper Functions + private func showDonation(indexPath: IndexPath) { + if #available(iOS 10, *) { + ImpactFeedbackGenerator().selectionChanged() + } + let donationVC = VLCDonationViewController(nibName: "VLCDonationViewController", bundle: nil) + let donationNC = UINavigationController(rootViewController: donationVC) + donationNC.modalPresentationStyle = .popover + donationNC.modalTransitionStyle = .flipHorizontal + donationNC.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) + present(donationNC, animated: true, completion: nil) + } private func forceRescanAlert() { NotificationFeedbackGenerator().warning() @@ -196,24 +244,8 @@ class SettingsController: UITableViewController { UIApplication.shared.open(url, options: [:], completionHandler: nil) } - private func showActionSheet(for sectionType: SectionType?) { - guard let sectionType = sectionType else { return } - guard !sectionType.containsSwitch else { return } - guard let preferenceKey = sectionType.preferenceKey else { - assertionFailure("SettingsController: No Preference Key Available.") - return - } - - var playbackTitle: String? = nil - if sectionType is PlaybackControlOptions { - playbackTitle = sectionType.description - } - specifierManager.playbackTitle = playbackTitle - - showActionSheet(preferenceKey: preferenceKey) - } - - private func showActionSheet(preferenceKey: String?) { + private func showActionSheet(title: String, preferenceKey: String) { + specifierManager.playbackTitle = title specifierManager.preferenceKey = preferenceKey specifierManager.settingsBundle = settingsBundle actionSheet.delegate = specifierManager @@ -238,9 +270,11 @@ class SettingsController: UITableViewController { } } - private func playHaptics(sectionType: SectionType?) { - guard let sectionType = sectionType else { return } - if !sectionType.containsSwitch { + private func playHaptics(settingsItem: SettingsItem) { + switch settingsItem.action { + case .toggle: + break + default: ImpactFeedbackGenerator().selectionChanged() } } @@ -276,227 +310,69 @@ class SettingsController: UITableViewController { extension SettingsController { + func reloadSettingsSections() { + // , passcodeLockSwitchOn: true + settingsSections = SettingsSection + .sections(isLabActivated: isLabActivated, + isBackingUp: isBackingUp, + isForwardBackwardEqual: userDefaults.bool(forKey: kVLCSettingPlaybackForwardBackwardEqual), + isTapSwipeEqual: userDefaults.bool(forKey: kVLCSettingPlaybackTapSwipeEqual)) + } + override func numberOfSections(in tableView: UITableView) -> Int { - var numberOfSections = SettingsSection.allCases.count - // Remove the last section if the lab is deactivated - if isLabActivated == false { - numberOfSections = numberOfSections - 1 - } - return numberOfSections + settingsSections.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let settingsSection = SettingsSection(rawValue: section) else { return 0 } - switch settingsSection { - case .main: - return MainOptions.allCases.count - case .donation: - return DonationOptions.allCases.count - case .generic: - return GenericOptions.allCases.count - case .privacy: - return PrivacyOptions.allCases.count - case .gestureControl: - return PlaybackControlOptions.allCases.count - case .video: - return VideoOptions.allCases.count - case .subtitles: - return SubtitlesOptions.allCases.count - case .audio: - return AudioOptions.allCases.count - case .casting: - return CastingOptions.allCases.count - case .mediaLibrary: - return MediaLibraryOptions.allCases.count - case .network: - return NetworkOptions.allCases.count - case .lab: - return Lab.allCases.count - case .reset: - return Reset.allCases.count - } + settingsSections[section].items.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if indexPath == [SettingsSection.privacy.rawValue, PrivacyOptions.enableBiometrics.rawValue] && !userDefaults.bool(forKey: kVLCSettingPasscodeOnKey) { - //If the passcode lock is on we return a default UITableViewCell else - //while hiding the biometric option row using a cell height of 0 - //constraint warnings will be printed to the console since the cell height (0) - //collapses on given constraints (Top, leading, trailing, Bottom of StackView to Cell) + guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as? SettingsCell else { return UITableViewCell() } - let forwardBackwardEqual = userDefaults.bool(forKey: kVLCSettingPlaybackForwardBackwardEqual) - let tapSwipeEqual = userDefaults.bool(forKey: kVLCSettingPlaybackTapSwipeEqual) + cell.settingsBundle = settingsBundle - // Here we skip the Settings Cell's initialization in order to avoid console warnings - // when the cell is supposed to be hidden and therefore the cell's height is equal to 0. - if indexPath.row == PlaybackControlOptions.backwardSkipLength.rawValue && - forwardBackwardEqual { - return UITableViewCell() - } else if indexPath.row == PlaybackControlOptions.forwardSkipLengthSwipe.rawValue && - tapSwipeEqual { - return UITableViewCell() - } else if indexPath.row == PlaybackControlOptions.backwardSkipLengthSwipe.rawValue && - (tapSwipeEqual || forwardBackwardEqual) { - return UITableViewCell() - } + let section = settingsSections[indexPath.section] + let settingsItem = section.items[indexPath.row] + cell.settingsItem = settingsItem - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as? SettingsCell else { - return UITableViewCell() - } - cell.settingsBundle = settingsBundle - guard let section = SettingsSection(rawValue: indexPath.section) else { - return UITableViewCell() - } - switch section { - case .main: - cell.sectionType = MainOptions(rawValue: indexPath.row) - case .generic: - cell.sectionType = GenericOptions(rawValue: indexPath.row) - if indexPath.row == GenericOptions.automaticallyPlayNextItem.rawValue { - cell.subtitleLabel.text = nil - } - case .donation: - cell.sectionType = DonationOptions(rawValue: indexPath.row) - case .privacy: - let privacy = PrivacyOptions(rawValue: indexPath.row) - let isPasscodeOn = userDefaults.bool(forKey: kVLCSettingPasscodeOnKey) - if indexPath.row == PrivacyOptions.enableBiometrics.rawValue { - if !isPasscodeOn || privacy?.preferenceKey == nil { - //If Passcode Lock Switch is off or Biometric Row Preference Key returns nil - //We hide the cell - cell.isHidden = true - } - } - cell.sectionType = privacy - cell.passcodeSwitchDelegate = self - cell.medialibraryHidingSwitchDelegate = self - case .gestureControl: - let gestureControlOptions = PlaybackControlOptions(rawValue: indexPath.row) - cell.sectionType = gestureControlOptions - cell.skipDurationDelegate = self - case .video: - cell.sectionType = VideoOptions(rawValue: indexPath.row) - case .subtitles: - cell.sectionType = SubtitlesOptions(rawValue: indexPath.row) - case .audio: - cell.sectionType = AudioOptions(rawValue: indexPath.row) - case .casting: - cell.sectionType = CastingOptions(rawValue: indexPath.row) - case .mediaLibrary: - let mediaLibOptions = MediaLibraryOptions(rawValue: indexPath.row) - if indexPath.row == MediaLibraryOptions.forceVLCToRescanTheMediaLibrary.rawValue { - cell.mainLabel.textColor = PresentationTheme.current.colors.orangeUI - } - cell.mediaLibraryBackupSwitchDelegate = self - cell.medialibraryDisableGroupingSwitchDelegate = self - if indexPath.row == MediaLibraryOptions.includeMediaLibInDeviceBackup.rawValue { - if isBackingUp { - cell.accessoryView = .none - cell.accessoryType = .none - cell.activityIndicator.startAnimating() - } else { - cell.activityIndicator.stopAnimating() - } - cell.showsActivityIndicator = isBackingUp - } - cell.sectionType = mediaLibOptions - if indexPath.row == 0 { - cell.accessoryView = .none - cell.accessoryType = .none - } - case .network: - cell.sectionType = NetworkOptions(rawValue: indexPath.row) - case .lab: - let lab = Lab(rawValue: indexPath.row) - cell.sectionType = lab - if indexPath.row == 1 { - cell.accessoryView = .none - cell.accessoryType = .none - } - case .reset: - cell.sectionType = Reset(rawValue: indexPath.row) - cell.accessoryView = .none - cell.accessoryType = .none - } return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard let section = SettingsSection(rawValue: indexPath.section) else { return } - if section == .main && indexPath.row == 0 { + + let section = settingsSections[indexPath.section] + let settingsItem = section.items[indexPath.row] + + playHaptics(settingsItem: settingsItem) + + switch settingsItem.action { + case .isLoading: + break + case .toggle: + break // we get a notification from the switch and do our work there + case .openPrivacySettings: openPrivacySettings() - return - } - if section == .mediaLibrary && indexPath.row == 0 { + case .forceRescanAlert: forceRescanAlert() - return - } - if section == .lab && indexPath.row == 1 { + case .exportMediaLibrary: exportMediaLibrary() - return - } - switch section { - case .main: - let mainSection = MainOptions(rawValue: indexPath.row) - playHaptics(sectionType: mainSection) - showActionSheet(for: mainSection) case .donation: - ImpactFeedbackGenerator().selectionChanged() - let donationVC = VLCDonationViewController(nibName: "VLCDonationViewController", bundle: nil) - let donationNC = UINavigationController(rootViewController: donationVC) - donationNC.modalPresentationStyle = .popover - donationNC.modalTransitionStyle = .flipHorizontal - donationNC.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) - present(donationNC, animated: true, completion: nil) - case .generic: - let genericSection = GenericOptions(rawValue: indexPath.row) - playHaptics(sectionType: genericSection) - showActionSheet(for: genericSection) - case .privacy: - let privacySection = PrivacyOptions(rawValue: indexPath.row) - playHaptics(sectionType: privacySection) - case .gestureControl: - let gestureSection = PlaybackControlOptions(rawValue: indexPath.row) - playHaptics(sectionType: gestureSection) - showActionSheet(for: gestureSection) - case .video: - let videoSection = VideoOptions(rawValue: indexPath.row) - playHaptics(sectionType: videoSection) - showActionSheet(for: videoSection) - case .subtitles: - let subtitleSection = SubtitlesOptions(rawValue: indexPath.row) - playHaptics(sectionType: subtitleSection) - showActionSheet(for: subtitleSection) - case .audio: - let audioSection = AudioOptions(rawValue: indexPath.row) - playHaptics(sectionType: audioSection) - showActionSheet(for: audioSection) - case .casting: - let castingSection = CastingOptions(rawValue: indexPath.row) - playHaptics(sectionType: castingSection) - showActionSheet(for: castingSection) - case .mediaLibrary: - break - case .network: - let networkSection = NetworkOptions(rawValue: indexPath.row) - playHaptics(sectionType: networkSection) - showActionSheet(for: networkSection) - case .lab: - break - case .reset: - let resetSection = Reset(rawValue: indexPath.row) - playHaptics(sectionType: resetSection) + showDonation(indexPath: indexPath) + case .displayResetAlert: displayResetAlert() + case .showActionSheet(let title, let preferenceKey, _): + showActionSheet(title: title, preferenceKey: preferenceKey) } } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { guard let headerView = tableView.dequeueReusableHeaderFooterView( withIdentifier: sectionHeaderReuseIdentifier) as? SettingsHeaderView else { return nil } - guard let description = SettingsSection.init(rawValue: section)?.description else { return nil } - headerView.sectionHeaderLabel.text = settingsBundle.localizedString(forKey: description, value: description, table: "Root") + guard let title = settingsSections[section].title else { return nil } + headerView.sectionHeaderLabel.text = settingsBundle.localizedString(forKey: title, value: title, table: "Root") return headerView } @@ -520,37 +396,7 @@ extension SettingsController { } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let automaticDimension = UITableView.automaticDimension - - if indexPath == [SettingsSection.privacy.rawValue, PrivacyOptions.enableBiometrics.rawValue] { - let isPasscodeOn = userDefaults.bool(forKey: kVLCSettingPasscodeOnKey) - let privacySection = PrivacyOptions(rawValue: indexPath.row) - if privacySection?.preferenceKey == nil { - //LAContext canEvaluatePolicy supports iOS 11.0.1 and above. - //If canEvaluatePolicy is not supported the preference key for the biometric row is nil. - //Therefore we never show the biometric options row in this case - return 0 - } - return isPasscodeOn ? automaticDimension : 0 //If Passcode Lock is turned off we hide the biometric options row - } - - let tapSwipeEqual = userDefaults.bool(forKey: kVLCSettingPlaybackTapSwipeEqual) - let forwardBackwardEqual = userDefaults.bool(forKey: kVLCSettingPlaybackForwardBackwardEqual) - let playbackControlsSection: Int = SettingsSection.gestureControl.rawValue - - // Hide the unused skip duration cells depending on the settings selected by the user - if indexPath == [playbackControlsSection, PlaybackControlOptions.backwardSkipLength.rawValue] && - forwardBackwardEqual { - return 0 - } else if indexPath == [playbackControlsSection, PlaybackControlOptions.forwardSkipLengthSwipe.rawValue] && - tapSwipeEqual { - return 0 - } else if indexPath == [playbackControlsSection, PlaybackControlOptions.backwardSkipLengthSwipe.rawValue] && - (tapSwipeEqual || forwardBackwardEqual) { - return 0 - } - - return automaticDimension + UITableView.automaticDimension } } @@ -559,14 +405,14 @@ extension SettingsController: MediaLibraryDeviceBackupDelegate { func medialibraryDidStartExclusion() { DispatchQueue.main.async { self.isBackingUp = true - self.tableView.reloadData() + self.reloadSettingsSections() } } func medialibraryDidCompleteExclusion() { DispatchQueue.main.async { self.isBackingUp = false - self.tableView.reloadData() + self.reloadSettingsSections() } } } @@ -574,48 +420,48 @@ extension SettingsController: MediaLibraryDeviceBackupDelegate { extension SettingsController: MediaLibraryHidingDelegate { func medialibraryDidStartHiding() { DispatchQueue.main.async { - self.tableView.reloadData() + self.reloadSettingsSections() } } func medialibraryDidCompleteHiding() { DispatchQueue.main.async { - self.tableView.reloadData() + self.reloadSettingsSections() } } } // MARK: - SwitchOn Delegates -extension SettingsController: PasscodeActivateDelegate { +extension SettingsController { func passcodeLockSwitchOn(state: Bool) { if state { - guard let passcodeLockController = PasscodeLockController(action: .set) else { return } + let passcodeLockController = PasscodeLockController(action: .set) let passcodeNavigationController = UINavigationController(rootViewController: passcodeLockController) passcodeNavigationController.modalPresentationStyle = .fullScreen present(passcodeNavigationController, animated: true) { - self.tableView.reloadData() //To show/hide biometric row + self.reloadSettingsSections() //To show/hide biometric row } } else { - tableView.reloadData() + reloadSettingsSections() } } } -extension SettingsController: MedialibraryHidingActivateDelegate { +extension SettingsController { func medialibraryHidingLockSwitchOn(state: Bool) { mediaLibraryService.hideMediaLibrary(state) } } -extension SettingsController: MediaLibraryBackupActivateDelegate { +extension SettingsController { func mediaLibraryBackupActivateSwitchOn(state: Bool) { mediaLibraryService.excludeFromDeviceBackup(state) } } -extension SettingsController: MediaLibraryDisableGroupingDelegate { +extension SettingsController { func medialibraryDisableGroupingSwitchOn(state: Bool) { notificationCenter.post(name: .VLCDisableGroupingDidChangeNotification, object: self) } diff --git a/Sources/Settings/Model/SettingsSection.swift b/Sources/Settings/Model/SettingsSection.swift index 811216e51..853778c0b 100644 --- a/Sources/Settings/Model/SettingsSection.swift +++ b/Sources/Settings/Model/SettingsSection.swift @@ -17,832 +17,779 @@ import Foundation import LocalAuthentication -enum SettingsSection: Int, CaseIterable, CustomStringConvertible { - case main - case donation - case generic - case privacy - case gestureControl - case video - case subtitles - case audio - case casting - case mediaLibrary - case network - case lab - case reset - - var description: String { - switch self { - case .main: - return "" - case .donation: - return "" - case .generic: - return "SETTINGS_GENERIC_TITLE" - case .privacy: - return "SETTINGS_PRIVACY_TITLE" - case .gestureControl: - return "SETTINGS_GESTURES" - case .video: - return "SETTINGS_VIDEO_TITLE" - case .subtitles: - return "SETTINGS_SUBTITLES_TITLE" - case .audio: - return "SETTINGS_AUDIO_TITLE" - case .casting: - return "SETTINGS_CASTING" - case .mediaLibrary: - return "SETTINGS_MEDIA_LIBRARY" - case .network: - return "SETTINGS_NETWORK" - case .lab: - return "SETTINGS_LAB" - case .reset: - return "SETTINGS_RESET_TITLE" - } - } -} - -enum MainOptions: Int, CaseIterable, SectionType { - case privacy - case appearance - - var description: String { - switch self { - case .privacy: - return "SETTINGS_PRIVACY_TITLE" - case .appearance: - return "SETTINGS_DARKTHEME" +// MARK: - SettingsItem +struct SettingsItem: Equatable { + let title: String + let subtitle: String? + let action: Action + let emphasizedTitle: Bool + + @available(*, deprecated, message: "access from self.action") + var preferenceKey: String? { + switch action { + case .toggle(let preferenceKey): + return preferenceKey + case .showActionSheet(_, let preferenceKey, _): + return preferenceKey + default: + return nil } } - var containsSwitch: Bool { - switch self { - case .privacy: - return false - case .appearance: - return false - } + init(title: String, subtitle: String?, action: Action, emphasizedTitle: Bool = false) { + self.title = title + self.subtitle = subtitle + self.action = action + self.emphasizedTitle = emphasizedTitle } - var containsInfobutton: Bool { return false } - - var subtitle: String? { - switch self { - case .privacy: - return "SETTINGS_PRIVACY_SUBTITLE" - case .appearance: - return "SETTINGS_THEME_SYSTEM" - } + enum Action: Equatable { + case isLoading + case toggle(preferenceKey: String) + case showActionSheet(title: String, preferenceKey: String, hasInfo: Bool) + case donation + case openPrivacySettings + case forceRescanAlert + case exportMediaLibrary + case displayResetAlert } +} - var preferenceKey: String? { - switch self { - case .privacy: - return kVLCSettingPasscodeOnKey - case .appearance: - return kVLCSettingAppTheme - } +// MARK: - SettingsSection +struct SettingsSection: Equatable { + let title: String? + let items: [SettingsItem] + + var isEmpty: Bool { + items.isEmpty + } + + static func sections(isLabActivated: Bool, isBackingUp: Bool, isForwardBackwardEqual: Bool, isTapSwipeEqual: Bool) -> [SettingsSection] { + [ + MainOptions.section(), + DonationOptions.section(), + GenericOptions.section(), + PrivacyOptions.section(), + GestureControlOptions.section(isForwardBackwardEqual: isForwardBackwardEqual, isTapSwipeEqual: isTapSwipeEqual), + VideoOptions.section(), + SubtitlesOptions.section(), + AudioOptions.section(), + CastingOptions.section(), + MediaLibraryOptions.section(isBackingUp: isBackingUp), + NetworkOptions.section(), + Lab.section(isLabActivated: isLabActivated), + Reset.section() + ].compactMap { $0 } } } -enum DonationOptions: Int, CaseIterable, SectionType { - case donate - - var description: String { - return "SETTINGS_DONATE" +// MARK: - MainOptions +enum MainOptions { + static var privacy: SettingsItem { + .init( + title: "SETTINGS_PRIVACY_TITLE", + subtitle: "SETTINGS_PRIVACY_SUBTITLE", + action: .openPrivacySettings + ) } - var containsSwitch: Bool { return false } - - var containsInfobutton: Bool { return false } - - var subtitle: String? { - return "SETTINGS_DONATE_LONG" + static var appearance: SettingsItem { + .init( + title: "SETTINGS_DARKTHEME", + subtitle: "SETTINGS_THEME_SYSTEM", + action: .showActionSheet(title: "SETTINGS_DARKTHEME", preferenceKey: kVLCSettingAppTheme, hasInfo: false) + ) } - var preferenceKey: String? { return nil } + static func section() -> SettingsSection? { + .init(title: nil, items: [ + privacy, + appearance + ]) + } } -enum GenericOptions: Int, CaseIterable, SectionType { - case defaultPlaybackSpeed - case continueAudioPlayback - case playVideoInFullScreen - case continueVideoPlayback - case automaticallyPlayNextItem - case enableTextScrollingInMediaList - case rememberPlayerState - case restoreLastPlayedMedia - - var description: String { - switch self { - case .defaultPlaybackSpeed: - return "SETTINGS_PLAYBACK_SPEED_DEFAULT" - case .continueAudioPlayback: - return "SETTINGS_CONTINUE_AUDIO_PLAYBACK" - case .playVideoInFullScreen: - return "SETTINGS_VIDEO_FULLSCREEN" - case .continueVideoPlayback: - return "SETTINGS_CONTINUE_VIDEO_PLAYBACK" - case .automaticallyPlayNextItem: - return "SETTINGS_NETWORK_PLAY_ALL" - case .enableTextScrollingInMediaList: - return "SETTINGS_ENABLE_MEDIA_CELL_TEXT_SCROLLING" - case .rememberPlayerState: - return "SETTINGS_REMEMBER_PLAYER_STATE" - case .restoreLastPlayedMedia: - return "SETTINGS_RESTORE_LAST_PLAYED_MEDIA" - } +// MARK: - DonationOptions +enum DonationOptions { + static var donate: SettingsItem { + .init( + title: "SETTINGS_DONATE", + subtitle: "SETTINGS_DONATE_LONG", + action: .donation + ) } - var containsSwitch: Bool { - switch self { - case .defaultPlaybackSpeed: - return false - case .continueAudioPlayback: - return false - case .playVideoInFullScreen: - return true - case .continueVideoPlayback: - return false - case .automaticallyPlayNextItem: - return false - case .enableTextScrollingInMediaList: - return true - case .rememberPlayerState: - return true - case .restoreLastPlayedMedia: - return true - } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_DONATE_TITLE", items: [donate]) } +} - var containsInfobutton: Bool { - switch self { - case .continueAudioPlayback: - return true - case .continueVideoPlayback: - return true - default: - return false - } +// MARK: - GenericOptions +enum GenericOptions { + static var defaultPlaybackSpeed: SettingsItem { + .init( + title: "SETTINGS_PLAYBACK_SPEED_DEFAULT", + subtitle: "1.00x", + action: .showActionSheet(title: "SETTINGS_PLAYBACK_SPEED_DEFAULT", preferenceKey: kVLCSettingPlaybackSpeedDefaultValue, hasInfo: false) + ) + } + + static var continueAudioPlayback: SettingsItem { + .init( + title: "SETTINGS_CONTINUE_AUDIO_PLAYBACK", + subtitle: "SETTINGS_CONTINUE_PLAYBACK_ALWAYS", + action: .showActionSheet(title: "SETTINGS_CONTINUE_AUDIO_PLAYBACK", preferenceKey: kVLCSettingContinueAudioPlayback, hasInfo: true) + ) + } + + static var playVideoInFullScreen: SettingsItem { + .init( + title: "SETTINGS_VIDEO_FULLSCREEN", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingVideoFullscreenPlayback) + ) + } + + static var continueVideoPlayback: SettingsItem { + .init( + title: "SETTINGS_CONTINUE_VIDEO_PLAYBACK", + subtitle: "SETTINGS_CONTINUE_PLAYBACK_ALWAYS", + action: .showActionSheet(title: "SETTINGS_CONTINUE_VIDEO_PLAYBACK", preferenceKey: kVLCSettingContinuePlayback, hasInfo: true) + ) + } + + static var automaticallyPlayNextItem: SettingsItem { + .init( + title: "SETTINGS_NETWORK_PLAY_ALL", + subtitle: nil, + action: .showActionSheet(title: "SETTINGS_NETWORK_PLAY_ALL", preferenceKey: kVLCAutomaticallyPlayNextItem, hasInfo: false) + ) + } + + static var enableTextScrollingInMediaList: SettingsItem { + .init( + title: "SETTINGS_NETWORK_PLAY_ALL", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingEnableMediaCellTextScrolling) + ) + } + + static var rememberPlayerState: SettingsItem { + .init( + title: "SETTINGS_REMEMBER_PLAYER_STATE", + subtitle: nil, + action: .toggle(preferenceKey: kVLCPlayerShouldRememberState) + ) + } + + static var restoreLastPlayedMedia: SettingsItem { + .init( + title: "SETTINGS_RESTORE_LAST_PLAYED_MEDIA", + subtitle: nil, + action: .toggle(preferenceKey: kVLCRestoreLastPlayedMedia) + ) + } + + static func section() -> SettingsSection? { + .init(title: "SETTINGS_GENERIC_TITLE", items: [ + defaultPlaybackSpeed, + continueAudioPlayback, + playVideoInFullScreen, + continueVideoPlayback, + automaticallyPlayNextItem, + enableTextScrollingInMediaList, + rememberPlayerState, + restoreLastPlayedMedia + ]) } - var subtitle: String? { - switch self { - case .defaultPlaybackSpeed: - return "1.00x" - case .continueAudioPlayback: - return "SETTINGS_CONTINUE_PLAYBACK_ALWAYS" - case .playVideoInFullScreen: - return nil - case .continueVideoPlayback: - return "SETTINGS_CONTINUE_PLAYBACK_ALWAYS" - case .automaticallyPlayNextItem: - return nil - case .enableTextScrollingInMediaList: - return nil - case .rememberPlayerState: - return nil - case .restoreLastPlayedMedia: - return nil - } - } +} - var preferenceKey: String? { - switch self { - case .defaultPlaybackSpeed: - return kVLCSettingPlaybackSpeedDefaultValue - case .continueAudioPlayback: - return kVLCSettingContinueAudioPlayback - case .playVideoInFullScreen: - return kVLCSettingVideoFullscreenPlayback - case .continueVideoPlayback: - return kVLCSettingContinuePlayback - case .automaticallyPlayNextItem: - return kVLCAutomaticallyPlayNextItem - case .enableTextScrollingInMediaList: - return kVLCSettingEnableMediaCellTextScrolling - case .rememberPlayerState: - return kVLCPlayerShouldRememberState - case .restoreLastPlayedMedia: - return kVLCRestoreLastPlayedMedia - } +// MARK: - PrivacyOptions +enum PrivacyOptions { + static var passcodeLock: SettingsItem { + .init( + title: "SETTINGS_PASSCODE_LOCK", + subtitle: "SETTINGS_PASSCODE_LOCK_SUBTITLE", + action: .toggle(preferenceKey: kVLCSettingPasscodeOnKey) + ) } -} -enum PrivacyOptions: Int, CaseIterable, SectionType { - case passcodeLock - case enableBiometrics - case hideLibraryInFilesApp - - var description: String { - switch self { - case .passcodeLock: - return "SETTINGS_PASSCODE_LOCK" - case .enableBiometrics: - let authContext = LAContext() - let _ = authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) + static var enableBiometrics: SettingsItem? { + let authContext = LAContext() + + if authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) { switch authContext.biometryType { - case .none: - return "" case .touchID: - return "SETTINGS_PASSCODE_LOCK_ALLOWTOUCHID" + return .init( + title: "SETTINGS_PASSCODE_LOCK_ALLOWTOUCHID", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPasscodeAllowTouchID) + ) case .faceID: - return "SETTINGS_PASSCODE_LOCK_ALLOWFACEID" + return .init( + title: "SETTINGS_PASSCODE_LOCK_ALLOWFACEID", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPasscodeAllowFaceID) + ) + case .none, .opticID: + fallthrough @unknown default: - return "" + return nil } - case .hideLibraryInFilesApp: - return "SETTINGS_HIDE_LIBRARY_IN_FILES_APP" } - } - var containsSwitch: Bool { - switch self { - case .passcodeLock: - return true - case .enableBiometrics: - return true - case .hideLibraryInFilesApp: - return true - } + return nil } - var containsInfobutton: Bool { return false } - - var subtitle: String? { - switch self { - case .passcodeLock: - return "SETTINGS_PASSCODE_LOCK_SUBTITLE" - case .enableBiometrics: - return nil - case .hideLibraryInFilesApp: - return "SETTINGS_HIDE_LIBRARY_IN_FILES_APP_SUBTITLE" - } + static var hideLibraryInFilesApp: SettingsItem { + .init( + title: "SETTINGS_HIDE_LIBRARY_IN_FILES_APP", + subtitle: "SETTINGS_HIDE_LIBRARY_IN_FILES_APP_SUBTITLE", + action: .toggle(preferenceKey: kVLCSettingHideLibraryInFilesApp) + ) } - var preferenceKey: String? { - switch self { - case .passcodeLock: - return kVLCSettingPasscodeOnKey - case .enableBiometrics: - let authContext = LAContext() - let _ = authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) - switch authContext.biometryType { - case .none: - return nil - case .touchID: - return kVLCSettingPasscodeAllowTouchID - case .faceID: - return kVLCSettingPasscodeAllowFaceID - @unknown default: - return nil - } - case .hideLibraryInFilesApp: - return kVLCSettingHideLibraryInFilesApp - } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_PRIVACY_TITLE", items: [ + passcodeLock, + enableBiometrics, + hideLibraryInFilesApp + ].compactMap({$0})) } } -enum PlaybackControlOptions: Int, CaseIterable, SectionType { - case swipeUpDownForVolume - case twoFingerTap - case swipeUpDownForBrightness - case swipeRightLeftToSeek - case pinchToClose - case forwardBackwardEqual - case tapSwipeEqual - case forwardSkipLength - case backwardSkipLength - case forwardSkipLengthSwipe - case backwardSkipLengthSwipe - case longTouchToSpeedUp - - var containsInfobutton: Bool { return false } - - var containsSwitch: Bool { - switch self { - case .swipeUpDownForVolume: - return true - case .twoFingerTap: - return true - case .swipeUpDownForBrightness: - return true - case .swipeRightLeftToSeek: - return true - case .pinchToClose: - return true - case .forwardBackwardEqual: - return true - case .tapSwipeEqual: - return true - case .forwardSkipLength: - return false - case .backwardSkipLength: - return false - case .forwardSkipLengthSwipe: - return false - case .backwardSkipLengthSwipe: - return false - case .longTouchToSpeedUp: - return true - } - } - - var description: String { +// MARK: - GestureControlOptions +enum GestureControlOptions { + static var swipeUpDownForVolume: SettingsItem { + .init( + title: "SETTINGS_GESTURES_VOLUME", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingVolumeGesture) + ) + } + + static var twoFingerTap: SettingsItem { + .init( + title: "SETTINGS_GESTURES_PLAYPAUSE", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPlayPauseGesture) + ) + } + + static var swipeUpDownForBrightness: SettingsItem { + .init( + title: "SETTINGS_GESTURES_BRIGHTNESS", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingBrightnessGesture) + ) + } + + static var swipeRightLeftToSeek: SettingsItem { + .init( + title: "SETTINGS_GESTURES_SEEK", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingSeekGesture) + ) + } + + static var pinchToClose: SettingsItem { + .init( + title: "SETTINGS_GESTURES_CLOSE", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingCloseGesture) + ) + } + + static var forwardBackwardEqual: SettingsItem { + .init( + title: "SETTINGS_GESTURES_FORWARD_BACKWARD_EQUAL", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPlaybackForwardBackwardEqual) + ) + } + + static var tapSwipeEqual: SettingsItem { + .init( + title: "SETTINGS_GESTURES_TAP_SWIPE_EQUAL", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPlaybackTapSwipeEqual) + ) + } + + static var forwardSkipLength: SettingsItem { + .init( + title: dynamicForwardSkipDescription(), + subtitle: nil, + action: .showActionSheet(title: dynamicForwardSkipDescription(), preferenceKey: kVLCSettingPlaybackForwardSkipLength, hasInfo: false) + ) + } + + static var backwardSkipLength: SettingsItem { + .init( + title: dynamicBackwardSkipDescription(), + subtitle: nil, + action: .showActionSheet(title: dynamicBackwardSkipDescription(), preferenceKey: kVLCSettingPlaybackBackwardSkipLength, hasInfo: false) + ) + } + + static var forwardSkipLengthSwipe: SettingsItem { + .init( + title: dynamicForwardSwipeDescription(), + subtitle: nil, + action: .showActionSheet(title: dynamicForwardSwipeDescription(), preferenceKey: kVLCSettingPlaybackForwardSkipLengthSwipe, hasInfo: false) + ) + } + + static var backwardSkipLengthSwipe: SettingsItem { + .init( + title: "SETTINGS_PLAYBACK_SKIP_BACKWARD_SWIPE", + subtitle: nil, + action: .showActionSheet(title: "SETTINGS_PLAYBACK_SKIP_BACKWARD_SWIPE", preferenceKey: kVLCSettingPlaybackBackwardSkipLengthSwipe, hasInfo: false) + ) + } + + static var longTouchToSpeedUp: SettingsItem { + .init( + title: "SETINGS_LONG_TOUCH_SPEED_UP", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPlaybackLongTouchSpeedUp) + ) + } + + static func section(isForwardBackwardEqual: Bool, isTapSwipeEqual: Bool) -> SettingsSection? { + .init(title: "SETTINGS_GESTURES", items: [ + swipeUpDownForVolume, + twoFingerTap, + swipeUpDownForBrightness, + swipeRightLeftToSeek, + pinchToClose, + forwardBackwardEqual, + tapSwipeEqual, + forwardSkipLength, + isForwardBackwardEqual ? nil : backwardSkipLength, + isTapSwipeEqual ? nil : forwardSkipLengthSwipe, + (isTapSwipeEqual || isForwardBackwardEqual) ? nil : backwardSkipLengthSwipe, + longTouchToSpeedUp + ].compactMap({$0})) + } + + private static func dynamicForwardSkipDescription() -> String { let forwardBackwardEqual = UserDefaults.standard.bool(forKey: kVLCSettingPlaybackForwardBackwardEqual) let tapSwipeEqual = UserDefaults.standard.bool(forKey: kVLCSettingPlaybackTapSwipeEqual) - switch self { - case .swipeUpDownForVolume: - return "SETTINGS_GESTURES_VOLUME" - case .twoFingerTap: - return "SETTINGS_GESTURES_PLAYPAUSE" - case .swipeUpDownForBrightness: - return "SETTINGS_GESTURES_BRIGHTNESS" - case .swipeRightLeftToSeek: - return "SETTINGS_GESTURES_SEEK" - case .pinchToClose: - return "SETTINGS_GESTURES_CLOSE" - case .forwardBackwardEqual: - return "SETTINGS_GESTURES_FORWARD_BACKWARD_EQUAL" - case .tapSwipeEqual: - return "SETTINGS_GESTURES_TAP_SWIPE_EQUAL" - case .forwardSkipLength: - if forwardBackwardEqual && tapSwipeEqual { - return "SETTINGS_PLAYBACK_SKIP_GENERIC" - } else if forwardBackwardEqual && !tapSwipeEqual { - return "SETTINGS_PLAYBACK_SKIP_TAP" - } else if !forwardBackwardEqual && !tapSwipeEqual { - return "SETTINGS_PLAYBACK_SKIP_FORWARD_TAP" - } - return "SETTINGS_PLAYBACK_SKIP_FORWARD" - case .backwardSkipLength: - if tapSwipeEqual { - return "SETTINGS_PLAYBACK_SKIP_BACKWARD" - } - return "SETTINGS_PLAYBACK_SKIP_BACKWARD_TAP" - case .forwardSkipLengthSwipe: - if forwardBackwardEqual { - return "SETTINGS_PLAYBACK_SKIP_SWIPE" - } - return "SETTINGS_PLAYBACK_SKIP_FORWARD_SWIPE" - case .backwardSkipLengthSwipe: - return "SETTINGS_PLAYBACK_SKIP_BACKWARD_SWIPE" - case .longTouchToSpeedUp: - return "SETINGS_LONG_TOUCH_SPEED_UP" - } - } - - var subtitle: String? { return nil } - var preferenceKey: String? { - switch self { - case .swipeUpDownForVolume: - return kVLCSettingVolumeGesture - case .twoFingerTap: - return kVLCSettingPlayPauseGesture - case .swipeUpDownForBrightness: - return kVLCSettingBrightnessGesture - case .swipeRightLeftToSeek: - return kVLCSettingSeekGesture - case .pinchToClose: - return kVLCSettingCloseGesture - case .forwardBackwardEqual: - return kVLCSettingPlaybackForwardBackwardEqual - case .tapSwipeEqual: - return kVLCSettingPlaybackTapSwipeEqual - case .forwardSkipLength: - return kVLCSettingPlaybackForwardSkipLength - case .backwardSkipLength: - return kVLCSettingPlaybackBackwardSkipLength - case .forwardSkipLengthSwipe: - return kVLCSettingPlaybackForwardSkipLengthSwipe - case .backwardSkipLengthSwipe: - return kVLCSettingPlaybackBackwardSkipLengthSwipe - case .longTouchToSpeedUp: - return kVLCSettingPlaybackLongTouchSpeedUp + if forwardBackwardEqual && tapSwipeEqual { + return "SETTINGS_PLAYBACK_SKIP_GENERIC" + } else if forwardBackwardEqual && !tapSwipeEqual { + return "SETTINGS_PLAYBACK_SKIP_TAP" + } else if !forwardBackwardEqual && !tapSwipeEqual { + return "SETTINGS_PLAYBACK_SKIP_FORWARD_TAP" + } else { + return "SETTINGS_PLAYBACK_SKIP_FORWARD" } } -} -enum VideoOptions: Int, CaseIterable, SectionType { - case deBlockingFilter - case deInterlace - case hardwareDecoding - case rememberPlayerBrightness - - var description: String { - switch self { - case .deBlockingFilter: - return "SETTINGS_SKIP_LOOP_FILTER" - case .deInterlace: - return "SETTINGS_DEINTERLACE" - case .hardwareDecoding: - return "SETTINGS_HWDECODING" - case .rememberPlayerBrightness: - return "SETTINGS_REMEMBER_PLAYER_BRIGHTNESS" - } - } + private static func dynamicBackwardSkipDescription() -> String { + let tapSwipeEqual = UserDefaults.standard.bool(forKey: kVLCSettingPlaybackTapSwipeEqual) - var containsSwitch: Bool { - switch self { - case .deBlockingFilter: - return false - case .deInterlace: - return false - case .hardwareDecoding: - return false - case .rememberPlayerBrightness: - return true + if tapSwipeEqual { + return "SETTINGS_PLAYBACK_SKIP_BACKWARD" + } else { + return "SETTINGS_PLAYBACK_SKIP_BACKWARD_TAP" } } - var containsInfobutton: Bool { - switch self { - case .deBlockingFilter: - return true - case .deInterlace: - return true - case .hardwareDecoding: - return true - case .rememberPlayerBrightness: - return false - } - } + private static func dynamicForwardSwipeDescription() -> String { + let forwardBackwardEqual = UserDefaults.standard.bool(forKey: kVLCSettingPlaybackForwardBackwardEqual) - var subtitle: String? { - switch self { - case .deBlockingFilter: - return "SETTINGS_SKIP_LOOP_FILTER_NONREF" - case .deInterlace: - return "SETTINGS_DEINTERLACE_OFF" - case .hardwareDecoding: - return "SETTINGS_HWDECODING_ON" - case .rememberPlayerBrightness: - return nil + if forwardBackwardEqual { + return "SETTINGS_PLAYBACK_SKIP_SWIPE" + } else { + return "SETTINGS_PLAYBACK_SKIP_FORWARD_SWIPE" } } +} - var preferenceKey: String? { - switch self { - case .deBlockingFilter: - return kVLCSettingSkipLoopFilter - case .deInterlace: - return kVLCSettingDeinterlace - case .hardwareDecoding: - return kVLCSettingHardwareDecoding - case .rememberPlayerBrightness: - return kVLCPlayerShouldRememberBrightness - } +// MARK: - PlaybackControlOptions +enum PlaybackControlOptions { + static var swipeUpDownForVolume: SettingsItem { + .init( + title: "SETTINGS_GESTURES_VOLUME", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingVolumeGesture) + ) + } + + static var twoFingerTap: SettingsItem { + .init( + title: "SETTINGS_GESTURES_PLAYPAUSE", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPlayPauseGesture) + ) + } + + static var swipeUpDownForBrightness: SettingsItem { + .init( + title: "SETTINGS_GESTURES_BRIGHTNESS", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingBrightnessGesture) + ) + } + + static var swipeRightLeftToSeek: SettingsItem { + .init( + title: "SETTINGS_GESTURES_SEEK", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingSeekGesture) + ) + } + + static var longTouchToSpeedUp: SettingsItem { + .init( + title: "SETINGS_LONG_TOUCH_SPEED_UP", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingPlaybackLongTouchSpeedUp) + ) + } + + static func section() -> SettingsSection? { + .init(title: "SETTINGS_GESTURES", items: [ + swipeUpDownForVolume, + twoFingerTap, + swipeUpDownForBrightness, + swipeRightLeftToSeek, + longTouchToSpeedUp + ]) } } -enum SubtitlesOptions: Int, CaseIterable, SectionType { - case disableSubtitles - case font - case relativeFontSize - case useBoldFont - case fontColor - case textEncoding - - var description: String { - switch self { - case .disableSubtitles: - return "SETTINGS_SUBTITLES_DISABLE" - case .font: - return "SETTINGS_SUBTITLES_FONT" - case .relativeFontSize: - return "SETTINGS_SUBTITLES_FONTSIZE" - case .useBoldFont: - return "SETTINGS_SUBTITLES_BOLDFONT" - case .fontColor: - return "SETTINGS_SUBTITLES_FONTCOLOR" - case .textEncoding: - return "SETTINGS_SUBTITLES_TEXT_ENCODING" - } +// MARK: - VideoOptions +enum VideoOptions { + static var deBlockingFilter: SettingsItem { + .init( + title: "SETTINGS_SKIP_LOOP_FILTER", + subtitle: "SETTINGS_SKIP_LOOP_FILTER_NONREF", + action: .showActionSheet(title: "SETTINGS_SKIP_LOOP_FILTER", preferenceKey: kVLCSettingSkipLoopFilter, hasInfo: true) + ) } - var containsSwitch: Bool { - switch self { - case .disableSubtitles: - return true - case .font: - return false - case .relativeFontSize: - return false - case .useBoldFont: - return true - case .fontColor: - return false - case .textEncoding: - return false - } + static var deInterlace: SettingsItem { + .init( + title: "SETTINGS_DEINTERLACE", + subtitle: "SETTINGS_DEINTERLACE_OFF", + action: .showActionSheet(title: "SETTINGS_DEINTERLACE", preferenceKey: kVLCSettingDeinterlace, hasInfo: true) + ) } - var subtitle: String? { - switch self { - case .disableSubtitles: - return "SETTINGS_SUBTITLES_DISABLE_LONG" - case .font: - return "Arial" - case .relativeFontSize: - return "SETTINGS_SUBTITLES_FONTSIZE_NORMAL" - case .useBoldFont: - return nil - case .fontColor: - return "SETTINGS_SUBTITLES_FONTCOLOR_BLACK" - case .textEncoding: - return "Western European (Windows-1252)" - } + static var hardwareDecoding: SettingsItem { + .init( + title: "SETTINGS_HWDECODING", + subtitle: "SETTINGS_HWDECODING_ON", + action: .showActionSheet(title: "SETTINGS_HWDECODING", preferenceKey: kVLCSettingHardwareDecoding, hasInfo: true) + ) } - var preferenceKey: String? { - switch self { - case .disableSubtitles: - return kVLCSettingDisableSubtitles - case .font: - return kVLCSettingSubtitlesFont - case .relativeFontSize: - return kVLCSettingSubtitlesFontSize - case .useBoldFont: - return kVLCSettingSubtitlesBoldFont - case .fontColor: - return kVLCSettingSubtitlesFontColor - case .textEncoding: - return kVLCSettingTextEncoding - } + static var rememberPlayerBrightness: SettingsItem { + .init( + title: "SETTINGS_REMEMBER_PLAYER_BRIGHTNESS", + subtitle: nil, + action: .toggle(preferenceKey: kVLCPlayerShouldRememberBrightness) + ) } - var containsInfobutton: Bool { return true } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_VIDEO_TITLE", items: [ + deBlockingFilter, + deInterlace, + hardwareDecoding, + rememberPlayerBrightness + ]) + } } -enum CastingOptions: Int, CaseIterable, SectionType { - case audioPassThrough - case conversionQuality - - var description: String { - switch self { - case .audioPassThrough: - return "SETTINGS_PTCASTING" - case .conversionQuality: - return "SETTINGS_CASTING_CONVERSION_QUALITY" - } +// MARK: - SubtitlesOptions +enum SubtitlesOptions { + static var disableSubtitles: SettingsItem { + .init( + title: "SETTINGS_SUBTITLES_DISABLE", + subtitle: "SETTINGS_SUBTITLES_DISABLE_LONG", + action: .toggle(preferenceKey: kVLCSettingDisableSubtitles) + ) + } + + static var font: SettingsItem { + .init( + title: "SETTINGS_SUBTITLES_FONT", + subtitle: "Arial", + action: .showActionSheet(title: "SETTINGS_SUBTITLES_FONT", preferenceKey: kVLCSettingSubtitlesFont, hasInfo: true) + ) + } + + static var relativeFontSize: SettingsItem { + .init( + title: "SETTINGS_SUBTITLES_FONTSIZE", + subtitle: "SETTINGS_SUBTITLES_FONTSIZE_NORMAL", + action: .showActionSheet(title: "SETTINGS_SUBTITLES_FONTSIZE", preferenceKey: kVLCSettingSubtitlesFontSize, hasInfo: true) + ) + } + + static var useBoldFont: SettingsItem { + .init( + title: "SETTINGS_SUBTITLES_BOLDFONT", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingSubtitlesBoldFont) + ) + } + + static var fontColor: SettingsItem { + .init( + title: "SETTINGS_SUBTITLES_FONTCOLOR", + subtitle: "SETTINGS_SUBTITLES_FONTCOLOR_BLACK", + action: .showActionSheet(title: "SETTINGS_SUBTITLES_FONTCOLOR", preferenceKey: kVLCSettingSubtitlesFontColor, hasInfo: true) + ) + } + + static var textEncoding: SettingsItem { + .init( + title: "SETTINGS_SUBTITLES_TEXT_ENCODING", + subtitle: "Western European (Windows-1252)", + action: .showActionSheet(title: "SETTINGS_SUBTITLES_TEXT_ENCODING", preferenceKey: kVLCSettingTextEncoding, hasInfo: true) + ) + } + + static func section() -> SettingsSection? { + .init(title: "SETTINGS_SUBTITLES_TITLE", items: [ + disableSubtitles, + font, + relativeFontSize, + useBoldFont, + fontColor, + textEncoding + ]) } +} - var containsSwitch: Bool { - switch self { - case .audioPassThrough: - return true - case .conversionQuality: - return false - } +// MARK: - CastingOptions +enum CastingOptions { + static var audioPassThrough: SettingsItem { + .init( + title: "SETTINGS_PTCASTING", + subtitle: "SETTINGS_PTCASTINGLONG", + action: .toggle(preferenceKey: kVLCSettingCastingAudioPassthrough) + ) } - var preferenceKey: String? { - switch self { - case .audioPassThrough: - return kVLCSettingCastingAudioPassthrough - case .conversionQuality: - return kVLCSettingCastingConversionQuality - } + static var conversionQuality: SettingsItem { + .init( + title: "SETTINGS_CASTING_CONVERSION_QUALITY", + subtitle: "SETTINGS_MEDIUM", + action: .showActionSheet(title: "SETTINGS_CASTING_CONVERSION_QUALITY", preferenceKey: kVLCSettingCastingConversionQuality, hasInfo: false) + ) } - var subtitle: String? { - switch self { - case .audioPassThrough: - return "SETTINGS_PTCASTINGLONG" - case .conversionQuality: - return "SETTINGS_MEDIUM" - } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_CASTING", items: [ + audioPassThrough, + conversionQuality + ]) } - - var containsInfobutton: Bool { return false } } -enum AudioOptions: Int, CaseIterable, SectionType { - case preampLevel - case timeStretchingAudio - case audioPlaybackInBackground - - var description: String { - switch self { - case .preampLevel: - return "SETTINGS_AUDIO_PREAMP_LEVEL" - case .timeStretchingAudio: - return "SETTINGS_TIME_STRETCH_AUDIO" - case .audioPlaybackInBackground: - return "SETTINGS_BACKGROUND_AUDIO" - } - } - - var containsSwitch: Bool { - switch self { - case .preampLevel: - return false - default: - return true - } - } - var subtitle: String? { - switch self { - case .preampLevel: - return "6 dB" - case .timeStretchingAudio: - return "SETTINGS_TIME_STRETCH_AUDIO_LONG" - default: - return nil - } +// MARK: - AudioOptions +enum AudioOptions { + static var preampLevel: SettingsItem { + .init( + title: "SETTINGS_AUDIO_PREAMP_LEVEL", + subtitle: "6 dB", + action: .showActionSheet(title: "SETTINGS_AUDIO_PREAMP_LEVEL", preferenceKey: kVLCSettingDefaultPreampLevel, hasInfo: false) + ) } - var preferenceKey: String? { - switch self { - case .preampLevel: - return kVLCSettingDefaultPreampLevel - case .timeStretchingAudio: - return kVLCSettingStretchAudio - case .audioPlaybackInBackground: - return kVLCSettingContinueAudioInBackgroundKey - } + static var timeStretchingAudio: SettingsItem { + .init( + title: "SETTINGS_TIME_STRETCH_AUDIO", + subtitle: "SETTINGS_TIME_STRETCH_AUDIO_LONG", + action: .toggle(preferenceKey: kVLCSettingStretchAudio) + ) } - var containsInfobutton: Bool { return false } -} - -enum MediaLibraryOptions: Int, CaseIterable, SectionType { - case forceVLCToRescanTheMediaLibrary - case optimiseItemNamesForDisplay - case disableGrouping - case showVideoThumbnails - case showAudioArtworks - case includeMediaLibInDeviceBackup - - var description: String { - switch self { - case .forceVLCToRescanTheMediaLibrary: - return "SETTINGS_MEDIA_LIBRARY_RESCAN" - case .optimiseItemNamesForDisplay: - return "SETTINGS_DECRAPIFY" - case .disableGrouping: - return "SETTINGS_DISABLE_GROUPING" - case .showVideoThumbnails: - return "SETTINGS_SHOW_THUMBNAILS" - case .showAudioArtworks: - return "SETTINGS_SHOW_ARTWORKS" - case .includeMediaLibInDeviceBackup: - return "SETTINGS_BACKUP_MEDIA_LIBRARY" - } + static var audioPlaybackInBackground: SettingsItem { + .init( + title: "SETTINGS_BACKGROUND_AUDIO", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingContinueAudioInBackgroundKey) + ) } - var containsSwitch: Bool { - switch self { - case .forceVLCToRescanTheMediaLibrary: - return false - default: - return true - } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_AUDIO_TITLE", items: [ + preampLevel, + timeStretchingAudio, + audioPlaybackInBackground + ]) } +} - var subtitle: String? { return nil } - - var preferenceKey: String? { - switch self { - case .forceVLCToRescanTheMediaLibrary: - return nil - case .optimiseItemNamesForDisplay: - return kVLCSettingsDecrapifyTitles - case .disableGrouping: - return kVLCSettingsDisableGrouping - case .showVideoThumbnails: - return kVLCSettingShowThumbnails - case .showAudioArtworks: - return kVLCSettingShowArtworks - case .includeMediaLibInDeviceBackup: - return kVLCSettingBackupMediaLibrary - } +// MARK: - MediaLibraryOptions +enum MediaLibraryOptions { + static var forceVLCToRescanTheMediaLibrary: SettingsItem { + .init( + title: "SETTINGS_MEDIA_LIBRARY_RESCAN", + subtitle: nil, + action: .forceRescanAlert, + emphasizedTitle: true + ) + } + + static var optimiseItemNamesForDisplay: SettingsItem { + .init( + title: "SETTINGS_DECRAPIFY", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingsDecrapifyTitles) + ) + } + + static var disableGrouping: SettingsItem { + .init( + title: "SETTINGS_DISABLE_GROUPING", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingsDisableGrouping) + ) + } + + static var showVideoThumbnails: SettingsItem { + .init( + title: "SETTINGS_SHOW_THUMBNAILS", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingShowThumbnails) + ) + } + + static var showAudioArtworks: SettingsItem { + .init( + title: "SETTINGS_SHOW_ARTWORKS", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingShowArtworks) + ) + } + + static var includeMediaLibInDeviceBackup: SettingsItem { + .init( + title: "SETTINGS_BACKUP_MEDIA_LIBRARY", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingBackupMediaLibrary) + ) + } + + static var includeMediaLibInDeviceBackupWhenBackingUp: SettingsItem { + .init( + title: "SETTINGS_BACKUP_MEDIA_LIBRARY", + subtitle: nil, + action: .isLoading + ) + } + + static func section(isBackingUp: Bool) -> SettingsSection? { + .init(title: "SETTINGS_MEDIA_LIBRARY", items: [ + forceVLCToRescanTheMediaLibrary, + optimiseItemNamesForDisplay, + disableGrouping, + showVideoThumbnails, + showAudioArtworks, + { + if isBackingUp { + return includeMediaLibInDeviceBackupWhenBackingUp + } else { + return includeMediaLibInDeviceBackup + } + }() + ]) } - - var containsInfobutton: Bool { return false } } -enum NetworkOptions: Int, CaseIterable, SectionType { - case networkCachingLevel - case ipv6SupportForWiFiSharing - case forceSMBv1 - case rtspctp - - var description: String { - switch self { - case .networkCachingLevel: - return "SETTINGS_NETWORK_CACHING_TITLE" - case .ipv6SupportForWiFiSharing: - return "SETTINGS_WIFISHARING_IPv6" - case .forceSMBv1: - return "SETTINGS_FORCE_SMBV1" - case .rtspctp: - return "SETTINGS_RTSP_TCP" - } +// MARK: - NetworkOptions +enum NetworkOptions { + static var networkCachingLevel: SettingsItem { + .init( + title: "SETTINGS_NETWORK_CACHING_TITLE", + subtitle: "SETTINGS_NETWORK_CACHING_LEVEL_NORMAL", + action: .showActionSheet(title: "SETTINGS_NETWORK_CACHING_TITLE", preferenceKey: kVLCSettingNetworkCaching, hasInfo: true) + ) } - var containsSwitch: Bool { - switch self { - case .networkCachingLevel: - return false - case .ipv6SupportForWiFiSharing: - return true - case .forceSMBv1: - return true - case .rtspctp: - return true - } + static var ipv6SupportForWiFiSharing: SettingsItem { + .init( + title: "SETTINGS_WIFISHARING_IPv6", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingWiFiSharingIPv6) + ) } - var subtitle: String? { - switch self { - case .networkCachingLevel: - return "SETTINGS_NETWORK_CACHING_LEVEL_NORMAL" - case .ipv6SupportForWiFiSharing: - return nil - case .forceSMBv1: - return "SETTINGS_FORCE_SMBV1_LONG" - case .rtspctp: - return nil - } + static var forceSMBv1: SettingsItem { + .init( + title: "SETTINGS_FORCE_SMBV1", + subtitle: "SETTINGS_FORCE_SMBV1_LONG", + action: .toggle(preferenceKey: kVLCForceSMBV1) + ) } - var preferenceKey: String? { - switch self { - case .networkCachingLevel: - return kVLCSettingNetworkCaching - case .ipv6SupportForWiFiSharing: - return kVLCSettingWiFiSharingIPv6 - case .forceSMBv1: - return kVLCForceSMBV1 - case .rtspctp: - return kVLCSettingNetworkRTSPTCP - } + static var rtspctp: SettingsItem { + .init( + title: "SETTINGS_RTSP_TCP", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSettingNetworkRTSPTCP) + ) } - var containsInfobutton: Bool { - switch self { - case .networkCachingLevel: - return true - case .ipv6SupportForWiFiSharing: - return false - case .forceSMBv1: - return false - case .rtspctp: - return false - } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_NETWORK", items: [ + networkCachingLevel, + ipv6SupportForWiFiSharing, + forceSMBv1, + rtspctp + ]) } } -enum Lab: Int, CaseIterable, SectionType { - case debugLogging - case exportLibrary - - var description: String { - switch self { - case .debugLogging: - return "SETTINGS_DEBUG_LOG" - case .exportLibrary: - return "SETTINGS_EXPORT_LIBRARY" - } +// MARK: - Lab +enum Lab { + static var debugLogging: SettingsItem { + .init( + title: "SETTINGS_DEBUG_LOG", + subtitle: nil, + action: .toggle(preferenceKey: kVLCSaveDebugLogs) + ) } - var containsSwitch: Bool { - switch self { - case .exportLibrary: - return false - default: - return true - } + static var exportLibrary: SettingsItem { + .init( + title: "SETTINGS_EXPORT_LIBRARY", + subtitle: nil, + action: .exportMediaLibrary + ) } - var subtitle: String? { return nil } + static func section(isLabActivated: Bool) -> SettingsSection? { + guard isLabActivated else { return nil } - var preferenceKey: String? { - switch self { - case .debugLogging: - return kVLCSaveDebugLogs - case .exportLibrary: - return nil - } + return .init(title: "SETTINGS_LAB", items: [ + debugLogging, + exportLibrary + ]) } - - var containsInfobutton: Bool { return false } } -enum Reset: Int, CaseIterable, SectionType { - case resetOptions - - var containsSwitch: Bool { return false } - - var subtitle: String? { return nil } - - var preferenceKey: String? { return kVLCResetSettings } - - var containsInfobutton: Bool { return false } +// MARK: - Reset +enum Reset { + static var resetOptions: SettingsItem { + .init( + title: "SETTINGS_RESET", + subtitle: nil, + action: .displayResetAlert + ) + } - var description: String { return "SETTINGS_RESET" } + static func section() -> SettingsSection? { + .init(title: "SETTINGS_RESET_TITLE", items: [resetOptions]) + } } diff --git a/Sources/Settings/View/SettingsCell.swift b/Sources/Settings/View/SettingsCell.swift index a076c54a9..0b01c47cf 100644 --- a/Sources/Settings/View/SettingsCell.swift +++ b/Sources/Settings/View/SettingsCell.swift @@ -11,40 +11,11 @@ import UIKit -protocol SectionType: CustomStringConvertible { - var containsSwitch: Bool { get } - var subtitle: String? { get } - var preferenceKey: String? { get } - var containsInfobutton: Bool { get } -} - -protocol PasscodeActivateDelegate: AnyObject { - func passcodeLockSwitchOn(state: Bool) -} - -protocol MedialibraryHidingActivateDelegate: AnyObject { - func medialibraryHidingLockSwitchOn(state: Bool) -} - -protocol MediaLibraryBackupActivateDelegate: AnyObject { - func mediaLibraryBackupActivateSwitchOn(state: Bool) -} - -protocol MediaLibraryDisableGroupingDelegate: AnyObject { - func medialibraryDisableGroupingSwitchOn(state: Bool) -} - class SettingsCell: UITableViewCell { - private let userDefaults = UserDefaults.standard - private let notificationCenter = NotificationCenter.default + private var userDefaults: UserDefaults { UserDefaults.standard } + private var notificationCenter: NotificationCenter { NotificationCenter.default } var settingsBundle = Bundle() - var showsActivityIndicator = false - weak var passcodeSwitchDelegate: PasscodeActivateDelegate? - weak var skipDurationDelegate: UITableViewController? - weak var medialibraryHidingSwitchDelegate: MedialibraryHidingActivateDelegate? - weak var mediaLibraryBackupSwitchDelegate: MediaLibraryBackupActivateDelegate? - weak var medialibraryDisableGroupingSwitchDelegate: MediaLibraryDisableGroupingDelegate? lazy var switchControl: UISwitch = { let switchControl = UISwitch() @@ -98,54 +69,78 @@ class SettingsCell: UITableViewCell { return stackView }() - var sectionType: SectionType? { + var settingsItem: SettingsItem? { didSet { - guard let sectionType = sectionType else { + guard let settingsItem = settingsItem else { assertionFailure("No Section Type provided") return } - mainLabel.text = settingsBundle.localizedString(forKey: sectionType.description, value: sectionType.description, table: "Root") - if let subtitle = sectionType.subtitle { + + mainLabel.text = settingsBundle.localizedString(forKey: settingsItem.title, value: settingsItem.title, table: "Root") + + mainLabel.textColor = settingsItem.emphasizedTitle + ? PresentationTheme.current.colors.orangeUI + : PresentationTheme.current.colors.cellTextColor + + if let subtitle = settingsItem.subtitle { //Handles No Value (No user-defaults value set) case subtitleLabel.text = settingsBundle.localizedString(forKey: subtitle, value: subtitle, table: "Root") } else { - subtitleLabel.text = sectionType.subtitle + subtitleLabel.text = settingsItem.subtitle } - switchControl.isHidden = !sectionType.containsSwitch - infoButton.isHidden = !sectionType.containsInfobutton - if switchControl.isHidden && infoButton.isHidden { + + switch settingsItem.action { + case .isLoading: + switchControl.isHidden = true + infoButton.isHidden = true + activityIndicator.isHidden = false + accessoryView = .none + accessoryType = .none + selectionStyle = .none + case .toggle(_): + switchControl.isHidden = false + infoButton.isHidden = true + activityIndicator.isHidden = true + accessoryView = switchControl + accessoryType = .none + selectionStyle = .none + case .showActionSheet(_, _, let hasInfo): + switchControl.isHidden = true + infoButton.isHidden = !hasInfo + activityIndicator.isHidden = true accessoryView = .none accessoryType = .disclosureIndicator selectionStyle = .default + case .donation: + switchControl.isHidden = true + infoButton.isHidden = true + activityIndicator.isHidden = true + accessoryView = .none + accessoryType = .disclosureIndicator + selectionStyle = .default + case .openPrivacySettings: + switchControl.isHidden = true + infoButton.isHidden = true + activityIndicator.isHidden = true + accessoryView = .none + accessoryType = .disclosureIndicator + selectionStyle = .default + case .forceRescanAlert, .exportMediaLibrary, .displayResetAlert: + switchControl.isHidden = true + infoButton.isHidden = true + activityIndicator.isHidden = true + accessoryView = .none + accessoryType = .none + selectionStyle = .default } - else { - //When Media Library is adding or removing files to device backup - //We show a Activity Indicator instead of a switch while the process - //is going on. On completion, we show the switch again - if showsActivityIndicator && sectionType.preferenceKey == kVLCSettingBackupMediaLibrary { - activityIndicator.isHidden = false - accessoryView = .none - selectionStyle = .none - } else { - if switchControl.isHidden == false { - activityIndicator.isHidden = true - accessoryView = switchControl - selectionStyle = .none - } else { - addSubview(infoButton) - infoButton.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - infoButton.centerYAnchor.constraint(equalTo: stackView.centerYAnchor), - infoButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -40) - ]) - - accessoryView = .none - accessoryType = .disclosureIndicator - selectionStyle = .default - } - } + + if !activityIndicator.isHidden { + activityIndicator.startAnimating() + } else { + activityIndicator.stopAnimating() } + updateSwitch() updateSubtitle() } @@ -189,6 +184,14 @@ class SettingsCell: UITableViewCell { activityIndicator.centerYAnchor.constraint(equalTo: stackView.centerYAnchor) ]) activityIndicator.isHidden = true + + addSubview(infoButton) + infoButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + infoButton.centerYAnchor.constraint(equalTo: stackView.centerYAnchor), + infoButton.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor, constant: -40) + ]) + infoButton.isHidden = true } private func setupObservers() { @@ -203,19 +206,20 @@ class SettingsCell: UITableViewCell { } @objc func handleSwitchAction(sender: UISwitch) { - guard let key = sectionType?.preferenceKey else { return } - userDefaults.set(sender.isOn ? true : false, forKey: key) - - if sectionType?.preferenceKey == kVLCSettingPasscodeOnKey { - passcodeSwitchDelegate?.passcodeLockSwitchOn(state: sender.isOn) - } else if sectionType?.preferenceKey == kVLCSettingHideLibraryInFilesApp { - medialibraryHidingSwitchDelegate?.medialibraryHidingLockSwitchOn(state: sender.isOn) - } else if sectionType?.preferenceKey == kVLCSettingBackupMediaLibrary { - mediaLibraryBackupSwitchDelegate?.mediaLibraryBackupActivateSwitchOn(state: sender.isOn) - } else if sectionType?.preferenceKey == kVLCSettingsDisableGrouping { - medialibraryDisableGroupingSwitchDelegate?.medialibraryDisableGroupingSwitchOn(state: sender.isOn) - } else if sectionType?.preferenceKey == kVLCSettingPlaybackTapSwipeEqual || sectionType?.preferenceKey == kVLCSettingPlaybackForwardBackwardEqual { - skipDurationDelegate?.tableView.reloadData() + guard let settingsItem else { return } + + switch settingsItem.action { + case .toggle(let preferenceKey): + let note = Notification(name: .VLCDidToggleSettingNotification, + object: nil, + userInfo: [ + SettingsController.toggleNotificationKey: preferenceKey, + SettingsController.toggleNotificationValue: sender.isOn + ]) + notificationCenter.post(note) + + default: + break } } @@ -237,7 +241,7 @@ class SettingsCell: UITableViewCell { } @objc func infoTapped(sender: UIButton) { - guard let settingSpecifier = getSettingsSpecifier(for: (sectionType?.preferenceKey)!) else { + guard let settingSpecifier = getSettingsSpecifier(for: (settingsItem?.preferenceKey)!) else { return } @@ -266,14 +270,19 @@ class SettingsCell: UITableViewCell { } private func updateSwitch() { - if let key = self.sectionType?.preferenceKey { - let value = self.userDefaults.bool(forKey: key) - self.switchControl.isOn = value ? true : false + guard let settingsItem else { return } + + switch settingsItem.action { + case .toggle(let preferenceKey): + let value = self.userDefaults.bool(forKey: preferenceKey) + self.switchControl.isOn = value + default: + break } } private func updateSubtitle() { - if let subtitle = self.getSubtitle(for: self.sectionType?.preferenceKey ?? "") { + if let subtitle = self.getSubtitle(for: self.settingsItem?.preferenceKey ?? "") { self.subtitleLabel.text = settingsBundle.localizedString(forKey: subtitle, value: subtitle, table: "Root") } } -- GitLab