Remove old settings

This commit is contained in:
Nathan Mattes 2023-09-27 18:02:59 +02:00
parent bbf7c541bb
commit b0886979e5
11 changed files with 0 additions and 1486 deletions

View File

@ -397,7 +397,6 @@
DB98EB6227B215EB0082E365 /* ReportResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */; };
DB98EB6527B216500082E365 /* ReportResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6427B216500082E365 /* ReportResultViewModel.swift */; };
DB98EB6927B21A7C0082E365 /* ReportResultActionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98EB6827B21A7C0082E365 /* ReportResultActionTableViewCell.swift */; };
DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9A486B26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift */; };
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BE825E4F5340051B173 /* SearchViewController.swift */; };
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */; };
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */; };
@ -730,11 +729,6 @@
46DAB0EBDDFB678347CD96FF /* Pods-MastodonTests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.asdk - release.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.asdk - release.xcconfig"; sourceTree = "<group>"; };
5B24BBD7262DB14800A9381B /* ReportViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = "<group>"; };
5B24BBD8262DB14800A9381B /* ReportStatusViewModel+Diffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReportStatusViewModel+Diffable.swift"; sourceTree = "<group>"; };
5B90C456262599800002E742 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsToggleTableViewCell.swift; sourceTree = "<group>"; };
5B90C45A262599800002E742 /* SettingsAppearanceTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAppearanceTableViewCell.swift; sourceTree = "<group>"; };
5B90C45B262599800002E742 /* SettingsLinkTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLinkTableViewCell.swift; sourceTree = "<group>"; };
5B90C45C262599800002E742 /* SettingsSectionHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSectionHeader.swift; sourceTree = "<group>"; };
5BB04FD4262E7AFF0043BFF6 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = "<group>"; };
5BB04FF4262F0E6D0043BFF6 /* ReportSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportSection.swift; sourceTree = "<group>"; };
5CE45680252519F42FEA2D13 /* Pods-ShareActionExtension.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareActionExtension.asdk - release.xcconfig"; path = "Target Support Files/Pods-ShareActionExtension/Pods-ShareActionExtension.asdk - release.xcconfig"; sourceTree = "<group>"; };
@ -938,7 +932,6 @@
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = "<group>"; };
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DB443CD32694627B00159B29 /* AppearanceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceView.swift; sourceTree = "<group>"; };
DB4481B825EE289600BEFB67 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = "<group>"; };
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
DB45FAD625CA6C76005A8AC7 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
@ -1062,9 +1055,6 @@
DB6B74FF272FF73800C70B6E /* UserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTableViewCell.swift; sourceTree = "<group>"; };
DB6B750327300B4000C70B6E /* TimelineFooterTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineFooterTableViewCell.swift; sourceTree = "<group>"; };
DB6D9F3426351B7A008423CD /* NotificationService+Decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationService+Decrypt.swift"; sourceTree = "<group>"; };
DB6D9F7C26358ED4008423CD /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = "<group>"; };
DB6D9F8326358EEC008423CD /* SettingsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsItem.swift; sourceTree = "<group>"; };
DB6D9F9626367249008423CD /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; };
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; };
DB7274F3273BB9B200577D95 /* ListBatchFetchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListBatchFetchViewModel.swift; sourceTree = "<group>"; };
@ -1124,7 +1114,6 @@
DB98EB6127B215EB0082E365 /* ReportResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultViewController.swift; sourceTree = "<group>"; };
DB98EB6427B216500082E365 /* ReportResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultViewModel.swift; sourceTree = "<group>"; };
DB98EB6827B21A7C0082E365 /* ReportResultActionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportResultActionTableViewCell.swift; sourceTree = "<group>"; };
DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsAppearanceTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
DB9D6BE825E4F5340051B173 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = "<group>"; };
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
@ -1768,32 +1757,11 @@
D8F917042A4B0657008A5370 /* General Settings */,
D81D12432A4E181C005009D4 /* Notification Settings */,
D8F917092A4B2AFF008A5370 /* About Mastodon */,
D8318A7E2A4466C900C0FB73 /* Legacy */,
D8318A7F2A4466D300C0FB73 /* SettingsCoordinator.swift */,
);
path = Settings;
sourceTree = "<group>";
};
5B90C457262599800002E742 /* View */ = {
isa = PBXGroup;
children = (
5B90C45C262599800002E742 /* SettingsSectionHeader.swift */,
DB443CD32694627B00159B29 /* AppearanceView.swift */,
);
path = View;
sourceTree = "<group>";
};
5B90C458262599800002E742 /* Cell */ = {
isa = PBXGroup;
children = (
5B90C45A262599800002E742 /* SettingsAppearanceTableViewCell.swift */,
DB98EB6A27B243470082E365 /* SettingsAppearanceTableViewCell+ViewModel.swift */,
5B90C459262599800002E742 /* SettingsToggleTableViewCell.swift */,
5B90C45B262599800002E742 /* SettingsLinkTableViewCell.swift */,
);
path = Cell;
sourceTree = "<group>";
};
5D03938E2612D200007FE196 /* Webview */ = {
isa = PBXGroup;
children = (
@ -1865,19 +1833,6 @@
path = Shared;
sourceTree = "<group>";
};
D8318A7E2A4466C900C0FB73 /* Legacy */ = {
isa = PBXGroup;
children = (
5B90C458262599800002E742 /* Cell */,
5B90C457262599800002E742 /* View */,
DB6D9F9626367249008423CD /* SettingsViewController.swift */,
5B90C456262599800002E742 /* SettingsViewModel.swift */,
DB6D9F7C26358ED4008423CD /* SettingsSection.swift */,
DB6D9F8326358EEC008423CD /* SettingsItem.swift */,
);
path = Legacy;
sourceTree = "<group>";
};
D8A6AB68291C50F3003AB663 /* Login */ = {
isa = PBXGroup;
children = (

View File

@ -1,59 +0,0 @@
//
// SettingsAppearanceTableViewCell+ViewModel.swift
// Mastodon
//
// Created by MainasuK on 2022-2-8.
//
import UIKit
import Combine
import CoreDataStack
extension SettingsAppearanceTableViewCell {
final class ViewModel: ObservableObject {
var disposeBag = Set<AnyCancellable>()
private var observations = Set<NSKeyValueObservation>()
// input
@Published public var customUserInterfaceStyle: UIUserInterfaceStyle = .unspecified
// output
@Published public var appearanceMode: SettingsItem.AppearanceMode = .system
init() {
UserDefaults.shared.observe(\.customUserInterfaceStyle, options: [.initial, .new]) { [weak self] defaults, _ in
guard let self = self else { return }
self.customUserInterfaceStyle = defaults.customUserInterfaceStyle
}
.store(in: &observations)
}
public func prepareForReuse() {
// do nothing
}
}
}
extension SettingsAppearanceTableViewCell.ViewModel {
func bind(cell: SettingsAppearanceTableViewCell) {
$customUserInterfaceStyle.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { customUserInterfaceStyle in
cell.appearanceViews.forEach { view in
view.selected = false
}
switch customUserInterfaceStyle {
case .unspecified:
cell.systemAppearanceView.selected = true
case .dark:
cell.darkAppearanceView.selected = true
case .light:
cell.lightAppearanceView.selected = true
@unknown default:
assertionFailure()
}
}
.store(in: &disposeBag)
}
}

View File

@ -1,135 +0,0 @@
//
// SettingsAppearanceTableViewCell.swift
// Mastodon
//
// Created by ihugo on 2021/4/8.
//
import UIKit
import Combine
import MastodonAsset
import MastodonLocalization
protocol SettingsAppearanceTableViewCellDelegate: AnyObject {
func settingsAppearanceTableViewCell(_ cell: SettingsAppearanceTableViewCell, didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode)
}
class SettingsAppearanceTableViewCell: UITableViewCell {
var disposeBag = Set<AnyCancellable>()
var observations = Set<NSKeyValueObservation>()
static let spacing: CGFloat = 28
weak var delegate: SettingsAppearanceTableViewCellDelegate?
public private(set) var viewModel = ViewModel()
lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.distribution = .fillEqually
view.spacing = SettingsAppearanceTableViewCell.spacing
return view
}()
let systemAppearanceView = AppearanceView(
image: Asset.Settings.automatic.image,
title: L10n.Scene.Settings.Section.Appearance.automatic
)
let darkAppearanceView = AppearanceView(
image: Asset.Settings.dark.image,
title: L10n.Scene.Settings.Section.Appearance.dark
)
let lightAppearanceView = AppearanceView(
image: Asset.Settings.light.image,
title: L10n.Scene.Settings.Section.Appearance.light
)
var appearanceViews: [AppearanceView] {
return [
systemAppearanceView,
darkAppearanceView,
lightAppearanceView,
]
}
override func prepareForReuse() {
super.prepareForReuse()
disposeBag.removeAll()
observations.removeAll()
viewModel.prepareForReuse()
}
// MARK: - Methods
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
viewModel.bind(cell: self)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
// remove separator line in section of group tableview
for subview in self.subviews {
if subview != self.contentView && subview.frame.width == self.frame.width {
subview.removeFromSuperview()
}
}
// remove grouped style table corner radius
layer.cornerRadius = 0
}
}
extension SettingsAppearanceTableViewCell {
// MARK: Private methods
private func setupUI() {
backgroundColor = .clear
selectionStyle = .none
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(stackView)
stackView.pinToParent()
stackView.addArrangedSubview(systemAppearanceView)
stackView.addArrangedSubview(darkAppearanceView)
stackView.addArrangedSubview(lightAppearanceView)
appearanceViews.forEach { view in
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
view.addGestureRecognizer(tapGestureRecognizer)
tapGestureRecognizer.addTarget(self, action: #selector(SettingsAppearanceTableViewCell.appearanceViewDidPressed(_:)))
}
}
}
// MARK: - Actions
extension SettingsAppearanceTableViewCell {
@objc func appearanceViewDidPressed(_ sender: UITapGestureRecognizer) {
let mode: SettingsItem.AppearanceMode
switch sender.view {
case systemAppearanceView:
mode = .system
case darkAppearanceView:
mode = .dark
case lightAppearanceView:
mode = .light
default:
assertionFailure()
return
}
delegate?.settingsAppearanceTableViewCell(self, didSelectAppearanceMode: mode)
}
}

View File

@ -1,35 +0,0 @@
//
// SettingsLinkTableViewCell.swift
// Mastodon
//
// Created by ihugo on 2021/4/8.
//
import UIKit
class SettingsLinkTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
textLabel?.alpha = highlighted ? 0.6 : 1.0
}
}
// MARK: - Methods
extension SettingsLinkTableViewCell {
func update(with link: SettingsItem.Link) {
textLabel?.text = link.title
textLabel?.textColor = link.textColor
}
}

View File

@ -1,75 +0,0 @@
//
// SettingsToggleTableViewCell.swift
// Mastodon
//
// Created by ihugo on 2021/4/8.
//
import UIKit
import Combine
import MastodonAsset
import MastodonLocalization
protocol SettingsToggleCellDelegate: AnyObject {
func settingsToggleCell(_ cell: SettingsToggleTableViewCell, switchValueDidChange switch: UISwitch)
}
class SettingsToggleTableViewCell: UITableViewCell {
var disposeBag = Set<AnyCancellable>()
private(set) lazy var switchButton: UISwitch = {
let view = UISwitch(frame:.zero)
view.onTintColor = Asset.Colors.Brand.blurple.color
return view
}()
weak var delegate: SettingsToggleCellDelegate?
override func prepareForReuse() {
super.prepareForReuse()
disposeBag.removeAll()
}
// MARK: - Methods
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
// MARK: Private methods
private func setupUI() {
selectionStyle = .none
accessoryView = switchButton
textLabel?.numberOfLines = 0
switchButton.addTarget(self, action: #selector(switchValueDidChange(sender:)), for: .valueChanged)
}
}
// MARK: - Actions
extension SettingsToggleTableViewCell {
@objc private func switchValueDidChange(sender: UISwitch) {
guard let delegate = delegate else { return }
delegate.settingsToggleCell(self, switchValueDidChange: sender)
}
}
extension SettingsToggleTableViewCell {
func update(enabled: Bool?) {
switchButton.isEnabled = enabled != nil
textLabel?.textColor = enabled != nil ? Asset.Colors.Label.primary.color : Asset.Colors.Label.secondary.color
switchButton.isOn = enabled ?? false
}
}

View File

@ -1,112 +0,0 @@
//
// SettingsItem.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-4-25.
//
import UIKit
import CoreData
import CoreDataStack
import MastodonAsset
import MastodonLocalization
enum SettingsItem {
case appearance(record: ManagedObjectRecord<Setting>)
case preference(settingRecord: ManagedObjectRecord<Setting>, preferenceType: PreferenceType)
case notification(settingRecord: ManagedObjectRecord<Setting>, switchMode: NotificationSwitchMode)
case boringZone(item: Link)
case spicyZone(item: Link)
enum AppearanceMode: String {
case system
case dark
case light
}
enum NotificationSwitchMode: CaseIterable, Hashable {
case favorite
case follow
case reblog
case mention
var title: String {
switch self {
case .favorite: return L10n.Scene.Settings.Section.Notifications.favorites
case .follow: return L10n.Scene.Settings.Section.Notifications.follows
case .reblog: return L10n.Scene.Settings.Section.Notifications.boosts
case .mention: return L10n.Scene.Settings.Section.Notifications.mentions
}
}
}
enum PreferenceType: CaseIterable {
case disableAvatarAnimation
case disableEmojiAnimation
case useDefaultBrowser
var title: String {
switch self {
case .disableAvatarAnimation: return L10n.Scene.Settings.Section.Preference.disableAvatarAnimation
case .disableEmojiAnimation: return L10n.Scene.Settings.Section.Preference.disableEmojiAnimation
case .useDefaultBrowser: return L10n.Scene.Settings.Section.Preference.usingDefaultBrowser
}
}
}
enum Link: CaseIterable, Hashable {
case accountSettings
case github
case termsOfService
case privacyPolicy
case clearMediaCache
case signOut
var title: String {
switch self {
case .accountSettings: return L10n.Scene.Settings.Section.BoringZone.accountSettings
case .github: return "GitHub"
case .termsOfService: return L10n.Scene.Settings.Section.BoringZone.terms
case .privacyPolicy: return L10n.Scene.Settings.Section.BoringZone.privacy
case .clearMediaCache: return L10n.Scene.Settings.Section.SpicyZone.clear
case .signOut: return L10n.Scene.Settings.Section.SpicyZone.signout
}
}
var textColor: UIColor? {
switch self {
case .accountSettings: return nil // tintColor
case .github: return nil
case .termsOfService: return nil
case .privacyPolicy: return nil
case .clearMediaCache: return .systemRed
case .signOut: return .systemRed
}
}
}
}
extension SettingsItem: Hashable {
func hash(into hasher: inout Hasher) {
switch self {
case .appearance(let record):
hasher.combine(String(describing: SettingsItem.AppearanceMode.self))
hasher.combine(record)
case .notification(let settingObjectID, let switchMode):
hasher.combine(String(describing: SettingsItem.notification.self))
hasher.combine(settingObjectID)
hasher.combine(switchMode)
case .preference(let settingObjectID, let preferenceType):
hasher.combine(String(describing: SettingsItem.preference.self))
hasher.combine(settingObjectID)
hasher.combine(preferenceType)
case .boringZone(let link):
hasher.combine(String(describing: SettingsItem.boringZone.self))
hasher.combine(link)
case .spicyZone(let link):
hasher.combine(String(describing: SettingsItem.spicyZone.self))
hasher.combine(link)
}
}
}

View File

@ -1,137 +0,0 @@
//
// SettingsSection.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-4-25.
//
import UIKit
import CoreData
import CoreDataStack
import MastodonAsset
import MastodonCore
import MastodonLocalization
enum SettingsSection: Hashable {
case appearance
case preference
case notifications
case boringZone
case spicyZone
var title: String {
switch self {
case .appearance: return L10n.Scene.Settings.Section.LookAndFeel.title
case .preference: return ""
case .notifications: return L10n.Scene.Settings.Section.Notifications.title
case .boringZone: return L10n.Scene.Settings.Section.BoringZone.title
case .spicyZone: return L10n.Scene.Settings.Section.SpicyZone.title
}
}
static func tableViewDiffableDataSource(
for tableView: UITableView,
managedObjectContext: NSManagedObjectContext,
settingsAppearanceTableViewCellDelegate: SettingsAppearanceTableViewCellDelegate,
settingsToggleCellDelegate: SettingsToggleCellDelegate
) -> UITableViewDiffableDataSource<SettingsSection, SettingsItem> {
UITableViewDiffableDataSource(tableView: tableView) { [
weak settingsAppearanceTableViewCellDelegate,
weak settingsToggleCellDelegate
] tableView, indexPath, item -> UITableViewCell? in
switch item {
case .appearance:
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsAppearanceTableViewCell.self), for: indexPath) as! SettingsAppearanceTableViewCell
cell.delegate = settingsAppearanceTableViewCellDelegate
return cell
case .preference(let record, _):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
cell.delegate = settingsToggleCellDelegate
managedObjectContext.performAndWait {
guard let setting = record.object(in: managedObjectContext) else { return }
SettingsSection.configureSettingToggle(cell: cell, item: item, setting: setting)
ManagedObjectObserver.observe(object: setting)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
SettingsSection.configureSettingToggle(cell: cell, item: item, setting: setting)
})
.store(in: &cell.disposeBag)
}
return cell
case .notification(let record, let switchMode):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsToggleTableViewCell.self), for: indexPath) as! SettingsToggleTableViewCell
managedObjectContext.performAndWait {
guard let setting = record.object(in: managedObjectContext) else { return }
if let subscription = setting.activeSubscription {
SettingsSection.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
}
ManagedObjectObserver.observe(object: setting)
.sink(receiveCompletion: { _ in
// do nothing
}, receiveValue: { [weak cell] change in
guard let cell = cell else { return }
guard case .update(let object) = change.changeType,
let setting = object as? Setting else { return }
guard let subscription = setting.activeSubscription else { return }
SettingsSection.configureSettingToggle(cell: cell, switchMode: switchMode, subscription: subscription)
})
.store(in: &cell.disposeBag)
}
cell.delegate = settingsToggleCellDelegate
return cell
case .boringZone(let item),
.spicyZone(let item):
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SettingsLinkTableViewCell.self), for: indexPath) as! SettingsLinkTableViewCell
cell.update(with: item)
return cell
} // end switch
}
}
public static func configureSettingToggle(
cell: SettingsToggleTableViewCell,
item: SettingsItem,
setting: Setting
) {
switch item {
case .preference(_, let preferenceType):
cell.textLabel?.text = preferenceType.title
switch preferenceType {
case .disableAvatarAnimation:
cell.switchButton.isOn = setting.preferredStaticAvatar
case .disableEmojiAnimation:
cell.switchButton.isOn = setting.preferredStaticEmoji
case .useDefaultBrowser:
cell.switchButton.isOn = setting.preferredUsingDefaultBrowser
}
default:
assertionFailure()
}
}
public static func configureSettingToggle(
cell: SettingsToggleTableViewCell,
switchMode: SettingsItem.NotificationSwitchMode,
subscription: NotificationSubscription
) {
cell.textLabel?.text = switchMode.title
let enabled: Bool?
switch switchMode {
case .favorite: enabled = subscription.alert.favourite
case .follow: enabled = subscription.alert.follow
case .reblog: enabled = subscription.alert.reblog
case .mention: enabled = subscription.alert.mention
}
cell.update(enabled: enabled)
}
}

View File

@ -1,515 +0,0 @@
//
// SettingsViewController.swift
// Mastodon
//
// Created by ihugo on 2021/4/7.
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import AuthenticationServices
import MetaTextKit
import MastodonSDK
import MastodonMeta
import MastodonAsset
import MastodonCore
import MastodonUI
import MastodonLocalization
class SettingsViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: SettingsViewModel! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var notificationPolicySubscription: AnyCancellable?
var triggerMenu: UIMenu {
let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone
let follower = L10n.Scene.Settings.Section.Notifications.Trigger.follower
let follow = L10n.Scene.Settings.Section.Notifications.Trigger.follow
let noOne = L10n.Scene.Settings.Section.Notifications.Trigger.noone
let menu = UIMenu(
image: nil,
identifier: nil,
options: .displayInline,
children: [
UIAction(title: anyone, image: UIImage(systemName: "person.3"), attributes: []) { [weak self] action in
self?.updateTrigger(policy: .all)
},
UIAction(title: follower, image: UIImage(systemName: "person.crop.circle.badge.plus"), attributes: []) { [weak self] action in
self?.updateTrigger(policy: .follower)
},
UIAction(title: follow, image: UIImage(systemName: "person.crop.circle.badge.checkmark"), attributes: []) { [weak self] action in
self?.updateTrigger(policy: .followed)
},
UIAction(title: noOne, image: UIImage(systemName: "nosign"), attributes: []) { [weak self] action in
self?.updateTrigger(policy: .none)
},
]
)
return menu
}
private let notifySectionHeaderStackView: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isLayoutMarginsRelativeArrangement = true
view.axis = .horizontal
view.spacing = 4
return view
}()
let notifyLabel = UILabel()
private(set) lazy var notifySectionHeader: UIView = {
let view = notifySectionHeaderStackView
notifyLabel.translatesAutoresizingMaskIntoConstraints = false
notifyLabel.adjustsFontForContentSizeCategory = true
notifyLabel.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .semibold))
notifyLabel.textColor = Asset.Colors.Label.primary.color
notifyLabel.text = L10n.Scene.Settings.Section.Notifications.Trigger.title
notifyLabel.adjustsFontSizeToFitWidth = true
notifyLabel.minimumScaleFactor = 0.5
view.addArrangedSubview(notifyLabel)
view.addArrangedSubview(whoButton)
whoButton.setContentHuggingPriority(.defaultHigh + 1, for: .horizontal)
whoButton.setContentHuggingPriority(.defaultHigh + 1, for: .vertical)
return view
}()
private(set) lazy var whoButton: UIButton = {
let whoButton = UIButton(type: .roundedRect)
whoButton.menu = triggerMenu
whoButton.showsMenuAsPrimaryAction = true
whoButton.setBackgroundColor(Asset.Colors.battleshipGrey.color, for: .normal)
whoButton.setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
whoButton.titleLabel?.adjustsFontForContentSizeCategory = true
whoButton.titleLabel?.font = UIFontMetrics(forTextStyle: .title3).scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .semibold))
whoButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
whoButton.layer.cornerRadius = 10
whoButton.clipsToBounds = true
whoButton.titleLabel?.adjustsFontSizeToFitWidth = true
whoButton.titleLabel?.minimumScaleFactor = 0.5
return whoButton
}()
private(set) lazy var tableView: UITableView = {
// init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Width' UIStackView:0x7f8c2b6c0590.width == 0)
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 320, height: 320), style: .insetGrouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .clear
tableView.separatorColor = SystemTheme.separator
tableView.register(SettingsAppearanceTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsAppearanceTableViewCell.self))
tableView.register(SettingsToggleTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsToggleTableViewCell.self))
tableView.register(SettingsLinkTableViewCell.self, forCellReuseIdentifier: String(describing: SettingsLinkTableViewCell.self))
return tableView
}()
let tableFooterLabel = MetaLabel(style: .settingTableFooter)
lazy var tableFooterView: UIView = {
// init with a frame to fix a conflict ('UIView-Encapsulated-Layout-Height' UIStackView:0x7ffe41e47da0.height == 0)
let view = UIStackView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))
view.isLayoutMarginsRelativeArrangement = true
view.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
view.axis = .vertical
view.alignment = .center
// tableFooterLabel.linkDelegate = self
view.addArrangedSubview(tableFooterLabel)
return view
}()
}
extension SettingsViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupView()
bindViewModel()
viewModel.viewDidLoad.send()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// make large title not collapsed
navigationController?.navigationBar.sizeToFit()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let footerView = self.tableView.tableFooterView else {
return
}
let width = self.tableView.bounds.size.width
let size = footerView.systemLayoutSizeFitting(CGSize(width: width, height: UIView.layoutFittingCompressedSize.height))
if footerView.frame.size.height != size.height {
footerView.frame.size.height = size.height
self.tableView.tableFooterView = footerView
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateSectionHeaderStackViewLayout()
}
// MAKR: - Private methods
private func updateSectionHeaderStackViewLayout() {
// accessibility
if traitCollection.preferredContentSizeCategory < .accessibilityMedium {
notifySectionHeaderStackView.axis = .horizontal
notifyLabel.numberOfLines = 1
} else {
notifySectionHeaderStackView.axis = .vertical
notifyLabel.numberOfLines = 0
}
}
private func bindViewModel() {
self.whoButton.setTitle(viewModel.setting.value.activeSubscription?.policy.title, for: .normal)
viewModel.setting
.sink { [weak self] setting in
guard let self = self else { return }
self.notificationPolicySubscription = ManagedObjectObserver.observe(object: setting)
.sink { _ in
// do nothing
} receiveValue: { [weak self] change in
guard let self = self else { return }
guard case let .update(object) = change.changeType,
let setting = object as? Setting else { return }
if let activeSubscription = setting.activeSubscription {
self.whoButton.setTitle(activeSubscription.policy.title, for: .normal)
} else {
// assertionFailure()
}
}
}
.store(in: &disposeBag)
let footer = "Mastodon for iOS v\(UIApplication.appVersion()) (\(UIApplication.appBuild()))"
let metaContent = PlaintextMetaContent(string: footer)
tableFooterLabel.configure(content: metaContent)
}
private func setupView() {
setupBackgroundColor()
setupNavigation()
view.addSubview(tableView)
tableView.pinToParent()
setupTableView()
updateSectionHeaderStackViewLayout()
}
private func setupBackgroundColor() {
view.backgroundColor = UIColor(dynamicProvider: { traitCollection in
switch traitCollection.userInterfaceLevel {
case .elevated where traitCollection.userInterfaceStyle == .dark:
return SystemTheme.systemElevatedBackgroundColor
default:
return .secondarySystemBackground
}
})
tableView.separatorColor = SystemTheme.separator
}
private func setupNavigation() {
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.rightBarButtonItem
= UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done,
target: self,
action: #selector(doneButtonDidClick))
navigationItem.title = L10n.Scene.Settings.title
}
private func setupTableView() {
viewModel.setupDiffableDataSource(
for: tableView,
settingsAppearanceTableViewCellDelegate: self,
settingsToggleCellDelegate: self
)
tableView.tableFooterView = tableFooterView
}
}
// Mark: - Actions
extension SettingsViewController {
@objc private func doneButtonDidClick() {
dismiss(animated: true, completion: nil)
}
}
// MARK: - UITableViewDelegate
extension SettingsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sections = viewModel.dataSource.snapshot().sectionIdentifiers
guard section < sections.count else { return nil }
let sectionIdentifier = sections[section]
let header: SettingsSectionHeader
switch sectionIdentifier {
case .preference:
return UIView()
case .notifications:
header = SettingsSectionHeader(
frame: CGRect(x: 0, y: 0, width: 375, height: 66),
customView: notifySectionHeader)
header.update(title: sectionIdentifier.title)
default:
header = SettingsSectionHeader(frame: CGRect(x: 0, y: 0, width: 375, height: 66))
header.update(title: sectionIdentifier.title)
}
header.preservesSuperviewLayoutMargins = true
return header
}
// remove the gap of table's footer
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
// remove the gap of table's footer
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let dataSource = viewModel.dataSource else { return }
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
switch item {
case .appearance:
// do nothing
break
case .notification:
// do nothing
break
case .preference:
// do nothing
break
case .boringZone(let link), .spicyZone(let link):
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
feedbackGenerator.impactOccurred()
switch link {
case .accountSettings:
case .github:
case .termsOfService, .privacyPolicy:
// same URL
case .clearMediaCache:
context.purgeCache()
.receive(on: RunLoop.main)
.sink { [weak self] byteCount in
guard let self = self else { return }
let byteCountFormatted = AppContext.byteCountFormatter.string(fromByteCount: Int64(byteCount))
let alertController = UIAlertController(
title: L10n.Common.Alerts.CleanCache.title,
message: L10n.Common.Alerts.CleanCache.message(byteCountFormatted),
preferredStyle: .alert
)
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil)
alertController.addAction(okAction)
_ = self.coordinator.present(scene: .alertController(alertController: alertController), from: nil, transition: .alertController(animated: true, completion: nil))
}
.store(in: &disposeBag)
case .signOut:
feedbackGenerator.impactOccurred()
alertToSignOut()
}
}
}
}
// Update setting into core data
extension SettingsViewController {
func updateTrigger(policy: Mastodon.API.Subscriptions.Policy) {
let objectID = self.viewModel.setting.value.objectID
let managedObjectContext = context.backgroundManagedObjectContext
managedObjectContext.performChanges {
let setting = managedObjectContext.object(with: objectID) as! Setting
let (subscription, _) = APIService.CoreData.createOrFetchSubscription(
into: managedObjectContext,
setting: setting,
policy: policy
)
let now = Date()
subscription.update(activedAt: now)
setting.didUpdate(at: now)
}
.sink { _ in
// do nothing
} receiveValue: { _ in
// do nothing
}
.store(in: &disposeBag)
}
}
// MARK: - SettingsAppearanceTableViewCellDelegate
extension SettingsViewController: SettingsAppearanceTableViewCellDelegate {
func settingsAppearanceTableViewCell(
_ cell: SettingsAppearanceTableViewCell,
didSelectAppearanceMode appearanceMode: SettingsItem.AppearanceMode
) {
guard let dataSource = viewModel.dataSource else { return }
guard let indexPath = tableView.indexPath(for: cell) else { return }
let item = dataSource.itemIdentifier(for: indexPath)
guard case .appearance = item else { return }
Task { @MainActor in
switch appearanceMode {
case .system:
UserDefaults.shared.customUserInterfaceStyle = .unspecified
case .dark:
UserDefaults.shared.customUserInterfaceStyle = .dark
case .light:
UserDefaults.shared.customUserInterfaceStyle = .light
}
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
feedbackGenerator.impactOccurred()
} // end Task
}
}
extension SettingsViewController: SettingsToggleCellDelegate {
func settingsToggleCell(_ cell: SettingsToggleTableViewCell, switchValueDidChange switch: UISwitch) {
guard let dataSource = viewModel.dataSource else { return }
guard let indexPath = tableView.indexPath(for: cell) else { return }
let isOn = `switch`.isOn
let item = dataSource.itemIdentifier(for: indexPath)
switch item {
case .notification(let record, let switchMode):
let managedObjectContext = context.backgroundManagedObjectContext
managedObjectContext.performChanges {
guard let setting = record.object(in: managedObjectContext) else { return }
guard let subscription = setting.activeSubscription else { return }
let alert = subscription.alert
switch switchMode {
case .favorite: alert.update(favourite: isOn)
case .follow: alert.update(follow: isOn)
case .reblog: alert.update(reblog: isOn)
case .mention: alert.update(mention: isOn)
}
// trigger setting update
alert.subscription.setting?.didUpdate(at: Date())
}
.sink { _ in
// do nothing
}
.store(in: &disposeBag)
case .preference(let record, let preferenceType):
let managedObjectContext = context.backgroundManagedObjectContext
managedObjectContext.performChanges {
guard let setting = record.object(in: managedObjectContext) else { return }
switch preferenceType {
case .disableAvatarAnimation:
setting.update(preferredStaticAvatar: isOn)
case .disableEmojiAnimation:
setting.update(preferredStaticEmoji: isOn)
case .useDefaultBrowser:
setting.update(preferredUsingDefaultBrowser: isOn)
}
}
.sink { result in
switch result {
case .success:
switch preferenceType {
case .disableAvatarAnimation:
UserDefaults.shared.preferredStaticAvatar = isOn
case .disableEmojiAnimation:
UserDefaults.shared.preferredStaticEmoji = isOn
case .useDefaultBrowser:
UserDefaults.shared.preferredUsingDefaultBrowser = isOn
}
case .failure(let error):
assertionFailure(error.localizedDescription)
break
}
}
.store(in: &disposeBag)
default:
assertionFailure()
break
}
}
}
// MARK: - MetaLabelDelegate
extension SettingsViewController: MetaLabelDelegate {
func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) {
switch meta {
case .url(_, _, let url, _):
guard let url = URL(string: url) else { return }
_ = coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true, completion: nil))
default:
assertionFailure()
}
}
}
// MARK: - ASAuthorizationControllerPresentationContextProviding
extension SettingsViewController: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return view.window!
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension SettingsViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .pageSheet
}
}
extension SettingsViewController {
var closeKeyCommand: UIKeyCommand {
UIKeyCommand(
title: L10n.Scene.Settings.Keyboard.closeSettingsWindow,
image: nil,
action: #selector(SettingsViewController.closeSettingsWindowKeyCommandHandler(_:)),
input: "w",
modifierFlags: .command,
propertyList: nil,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
override var keyCommands: [UIKeyCommand]? {
return [closeKeyCommand]
}
@objc private func closeSettingsWindowKeyCommandHandler(_ sender: UIKeyCommand) {
dismiss(animated: true, completion: nil)
}
}

View File

@ -1,159 +0,0 @@
//
// SettingsViewModel.swift
// Mastodon
//
// Created by ihugo on 2021/4/7.
//
import Combine
import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
import UIKit
import AuthenticationServices
import MastodonCore
class SettingsViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
let authContext: AuthContext
var mastodonAuthenticationController: MastodonAuthenticationController?
let setting: CurrentValueSubject<Setting, Never>
var updateDisposeBag = Set<AnyCancellable>()
var createDisposeBag = Set<AnyCancellable>()
let viewDidLoad = PassthroughSubject<Void, Never>()
// output
var dataSource: UITableViewDiffableDataSource<SettingsSection, SettingsItem>!
/// create a subscription when:
/// - does not has one
/// - does not find subscription for selected trigger when change trigger
let createSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>()
let currentInstance = CurrentValueSubject<Mastodon.Entity.Instance?, Never>(nil)
/// update a subscription when:
/// - change switch for specified alerts
let updateSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>()
init(context: AppContext, authContext: AuthContext, setting: Setting) {
self.context = context
self.authContext = authContext
self.setting = CurrentValueSubject(setting)
self.setting
.sink(receiveValue: { [weak self] setting in
guard let self = self else { return }
self.processDataSource(setting)
})
.store(in: &disposeBag)
context.apiService.instance(domain: authContext.mastodonAuthenticationBox.domain)
.sink { [weak self] completion in
guard let self = self else { return }
switch completion {
case .failure(_):
self.currentInstance.value = nil
case .finished:
break
}
} receiveValue: { [weak self] response in
guard let self = self else { return }
self.currentInstance.value = response.value
}
.store(in: &disposeBag)
}
}
extension SettingsViewModel {
func openAuthenticationPage(
authenticateURL: URL,
presentationContextProvider: ASWebAuthenticationPresentationContextProviding
) {
let authenticationController = MastodonAuthenticationController(
context: self.context,
authenticateURL: authenticateURL
)
self.mastodonAuthenticationController = authenticationController
authenticationController.authenticationSession?.presentationContextProvider = presentationContextProvider
authenticationController.authenticationSession?.start()
}
// MARK: - Private methods
private func processDataSource(_ setting: Setting) {
guard let dataSource = self.dataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<SettingsSection, SettingsItem>()
// appearance
let appearanceItems = [
SettingsItem.appearance(record: .init(objectID: setting.objectID))
]
snapshot.appendSections([.appearance])
snapshot.appendItems(appearanceItems, toSection: .appearance)
// preference
snapshot.appendSections([.preference])
let preferenceItems: [SettingsItem] = SettingsItem.PreferenceType.allCases.map { preferenceType in
SettingsItem.preference(settingRecord: .init(objectID: setting.objectID), preferenceType: preferenceType)
}
snapshot.appendItems(preferenceItems,toSection: .preference)
// notification
let notificationItems = SettingsItem.NotificationSwitchMode.allCases.map { mode in
SettingsItem.notification(settingRecord: .init(objectID: setting.objectID), switchMode: mode)
}
snapshot.appendSections([.notifications])
snapshot.appendItems(notificationItems, toSection: .notifications)
// boring zone
let boringZoneSettingsItems: [SettingsItem] = {
let links: [SettingsItem.Link] = [
.accountSettings,
.github,
.termsOfService,
.privacyPolicy
]
let items = links.map { SettingsItem.boringZone(item: $0) }
return items
}()
snapshot.appendSections([.boringZone])
snapshot.appendItems(boringZoneSettingsItems, toSection: .boringZone)
let spicyZoneSettingsItems: [SettingsItem] = {
let links: [SettingsItem.Link] = [
.clearMediaCache,
.signOut
]
let items = links.map { SettingsItem.spicyZone(item: $0) }
return items
}()
snapshot.appendSections([.spicyZone])
snapshot.appendItems(spicyZoneSettingsItems, toSection: .spicyZone)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
extension SettingsViewModel {
func setupDiffableDataSource(
for tableView: UITableView,
settingsAppearanceTableViewCellDelegate: SettingsAppearanceTableViewCellDelegate,
settingsToggleCellDelegate: SettingsToggleCellDelegate
) {
dataSource = SettingsSection.tableViewDiffableDataSource(
for: tableView,
managedObjectContext: context.managedObjectContext,
settingsAppearanceTableViewCellDelegate: settingsAppearanceTableViewCellDelegate,
settingsToggleCellDelegate: settingsToggleCellDelegate
)
processDataSource(self.setting.value)
}
}

View File

@ -1,147 +0,0 @@
//
// AppearanceView.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-7-6.
//
import UIKit
import MastodonAsset
import MastodonLocalization
import MastodonUI
class AppearanceView: UIView {
let imageViewShadowBackgroundContainer = ShadowBackgroundContainer()
lazy var imageView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.layer.masksToBounds = true
view.layer.cornerRadius = 4
view.layer.cornerCurve = .continuous
// accessibility
view.accessibilityIgnoresInvertColors = true
return view
}()
lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .regular)
label.textColor = Asset.Colors.Label.primary.color
label.textAlignment = .center
return label
}()
lazy var checkmarkButton: UIButton = {
let button = UIButton()
button.isUserInteractionEnabled = false
button.setImage(UIImage(systemName: "circle"), for: .normal)
button.setImage(UIImage(systemName: "checkmark.circle.fill"), for: .selected)
button.imageView?.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body)
button.imageView?.tintColor = Asset.Colors.Label.primary.color
button.imageView?.contentMode = .scaleAspectFill
return button
}()
lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 8
view.distribution = .equalSpacing
return view
}()
var selected: Bool = false {
didSet { setNeedsLayout() }
}
// MARK: - Methods
init(image: UIImage?, title: String) {
super.init(frame: .zero)
setupUI()
imageView.image = image
titleLabel.text = title
}
override var isAccessibilityElement: Bool {
get { return true }
set { }
}
override var accessibilityLabel: String? {
get { titleLabel.text }
set { }
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension AppearanceView {
private func setupUI() {
imageView.translatesAutoresizingMaskIntoConstraints = false
imageViewShadowBackgroundContainer.addSubview(imageView)
imageView.pinToParent()
imageViewShadowBackgroundContainer.cornerRadius = 4
stackView.addArrangedSubview(imageViewShadowBackgroundContainer)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(checkmarkButton)
addSubview(stackView)
translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.pinToParent()
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 121.0 / 100.0), // height / width
])
}
private func configureForSelection() {
if selected {
accessibilityTraits.insert(.selected)
} else {
accessibilityTraits.remove(.selected)
}
checkmarkButton.isSelected = selected
}
override func layoutSubviews() {
super.layoutSubviews()
configureForSelection()
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
setNeedsLayout()
}
}
extension AppearanceView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
self.alpha = 0.5
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
UIView.animate(withDuration: 0.33) {
self.alpha = 1
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
UIView.animate(withDuration: 0.33) {
self.alpha = 1
}
}
}

View File

@ -1,67 +0,0 @@
//
// SettingsSectionHeader.swift
// Mastodon
//
// Created by ihugo on 2021/4/8.
//
import UIKit
import MastodonAsset
import MastodonLocalization
struct GroupedTableViewConstraints {
static let topMargin: CGFloat = 40
static let bottomMargin: CGFloat = 10
}
/// section header which supports add a custom view blelow the title
class SettingsSectionHeader: UIView {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 13, weight: .regular)
label.textColor = Asset.Colors.Label.secondary.color
return label
}()
lazy var stackView: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.isLayoutMarginsRelativeArrangement = true
view.layoutMargins = UIEdgeInsets(
top: GroupedTableViewConstraints.topMargin,
left: 0,
bottom: GroupedTableViewConstraints.bottomMargin,
right: 0
)
view.axis = .vertical
return view
}()
init(frame: CGRect, customView: UIView? = nil) {
super.init(frame: frame)
backgroundColor = .clear
stackView.addArrangedSubview(titleLabel)
if let view = customView {
stackView.addArrangedSubview(view)
}
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: self.readableContentGuide.leadingAnchor),
stackView.trailingAnchor.constraint(lessThanOrEqualTo: self.readableContentGuide.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
stackView.topAnchor.constraint(equalTo: self.topAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(title: String?) {
titleLabel.text = title?.uppercased()
}
}