From d70f734957e900f3794c3b27a3480e0a2b8fc57b Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 19 Apr 2022 21:34:49 +0800 Subject: [PATCH] feat: add discovery intro banner --- Localization/app.json | 3 +- Mastodon.xcodeproj/project.pbxproj | 12 +++ .../Posts/DiscoveryPostsViewController.swift | 25 +++++ .../View/DiscoveryIntroBannerView.swift | 101 ++++++++++++++++++ .../Preference/Preference+Discovery.swift | 19 ++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift create mode 100644 MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift diff --git a/Localization/app.json b/Localization/app.json index 6c4aae7a9..a8fc9031f 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -499,7 +499,8 @@ "hashtags": "Hashtags", "news": "News", "for_you": "For You" - } + }, + "intro": "These are the posts gaining traction in your corner of Mastodon." }, "favorite": { "title": "Your Favorites" diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index d7e76cd30..6686f1e7d 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -148,6 +148,7 @@ DB0618072785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */; }; DB06180A2785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */; }; DB084B5725CBC56C00F898ED /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Status.swift */; }; + DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */; }; DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; DB0C946526A6FD4D0088FB11 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB0C946426A6FD4D0088FB11 /* AlamofireImage */; }; DB0C947726A7FE840088FB11 /* NotificationAvatarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */; }; @@ -870,6 +871,7 @@ DB0618062785A8880030EE79 /* MastodonRegisterViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewModel+Diffable.swift"; sourceTree = ""; }; DB0618092785B2AB0030EE79 /* MastodonRegisterAvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonRegisterAvatarTableViewCell.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = ""; }; + DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryIntroBannerView.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; DB0C947626A7FE840088FB11 /* NotificationAvatarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAvatarButton.swift; sourceTree = ""; }; DB0EF72A26FDB1D200347686 /* SidebarListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListCollectionViewCell.swift; sourceTree = ""; }; @@ -2023,6 +2025,14 @@ path = CoreDataStack; sourceTree = ""; }; + DB0A322F280EEA00001729D2 /* View */ = { + isa = PBXGroup; + children = ( + DB0A322D280EE9FD001729D2 /* DiscoveryIntroBannerView.swift */, + ); + path = View; + sourceTree = ""; + }; DB0C947826A7FE950088FB11 /* Button */ = { isa = PBXGroup; children = ( @@ -3108,6 +3118,7 @@ DBDFF1912805544800557A48 /* Discovery */ = { isa = PBXGroup; children = ( + DB0A322F280EEA00001729D2 /* View */, DBDFF19828055A0900557A48 /* Posts */, DB3E6FDE2806A41200B035AE /* Hashtags */, DB3E6FED2806D7FC00B035AE /* News */, @@ -4085,6 +4096,7 @@ DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */, DB7F48452620241000796008 /* ProfileHeaderViewModel.swift in Sources */, DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */, + DB0A322E280EE9FD001729D2 /* DiscoveryIntroBannerView.swift in Sources */, 2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */, 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */, 5B90C45F262599800002E742 /* SettingsToggleTableViewCell.swift in Sources */, diff --git a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift index 30e2faf96..259b21d36 100644 --- a/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift +++ b/Mastodon/Scene/Discovery/Posts/DiscoveryPostsViewController.swift @@ -8,6 +8,7 @@ import os.log import UIKit import Combine +import MastodonUI final class DiscoveryPostsViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -31,6 +32,8 @@ final class DiscoveryPostsViewController: UIViewController, NeedsDependency, Med }() let refreshControl = UIRefreshControl() + + let discoveryIntroBannerView = DiscoveryIntroBannerView() deinit { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) @@ -60,6 +63,21 @@ extension DiscoveryPostsViewController { tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) + + discoveryIntroBannerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(discoveryIntroBannerView) + NSLayoutConstraint.activate([ + discoveryIntroBannerView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + discoveryIntroBannerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + discoveryIntroBannerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ]) + + discoveryIntroBannerView.delegate = self + discoveryIntroBannerView.isHidden = UserDefaults.shared.discoveryIntroBannerNeedsHidden + UserDefaults.shared.publisher(for: \.discoveryIntroBannerNeedsHidden) + .receive(on: DispatchQueue.main) + .assign(to: \.isHidden, on: discoveryIntroBannerView) + .store(in: &disposeBag) tableView.delegate = self viewModel.setupDiffableDataSource( @@ -146,3 +164,10 @@ extension DiscoveryPostsViewController: ScrollViewContainer { tableView } } + +// MARK: - DiscoveryIntroBannerViewDelegate +extension DiscoveryPostsViewController: DiscoveryIntroBannerViewDelegate { + func discoveryIntroBannerView(_ bannerView: DiscoveryIntroBannerView, closeButtonDidPressed button: UIButton) { + UserDefaults.shared.discoveryIntroBannerNeedsHidden = true + } +} diff --git a/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift b/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift new file mode 100644 index 000000000..e3e1c4547 --- /dev/null +++ b/Mastodon/Scene/Discovery/View/DiscoveryIntroBannerView.swift @@ -0,0 +1,101 @@ +// +// DiscoveryIntroBannerView.swift +// Mastodon +// +// Created by MainasuK on 2022-4-19. +// + +import os.log +import UIKit +import Combine +import MastodonAsset + +public protocol DiscoveryIntroBannerViewDelegate: AnyObject { + func discoveryIntroBannerView(_ bannerView: DiscoveryIntroBannerView, closeButtonDidPressed button: UIButton) +} + +public final class DiscoveryIntroBannerView: UIView { + + let logger = Logger(subsystem: "DiscoveryIntroBannerView", category: "View") + + var _disposeBag = Set() + + public weak var delegate: DiscoveryIntroBannerViewDelegate? + + let label: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 16, weight: .regular)) + label.textColor = Asset.Colors.Label.primary.color + label.text = "These are the posts gaining traction in your corner of Mastodon." // TODO: i18n + label.numberOfLines = 0 + return label + }() + + let closeButton: HitTestExpandedButton = { + let button = HitTestExpandedButton(type: .system) + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = Asset.Colors.Label.secondary.color + return button + }() + + public override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + +} + +extension DiscoveryIntroBannerView { + private func _init() { + preservesSuperviewLayoutMargins = true + + setupAppearance(theme: ThemeService.shared.currentTheme.value) + ThemeService.shared.currentTheme + .receive(on: DispatchQueue.main) + .sink { [weak self] theme in + guard let self = self else { return } + self.setupAppearance(theme: theme) + } + .store(in: &_disposeBag) + + closeButton.translatesAutoresizingMaskIntoConstraints = false + addSubview(closeButton) + NSLayoutConstraint.activate([ + closeButton.topAnchor.constraint(equalTo: topAnchor, constant: 16).priority(.required - 1), + layoutMarginsGuide.trailingAnchor.constraint(equalTo: closeButton.trailingAnchor), + closeButton.heightAnchor.constraint(equalToConstant: 20).priority(.required - 1), + closeButton.widthAnchor.constraint(equalToConstant: 20).priority(.required - 1), + ]) + + label.translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: topAnchor, constant: 16).priority(.required - 1), + label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + closeButton.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10), + bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 16).priority(.required - 1), + ]) + + closeButton.addTarget(self, action: #selector(DiscoveryIntroBannerView.closeButtonDidPressed(_:)), for: .touchUpInside) + } +} + +extension DiscoveryIntroBannerView { + @objc private func closeButtonDidPressed(_ sender: UIButton) { + logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") + delegate?.discoveryIntroBannerView(self, closeButtonDidPressed: sender) + } +} + +extension DiscoveryIntroBannerView { + + private func setupAppearance(theme: Theme) { + backgroundColor = theme.systemBackgroundColor + } + +} diff --git a/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift new file mode 100644 index 000000000..0c6a9c544 --- /dev/null +++ b/MastodonSDK/Sources/MastodonCommon/Preference/Preference+Discovery.swift @@ -0,0 +1,19 @@ +// +// Preference+Discovery.swift +// +// +// Created by MainasuK on 2022-4-19. +// + +import Foundation + +extension UserDefaults { + + @objc public dynamic var discoveryIntroBannerNeedsHidden: Bool { + get { + return bool(forKey: #function) + } + set { self[#function] = newValue } + } + +}