From 187bc423730992538bde0c9b9ada155d151d4a53 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Tue, 2 Mar 2021 22:55:35 -0800 Subject: [PATCH] Official account and website in about --- Extensions/UIVIewController+Extensions.swift | 23 +++++++ Localizations/Localizable.strings | 3 + Metatext.xcodeproj/project.pbxproj | 6 ++ View Controllers/ExploreViewController.swift | 63 ++++++++++++++----- .../MainNavigationViewController.swift | 63 ++++++++----------- .../NotificationsViewController.swift | 4 +- View Controllers/TableViewController.swift | 30 +++------ .../TimelinesViewController.swift | 6 ++ .../View Models/NavigationViewModel.swift | 12 ++++ Views/NavigationHandling.swift | 8 +++ Views/SwiftUI/AboutView.swift | 24 ++++++- Views/SwiftUI/SecondaryNavigationView.swift | 2 +- 12 files changed, 164 insertions(+), 80 deletions(-) create mode 100644 Views/NavigationHandling.swift diff --git a/Extensions/UIVIewController+Extensions.swift b/Extensions/UIVIewController+Extensions.swift index 1804419..f4786a4 100644 --- a/Extensions/UIVIewController+Extensions.swift +++ b/Extensions/UIVIewController+Extensions.swift @@ -1,5 +1,6 @@ // Copyright © 2020 Metabolist. All rights reserved. +import SafariServices import UIKit import ViewModels @@ -16,4 +17,26 @@ extension UIViewController { present(alertController, animated: true) } + + #if !IS_SHARE_EXTENSION + func open(url: URL, identityContext: IdentityContext) { + func openWithRegardToBrowserSetting(url: URL) { + if identityContext.appPreferences.openLinksInDefaultBrowser || !url.isHTTPURL { + UIApplication.shared.open(url) + } else { + present(SFSafariViewController(url: url), animated: true) + } + } + + if identityContext.appPreferences.useUniversalLinks { + UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { success in + if !success { + openWithRegardToBrowserSetting(url: url) + } + } + } else { + openWithRegardToBrowserSetting(url: url) + } + } + #endif } diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 946e79f..bfe7396 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -2,6 +2,9 @@ "about" = "About"; "about.acknowledgments" = "Acknowledgments"; +"about.made-by-metabolist" = "Made by Metabolist"; +"about.official-account" = "Official Account"; +"about.website" = "Website"; "accessibility.activate-link-%@" = "Activate link: %@"; "accessibility.copy-text" = "Copy text"; "account.%@-followers" = "%@'s Followers"; diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index 4c64b76..5f39b92 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0030981250C6C8500EACB32 /* URL+Extensions.swift */; }; D005A1D825EF189A008B2E63 /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1D725EF189A008B2E63 /* ReportViewController.swift */; }; D005A1E625EF3D11008B2E63 /* ReportHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */; }; + D005A20025EF574F008B2E63 /* NavigationHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1FF25EF574F008B2E63 /* NavigationHandling.swift */; }; + D005A20125EF574F008B2E63 /* NavigationHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1FF25EF574F008B2E63 /* NavigationHandling.swift */; }; D00702292555E51200F38136 /* ConversationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702282555E51200F38136 /* ConversationTableViewCell.swift */; }; D00702312555F4AE00F38136 /* ConversationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702302555F4AE00F38136 /* ConversationView.swift */; }; D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */; }; @@ -262,6 +264,7 @@ D0030981250C6C8500EACB32 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; D005A1D725EF189A008B2E63 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = ""; }; D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportHeaderView.swift; sourceTree = ""; }; + D005A1FF25EF574F008B2E63 /* NavigationHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationHandling.swift; sourceTree = ""; }; D00702282555E51200F38136 /* ConversationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTableViewCell.swift; sourceTree = ""; }; D00702302555F4AE00F38136 /* ConversationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationView.swift; sourceTree = ""; }; D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContentConfiguration.swift; sourceTree = ""; }; @@ -735,6 +738,7 @@ D0C7D42024F76169001EBDBB /* Views */ = { isa = PBXGroup; children = ( + D005A1FF25EF574F008B2E63 /* NavigationHandling.swift */, D021A66425C3E170008A0C0D /* SwiftUI */, D021A66325C3E167008A0C0D /* UIKit */, D0EA59472522B8B600804347 /* ViewConstants.swift */, @@ -1084,6 +1088,7 @@ D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */, D021A60A25C36B32008A0C0D /* IdentityTableViewCell.swift in Sources */, D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */, + D005A20025EF574F008B2E63 /* NavigationHandling.swift in Sources */, D0F4362D25C10B9600E4F896 /* AddIdentityViewController.swift in Sources */, D035D8F925E4338D00E597C9 /* ImageDiskCache.swift in Sources */, D0625E59250F092900502611 /* StatusTableViewCell.swift in Sources */, @@ -1203,6 +1208,7 @@ D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */, D021A67B25C3E32A008A0C0D /* PlayerView.swift in Sources */, D052DBDD25EAF01800FFB628 /* URL+Extensions.swift in Sources */, + D005A20125EF574F008B2E63 /* NavigationHandling.swift in Sources */, D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */, D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */, D00CB23D25C9305D008EF267 /* NSMutableAttributedString+Extensions.swift in Sources */, diff --git a/View Controllers/ExploreViewController.swift b/View Controllers/ExploreViewController.swift index 4adcba6..834bea3 100644 --- a/View Controllers/ExploreViewController.swift +++ b/View Controllers/ExploreViewController.swift @@ -5,6 +5,7 @@ import UIKit import ViewModels final class ExploreViewController: UICollectionViewController { + private let webfingerIndicatorView = WebfingerIndicatorView() private let viewModel: ExploreViewModel private let rootViewModel: RootViewModel private var cancellables = Set() @@ -30,6 +31,7 @@ final class ExploreViewController: UICollectionViewController { fatalError("init(coder:) has not been implemented") } + // swiftlint:disable:next function_body_length override func viewDidLoad() { super.viewDidLoad() @@ -60,6 +62,9 @@ final class ExploreViewController: UICollectionViewController { searchController.searchBar.keyboardType = .twitter navigationItem.searchController = searchController + view.addSubview(webfingerIndicatorView) + webfingerIndicatorView.translatesAutoresizingMaskIntoConstraints = false + viewModel.identityContext.$appPreferences.sink { appPreferences in searchController.searchBar.scopeButtonTitles = SearchScope.allCases.map { $0.title(statusWord: appPreferences.statusWord) @@ -67,6 +72,11 @@ final class ExploreViewController: UICollectionViewController { } .store(in: &cancellables) + NSLayoutConstraint.activate([ + webfingerIndicatorView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), + webfingerIndicatorView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor) + ]) + viewModel.events.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables) viewModel.$loading.sink { [weak self] in @@ -126,6 +136,43 @@ extension ExploreViewController: ScrollableToTop { } } +extension ExploreViewController: NavigationHandling { + func handle(navigation: Navigation) { + switch navigation { + case let .collection(collectionService): + let vc = TableViewController( + viewModel: CollectionItemsViewModel( + collectionService: collectionService, + identityContext: viewModel.identityContext), + rootViewModel: rootViewModel, + parentNavigationController: nil) + + show(vc, sender: self) + webfingerIndicatorView.stopAnimating() + case let .profile(profileService): + let vc = ProfileViewController( + viewModel: ProfileViewModel( + profileService: profileService, + identityContext: viewModel.identityContext), + rootViewModel: rootViewModel, + identityContext: viewModel.identityContext, + parentNavigationController: nil) + + show(vc, sender: self) + webfingerIndicatorView.stopAnimating() + case let .url(url): + open(url: url, identityContext: viewModel.identityContext) + webfingerIndicatorView.stopAnimating() + case .webfingerStart: + webfingerIndicatorView.startAnimating() + case .webfingerEnd: + webfingerIndicatorView.stopAnimating() + default: + break + } + } +} + private extension ExploreViewController { static let bottomInset: CGFloat = .newStatusButtonDimension + .defaultSpacing * 4 @@ -152,20 +199,4 @@ private extension ExploreViewController { handle(navigation: navigation) } } - - func handle(navigation: Navigation) { - switch navigation { - case let .collection(collectionService): - let vc = TableViewController( - viewModel: CollectionItemsViewModel( - collectionService: collectionService, - identityContext: viewModel.identityContext), - rootViewModel: rootViewModel, - parentNavigationController: nil) - - show(vc, sender: self) - default: - break - } - } } diff --git a/View Controllers/MainNavigationViewController.swift b/View Controllers/MainNavigationViewController.swift index c0a863c..d37cffb 100644 --- a/View Controllers/MainNavigationViewController.swift +++ b/View Controllers/MainNavigationViewController.swift @@ -50,6 +50,7 @@ final class MainNavigationViewController: UITabBarController { .store(in: &cancellables) viewModel.navigations + .receive(on: DispatchQueue.main) .sink { [weak self] in self?.handle(navigation: $0) } .store(in: &cancellables) @@ -78,6 +79,30 @@ extension MainNavigationViewController: UITabBarControllerDelegate { } } +extension MainNavigationViewController: NavigationHandling { + func handle(navigation: Navigation) { + switch navigation { + case .notification: + let index = NavigationViewModel.Tab.notifications.rawValue + + guard let viewControllers = viewControllers, + viewControllers.count > index, + let notificationsNavigationController = viewControllers[index] as? UINavigationController, + let notificationsViewController = + notificationsNavigationController.viewControllers.first as? NotificationsViewController + else { break } + + selectedIndex = index + notificationsNavigationController.popToRootViewController(animated: false) + notificationsViewController.handle(navigation: navigation) + default: + ((selectedViewController as? UINavigationController)? + .topViewController as? NavigationHandling)? + .handle(navigation: navigation) + } + } +} + private extension MainNavigationViewController { static let secondaryNavigationViewTag = UUID().hashValue static let newStatusViewTag = UUID().hashValue @@ -211,42 +236,4 @@ private extension MainNavigationViewController { dismiss(animated: true) } } - - func handle(navigation: Navigation) { - switch navigation { - case let .collection(collectionService): - let vc = TableViewController( - viewModel: CollectionItemsViewModel( - collectionService: collectionService, - identityContext: viewModel.identityContext), - rootViewModel: rootViewModel) - - selectedViewController?.show(vc, sender: self) - case let .profile(profileService): - let vc = ProfileViewController( - viewModel: ProfileViewModel( - profileService: profileService, - identityContext: viewModel.identityContext), - rootViewModel: rootViewModel, - identityContext: viewModel.identityContext, - parentNavigationController: nil) - - selectedViewController?.show(vc, sender: self) - case .notification: - let index = NavigationViewModel.Tab.notifications.rawValue - - guard let viewControllers = viewControllers, - viewControllers.count > index, - let notificationsNavigationController = viewControllers[index] as? UINavigationController, - let notificationsViewController = - notificationsNavigationController.viewControllers.first as? NotificationsViewController - else { break } - - selectedIndex = index - notificationsNavigationController.popToRootViewController(animated: false) - notificationsViewController.handle(navigation: navigation) - default: - break - } - } } diff --git a/View Controllers/NotificationsViewController.swift b/View Controllers/NotificationsViewController.swift index 05619ca..adee07b 100644 --- a/View Controllers/NotificationsViewController.swift +++ b/View Controllers/NotificationsViewController.swift @@ -71,7 +71,7 @@ final class NotificationsViewController: UIPageViewController { } } -extension NotificationsViewController { +extension NotificationsViewController: NavigationHandling { func handle(navigation: Navigation) { switch navigation { case .notification: @@ -81,7 +81,7 @@ extension NotificationsViewController { setViewControllers([firstViewController], direction: .reverse, animated: false) firstViewController.handle(navigation: navigation) default: - break + (viewControllers?.first as? TableViewController)?.handle(navigation: navigation) } } } diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index b1b368a..9615a70 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -3,7 +3,6 @@ import AVKit import Combine import Mastodon -import SafariServices import SDWebImage import SwiftUI import ViewModels @@ -257,7 +256,9 @@ extension TableViewController { } } } +} +extension TableViewController: NavigationHandling { func handle(navigation: Navigation) { switch navigation { case let .collection(collectionService): @@ -273,6 +274,8 @@ extension TableViewController { } else { show(vc, sender: self) } + + webfingerIndicatorView.stopAnimating() case let .profile(profileService): let vc = ProfileViewController( viewModel: ProfileViewModel( @@ -287,10 +290,13 @@ extension TableViewController { } else { show(vc, sender: self) } + + webfingerIndicatorView.stopAnimating() case let .notification(notificationService): navigate(toNotification: notificationService.notification) case let .url(url): - open(url: url) + open(url: url, identityContext: viewModel.identityContext) + webfingerIndicatorView.stopAnimating() case .searchScope: break case .webfingerStart: @@ -562,26 +568,6 @@ private extension TableViewController { viewModel.select(indexPath: indexPath) } - func open(url: URL) { - func openWithRegardToBrowserSetting(url: URL) { - if viewModel.identityContext.appPreferences.openLinksInDefaultBrowser || !url.isHTTPURL { - UIApplication.shared.open(url) - } else { - present(SFSafariViewController(url: url), animated: true) - } - } - - if viewModel.identityContext.appPreferences.useUniversalLinks { - UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { success in - if !success { - openWithRegardToBrowserSetting(url: url) - } - } - } else { - openWithRegardToBrowserSetting(url: url) - } - } - func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) { switch attachmentViewModel.attachment.type { case .audio, .video: diff --git a/View Controllers/TimelinesViewController.swift b/View Controllers/TimelinesViewController.swift index 32a16c4..9e5c788 100644 --- a/View Controllers/TimelinesViewController.swift +++ b/View Controllers/TimelinesViewController.swift @@ -113,3 +113,9 @@ extension TimelinesViewController: ScrollableToTop { (viewControllers?.first as? TableViewController)?.scrollToTop(animated: animated) } } + +extension TimelinesViewController: NavigationHandling { + func handle(navigation: Navigation) { + (viewControllers?.first as? TableViewController)?.handle(navigation: navigation) + } +} diff --git a/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift b/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift index edd755f..6ee1160 100644 --- a/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift @@ -130,6 +130,14 @@ public extension NavigationViewModel { titleComponents: ["preferences.blocked-users"]))) } + func navigateToOfficialAccount() { + presentingSecondaryNavigation = false + presentedNewStatusViewModel = nil + identityContext.service.navigationService.item(url: Self.officialAccountURL) + .sink { [weak self] in self?.navigationsSubject.send($0) } + .store(in: &cancellables) + } + func navigate(pushNotification: PushNotification) { switch pushNotification.notificationType { case .followRequest: @@ -184,3 +192,7 @@ public extension NavigationViewModel { return conversationsViewModel } } + +private extension NavigationViewModel { + static let officialAccountURL = URL(string: "https://mastodon.social/@metabolist")! +} diff --git a/Views/NavigationHandling.swift b/Views/NavigationHandling.swift new file mode 100644 index 0000000..1cd2890 --- /dev/null +++ b/Views/NavigationHandling.swift @@ -0,0 +1,8 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation +import ViewModels + +protocol NavigationHandling { + func handle(navigation: Navigation) +} diff --git a/Views/SwiftUI/AboutView.swift b/Views/SwiftUI/AboutView.swift index 1857981..ff08abf 100644 --- a/Views/SwiftUI/AboutView.swift +++ b/Views/SwiftUI/AboutView.swift @@ -1,8 +1,11 @@ // Copyright © 2021 Metabolist. All rights reserved. import SwiftUI +import ViewModels struct AboutView: View { + @StateObject var viewModel: NavigationViewModel + var body: some View { Form { Section { @@ -14,6 +17,24 @@ struct AboutView: View { .padding() } .frame(maxWidth: .infinity, alignment: .center) + Section(header: Text("about.made-by-metabolist")) { + Button { + viewModel.navigateToOfficialAccount() + } label: { + Label { + Text("about.official-account").foregroundColor(.primary) + } icon: { + Image(systemName: "checkmark.seal") + } + } + Link(destination: Self.websiteURL) { + Label { + Text("about.website").foregroundColor(.primary) + } icon: { + Image(systemName: "link") + } + } + } Section { NavigationLink( destination: AcknowledgmentsView()) { @@ -26,6 +47,7 @@ struct AboutView: View { } private extension AboutView { + static let websiteURL = URL(string: "https://metabolist.org")! static var version: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" } @@ -40,7 +62,7 @@ import PreviewViewModels struct AboutView_Previews: PreviewProvider { static var previews: some View { - AboutView() + AboutView(viewModel: NavigationViewModel(identityContext: .preview)) } } #endif diff --git a/Views/SwiftUI/SecondaryNavigationView.swift b/Views/SwiftUI/SecondaryNavigationView.swift index c1762f5..b4541bf 100644 --- a/Views/SwiftUI/SecondaryNavigationView.swift +++ b/Views/SwiftUI/SecondaryNavigationView.swift @@ -70,7 +70,7 @@ struct SecondaryNavigationView: View { Label("secondary-navigation.preferences", systemImage: "gear") } NavigationLink( - destination: AboutView() + destination: AboutView(viewModel: viewModel) .environmentObject(rootViewModel)) { Label("secondary-navigation.about", systemImage: "info.circle") }