From 921c97b2a7270f5dbcd769b6188a9df7c3d29c96 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 27 Jun 2023 19:04:41 +0200 Subject: [PATCH] Implement links on about-mastodon-screen (IOS-14) --- Mastodon.xcodeproj/project.pbxproj | 18 +++- Mastodon/Coordinator/SceneCoordinator.swift | 43 +++++++++- .../AboutMastodonTableViewCell.swift | 18 ++++ .../About Mastodon/AboutSettings.swift | 38 +++++++++ .../About Mastodon/AboutViewController.swift | 82 +++++++++++++++++++ .../Scene/Settings/AboutViewController.swift | 3 - .../Legacy/SettingsViewController.swift | 15 ---- .../Settings/Legacy/SettingsViewModel.swift | 5 -- .../Scene/Settings/SettingsCoordinator.swift | 40 +++++++-- .../Sources/MastodonCore/AppContext.swift | 6 ++ .../MastodonSDK/API/Mastodon+API.swift | 4 + 11 files changed, 241 insertions(+), 31 deletions(-) create mode 100644 Mastodon/Scene/Settings/About Mastodon/AboutMastodonTableViewCell.swift create mode 100644 Mastodon/Scene/Settings/About Mastodon/AboutSettings.swift create mode 100644 Mastodon/Scene/Settings/About Mastodon/AboutViewController.swift delete mode 100644 Mastodon/Scene/Settings/AboutViewController.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index fcc2ac056..a9aa83c7b 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -160,6 +160,8 @@ D8F8A03C29CA5CB6000195DD /* HashtagWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */; }; D8F917012A4AD8A5008A5370 /* SettingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917002A4AD8A4008A5370 /* SettingsTableViewCell.swift */; }; D8F917032A4B063D008A5370 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F917022A4B063D008A5370 /* Settings.swift */; }; + D8F9170B2A4B2C80008A5370 /* AboutSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170A2A4B2C80008A5370 /* AboutSettings.swift */; }; + D8F9170D2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170C2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift */; }; D8F9170F2A4B47EF008A5370 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F9170E2A4B47EF008A5370 /* Coordinator.swift */; }; DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; @@ -814,6 +816,8 @@ D8F8A03B29CA5CB6000195DD /* HashtagWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagWidget.swift; sourceTree = ""; }; D8F917002A4AD8A4008A5370 /* SettingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewCell.swift; sourceTree = ""; }; D8F917022A4B063D008A5370 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + D8F9170A2A4B2C80008A5370 /* AboutSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettings.swift; sourceTree = ""; }; + D8F9170C2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutMastodonTableViewCell.swift; sourceTree = ""; }; D8F9170E2A4B47EF008A5370 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; DB0009A826AEE5DC009B9D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; DB0009AD26AEE5E4009B9D2D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Intents.strings; sourceTree = ""; }; @@ -1738,12 +1742,12 @@ 5B90C455262599800002E742 /* Settings */ = { isa = PBXGroup; children = ( + D8F917092A4B2AFF008A5370 /* About Mastodon */, D8F916FF2A4AD898008A5370 /* Settings Overview */, D8318A7E2A4466C900C0FB73 /* Legacy */, D8318A7F2A4466D300C0FB73 /* SettingsCoordinator.swift */, D8318A832A4468A800C0FB73 /* GeneralSettingsViewController.swift */, D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */, - D8318A892A4468DC00C0FB73 /* AboutViewController.swift */, D8318A8B2A4468E600C0FB73 /* SupportMastodonViewController.swift */, ); path = Settings; @@ -1888,6 +1892,16 @@ path = "Settings Overview"; sourceTree = ""; }; + D8F917092A4B2AFF008A5370 /* About Mastodon */ = { + isa = PBXGroup; + children = ( + D8318A892A4468DC00C0FB73 /* AboutViewController.swift */, + D8F9170A2A4B2C80008A5370 /* AboutSettings.swift */, + D8F9170C2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift */, + ); + path = "About Mastodon"; + sourceTree = ""; + }; DB01409B25C40BB600F9F3CF /* Onboarding */ = { isa = PBXGroup; children = ( @@ -3790,6 +3804,7 @@ DB0FCB942797E2B0006C02E2 /* SearchResultViewModel+Diffable.swift in Sources */, DB63F752279944AA00455B82 /* SearchHistorySectionHeaderCollectionReusableView.swift in Sources */, DB3E6FDD2806A40F00B035AE /* DiscoveryHashtagsViewController.swift in Sources */, + D8F9170B2A4B2C80008A5370 /* AboutSettings.swift in Sources */, DB938EED2623F79B00E5B6C1 /* ThreadViewModel.swift in Sources */, DB6988DE2848D11C002398EF /* PagerTabStripNavigateable.swift in Sources */, 2DCB73FD2615C13900EC03D4 /* SearchRecommendCollectionHeader.swift in Sources */, @@ -3825,6 +3840,7 @@ DBA5E7A9263BD3A4004598BB /* ContextMenuImagePreviewViewController.swift in Sources */, DBF156E22702DA6900EC00B7 /* UIStatusBarManager+HandleTapAction.m in Sources */, 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, + D8F9170D2A4B3C6F008A5370 /* AboutMastodonTableViewCell.swift in Sources */, DB697DE1278F5296004EF2F7 /* DataSourceFacade+Model.swift in Sources */, DBCC3B8F26148F7B0045B23D /* CachedProfileViewModel.swift in Sources */, DB4F097526A037F500D62E92 /* SearchHistoryViewModel.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 01771cc03..6f006f1ca 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -31,6 +31,8 @@ final public class SceneCoordinator { private(set) var secondaryStackHashValues = Set() var childCoordinator: Coordinator? + + private var mastodonAuthenticationController: MastodonAuthenticationController? init( scene: UIScene, @@ -542,7 +544,6 @@ private extension SceneCoordinator { settingsCoordinator.start() viewController = settingsCoordinator.navigationController childCoordinator = settingsCoordinator - case .editStatus(let viewModel): let composeViewController = ComposeViewController(viewModel: viewModel) viewController = composeViewController @@ -595,10 +596,50 @@ extension SceneCoordinator: SettingsCoordinatorDelegate { } } + alertController.addAction(cancelAction) alertController.addAction(signOutAction) settingsCoordinator.navigationController.present(alertController, animated: true) + } + + @MainActor + func openGithubURL(_ settingsCoordinator: SettingsCoordinator) { + guard let githubURL = URL(string: "https://github.com/mastodon/mastodon-ios") else { return } + + _ = present( + scene: .safari(url: githubURL), + from: settingsCoordinator.navigationController, + transition: .safariPresent(animated: true) + ) + } + + @MainActor + func openPrivacyURL(_ settingsCoordinator: SettingsCoordinator) { + guard let authContext else { return } + + let domain = authContext.mastodonAuthenticationBox.domain + let privacyURL = Mastodon.API.privacyURL(domain: domain) + + _ = present(scene: .safari(url: privacyURL), + from: settingsCoordinator.navigationController, + transition: .safariPresent(animated: true)) + + } + + func openProfileSettingsURL(_ settingsCoordinator: SettingsCoordinator) { + guard let authContext else { return } + + let domain = authContext.mastodonAuthenticationBox.domain + let profileSettingsURL = Mastodon.API.profileSettingsURL(domain: domain) + + let authenticationController = MastodonAuthenticationController(context: appContext, authenticateURL: profileSettingsURL) + + authenticationController.authenticationSession?.presentationContextProvider = settingsCoordinator + authenticationController.authenticationSession?.start() + + self.mastodonAuthenticationController = authenticationController + } } diff --git a/Mastodon/Scene/Settings/About Mastodon/AboutMastodonTableViewCell.swift b/Mastodon/Scene/Settings/About Mastodon/AboutMastodonTableViewCell.swift new file mode 100644 index 000000000..29a9969d7 --- /dev/null +++ b/Mastodon/Scene/Settings/About Mastodon/AboutMastodonTableViewCell.swift @@ -0,0 +1,18 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonAsset + +class AboutMastodonTableViewCell: UITableViewCell { + static let reuseIdentifier = "AboutMastodonTableViewCell" + + func configure(with entry: AboutSettingsEntry) { + var contentConfiguration = UIListContentConfiguration.valueCell() + + contentConfiguration.text = entry.text + contentConfiguration.secondaryText = entry.secondaryText + contentConfiguration.textProperties.color = Asset.Colors.Brand.blurple.color + + self.contentConfiguration = contentConfiguration + } +} diff --git a/Mastodon/Scene/Settings/About Mastodon/AboutSettings.swift b/Mastodon/Scene/Settings/About Mastodon/AboutSettings.swift new file mode 100644 index 000000000..b0df9610d --- /dev/null +++ b/Mastodon/Scene/Settings/About Mastodon/AboutSettings.swift @@ -0,0 +1,38 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import MastodonCore + +struct AboutSettingsSection: Hashable { + let entries: [AboutSettingsEntry] +} + +enum AboutSettingsEntry: Hashable { + case evenMoreSettings + case contributeToMastodon + case privacyPolicy + case clearMediaCache(Int) + + var text: String { + switch self { + //TODO: @zeitschlag Add Localization + case .evenMoreSettings: + return "Even More Settings" + case .contributeToMastodon: + return "Contribute to Mastodon" + case .privacyPolicy: + return "Privacy Policy" + case .clearMediaCache(_): + return "Clear Media Storage" + } + } + + var secondaryText: String? { + switch self { + case .evenMoreSettings, .contributeToMastodon, .privacyPolicy: + return nil + case .clearMediaCache(let mediaStorage): + return AppContext.byteCountFormatter.string(fromByteCount: Int64(mediaStorage)) + } + } +} diff --git a/Mastodon/Scene/Settings/About Mastodon/AboutViewController.swift b/Mastodon/Scene/Settings/About Mastodon/AboutViewController.swift new file mode 100644 index 000000000..ac825382b --- /dev/null +++ b/Mastodon/Scene/Settings/About Mastodon/AboutViewController.swift @@ -0,0 +1,82 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonCore + +protocol AboutViewControllerDelegate: AnyObject { + func didSelect(_ viewController: AboutViewController, entry: AboutSettingsEntry) +} + +class AboutViewController: UIViewController { + + let tableView: UITableView + let sections: [AboutSettingsSection] + var tableViewDataSource: UITableViewDiffableDataSource? + weak var delegate: AboutViewControllerDelegate? + + init() { + + tableView = UITableView(frame: .zero, style: .insetGrouped) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.register(AboutMastodonTableViewCell.self, forCellReuseIdentifier: AboutMastodonTableViewCell.reuseIdentifier) + + sections = [ + AboutSettingsSection(entries: [ + .evenMoreSettings, + .contributeToMastodon, + .privacyPolicy + ]), + AboutSettingsSection(entries: [ + .clearMediaCache(AppContext.shared.currentDiskUsage()) + ]) + ] + + super.init(nibName: nil, bundle: nil) + + let tableViewDataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, itemIdentifier in + + guard let self, + let cell = tableView.dequeueReusableCell(withIdentifier: AboutMastodonTableViewCell.reuseIdentifier, for: indexPath) as? AboutMastodonTableViewCell else { fatalError("WTF?? Wrong Cell dude!") } + + let entry = sections[indexPath.section].entries[indexPath.row] + cell.configure(with: entry) + + return cell + } + + tableView.delegate = self + tableView.dataSource = tableViewDataSource + self.tableViewDataSource = tableViewDataSource + + view.addSubview(tableView) + view.backgroundColor = .systemGroupedBackground + + tableView.pinToParent() + } + + 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) + } +} + +extension AboutViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + let entry = sections[indexPath.section].entries[indexPath.row] + delegate?.didSelect(self, entry: entry) + + tableView.deselectRow(at: indexPath, animated: true) + } +} + diff --git a/Mastodon/Scene/Settings/AboutViewController.swift b/Mastodon/Scene/Settings/AboutViewController.swift deleted file mode 100644 index 89dbb24d4..000000000 --- a/Mastodon/Scene/Settings/AboutViewController.swift +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright © 2023 Mastodon gGmbH. All rights reserved. - -import Foundation diff --git a/Mastodon/Scene/Settings/Legacy/SettingsViewController.swift b/Mastodon/Scene/Settings/Legacy/SettingsViewController.swift index 024d6165b..6642d72b4 100644 --- a/Mastodon/Scene/Settings/Legacy/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/Legacy/SettingsViewController.swift @@ -314,24 +314,9 @@ extension SettingsViewController: UITableViewDelegate { feedbackGenerator.impactOccurred() switch link { case .accountSettings: - let domain = viewModel.authContext.mastodonAuthenticationBox.domain - guard let url = URL(string: "https://\(domain)/auth/edit") else { return } - viewModel.openAuthenticationPage(authenticateURL: url, presentationContextProvider: self) case .github: - guard let url = URL(string: "https://github.com/mastodon/mastodon-ios") else { break } - _ = coordinator.present( - scene: .safari(url: url), - from: self, - transition: .safariPresent(animated: true, completion: nil) - ) case .termsOfService, .privacyPolicy: // same URL - guard let url = viewModel.privacyURL else { break } - _ = coordinator.present( - scene: .safari(url: url), - from: self, - transition: .safariPresent(animated: true, completion: nil) - ) case .clearMediaCache: context.purgeCache() .receive(on: RunLoop.main) diff --git a/Mastodon/Scene/Settings/Legacy/SettingsViewModel.swift b/Mastodon/Scene/Settings/Legacy/SettingsViewModel.swift index 945d67f56..6698dd535 100644 --- a/Mastodon/Scene/Settings/Legacy/SettingsViewModel.swift +++ b/Mastodon/Scene/Settings/Legacy/SettingsViewModel.swift @@ -41,11 +41,6 @@ class SettingsViewModel { /// - change switch for specified alerts let updateSubscriptionSubject = PassthroughSubject<(triggerBy: String, values: [Bool?]), Never>() - lazy var privacyURL: URL? = { - let domain = authContext.mastodonAuthenticationBox.domain - return Mastodon.API.privacyURL(domain: domain) - }() - init(context: AppContext, authContext: AuthContext, setting: Setting) { self.context = context self.authContext = authContext diff --git a/Mastodon/Scene/Settings/SettingsCoordinator.swift b/Mastodon/Scene/Settings/SettingsCoordinator.swift index 470c0771d..0cca81d10 100644 --- a/Mastodon/Scene/Settings/SettingsCoordinator.swift +++ b/Mastodon/Scene/Settings/SettingsCoordinator.swift @@ -1,12 +1,16 @@ // Copyright © 2023 Mastodon gGmbH. All rights reserved. import UIKit +import AuthenticationServices protocol SettingsCoordinatorDelegate: AnyObject { func logout(_ settingsCoordinator: SettingsCoordinator) + func openGithubURL(_ settingsCoordinator: SettingsCoordinator) + func openPrivacyURL(_ settingsCoordinator: SettingsCoordinator) + func openProfileSettingsURL(_ settingsCoordinator: SettingsCoordinator) } -class SettingsCoordinator: Coordinator { +class SettingsCoordinator: NSObject, Coordinator { let navigationController: UINavigationController let presentedOn: UIViewController @@ -15,11 +19,11 @@ class SettingsCoordinator: Coordinator { private let settingsViewController: SettingsViewController - init(presentedOn: UIViewController) { + init(presentedOn: UIViewController, accountName: String) { self.presentedOn = presentedOn navigationController = UINavigationController() - settingsViewController = SettingsViewController(accountName: "born2jort") + settingsViewController = SettingsViewController(accountName: accountName) } func start() { @@ -44,8 +48,10 @@ extension SettingsCoordinator: SettingsViewControllerDelegate { break // show notifications case .aboutMastodon: - break - // show about + let aboutViewController = AboutViewController() + aboutViewController.delegate = self + + navigationController.pushViewController(aboutViewController, animated: true) case .supportMastodon: break // present support-screen @@ -53,5 +59,27 @@ extension SettingsCoordinator: SettingsViewControllerDelegate { delegate?.logout(self) } } - +} + +extension SettingsCoordinator: AboutViewControllerDelegate { + func didSelect(_ viewController: AboutViewController, entry: AboutSettingsEntry) { + switch entry { + case .evenMoreSettings: + delegate?.openProfileSettingsURL(self) + case .contributeToMastodon: + delegate?.openGithubURL(self) + case .privacyPolicy: + delegate?.openPrivacyURL(self) + case .clearMediaCache(_): + //TODO: appContext.purgeCache() + //FIXME: maybe we should inject an AppContext/AuthContext here instead of delegating everything to SceneCoordinator? + break + } + } +} + +extension SettingsCoordinator: ASWebAuthenticationPresentationContextProviding { + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return navigationController.view.window! + } } diff --git a/MastodonSDK/Sources/MastodonCore/AppContext.swift b/MastodonSDK/Sources/MastodonCore/AppContext.swift index 35ad3247d..b5a9c0a9d 100644 --- a/MastodonSDK/Sources/MastodonCore/AppContext.swift +++ b/MastodonSDK/Sources/MastodonCore/AppContext.swift @@ -136,6 +136,12 @@ extension AppContext { .reduce(0, +) .eraseToAnyPublisher() } + + public func currentDiskUsage() -> Int { + let alamoFireDiskBytes = ImageDownloader.defaultURLCache().currentDiskUsage + //TODO: Add temp directory files + return alamoFireDiskBytes + } private static func purgeAlamofireImageCache() -> AnyPublisher { Future { promise in diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift index cba9e91b6..4175e8444 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API.swift @@ -95,6 +95,10 @@ extension Mastodon.API { public static func privacyURL(domain: String) -> URL { return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/terms")! } + + public static func profileSettingsURL(domain: String) -> URL { + return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/auth/edit")! + } } extension Mastodon.API {