diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index fd58693b2..d15860a60 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -178,6 +178,7 @@ D8FAAE3D2AD042E700DC1832 /* AdminTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FAAE3C2AD042E700DC1832 /* AdminTableViewCell.swift */; }; D8FAAE3F2AD0430E00DC1832 /* ContactAdminTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FAAE3E2AD0430E00DC1832 /* ContactAdminTableViewCell.swift */; }; D8FAAE412AD0475900DC1832 /* AboutInstanceTableViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FAAE402AD0475900DC1832 /* AboutInstanceTableViewHeader.swift */; }; + D8FAAE432AD047B200DC1832 /* AboutInstanceTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FAAE422AD047B200DC1832 /* AboutInstanceTableFooterView.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 */; }; DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */; }; @@ -843,6 +844,7 @@ D8FAAE3C2AD042E700DC1832 /* AdminTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminTableViewCell.swift; sourceTree = ""; }; D8FAAE3E2AD0430E00DC1832 /* ContactAdminTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAdminTableViewCell.swift; sourceTree = ""; }; D8FAAE402AD0475900DC1832 /* AboutInstanceTableViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstanceTableViewHeader.swift; sourceTree = ""; }; + D8FAAE422AD047B200DC1832 /* AboutInstanceTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutInstanceTableFooterView.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 = ""; }; DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareActivityProvider.swift; sourceTree = ""; }; @@ -1953,6 +1955,7 @@ D8FAAE3C2AD042E700DC1832 /* AdminTableViewCell.swift */, D8FAAE3E2AD0430E00DC1832 /* ContactAdminTableViewCell.swift */, D8FAAE402AD0475900DC1832 /* AboutInstanceTableViewHeader.swift */, + D8FAAE422AD047B200DC1832 /* AboutInstanceTableFooterView.swift */, D81439852AD415DE0071A88F /* AboutInstance.swift */, ); path = "Table View Components"; @@ -3664,6 +3667,7 @@ DBDFF1902805543100557A48 /* DiscoveryPostsViewController.swift in Sources */, DB697DD9278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, + D8FAAE432AD047B200DC1832 /* AboutInstanceTableFooterView.swift in Sources */, D808B94E296EFBBA0031EB1E /* StatusEditHistoryTableViewCell.swift in Sources */, 2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */, D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */, diff --git a/Mastodon/Scene/Settings/Server Details/AboutInstanceViewController.swift b/Mastodon/Scene/Settings/Server Details/AboutInstanceViewController.swift index 2f6a40a7c..7b7e7cc93 100644 --- a/Mastodon/Scene/Settings/Server Details/AboutInstanceViewController.swift +++ b/Mastodon/Scene/Settings/Server Details/AboutInstanceViewController.swift @@ -9,12 +9,14 @@ protocol AboutInstanceViewControllerDelegate: AnyObject { } class AboutInstanceViewController: UIViewController { - + weak var delegate: AboutInstanceViewControllerDelegate? var dataSource: UITableViewDiffableDataSource? let tableView: UITableView let headerView: AboutInstanceTableHeaderView + let footerView: AboutInstanceTableFooterView + var instance: Mastodon.Entity.V2.Instance? init() { @@ -24,6 +26,8 @@ class AboutInstanceViewController: UIViewController { tableView.register(AdminTableViewCell.self, forCellReuseIdentifier: AdminTableViewCell.reuseIdentifier) headerView = AboutInstanceTableHeaderView() + footerView = AboutInstanceTableFooterView() + super.init(nibName: nil, bundle: nil) let dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, itemIdentifier in @@ -72,14 +76,23 @@ class AboutInstanceViewController: UIViewController { super.viewDidLoad() tableView.tableHeaderView = headerView + tableView.tableFooterView = footerView + + headerView.frame.size.height = 1 + footerView.frame.size.height = 2 } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - if let tableFooterView = tableView.tableHeaderView { + if let tableHeaderView = tableView.tableHeaderView { + tableHeaderView.frame.size = tableHeaderView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + tableView.tableHeaderView = tableHeaderView + } + + if let tableFooterView = tableView.tableFooterView { tableFooterView.frame.size = tableFooterView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) - tableView.tableHeaderView = tableFooterView + tableView.tableFooterView = tableFooterView } super.viewWillLayoutSubviews() @@ -115,6 +128,16 @@ class AboutInstanceViewController: UIViewController { } } } + + func updateFooter(with extendedDescription: Mastodon.Entity.ExtendedDescription) { + DispatchQueue.main.async { + self.footerView.update(with: extendedDescription) + if let tableFooterView = self.tableView.tableFooterView { + tableFooterView.frame.size = tableFooterView.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize) + self.tableView.tableFooterView = tableFooterView + } + } + } } extension AboutInstanceViewController: UITableViewDelegate { diff --git a/Mastodon/Scene/Settings/Server Details/ServerDetailsViewController.swift b/Mastodon/Scene/Settings/Server Details/ServerDetailsViewController.swift index cf86ee34b..12992c7cb 100644 --- a/Mastodon/Scene/Settings/Server Details/ServerDetailsViewController.swift +++ b/Mastodon/Scene/Settings/Server Details/ServerDetailsViewController.swift @@ -2,6 +2,7 @@ import UIKit import MastodonSDK +import MetaTextKit enum ServerDetailsTab: Int, CaseIterable { case about = 0 @@ -22,10 +23,11 @@ protocol ServerDetailsViewControllerDelegate: AnyObject { class ServerDetailsViewController: UIViewController { - weak var delegate: (ServerDetailsViewControllerDelegate & AboutInstanceViewControllerDelegate & InstanceRulesViewControllerDelegate)? { + weak var delegate: (ServerDetailsViewControllerDelegate & AboutInstanceViewControllerDelegate & InstanceRulesViewControllerDelegate & MetaLabelDelegate)? { didSet { aboutInstanceViewController.delegate = delegate instanceRulesViewController.delegate = delegate + aboutInstanceViewController.footerView.contentLabel.linkDelegate = delegate } } let pageController: UIPageViewController @@ -121,6 +123,10 @@ class ServerDetailsViewController: UIViewController { aboutInstanceViewController.update(with: instance) instanceRulesViewController.update(with: instance) } + + func updateFooter(with extendedDescription: Mastodon.Entity.ExtendedDescription) { + aboutInstanceViewController.updateFooter(with: extendedDescription) + } } //MARK: - UIPageViewControllerDataSource diff --git a/Mastodon/Scene/Settings/Server Details/Table View Components/AboutInstanceTableFooterView.swift b/Mastodon/Scene/Settings/Server Details/Table View Components/AboutInstanceTableFooterView.swift new file mode 100644 index 000000000..52da89009 --- /dev/null +++ b/Mastodon/Scene/Settings/Server Details/Table View Components/AboutInstanceTableFooterView.swift @@ -0,0 +1,62 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import UIKit +import MetaTextKit +import MastodonSDK +import MastodonMeta +import MastodonCore +import MastodonAsset + +class AboutInstanceTableFooterView: UIView { + let headlineLabel: UILabel + let contentLabel: MetaLabel + + init() { + + headlineLabel = UILabel() + headlineLabel.translatesAutoresizingMaskIntoConstraints = false + headlineLabel.font = UIFontMetrics(forTextStyle: .title2).scaledFont(for: .systemFont(ofSize: 22, weight: .bold)) + + contentLabel = MetaLabel(style: .aboutInstance) + contentLabel.numberOfLines = 0 + contentLabel.translatesAutoresizingMaskIntoConstraints = false + super.init(frame: .zero) + + addSubview(headlineLabel) + addSubview(contentLabel) + + setupConstraints() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func setupConstraints() { + + let horizontalMargin = 16.0 + + let constraints = [ + headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: 24), + headlineLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalMargin), + trailingAnchor.constraint(equalTo: headlineLabel.trailingAnchor, constant: horizontalMargin), + + contentLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: 8), + contentLabel.leadingAnchor.constraint(equalTo: headlineLabel.leadingAnchor), + contentLabel.trailingAnchor.constraint(equalTo: headlineLabel.trailingAnchor), +// contentLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 3000), + bottomAnchor.constraint(equalTo: contentLabel.bottomAnchor), + ] + + NSLayoutConstraint.activate(constraints) + } + + func update(with extendedDescription: Mastodon.Entity.ExtendedDescription) { + headlineLabel.text = "A legal notice" + + if let metaContent = try? MastodonMetaContent.convert(document: MastodonContent(content: extendedDescription.content, emojis: [:])) { + contentLabel.configure(content: metaContent) + } else { + let content = PlaintextMetaContent(string: extendedDescription.content) + contentLabel.configure(content: content) + } + } +} diff --git a/Mastodon/Scene/Settings/SettingsCoordinator.swift b/Mastodon/Scene/Settings/SettingsCoordinator.swift index 17796da96..2c4973a30 100644 --- a/Mastodon/Scene/Settings/SettingsCoordinator.swift +++ b/Mastodon/Scene/Settings/SettingsCoordinator.swift @@ -6,6 +6,7 @@ import MastodonCore import CoreDataStack import MastodonSDK import Combine +import MetaTextKit protocol SettingsCoordinatorDelegate: AnyObject { func logout(_ settingsCoordinator: SettingsCoordinator) @@ -80,6 +81,15 @@ extension SettingsCoordinator: SettingsViewControllerDelegate { } .store(in: &disposeBag) + appContext.apiService.extendedDescription(domain: domain) + .sink { _ in + + } receiveValue: { content in + serverDetailsViewController.updateFooter(with: content.value) + } + .store(in: &disposeBag) + + navigationController.pushViewController(serverDetailsViewController, animated: true) case .aboutMastodon: let aboutViewController = AboutViewController() @@ -228,3 +238,29 @@ extension SettingsCoordinator: AboutInstanceViewControllerDelegate { extension SettingsCoordinator: InstanceRulesViewControllerDelegate { } + +extension SettingsCoordinator: MetaLabelDelegate { + @MainActor + func metaLabel(_ metaLabel: MetaLabel, didSelectMeta meta: Meta) { + switch meta { + case .url(_, _, let url, _): + guard let url = URL(string: url) else { return } + _ = sceneCoordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) + case .mention(_, _, let userInfo): + guard let href = userInfo?["href"] as? String, + let url = URL(string: href) else { return } + _ = sceneCoordinator.present(scene: .safari(url: url), from: nil, transition: .safariPresent(animated: true, completion: nil)) + case .hashtag(_, let hashtag, _): + let hashtagTimelineViewModel = HashtagTimelineViewModel(context: appContext, authContext: authContext, hashtag: hashtag) + _ = sceneCoordinator.present(scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel), from: nil, transition: .show) + case .email(let email, _): + if let emailUrl = URL(string: "mailto:\(email)"), UIApplication.shared.canOpenURL(emailUrl) { + UIApplication.shared.open(emailUrl) + } + case .emoji: + break + } + } + + +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift index b352bdbc2..cee24e98b 100644 --- a/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift +++ b/MastodonSDK/Sources/MastodonUI/Extension/MetaLabel.swift @@ -30,6 +30,7 @@ extension MetaLabel { case accountListUsername case sidebarHeadline(isSelected: Bool) case sidebarSubheadline(isSelected: Bool) + case aboutInstance } public convenience init(style: Style) { @@ -129,6 +130,10 @@ extension MetaLabel { case .sidebarSubheadline(let isSelected): font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular), maximumPointSize: 18) textColor = isSelected ? .white : Asset.Colors.Label.secondary.color + case .aboutInstance: + font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17, weight: .regular)) + textColor = .label + paragraphStyle.paragraphSpacing = 0 } self.font = font