From 6af94352e2f4af329d0d405ed4244f1a516d7506 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 5 Apr 2024 14:27:06 +0200 Subject: [PATCH] Remove NavigationBarTitleView and add first draft of TimelineStatusPill (IOS-234) --- Mastodon.xcodeproj/project.pbxproj | 20 +- .../HomeTimelineViewController.swift | 80 +------ ...omeTimelineViewModel+LoadLatestState.swift | 8 +- ...omeTimelineViewModel+LoadOldestState.swift | 5 +- .../HomeTimeline/HomeTimelineViewModel.swift | 12 - .../HomeTimeline/TimelineStatusPill.swift | 73 ++++++ .../HomeTimelineNavigationBarTitleView.swift | 221 ------------------ ...eTimelineNavigationBarTitleViewModel.swift | 182 --------------- 8 files changed, 90 insertions(+), 511 deletions(-) create mode 100644 Mastodon/Scene/HomeTimeline/TimelineStatusPill.swift delete mode 100644 Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift delete mode 100644 Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index d4139d7a9..9c96d3929 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -91,8 +91,6 @@ 2D7867192625B77500211898 /* NotificationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7867182625B77500211898 /* NotificationItem.swift */; }; 2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */; }; 2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */; }; - 2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */; }; - 2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift */; }; 2D84350525FF858100EECE90 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D84350425FF858100EECE90 /* UIScrollView.swift */; }; 2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; 2DAC9E38262FC2320062E1A6 /* SuggestionAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */; }; @@ -154,6 +152,7 @@ D8318A882A4468D300C0FB73 /* NotificationSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */; }; D8318A8A2A4468DC00C0FB73 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8318A892A4468DC00C0FB73 /* AboutViewController.swift */; }; D8363B1629469CE200A74079 /* OnboardingNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8363B1529469CE200A74079 /* OnboardingNextView.swift */; }; + D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */; }; D84FA0932AE6915800987F47 /* MBProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = D84FA0922AE6915800987F47 /* MBProgressHUD */; }; D852C23C2AC5D02C00309232 /* AboutInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23B2AC5D02C00309232 /* AboutInstanceViewController.swift */; }; D852C23E2AC5D03300309232 /* InstanceRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D852C23D2AC5D03300309232 /* InstanceRulesViewController.swift */; }; @@ -715,8 +714,6 @@ 2D7867182625B77500211898 /* NotificationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItem.swift; sourceTree = ""; }; 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewControllerAppearance.swift; sourceTree = ""; }; 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModelNavigationDelegateShim.swift; sourceTree = ""; }; - 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleViewModel.swift; sourceTree = ""; }; - 2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleView.swift; sourceTree = ""; }; 2D84350425FF858100EECE90 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; 2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 2DAC9E37262FC2320062E1A6 /* SuggestionAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountViewController.swift; sourceTree = ""; }; @@ -779,6 +776,7 @@ D8318A872A4468D300C0FB73 /* NotificationSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsViewController.swift; sourceTree = ""; }; D8318A892A4468DC00C0FB73 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; D8363B1529469CE200A74079 /* OnboardingNextView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = OnboardingNextView.swift; sourceTree = ""; tabWidth = 4; }; + D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusPill.swift; sourceTree = ""; }; D84C099D2B0F9E33009E685E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D84C099F2B0F9E41009E685E /* Setup.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Setup.md; sourceTree = ""; }; D84C09A02B0F9E41009E685E /* How-it-works.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "How-it-works.md"; sourceTree = ""; }; @@ -1501,13 +1499,13 @@ 2D38F1D325CD463600561493 /* HomeTimeline */ = { isa = PBXGroup; children = ( - DB1F239626117C360057430E /* View */, 2D38F1D425CD465300561493 /* HomeTimelineViewController.swift */, DB697DD8278F4CED004EF2F7 /* HomeTimelineViewController+DataSourceProvider.swift */, 2D38F1E425CD46C100561493 /* HomeTimelineViewModel.swift */, 2D5A3D2725CF8BC9002347D6 /* HomeTimelineViewModel+Diffable.swift */, 2D38F1EA25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift */, 2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */, + D84738D32BBD9ABE00ECD52B /* TimelineStatusPill.swift */, ); path = HomeTimeline; sourceTree = ""; @@ -1983,15 +1981,6 @@ path = TableView; sourceTree = ""; }; - DB1F239626117C360057430E /* View */ = { - isa = PBXGroup; - children = ( - 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */, - 2D8434FA25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift */, - ); - path = View; - sourceTree = ""; - }; DB3D0FF725BAA68500EAA174 /* Supporting Files */ = { isa = PBXGroup; children = ( @@ -3484,7 +3473,6 @@ DB0617FF27855D6C0030EE79 /* MastodonServerRulesViewModel+Diffable.swift in Sources */, DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */, DB0FCB982797F6BF006C02E2 /* UserTableViewCell+ViewModel.swift in Sources */, - 2D8434FB25FF46B300EECE90 /* HomeTimelineNavigationBarTitleView.swift in Sources */, DB697DD6278F4C29004EF2F7 /* DataSourceProvider.swift in Sources */, DB0FCB8E2796C0B7006C02E2 /* TrendCollectionViewCell.swift in Sources */, 0F1E2D0B2615C39400C38565 /* DoubleTitleLabelNavigationBarTitleView.swift in Sources */, @@ -3494,7 +3482,6 @@ 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 */, DB938F0F2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift in Sources */, DB6180F226391CF40018D199 /* MediaPreviewImageViewModel.swift in Sources */, @@ -3763,6 +3750,7 @@ DB6B74FC272FF55800C70B6E /* UserSection.swift in Sources */, DB0FCB862796BDA1006C02E2 /* SearchSection.swift in Sources */, DB1D61CF26F1B33600DA8662 /* WelcomeViewModel.swift in Sources */, + D84738D42BBD9ABE00ECD52B /* TimelineStatusPill.swift in Sources */, D8B5E4F42A4ED0240008970C /* NotificationSettingsViewModel.swift in Sources */, DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */, DB4F0966269ED52200D62E92 /* SearchResultViewModel.swift in Sources */, diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 100b41300..da8a1c747 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -44,8 +44,6 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media return emptyView }() - let titleView = HomeTimelineNavigationBarTitleView() - lazy var timelineSelectorButton = { let button = UIButton(type: .custom) button.setAttributedTitle( @@ -101,6 +99,8 @@ final class HomeTimelineViewController: UIViewController, NeedsDependency, Media }() let refreshControl = RefreshControl() + let timelinePill = TimelineStatusPill() + private func generateTimeSelectorMenu() -> UIMenu { let showFollowingAction = UIAction(title: L10n.Scene.HomeTimeline.TimelineMenu.following, image: .init(systemName: "house")) { [weak self] _ in @@ -170,33 +170,6 @@ extension HomeTimelineViewController { settingBarButtonItem.action = #selector(HomeTimelineViewController.settingBarButtonItemPressed(_:)) self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: timelineSelectorButton) - -// navigationItem.titleView = titleView -// titleView.delegate = self - - viewModel?.homeTimelineNavigationBarTitleViewModel.state - .removeDuplicates() - .receive(on: DispatchQueue.main) - .sink { [weak self] state in - guard let self = self else { return } - self.titleView.configure(state: state) - } - .store(in: &disposeBag) - - viewModel?.homeTimelineNavigationBarTitleViewModel.state - .removeDuplicates() - .filter { $0 == .publishedButton } - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - guard let self = self else { return } - guard UserDefaults.shared.lastVersionPromptedForReview == nil else { return } - guard UserDefaults.shared.processCompletedCount > 3 else { return } - guard let windowScene = self.view.window?.windowScene else { return } - let version = UIApplication.appVersion() - UserDefaults.shared.lastVersionPromptedForReview = version - SKStoreReviewController.requestReview(in: windowScene) - } - .store(in: &disposeBag) tableView.refreshControl = refreshControl refreshControl.addTarget(self, action: #selector(HomeTimelineViewController.refreshControlValueChanged(_:)), for: .valueChanged) @@ -326,6 +299,15 @@ extension HomeTimelineViewController { } .store(in: &disposeBag) +// timelinePill.translatesAutoresizingMaskIntoConstraints = false +// view.addSubview(timelinePill) +// +// // has to up updated and animated +// timelinePill.update(with: .postSent) +// NSLayoutConstraint.activate([ +// timelinePill.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8), +// timelinePill.centerXAnchor.constraint(equalTo: view.centerXAnchor), +// ]) } override func viewWillAppear(_ animated: Bool) { @@ -480,15 +462,6 @@ extension HomeTimelineViewController { } // MARK: - UIScrollViewDelegate extension HomeTimelineViewController { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - switch scrollView { - case tableView: - viewModel?.homeTimelineNavigationBarTitleViewModel.handleScrollViewDidScroll(scrollView) - default: - break - } - } - func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { switch scrollView { case tableView: @@ -644,37 +617,6 @@ extension HomeTimelineViewController: ScrollViewContainer { // MARK: - StatusTableViewCellDelegate extension HomeTimelineViewController: StatusTableViewCellDelegate { } -// MARK: - HomeTimelineNavigationBarTitleViewDelegate -extension HomeTimelineViewController: HomeTimelineNavigationBarTitleViewDelegate { - func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, logoButtonDidPressed sender: UIButton) { - if shouldRestoreScrollPosition() { - restorePositionWhenScrollToTop() - } else { - savePositionBeforeScrollToTop() - scrollToTop(animated: true) - } - } - - func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton) { - switch titleView.state { - case .newPostButton: - guard let diffableDataSource = viewModel?.diffableDataSource else { return } - let indexPath = IndexPath(row: 0, section: 0) - guard diffableDataSource.itemIdentifier(for: indexPath) != nil else { return } - - savePositionBeforeScrollToTop() - tableView.scrollToRow(at: indexPath, at: .top, animated: true) - case .offlineButton: - // TODO: retry - break - case .publishedButton: - break - default: - break - } - } -} - extension HomeTimelineViewController { override var keyCommands: [UIKeyCommand]? { return navigationKeyCommands + statusNavigationKeyCommands diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift index aca9f5f45..97093660d 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadLatestState.swift @@ -129,7 +129,6 @@ extension HomeTimelineViewModel.LoadLatestState { } await enter(state: Idle.self) - viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(.finished) // stop refresher if no new statuses let statuses = response.value @@ -137,11 +136,7 @@ extension HomeTimelineViewModel.LoadLatestState { if newStatuses.isEmpty { viewModel.didLoadLatest.send() - } else { - if !latestStatusIDs.isEmpty { - viewModel.homeTimelineNavigationBarTitleViewModel.newPostsIncoming() - } - + } else { viewModel.dataController.records = { var newRecords: [MastodonFeed] = newStatuses.map { MastodonFeed.fromStatus(.fromEntity($0), kind: .home) @@ -168,7 +163,6 @@ extension HomeTimelineViewModel.LoadLatestState { } catch { await enter(state: Idle.self) viewModel.didLoadLatest.send() - viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(.failure(error)) } } // end Task } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift index 668095b24..4436c9b79 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+LoadOldestState.swift @@ -83,12 +83,9 @@ extension HomeTimelineViewModel.LoadOldestState { } else { await self.enter(state: Idle.self) } - - viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(.finished) - + } catch { await self.enter(state: Fail.self) - viewModel.homeTimelineNavigationBarTitleViewModel.receiveLoadingStateCompletion(.failure(error)) } } // end Task } diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift index a67d44f8d..e934fe1d0 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel.swift @@ -25,7 +25,6 @@ final class HomeTimelineViewModel: NSObject { let context: AppContext let authContext: AuthContext let dataController: FeedDataController - let homeTimelineNavigationBarTitleViewModel: HomeTimelineNavigationBarTitleViewModel let listBatchFetchViewModel = ListBatchFetchViewModel() var presentedSuggestions = false @@ -81,7 +80,6 @@ final class HomeTimelineViewModel: NSObject { self.context = context self.authContext = authContext self.dataController = FeedDataController(context: context, authContext: authContext) - self.homeTimelineNavigationBarTitleViewModel = HomeTimelineNavigationBarTitleViewModel(context: context) super.init() self.dataController.records = (try? FileManager.default.cachedHomeTimeline(for: authContext.mastodonAuthenticationBox).map { MastodonFeed.fromStatus($0, kind: .home) @@ -92,16 +90,6 @@ final class HomeTimelineViewModel: NSObject { self?.loadLatestStateMachine.enter(LoadLatestState.Loading.self) } .store(in: &disposeBag) - - // refresh after publish post - homeTimelineNavigationBarTitleViewModel.isPublished - .delay(for: 2, scheduler: DispatchQueue.main) - .sink { [weak self] isPublished in - guard let self = self else { return } - self.homeTimelineNeedRefresh.send() - } - .store(in: &disposeBag) - self.dataController.$records .removeDuplicates() .receive(on: DispatchQueue.main) diff --git a/Mastodon/Scene/HomeTimeline/TimelineStatusPill.swift b/Mastodon/Scene/HomeTimeline/TimelineStatusPill.swift new file mode 100644 index 000000000..e24c512f9 --- /dev/null +++ b/Mastodon/Scene/HomeTimeline/TimelineStatusPill.swift @@ -0,0 +1,73 @@ +// Copyright © 2024 Mastodon gGmbH. All rights reserved. + +import UIKit +import MastodonAsset + +class TimelineStatusPill: UIButton { + + func update(with state: State) { + var configuration = UIButton.Configuration.filled() + + + configuration.attributedTitle = AttributedString( + state.title, attributes: AttributeContainer( + [ + .font: UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 15, weight: .bold)), + .foregroundColor: UIColor.white + ] + )) + + let image = state.image? + .withConfiguration(UIImage.SymbolConfiguration(paletteColors: [.white])) + .withConfiguration(UIImage.SymbolConfiguration(textStyle: .subheadline)) + .withConfiguration(UIImage.SymbolConfiguration(pointSize: 12, weight: .bold, scale: .medium)) + + configuration.image = image + configuration.imagePadding = 8 + configuration.baseBackgroundColor = state.backgroundColor + configuration.cornerStyle = .capsule + + self.configuration = configuration + } + + public enum State { + case newPosts + case postSent + case offline + + var image: UIImage? { + switch self { + case .newPosts: + return UIImage(systemName: "chevron.up") + case .postSent: + return UIImage(systemName: "checkmark") + case .offline: + return UIImage(systemName: "bolt.horizontal.fill") + } + } + + var backgroundColor: UIColor { + switch self { + case .newPosts: + return Asset.Colors.Brand.blurple.color + case .postSent: + return .systemGreen + case .offline: + return .systemGray + } + } + + var title: String { + //TODO: Localization + switch self { + case .newPosts: + return "New Posts" + case .postSent: + return "Post Sent" + case .offline: + return "Offline" + } + } + } + +} diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift deleted file mode 100644 index 28475c2d7..000000000 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleView.swift +++ /dev/null @@ -1,221 +0,0 @@ -// -// HomeTimelineNavigationBarTitleView.swift -// Mastodon -// -// Created by sxiaojian on 2021/3/15. -// - -import UIKit -import MastodonUI -import MastodonAsset -import MastodonLocalization - -protocol HomeTimelineNavigationBarTitleViewDelegate: AnyObject { - func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, logoButtonDidPressed sender: UIButton) - func homeTimelineNavigationBarTitleView(_ titleView: HomeTimelineNavigationBarTitleView, buttonDidPressed sender: UIButton) -} - -final class HomeTimelineNavigationBarTitleView: UIView { - - let containerView = UIStackView() - - let logoButton = HighlightDimmableButton() - let button = RoundedEdgesButton() - let label = UILabel() - - // input - private var blockingState: HomeTimelineNavigationBarTitleViewModel.State? - weak var delegate: HomeTimelineNavigationBarTitleViewDelegate? - - // output - private(set) var state: HomeTimelineNavigationBarTitleViewModel.State = .logo - - override init(frame: CGRect) { - super.init(frame: frame) - _init() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - _init() - } - -} - -extension HomeTimelineNavigationBarTitleView { - private func _init() { - containerView.translatesAutoresizingMaskIntoConstraints = false - addSubview(containerView) - containerView.pinToParent() - - containerView.addArrangedSubview(logoButton) - button.translatesAutoresizingMaskIntoConstraints = false - containerView.addArrangedSubview(button) - NSLayoutConstraint.activate([ - button.heightAnchor.constraint(equalToConstant: 24).priority(.defaultHigh) - ]) - containerView.addArrangedSubview(label) - - configure(state: .logo) - logoButton.addTarget(self, action: #selector(HomeTimelineNavigationBarTitleView.logoButtonDidPressed(_:)), for: .touchUpInside) - button.addTarget(self, action: #selector(HomeTimelineNavigationBarTitleView.buttonDidPressed(_:)), for: .touchUpInside) - - logoButton.accessibilityIdentifier = "TitleButton" - logoButton.accessibilityTraits = [.header, .button] - button.accessibilityIdentifier = "TitleButton" - } -} - -extension HomeTimelineNavigationBarTitleView { - @objc private func logoButtonDidPressed(_ sender: UIButton) { - delegate?.homeTimelineNavigationBarTitleView(self, logoButtonDidPressed: sender) - } - - @objc private func buttonDidPressed(_ sender: UIButton) { - delegate?.homeTimelineNavigationBarTitleView(self, buttonDidPressed: sender) - } -} - -extension HomeTimelineNavigationBarTitleView { - - func resetContainer() { - logoButton.isHidden = true - button.isHidden = true - label.isHidden = true - } - - func configure(state: HomeTimelineNavigationBarTitleViewModel.State) { - self.state = state - - // check state block or not - guard blockingState == nil else { - return - } - - resetContainer() - - switch state { - case .logo: - logoButton.tintColor = Asset.Colors.Label.primary.color - logoButton.setImage(Asset.Asset.mastodonTextLogo.image.withRenderingMode(.alwaysTemplate), for: .normal) - logoButton.contentMode = .center - logoButton.isHidden = false - logoButton.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.Accessibility.logoLabel // TODO :i18n - logoButton.accessibilityHint = L10n.Scene.HomeTimeline.NavigationBarState.Accessibility.logoHint - case .newPostButton: - configureButton( - title: L10n.Scene.HomeTimeline.NavigationBarState.newPosts, - textColor: .white, - backgroundColor: Asset.Colors.Brand.blurple.color - ) - button.isHidden = false - button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.newPosts - case .offlineButton: - configureButton( - title: L10n.Scene.HomeTimeline.NavigationBarState.offline, - textColor: .white, - backgroundColor: Asset.Colors.danger.color - ) - button.isHidden = false - button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.offline - case .publishingPostLabel: - label.font = .systemFont(ofSize: 17, weight: .semibold) - label.textColor = Asset.Colors.Label.primary.color - label.text = L10n.Scene.HomeTimeline.NavigationBarState.publishing - label.textAlignment = .center - label.isHidden = false - button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.publishing - case .publishedButton: - blockingState = state - configureButton( - title: L10n.Scene.HomeTimeline.NavigationBarState.published, - textColor: .white, - backgroundColor: Asset.Colors.successGreen.color - ) - button.isHidden = false - button.accessibilityLabel = L10n.Scene.HomeTimeline.NavigationBarState.published - - let presentDuration: TimeInterval = 0.33 - let scaleAnimator = UIViewPropertyAnimator(duration: presentDuration, timingParameters: UISpringTimingParameters()) - button.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) - scaleAnimator.addAnimations { - self.button.transform = .identity - } - let alphaAnimator = UIViewPropertyAnimator(duration: presentDuration, curve: .easeInOut) - button.alpha = 0.3 - alphaAnimator.addAnimations { - self.button.alpha = 1 - } - scaleAnimator.startAnimation() - alphaAnimator.startAnimation() - - let dismissDuration: TimeInterval = 3 - let dissolveAnimator = UIViewPropertyAnimator(duration: dismissDuration, curve: .easeInOut) - dissolveAnimator.addAnimations({ - self.button.alpha = 0 - }, delayFactor: 0.9) // at 2.7s - dissolveAnimator.addCompletion { _ in - self.blockingState = nil - self.configure(state: self.state) - self.button.alpha = 1 - } - dissolveAnimator.startAnimation() - } - } - - private func configureButton(title: String, textColor: UIColor, backgroundColor: UIColor) { - button.setBackgroundImage(.placeholder(color: backgroundColor), for: .normal) - button.setBackgroundImage(.placeholder(color: backgroundColor.withAlphaComponent(0.5)), for: .highlighted) - button.setTitleColor(textColor, for: .normal) - button.setTitleColor(textColor.withAlphaComponent(0.5), for: .highlighted) - button.setTitle(title, for: .normal) - button.contentEdgeInsets = UIEdgeInsets(top: 1, left: 16, bottom: 1, right: 16) - button.titleLabel?.font = .systemFont(ofSize: 15, weight: .bold) - } - -} - -#if canImport(SwiftUI) && DEBUG -import SwiftUI - -struct HomeTimelineNavigationBarTitleView_Previews: PreviewProvider { - - static var previews: some View { - Group { - UIViewPreview(width: 375) { - let titleView = HomeTimelineNavigationBarTitleView() - titleView.configure(state: .logo) - return titleView - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 150) { - let titleView = HomeTimelineNavigationBarTitleView() - titleView.configure(state: .newPostButton) - return titleView - } - .previewLayout(.fixed(width: 150, height: 24)) - UIViewPreview(width: 120) { - let titleView = HomeTimelineNavigationBarTitleView() - titleView.configure(state: .offlineButton) - return titleView - } - .previewLayout(.fixed(width: 120, height: 24)) - UIViewPreview(width: 375) { - let titleView = HomeTimelineNavigationBarTitleView() - titleView.configure(state: .publishingPostLabel) - return titleView - } - .previewLayout(.fixed(width: 375, height: 44)) - UIViewPreview(width: 120) { - let titleView = HomeTimelineNavigationBarTitleView() - titleView.configure(state: .publishedButton) - return titleView - } - .previewLayout(.fixed(width: 120, height: 24)) - } - } - -} - -#endif - diff --git a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleViewModel.swift b/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleViewModel.swift deleted file mode 100644 index 9f736619b..000000000 --- a/Mastodon/Scene/HomeTimeline/View/HomeTimelineNavigationBarTitleViewModel.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// HomeTimelineNavigationBarTitleViewModel.swift -// Mastodon -// -// Created by sxiaojian on 2021/3/15. -// - -import Combine -import Foundation -import UIKit -import MastodonCore - -final class HomeTimelineNavigationBarTitleViewModel { - - static let offlineCounterLimit = 3 - - var disposeBag = Set() - private(set) var publishingProgressSubscription: AnyCancellable? - - // input - let context: AppContext - var networkErrorCount = CurrentValueSubject(0) - var networkErrorPublisher = PassthroughSubject() - - // output - let state = CurrentValueSubject(.logo) - let hasNewPosts = CurrentValueSubject(false) - let isOffline = CurrentValueSubject(false) - let isPublishingPost = CurrentValueSubject(false) - let isPublished = CurrentValueSubject(false) - let publishingProgress = PassthroughSubject() - - init(context: AppContext) { - self.context = context - - networkErrorPublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - guard let self = self else { return } - self.networkErrorCount.value = self.networkErrorCount.value + 1 - } - .store(in: &disposeBag) - - networkErrorCount - .receive(on: DispatchQueue.main) - .map { count in - return count >= HomeTimelineNavigationBarTitleViewModel.offlineCounterLimit - } - .assign(to: \.value, on: isOffline) - .store(in: &disposeBag) - - Publishers.CombineLatest( - context.publisherService.$statusPublishers, - context.publisherService.statusPublishResult.prepend(.failure(AppError.badRequest)) - ) - .receive(on: DispatchQueue.main) - .sink { [weak self] statusPublishers, publishResult in - guard let self = self else { return } - - if statusPublishers.isEmpty { - self.isPublishingPost.value = false - self.isPublished.value = false - } else { - self.isPublishingPost.value = true - switch publishResult { - case .success: - self.isPublished.value = true - case .failure: - self.isPublished.value = false - } - } - } - .store(in: &disposeBag) - - Publishers.CombineLatest4( - hasNewPosts.eraseToAnyPublisher(), - isOffline.eraseToAnyPublisher(), - isPublishingPost.eraseToAnyPublisher(), - isPublished.eraseToAnyPublisher() - ) - .map { hasNewPosts, isOffline, isPublishingPost, isPublished -> State in - guard !isPublished else { return .publishedButton } - guard !isPublishingPost else { return .publishingPostLabel } - guard !isOffline else { return .offlineButton } - guard !hasNewPosts else { return .newPostButton } - return .logo - } - .receive(on: DispatchQueue.main) - .assign(to: \.value, on: state) - .store(in: &disposeBag) - -// state -// .removeDuplicates() -// .receive(on: DispatchQueue.main) -// .sink { [weak self] state in -// guard let self = self else { return } -// switch state { -// case .publishingPostLabel: -// self.setupPublishingProgress() -// default: -// self.suspendPublishingProgress() -// } -// } -// .store(in: &disposeBag) - } -} - -extension HomeTimelineNavigationBarTitleViewModel { - // state order by priority from low to high - enum State: String { - case logo - case newPostButton - case offlineButton - case publishingPostLabel - case publishedButton - } -} - -// MARK: - New post state -extension HomeTimelineNavigationBarTitleViewModel { - - func newPostsIncoming() { - hasNewPosts.value = true - } - - private func resetNewPostState() { - hasNewPosts.value = false - } - -} - -// MARK: - Offline state -extension HomeTimelineNavigationBarTitleViewModel { - - func resetOfflineCounterListener() { - networkErrorCount.value = 0 - } - - func receiveLoadingStateCompletion(_ completion: Subscribers.Completion) { - switch completion { - case .failure: - networkErrorPublisher.send() - case .finished: - resetOfflineCounterListener() - } - } - - func handleScrollViewDidScroll(_ scrollView: UIScrollView) { - guard hasNewPosts.value else { return } - - let contentOffsetY = scrollView.contentOffset.y - let isScrollToTop = contentOffsetY < -scrollView.contentInset.top - guard isScrollToTop else { return } - resetNewPostState() - } - -} - -// MARK: Publish post state -//extension HomeTimelineNavigationBarTitleViewModel { -// -// func setupPublishingProgress() { -// let progressUpdatePublisher = Timer.publish(every: 0.016, on: .main, in: .common) // ~ 60FPS -// .autoconnect() -// .share() -// .eraseToAnyPublisher() -// -// publishingProgressSubscription = progressUpdatePublisher -// .map { _ in Float(0) } -// .scan(0.0) { progress, _ -> Float in -// return 0.95 * progress + 0.05 // progress + 0.05 * (1.0 - progress). ~ 1 sec to 0.95 (under 60FPS) -// } -// .subscribe(publishingProgress) -// } -// -// func suspendPublishingProgress() { -// publishingProgressSubscription = nil -// publishingProgress.send(0) -// } -// -//} -