// Copyright © 2023 Mastodon gGmbH. All rights reserved. import UIKit import MastodonSDK import MastodonCore import CoreDataStack import MastodonLocalization import MastodonUI struct GeneralSettingsViewModel { var selectedAppearence: GeneralSetting.Appearance var playAnimations: Bool var selectedOpenLinks: GeneralSetting.OpenLinksIn var askBeforePostingWithoutAltText: Bool var askBeforeUnfollowingSomeone: Bool var askBeforeBoostingAPost: Bool var askBeforeDeletingAPost: Bool var defaultPostLanguage: String } protocol GeneralSettingsViewControllerDelegate: AnyObject { func save(_ viewController: UIViewController, setting: Setting, viewModel: GeneralSettingsViewModel) func showLanguagePicker(_ viewModel: GeneralSettingsViewModel, onLanguageSelected: @escaping OnLanguageSelected) } class GeneralSettingsViewController: UIViewController { weak var delegate: GeneralSettingsViewControllerDelegate? let tableView: UITableView var tableViewDataSource: GeneralSettingsDiffableTableViewDataSource? private(set) var viewModel: GeneralSettingsViewModel let setting: Setting let sections: [GeneralSettingsSection] init(appContext: AppContext, setting: Setting) { tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.translatesAutoresizingMaskIntoConstraints = false tableView.register(GeneralSettingSelectionCell.self, forCellReuseIdentifier: GeneralSettingSelectionCell.reuseIdentifier) tableView.register(GeneralSettingToggleTableViewCell.self, forCellReuseIdentifier: GeneralSettingToggleTableViewCell.reuseIdentifier) sections = [ GeneralSettingsSection(type: .appearance, entries: [ .appearance(.light), .appearance(.dark), .appearance(.system) ]), GeneralSettingsSection(type: .askBefore, entries: [ .askBefore(.postingWithoutAltText), .askBefore(.unfollowingSomeone), .askBefore(.boostingAPost), .askBefore(.deletingAPost) ]), GeneralSettingsSection(type: .design, entries: [ .design(.showAnimations) ]), GeneralSettingsSection(type: .language, entries: [ .language(.defaultPostLanguage) ]), GeneralSettingsSection(type: .links, entries: [ .openLinksIn(.mastodon), .openLinksIn(.browser), ]) ] let openLinksIn: GeneralSetting.OpenLinksIn if UserDefaults.shared.preferredUsingDefaultBrowser { openLinksIn = .browser } else { openLinksIn = .mastodon } let playAnimations = (UserDefaults.shared.preferredStaticAvatar == false && UserDefaults.shared.preferredStaticEmoji == false) viewModel = GeneralSettingsViewModel( selectedAppearence: GeneralSetting.Appearance(rawValue: UserDefaults.shared.customUserInterfaceStyle.rawValue) ?? .system, playAnimations: playAnimations, selectedOpenLinks: openLinksIn, askBeforePostingWithoutAltText: UserDefaults.shared.askBeforePostingWithoutAltText, askBeforeUnfollowingSomeone: UserDefaults.shared.askBeforeUnfollowingSomeone, askBeforeBoostingAPost: UserDefaults.shared.askBeforeBoostingAPost, askBeforeDeletingAPost: UserDefaults.shared.askBeforeDeletingAPost, defaultPostLanguage: UserDefaults.shared.defaultPostLanguage ) self.setting = setting super.init(nibName: nil, bundle: nil) tableView.delegate = self let tableViewDataSource = GeneralSettingsDiffableTableViewDataSource(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in let cell: UITableViewCell switch itemIdentifier { case .appearance(let setting): guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingSelectionCell.reuseIdentifier, for: indexPath) as? GeneralSettingSelectionCell else { fatalError("WTF? Wrong Cell!") } selectionCell.configure(with: .appearance(setting), viewModel: self.viewModel) cell = selectionCell case .askBefore(let setting): guard let toggleCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingToggleTableViewCell.reuseIdentifier, for: indexPath) as? GeneralSettingToggleTableViewCell else { fatalError("WTF? Wrong Cell!") } toggleCell.configure(with: .askBefore(setting), viewModel: self.viewModel) toggleCell.delegate = self cell = toggleCell case .design(let setting): guard let toggleCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingToggleTableViewCell.reuseIdentifier, for: indexPath) as? GeneralSettingToggleTableViewCell else { fatalError("WTF? Wrong Cell!") } toggleCell.configure(with: .design(setting), viewModel: self.viewModel) toggleCell.delegate = self cell = toggleCell case let .language(setting): guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingSelectionCell.reuseIdentifier, for: indexPath) as? GeneralSettingSelectionCell else { fatalError("WTF? Wrong Cell!") } selectionCell.configure(with: .language(setting), viewModel: self.viewModel) cell = selectionCell case .openLinksIn(let setting): guard let selectionCell = tableView.dequeueReusableCell(withIdentifier: GeneralSettingSelectionCell.reuseIdentifier, for: indexPath) as? GeneralSettingSelectionCell else { fatalError("WTF? Wrong Cell!") } selectionCell.configure(with: .openLinksIn(setting), viewModel: self.viewModel) cell = selectionCell } return cell }) self.tableViewDataSource = tableViewDataSource view.backgroundColor = .systemGroupedBackground view.addSubview(tableView) tableView.pinTo(to: view) title = L10n.Scene.Settings.General.title } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() var snapshot = NSDiffableDataSourceSnapshot() for section in sections { snapshot.appendSections([section]) snapshot.appendItems(section.entries) } tableViewDataSource?.apply(snapshot, animatingDifferences: false) } } extension GeneralSettingsViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // switch section let section = sections[indexPath.section].entries[indexPath.row] switch section { case .appearance(let appearanceOption): viewModel.selectedAppearence = appearanceOption if let snapshot = tableViewDataSource?.snapshot() { tableViewDataSource?.applySnapshotUsingReloadData(snapshot) } case .askBefore(let askBefore): guard let cell = tableView.cellForRow(at: indexPath) as? GeneralSettingToggleTableViewCell else { return} let newValue = (cell.toggle.isOn == false) cell.toggle.setOn(newValue, animated: true) toggle(cell, setting: .askBefore(askBefore), isOn: newValue) case .design(let design): guard let cell = tableView.cellForRow(at: indexPath) as? GeneralSettingToggleTableViewCell else { return} let newValue = (cell.toggle.isOn == false) cell.toggle.setOn(newValue, animated: true) toggle(cell, setting: .design(design), isOn: newValue) case .language: delegate?.showLanguagePicker(viewModel) { [weak self] language in guard let self else { return } viewModel.defaultPostLanguage = language UserDefaults.shared.defaultPostLanguage = language if let snapshot = tableViewDataSource?.snapshot() { tableViewDataSource?.applySnapshotUsingReloadData(snapshot) } } case .openLinksIn(let openLinksInOption): viewModel.selectedOpenLinks = openLinksInOption if let snapshot = tableViewDataSource?.snapshot() { tableViewDataSource?.applySnapshotUsingReloadData(snapshot) } } tableView.deselectRow(at: indexPath, animated: true) delegate?.save(self, setting: setting, viewModel: viewModel) } } extension GeneralSettingsViewController: GeneralSettingToggleTableViewCellDelegate { func toggle(_ cell: GeneralSettingToggleTableViewCell, setting: GeneralSetting, isOn: Bool) { switch setting { case .appearance, .openLinksIn, .language: assertionFailure("No toggle") case let .askBefore(askBefore): switch askBefore { case .postingWithoutAltText: UserDefaults.shared.askBeforePostingWithoutAltText = isOn case .unfollowingSomeone: UserDefaults.shared.askBeforeUnfollowingSomeone = isOn case .boostingAPost: UserDefaults.shared.askBeforeBoostingAPost = isOn case .deletingAPost: UserDefaults.shared.askBeforeDeletingAPost = isOn } case let .design(designSetting): switch designSetting { case .showAnimations: viewModel.playAnimations = isOn } } delegate?.save(self, setting: self.setting, viewModel: viewModel) } }