Official account and website in about

This commit is contained in:
Justin Mazzocchi 2021-03-02 22:55:35 -08:00
parent 8e48a8bab8
commit 187bc42373
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
12 changed files with 164 additions and 80 deletions

View file

@ -1,5 +1,6 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import SafariServices
import UIKit import UIKit
import ViewModels import ViewModels
@ -16,4 +17,26 @@ extension UIViewController {
present(alertController, animated: true) 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
} }

View file

@ -2,6 +2,9 @@
"about" = "About"; "about" = "About";
"about.acknowledgments" = "Acknowledgments"; "about.acknowledgments" = "Acknowledgments";
"about.made-by-metabolist" = "Made by Metabolist";
"about.official-account" = "Official Account";
"about.website" = "Website";
"accessibility.activate-link-%@" = "Activate link: %@"; "accessibility.activate-link-%@" = "Activate link: %@";
"accessibility.copy-text" = "Copy text"; "accessibility.copy-text" = "Copy text";
"account.%@-followers" = "%@'s Followers"; "account.%@-followers" = "%@'s Followers";

View file

@ -10,6 +10,8 @@
D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0030981250C6C8500EACB32 /* URL+Extensions.swift */; }; D0030982250C6C8500EACB32 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0030981250C6C8500EACB32 /* URL+Extensions.swift */; };
D005A1D825EF189A008B2E63 /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1D725EF189A008B2E63 /* ReportViewController.swift */; }; D005A1D825EF189A008B2E63 /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1D725EF189A008B2E63 /* ReportViewController.swift */; };
D005A1E625EF3D11008B2E63 /* ReportHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D005A1E525EF3D11008B2E63 /* ReportHeaderView.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 */; }; D00702292555E51200F38136 /* ConversationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702282555E51200F38136 /* ConversationTableViewCell.swift */; };
D00702312555F4AE00F38136 /* ConversationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702302555F4AE00F38136 /* ConversationView.swift */; }; D00702312555F4AE00F38136 /* ConversationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702302555F4AE00F38136 /* ConversationView.swift */; };
D00702362555F4C500F38136 /* ConversationContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00702352555F4C500F38136 /* ConversationContentConfiguration.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 = "<group>"; }; D0030981250C6C8500EACB32 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
D005A1D725EF189A008B2E63 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = "<group>"; }; D005A1D725EF189A008B2E63 /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = "<group>"; };
D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportHeaderView.swift; sourceTree = "<group>"; }; D005A1E525EF3D11008B2E63 /* ReportHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportHeaderView.swift; sourceTree = "<group>"; };
D005A1FF25EF574F008B2E63 /* NavigationHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationHandling.swift; sourceTree = "<group>"; };
D00702282555E51200F38136 /* ConversationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTableViewCell.swift; sourceTree = "<group>"; }; D00702282555E51200F38136 /* ConversationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTableViewCell.swift; sourceTree = "<group>"; };
D00702302555F4AE00F38136 /* ConversationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationView.swift; sourceTree = "<group>"; }; D00702302555F4AE00F38136 /* ConversationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationView.swift; sourceTree = "<group>"; };
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContentConfiguration.swift; sourceTree = "<group>"; }; D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContentConfiguration.swift; sourceTree = "<group>"; };
@ -735,6 +738,7 @@
D0C7D42024F76169001EBDBB /* Views */ = { D0C7D42024F76169001EBDBB /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D005A1FF25EF574F008B2E63 /* NavigationHandling.swift */,
D021A66425C3E170008A0C0D /* SwiftUI */, D021A66425C3E170008A0C0D /* SwiftUI */,
D021A66325C3E167008A0C0D /* UIKit */, D021A66325C3E167008A0C0D /* UIKit */,
D0EA59472522B8B600804347 /* ViewConstants.swift */, D0EA59472522B8B600804347 /* ViewConstants.swift */,
@ -1084,6 +1088,7 @@
D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */, D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
D021A60A25C36B32008A0C0D /* IdentityTableViewCell.swift in Sources */, D021A60A25C36B32008A0C0D /* IdentityTableViewCell.swift in Sources */,
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */, D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */,
D005A20025EF574F008B2E63 /* NavigationHandling.swift in Sources */,
D0F4362D25C10B9600E4F896 /* AddIdentityViewController.swift in Sources */, D0F4362D25C10B9600E4F896 /* AddIdentityViewController.swift in Sources */,
D035D8F925E4338D00E597C9 /* ImageDiskCache.swift in Sources */, D035D8F925E4338D00E597C9 /* ImageDiskCache.swift in Sources */,
D0625E59250F092900502611 /* StatusTableViewCell.swift in Sources */, D0625E59250F092900502611 /* StatusTableViewCell.swift in Sources */,
@ -1203,6 +1208,7 @@
D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */, D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */,
D021A67B25C3E32A008A0C0D /* PlayerView.swift in Sources */, D021A67B25C3E32A008A0C0D /* PlayerView.swift in Sources */,
D052DBDD25EAF01800FFB628 /* URL+Extensions.swift in Sources */, D052DBDD25EAF01800FFB628 /* URL+Extensions.swift in Sources */,
D005A20125EF574F008B2E63 /* NavigationHandling.swift in Sources */,
D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */, D021A69025C3E4B8008A0C0D /* EmojiContentConfiguration.swift in Sources */,
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */, D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
D00CB23D25C9305D008EF267 /* NSMutableAttributedString+Extensions.swift in Sources */, D00CB23D25C9305D008EF267 /* NSMutableAttributedString+Extensions.swift in Sources */,

View file

@ -5,6 +5,7 @@ import UIKit
import ViewModels import ViewModels
final class ExploreViewController: UICollectionViewController { final class ExploreViewController: UICollectionViewController {
private let webfingerIndicatorView = WebfingerIndicatorView()
private let viewModel: ExploreViewModel private let viewModel: ExploreViewModel
private let rootViewModel: RootViewModel private let rootViewModel: RootViewModel
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -30,6 +31,7 @@ final class ExploreViewController: UICollectionViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
// swiftlint:disable:next function_body_length
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -60,6 +62,9 @@ final class ExploreViewController: UICollectionViewController {
searchController.searchBar.keyboardType = .twitter searchController.searchBar.keyboardType = .twitter
navigationItem.searchController = searchController navigationItem.searchController = searchController
view.addSubview(webfingerIndicatorView)
webfingerIndicatorView.translatesAutoresizingMaskIntoConstraints = false
viewModel.identityContext.$appPreferences.sink { appPreferences in viewModel.identityContext.$appPreferences.sink { appPreferences in
searchController.searchBar.scopeButtonTitles = SearchScope.allCases.map { searchController.searchBar.scopeButtonTitles = SearchScope.allCases.map {
$0.title(statusWord: appPreferences.statusWord) $0.title(statusWord: appPreferences.statusWord)
@ -67,6 +72,11 @@ final class ExploreViewController: UICollectionViewController {
} }
.store(in: &cancellables) .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.events.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables)
viewModel.$loading.sink { [weak self] in 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 { private extension ExploreViewController {
static let bottomInset: CGFloat = .newStatusButtonDimension + .defaultSpacing * 4 static let bottomInset: CGFloat = .newStatusButtonDimension + .defaultSpacing * 4
@ -152,20 +199,4 @@ private extension ExploreViewController {
handle(navigation: navigation) 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
}
}
} }

View file

@ -50,6 +50,7 @@ final class MainNavigationViewController: UITabBarController {
.store(in: &cancellables) .store(in: &cancellables)
viewModel.navigations viewModel.navigations
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.handle(navigation: $0) } .sink { [weak self] in self?.handle(navigation: $0) }
.store(in: &cancellables) .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 { private extension MainNavigationViewController {
static let secondaryNavigationViewTag = UUID().hashValue static let secondaryNavigationViewTag = UUID().hashValue
static let newStatusViewTag = UUID().hashValue static let newStatusViewTag = UUID().hashValue
@ -211,42 +236,4 @@ private extension MainNavigationViewController {
dismiss(animated: true) 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
}
}
} }

View file

@ -71,7 +71,7 @@ final class NotificationsViewController: UIPageViewController {
} }
} }
extension NotificationsViewController { extension NotificationsViewController: NavigationHandling {
func handle(navigation: Navigation) { func handle(navigation: Navigation) {
switch navigation { switch navigation {
case .notification: case .notification:
@ -81,7 +81,7 @@ extension NotificationsViewController {
setViewControllers([firstViewController], direction: .reverse, animated: false) setViewControllers([firstViewController], direction: .reverse, animated: false)
firstViewController.handle(navigation: navigation) firstViewController.handle(navigation: navigation)
default: default:
break (viewControllers?.first as? TableViewController)?.handle(navigation: navigation)
} }
} }
} }

View file

@ -3,7 +3,6 @@
import AVKit import AVKit
import Combine import Combine
import Mastodon import Mastodon
import SafariServices
import SDWebImage import SDWebImage
import SwiftUI import SwiftUI
import ViewModels import ViewModels
@ -257,7 +256,9 @@ extension TableViewController {
} }
} }
} }
}
extension TableViewController: NavigationHandling {
func handle(navigation: Navigation) { func handle(navigation: Navigation) {
switch navigation { switch navigation {
case let .collection(collectionService): case let .collection(collectionService):
@ -273,6 +274,8 @@ extension TableViewController {
} else { } else {
show(vc, sender: self) show(vc, sender: self)
} }
webfingerIndicatorView.stopAnimating()
case let .profile(profileService): case let .profile(profileService):
let vc = ProfileViewController( let vc = ProfileViewController(
viewModel: ProfileViewModel( viewModel: ProfileViewModel(
@ -287,10 +290,13 @@ extension TableViewController {
} else { } else {
show(vc, sender: self) show(vc, sender: self)
} }
webfingerIndicatorView.stopAnimating()
case let .notification(notificationService): case let .notification(notificationService):
navigate(toNotification: notificationService.notification) navigate(toNotification: notificationService.notification)
case let .url(url): case let .url(url):
open(url: url) open(url: url, identityContext: viewModel.identityContext)
webfingerIndicatorView.stopAnimating()
case .searchScope: case .searchScope:
break break
case .webfingerStart: case .webfingerStart:
@ -562,26 +568,6 @@ private extension TableViewController {
viewModel.select(indexPath: indexPath) 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) { func present(attachmentViewModel: AttachmentViewModel, statusViewModel: StatusViewModel) {
switch attachmentViewModel.attachment.type { switch attachmentViewModel.attachment.type {
case .audio, .video: case .audio, .video:

View file

@ -113,3 +113,9 @@ extension TimelinesViewController: ScrollableToTop {
(viewControllers?.first as? TableViewController)?.scrollToTop(animated: animated) (viewControllers?.first as? TableViewController)?.scrollToTop(animated: animated)
} }
} }
extension TimelinesViewController: NavigationHandling {
func handle(navigation: Navigation) {
(viewControllers?.first as? TableViewController)?.handle(navigation: navigation)
}
}

View file

@ -130,6 +130,14 @@ public extension NavigationViewModel {
titleComponents: ["preferences.blocked-users"]))) 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) { func navigate(pushNotification: PushNotification) {
switch pushNotification.notificationType { switch pushNotification.notificationType {
case .followRequest: case .followRequest:
@ -184,3 +192,7 @@ public extension NavigationViewModel {
return conversationsViewModel return conversationsViewModel
} }
} }
private extension NavigationViewModel {
static let officialAccountURL = URL(string: "https://mastodon.social/@metabolist")!
}

View file

@ -0,0 +1,8 @@
// Copyright © 2021 Metabolist. All rights reserved.
import Foundation
import ViewModels
protocol NavigationHandling {
func handle(navigation: Navigation)
}

View file

@ -1,8 +1,11 @@
// Copyright © 2021 Metabolist. All rights reserved. // Copyright © 2021 Metabolist. All rights reserved.
import SwiftUI import SwiftUI
import ViewModels
struct AboutView: View { struct AboutView: View {
@StateObject var viewModel: NavigationViewModel
var body: some View { var body: some View {
Form { Form {
Section { Section {
@ -14,6 +17,24 @@ struct AboutView: View {
.padding() .padding()
} }
.frame(maxWidth: .infinity, alignment: .center) .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 { Section {
NavigationLink( NavigationLink(
destination: AcknowledgmentsView()) { destination: AcknowledgmentsView()) {
@ -26,6 +47,7 @@ struct AboutView: View {
} }
private extension AboutView { private extension AboutView {
static let websiteURL = URL(string: "https://metabolist.org")!
static var version: String { static var version: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
} }
@ -40,7 +62,7 @@ import PreviewViewModels
struct AboutView_Previews: PreviewProvider { struct AboutView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
AboutView() AboutView(viewModel: NavigationViewModel(identityContext: .preview))
} }
} }
#endif #endif

View file

@ -70,7 +70,7 @@ struct SecondaryNavigationView: View {
Label("secondary-navigation.preferences", systemImage: "gear") Label("secondary-navigation.preferences", systemImage: "gear")
} }
NavigationLink( NavigationLink(
destination: AboutView() destination: AboutView(viewModel: viewModel)
.environmentObject(rootViewModel)) { .environmentObject(rootViewModel)) {
Label("secondary-navigation.about", systemImage: "info.circle") Label("secondary-navigation.about", systemImage: "info.circle")
} }