From bb4f9f8e20f3706836f520f49f2ddb3ab39320c7 Mon Sep 17 00:00:00 2001 From: CMK Date: Wed, 16 Feb 2022 19:47:51 +0800 Subject: [PATCH] feat: restore keyboard shortcut supports --- Mastodon.xcodeproj/project.pbxproj | 10 +- .../Provider/DataSourceFacade+Status.swift | 4 + ...tatusTableViewControllerNavigateable.swift | 191 +++++++ ...ider+TableViewControllerNavigateable.swift | 156 ++++++ .../HomeTimelineViewController.swift | 45 +- .../NotificationTimelineViewController.swift | 121 +++++ .../NotificationViewController.swift | 498 +++--------------- .../Favorite/FavoriteViewController.swift | 108 +--- .../Scene/Profile/ProfileViewController.swift | 11 + .../Timeline/UserTimelineViewController.swift | 17 + .../Scene/Thread/ThreadViewController.swift | 136 +---- 11 files changed, 631 insertions(+), 666 deletions(-) create mode 100644 Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift create mode 100644 Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 0fc94fd3..aaf6a2cf 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -547,6 +547,8 @@ DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */; }; DBD376B2269302A4007FEC24 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD376B1269302A4007FEC24 /* UITableViewCell.swift */; }; DBD5B1F627BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F527BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift */; }; + DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */; }; + DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */; }; DBD9149025DF6D8D00903DFD /* APIService+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */; }; DBE0821525CD382600FD6BBD /* MastodonRegisterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */; }; DBE0822425CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */; }; @@ -1281,6 +1283,8 @@ DBD376AB2692ECDB007FEC24 /* ThemePreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePreference.swift; sourceTree = ""; }; DBD376B1269302A4007FEC24 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; DBD5B1F527BCD3D200BD6B38 /* SuggestionAccountTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountTableViewCell+ViewModel.swift"; sourceTree = ""; }; + DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+TableViewControllerNavigateable.swift"; sourceTree = ""; }; + DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceProvider+StatusTableViewControllerNavigateable.swift"; sourceTree = ""; }; DBD9148F25DF6D8D00903DFD /* APIService+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Onboarding.swift"; sourceTree = ""; }; DBDC1CF9272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ku-TR"; path = "ku-TR.lproj/Intents.strings"; sourceTree = ""; }; DBDC1CFC272C0FD600055C3D /* ku-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ku-TR"; path = "ku-TR.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -2494,6 +2498,8 @@ DB697DDA278F4DE3004EF2F7 /* DataSourceProvider+StatusTableViewCellDelegate.swift */, DB023D2927A0FE5C005AC798 /* DataSourceProvider+NotificationTableViewCellDelegate.swift */, DB0FCB7727957678006C02E2 /* DataSourceProvider+UITableViewDelegate.swift */, + DBD5B1F727BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift */, + DBD5B1F927BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift */, ); path = Provider; sourceTree = ""; @@ -2649,11 +2655,11 @@ DB9D6BFD25E4F57B0051B173 /* Notification */, DB938EEB2623F52600E5B6C1 /* Thread */, 5B24BBD6262DB14800A9381B /* Report */, - DB9D6BEE25E4F5370051B173 /* Search */, DB789A1025F9F29B0071ACA0 /* Compose */, DB6180DE263919350018D199 /* MediaPreview */, 2DAC9E36262FC20B0062E1A6 /* SuggestionAccount */, DB9D6C0825E4F5A60051B173 /* Profile */, + DB9D6BEE25E4F5370051B173 /* Search */, 5B90C455262599800002E742 /* Settings */, ); path = Scene; @@ -4128,6 +4134,7 @@ DB1FD45025F26FA1004CFCFC /* MastodonPickServerViewModel+Diffable.swift in Sources */, DBD376AC2692ECDB007FEC24 /* ThemePreference.swift in Sources */, DB4F097D26A03A5B00D62E92 /* SearchHistoryItem.swift in Sources */, + DBD5B1FA27BD013700BD6B38 /* DataSourceProvider+StatusTableViewControllerNavigateable.swift in Sources */, DB68046C2636DC9E00430867 /* MastodonNotification.swift in Sources */, DBAE3F9E2616E308004B8251 /* APIService+Mute.swift in Sources */, DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, @@ -4241,6 +4248,7 @@ DB0FCB962797E6C2006C02E2 /* SearchResultViewController+DataSourceProvider.swift in Sources */, DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */, DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */, + DBD5B1F827BCFD9D00BD6B38 /* DataSourceProvider+TableViewControllerNavigateable.swift in Sources */, 0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */, DB0FCB882796BDA9006C02E2 /* SearchItem.swift in Sources */, DB336F3D278D80040031E64B /* FeedFetchedResultsController.swift in Sources */, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index a98d34f3..eab85e95 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -10,6 +10,7 @@ import CoreDataStack import MastodonUI import MastodonLocalization +// Delete extension DataSourceFacade { static func responseToDeleteStatus( @@ -25,6 +26,7 @@ extension DataSourceFacade { } +// Share extension DataSourceFacade { @MainActor @@ -74,6 +76,7 @@ extension DataSourceFacade { } } +// ActionToolBar extension DataSourceFacade { @MainActor static func responseToActionToolbar( @@ -133,6 +136,7 @@ extension DataSourceFacade { } +// menu extension DataSourceFacade { struct MenuContext { diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift new file mode 100644 index 00000000..fb4a7d84 --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift @@ -0,0 +1,191 @@ +// +// DataSourceProvider+StatusTableViewControllerNavigateable.swift +// Mastodon +// +// Created by MainasuK on 2022-2-16. +// + +import os.log +import UIKit +import CoreDataStack + +extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider & StatusTableViewControllerNavigateableRelay { + + var statusNavigationKeyCommands: [UIKeyCommand] { + StatusTableViewNavigation.allCases.map { navigation in + UIKeyCommand( + title: navigation.title, + image: nil, + action: #selector(Self.statusKeyCommandHandlerRelay(_:)), + input: navigation.input, + modifierFlags: navigation.modifierFlags, + propertyList: navigation.propertyList, + alternates: [], + discoverabilityTitle: nil, + attributes: [], + state: .off + ) + } + } + +} + +extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider { + + func statusKeyCommandHandler(_ sender: UIKeyCommand) { + guard let rawValue = sender.propertyList as? String, + let navigation = StatusTableViewNavigation(rawValue: rawValue) else { return } + + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, navigation.title) + Task { + switch navigation { + case .openAuthorProfile: await openAuthorProfile(target: .status) + case .openRebloggerProfile: await openAuthorProfile(target: .reblog) + case .replyStatus: await replyStatus() + case .toggleReblog: await toggleReblog() + case .toggleFavorite: await toggleFavorite() + case .toggleContentWarning: await toggleContentWarning() + case .previewImage: await previewImage() + } + } + } + +} + +// status coordinate +extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider { + + @MainActor + private func statusRecord() async -> ManagedObjectRecord? { + guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return nil } + let source = DataSourceItem.Source(indexPath: indexPathForSelectedRow) + guard let item = await item(from: source) else { return nil } + + switch item { + case .status(let record): + return record + case .notification(let record): + let _statusRecord: ManagedObjectRecord? = try? await context.managedObjectContext.perform { + guard let notification = record.object(in: self.context.managedObjectContext) else { return nil } + guard let status = notification.status else { return nil } + return .init(objectID: status.objectID) + } + guard let statusRecord = _statusRecord else { + return nil + } + return statusRecord + default: + return nil + } + } + + @MainActor + private func openAuthorProfile(target: DataSourceFacade.StatusTarget) async { + guard let status = await statusRecord() else { return } + await DataSourceFacade.coordinateToProfileScene( + provider: self, + target: target, + status: status + ) + } + + @MainActor + private func replyStatus() async { + guard let status = await statusRecord() else { return } + + guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + let selectionFeedbackGenerator = UISelectionFeedbackGenerator() + selectionFeedbackGenerator.selectionChanged() + + let composeViewModel = ComposeViewModel( + context: self.context, + composeKind: .reply(status: status), + authenticationBox: authenticationBox + ) + self.coordinator.present( + scene: .compose(viewModel: composeViewModel), + from: self, + transition: .modal(animated: true, completion: nil) + ) + } + + @MainActor + private func previewImage() async { + guard let status = await statusRecord() else { return } + + guard let provider = self as? (DataSourceProvider & MediaPreviewableViewController) else { return } + guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow, + let cell = tableView.cellForRow(at: indexPathForSelectedRow) as? StatusTableViewCell + else { return } + + guard let mediaView = cell.statusView.mediaGridContainerView.mediaViews.first else { return } + + do { + try await DataSourceFacade.coordinateToMediaPreviewScene( + dependency: provider, + status: status, + previewContext: DataSourceFacade.AttachmentPreviewContext( + containerView: .mediaGridContainerView(cell.statusView.mediaGridContainerView), + mediaView: mediaView, + index: 0 + ) + ) + } catch { + assertionFailure() + } + } + +} + +// toggle +extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvider { + + @MainActor + private func toggleReblog() async { + guard let status = await statusRecord() else { return } + + guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + try await DataSourceFacade.responseToStatusReblogAction( + provider: self, + status: status, + authenticationBox: authenticationBox + ) + } catch { + assertionFailure() + } + } + + @MainActor + private func toggleFavorite() async { + guard let status = await statusRecord() else { return } + + guard let authenticationBox = self.context.authenticationService.activeMastodonAuthenticationBox.value else { return } + + do { + try await DataSourceFacade.responseToStatusFavoriteAction( + provider: self, + status: status, + authenticationBox: authenticationBox + ) + } catch { + assertionFailure() + } + } + + @MainActor + private func toggleContentWarning() async { + guard let status = await statusRecord() else { return } + + do { + try await DataSourceFacade.responseToToggleSensitiveAction( + dependency: self, + status: status + ) + } catch { + assertionFailure() + } + } + +} diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift new file mode 100644 index 00000000..f7e50cff --- /dev/null +++ b/Mastodon/Protocol/Provider/DataSourceProvider+TableViewControllerNavigateable.swift @@ -0,0 +1,156 @@ +// +// DataSourceProvider+TableViewControllerNavigateable.swift +// Mastodon +// +// Created by MainasuK on 2022-2-16. +// + +import os.log +import UIKit + +extension TableViewControllerNavigateableCore where Self: TableViewControllerNavigateableRelay { + var navigationKeyCommands: [UIKeyCommand] { + TableViewNavigation.allCases.map { navigation in + UIKeyCommand( + title: navigation.title, + image: nil, + action: #selector(Self.navigateKeyCommandHandlerRelay(_:)), + input: navigation.input, + modifierFlags: navigation.modifierFlags, + propertyList: navigation.propertyList, + alternates: [], + discoverabilityTitle: nil, + attributes: [], + state: .off + ) + } + } +} + +extension TableViewControllerNavigateableCore { + + func navigateKeyCommandHandler(_ sender: UIKeyCommand) { + guard let rawValue = sender.propertyList as? String, + let navigation = TableViewNavigation(rawValue: rawValue) else { return } + + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, navigation.title) + switch navigation { + case .up: navigate(direction: .up) + case .down: navigate(direction: .down) + case .back: back() + case .open: open() + } + } + +} + + +// navigate status up/down +extension TableViewControllerNavigateableCore where Self: DataSourceProvider { + + func navigate(direction: TableViewNavigationDirection) { + if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { + // navigate up/down on the current selected item + Task { + await navigateToStatus(direction: direction, indexPath: indexPathForSelectedRow) + } + } else { + // set first visible item selected + navigateToFirstVisibleStatus() + } + } + + @MainActor + private func navigateToStatus( + direction: TableViewNavigationDirection, + indexPath: IndexPath + ) async { + let row: Int = { + let index = indexPath.row + switch direction { + case .up: return index - 1 + case .down: return index + 1 + } + }() + let indexPath = IndexPath(row: row , section: indexPath.section) + guard indexPath.section >= 0, indexPath.section < tableView.numberOfSections, + indexPath.row >= 0, indexPath.row < tableView.numberOfRows(inSection: indexPath.section) + else { return } + + let scrollPosition: UITableView.ScrollPosition = overrideNavigationScrollPosition ?? Self.navigateScrollPosition(tableView: tableView, indexPath: indexPath) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: scrollPosition) + } + + private func navigateToFirstVisibleStatus() { + guard var indexPathsForVisibleRows = tableView.indexPathsForVisibleRows?.sorted() else { return } + + if indexPathsForVisibleRows.first?.row != 0 { + // drop first when visible not the first cell of table + indexPathsForVisibleRows.removeFirst() + } + + guard let indexPath = indexPathsForVisibleRows.first else { return } + let scrollPosition: UITableView.ScrollPosition = overrideNavigationScrollPosition ?? Self.navigateScrollPosition(tableView: tableView, indexPath: indexPath) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: scrollPosition) + } + + static func validNavigateableItem(_ item: DataSourceItem) -> Bool { + switch item { + case .status, + .notification: + return true + default: + return false + } + } + +} + +extension TableViewControllerNavigateableCore { + // check is visible and not the first and last + static func navigateScrollPosition(tableView: UITableView, indexPath: IndexPath) -> UITableView.ScrollPosition { + let middleVisibleIndexPaths = (tableView.indexPathsForVisibleRows ?? []) + .sorted() + .dropFirst() + .dropLast() + guard middleVisibleIndexPaths.contains(indexPath) else { + return .top + } + guard middleVisibleIndexPaths.count > 2 else { + return .middle + } + return .none + } + +} + +extension TableViewControllerNavigateableCore where Self: DataSourceProvider { + func open() { + guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return } + let source = DataSourceItem.Source(indexPath: indexPathForSelectedRow) + + Task { @MainActor in + guard let item = await item(from: source) else { return } + switch item { + case .status(let record): + await DataSourceFacade.coordinateToStatusThreadScene( + provider: self, + target: .status, + status: record + ) + case .notification(let record): + assertionFailure() + default: + assertionFailure() + } + } // end Task +// StatusProviderFacade.coordinateToStatusThreadScene(for: .primary, provider: self, indexPath: indexPathForSelectedRow) + } +} + +extension TableViewControllerNavigateableCore where Self: UIViewController { + func back() { + UserDefaults.shared.backKeyCommandPressDate = Date() + navigationController?.popViewController(animated: true) + } +} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index b8ad6d05..fda5a471 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -540,19 +540,6 @@ extension HomeTimelineViewController: UITableViewDelegate, AutoGenerateTableView } // sourcery:end - -// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { -// aspectTableView(tableView, estimatedHeightForRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) -// } - } // MARK: - TimelineMiddleLoaderTableViewCellDelegate @@ -633,19 +620,19 @@ extension HomeTimelineViewController: HomeTimelineNavigationBarTitleViewDelegate } } -//extension HomeTimelineViewController { -// override var keyCommands: [UIKeyCommand]? { -// return navigationKeyCommands + statusNavigationKeyCommands -// } -//} -// -//// MARK: - StatusTableViewControllerNavigateable -//extension HomeTimelineViewController: StatusTableViewControllerNavigateable { -// @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// navigateKeyCommandHandler(sender) -// } -// -// @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// statusKeyCommandHandler(sender) -// } -//} +extension HomeTimelineViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} + +// MARK: - StatusTableViewControllerNavigateable +extension HomeTimelineViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +} diff --git a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift index 24663082..bdb4d05c 100644 --- a/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift +++ b/Mastodon/Scene/Notification/NotificationTimeline/NotificationTimelineViewController.swift @@ -9,6 +9,7 @@ import os.log import UIKit import Combine import CoreDataStack +import MastodonLocalization final class NotificationTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController { @@ -183,3 +184,123 @@ extension NotificationTimelineViewController: ScrollViewContainer { var scrollView: UIScrollView? { tableView } } + +extension NotificationTimelineViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + } +} + +extension NotificationTimelineViewController: TableViewControllerNavigateable { + + func navigate(direction: TableViewNavigationDirection) { + if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { + // navigate up/down on the current selected item + navigateToStatus(direction: direction, indexPath: indexPathForSelectedRow) + } else { + // set first visible item selected + navigateToFirstVisibleStatus() + } + } + + private func navigateToStatus(direction: TableViewNavigationDirection, indexPath: IndexPath) { + guard let diffableDataSource = viewModel.diffableDataSource else { return } + let items = diffableDataSource.snapshot().itemIdentifiers + guard let selectedItem = diffableDataSource.itemIdentifier(for: indexPath), + let selectedItemIndex = items.firstIndex(of: selectedItem) else { + return + } + + let _navigateToItem: NotificationItem? = { + var index = selectedItemIndex + while 0.. 1 { + // drop first when visible not the first cell of table + visibleItems.removeFirst() + } + guard let item = visibleItems.first, let indexPath = diffableDataSource.indexPath(for: item) else { return } + let scrollPosition: UITableView.ScrollPosition = overrideNavigationScrollPosition ?? Self.navigateScrollPosition(tableView: tableView, indexPath: indexPath) + tableView.selectRow(at: indexPath, animated: true, scrollPosition: scrollPosition) + } + + static func validNavigateableItem(_ item: NotificationItem) -> Bool { + switch item { + case .feed: + return true + default: + return false + } + } + + func open() { + guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return } + guard let diffableDataSource = viewModel.diffableDataSource else { return } + guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return } + + Task { @MainActor in + switch item { + case .feed(let record): + guard let feed = record.object(in: self.context.managedObjectContext) else { return } + guard let notification = feed.notification else { return } + + if let stauts = notification.status { + let threadViewModel = ThreadViewModel( + context: self.context, + optionalRoot: .root(context: .init(status: .init(objectID: stauts.objectID))) + ) + self.coordinator.present( + scene: .thread(viewModel: threadViewModel), + from: self, + transition: .show + ) + } else { + let profileViewModel = ProfileViewModel( + context: self.context, + optionalMastodonUser: notification.account + ) + self.coordinator.present( + scene: .profile(viewModel: profileViewModel), + from: self, + transition: .show + ) + } + default: + break + } + } // end Task + } + + func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + +} diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index e1f418e2..dd4d9704 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -93,96 +93,6 @@ extension NotificationViewController { } } .store(in: &disposeBag) - -// segmentControl.translatesAutoresizingMaskIntoConstraints = false -// navigationItem.titleView = segmentControl -// NSLayoutConstraint.activate([ -// segmentControl.widthAnchor.constraint(equalToConstant: 287) -// ]) -// segmentControl.addTarget(self, action: #selector(NotificationViewController.segmentedControlValueChanged(_:)), for: .valueChanged) -// -// tableView.translatesAutoresizingMaskIntoConstraints = false -// view.addSubview(tableView) -// NSLayoutConstraint.activate([ -// tableView.topAnchor.constraint(equalTo: view.topAnchor), -// tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), -// tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), -// tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), -// ]) -// -// tableView.refreshControl = refreshControl -// refreshControl.addTarget(self, action: #selector(NotificationViewController.refreshControlValueChanged(_:)), for: .valueChanged) -// -// tableView.delegate = self -// viewModel.tableView = tableView -// viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self -// viewModel.setupDiffableDataSource( -// for: tableView, -// dependency: self, -// delegate: self, -// statusTableViewCellDelegate: self -// ) -// viewModel.viewDidLoad.send() -// -// // bind refresh control -// viewModel.isFetchingLatestNotification -// .receive(on: DispatchQueue.main) -// .sink { [weak self] isFetching in -// guard let self = self else { return } -// if !isFetching { -// UIView.animate(withDuration: 0.5) { [weak self] in -// guard let self = self else { return } -// self.refreshControl.endRefreshing() -// } -// } -// } -// .store(in: &disposeBag) -// -// viewModel.dataSourceDidUpdated -// .receive(on: RunLoop.main) -// .sink { [weak self] in -// guard let self = self else { return } -// guard self.viewModel.needsScrollToTopAfterDataSourceUpdate else { return } -// self.viewModel.needsScrollToTopAfterDataSourceUpdate = false -// DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) { -// self.scrollToTop(animated: true) -// } -// } -// .store(in: &disposeBag) -// -// viewModel.selectedIndex -// .removeDuplicates() -// .receive(on: DispatchQueue.main) -// .sink { [weak self] segment in -// guard let self = self else { return } -// self.segmentControl.selectedSegmentIndex = segment.rawValue -// -// // trigger scroll-to-top after data reload -// self.viewModel.needsScrollToTopAfterDataSourceUpdate = true -// -// guard let domain = self.viewModel.activeMastodonAuthenticationBox.value?.domain, let userID = self.viewModel.activeMastodonAuthenticationBox.value?.userID else { -// return -// } -// -// self.viewModel.needsScrollToTopAfterDataSourceUpdate = true -// -// switch segment { -// case .everyThing: -// self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID) -// case .mentions: -// self.viewModel.notificationPredicate.value = MastodonNotification.predicate(domain: domain, userID: userID, typeRaw: Mastodon.Entity.Notification.NotificationType.mention.rawValue) -// } -// } -// .store(in: &disposeBag) -// -// segmentControl.observe(\.selectedSegmentIndex, options: [.new]) { [weak self] segmentControl, _ in -// guard let self = self else { return } -// // scroll to top when select same segment -// if segmentControl.selectedSegmentIndex == self.viewModel.selectedIndex.value.rawValue { -// self.scrollToTop(animated: true) -// } -// } -// .store(in: &observations) } override func viewWillAppear(_ animated: Bool) { @@ -197,19 +107,12 @@ extension NotificationViewController { // needs trigger manually after onboarding dismiss -// setNeedsStatusBarAppearanceUpdate() + setNeedsStatusBarAppearanceUpdate() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) -// DispatchQueue.main.async { [weak self] in -// guard let self = self else { return } -// if (self.viewModel.fetchedResultsController.fetchedObjects ?? []).count == 0 { -//// self.viewModel.loadLatestStateMachine.enter(NotificationViewModel.LoadLatestState.Loading.self) -// } -// } -// // reset notification count context.notificationService.clearNotificationCountForActiveUser() } @@ -265,335 +168,6 @@ extension NotificationViewController { } } -// MARK: - UITableViewDelegate - -extension NotificationViewController: UITableViewDelegate { - -// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { -// aspectTableView(tableView, estimatedHeightForRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// guard let diffableDataSource = viewModel.diffableDataSource else { return } -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } -// switch item { -// case .notificationStatus: -// aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath) -// case .bottomLoader: -// if !tableView.isDragging, !tableView.isDecelerating { -// viewModel.loadOldestStateMachine.enter(NotificationViewModel.LoadOldestState.Loading.self) -// } -// default: -// break -// } -// } -// -// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didSelectRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { -// return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point) -// } -// -// func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { -// return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration) -// } -// -// func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { -// return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration) -// } -// -// func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { -// aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator) -// } - -} - -//extension NotificationViewController { -// private func open(item: NotificationItem) { -// switch item { -// case .notification(let objectID, _): -// let notification = context.managedObjectContext.object(with: objectID) as! MastodonNotification -// if let status = notification.status { -// let viewModel = ThreadViewModel( -// context: context, -// optionalRoot: .root(context: .init(status: status.asRecord)) -// ) -// coordinator.present(scene: .thread(viewModel: viewModel), from: self, transition: .show) -// } else { -// let viewModel = ProfileViewModel( -// context: context, -// optionalMastodonUser: notification.account -// ) -// coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) -// } -// default: -// break -// } -// } -//} - -// MARK: - NotificationTableViewCellDelegate -//extension NotificationViewController: NotificationTableViewCellDelegate { -// -// func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, avatarImageViewDidPressed imageView: UIImageView) { -// guard let diffableDataSource = viewModel.diffableDataSource else { return } -// guard let indexPath = tableView.indexPath(for: cell) else { return } -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } -// switch item { -// case .notification(let objectID, _): -// guard let notification = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return } -// let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account) -// coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) -// default: -// break -// } -// } -// -// func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, authorNameLabelDidPressed label: MetaLabel) { -// guard let diffableDataSource = viewModel.diffableDataSource else { return } -// guard let indexPath = tableView.indexPath(for: cell) else { return } -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } -// switch item { -// case .notification(let objectID, _): -// guard let notification = try? viewModel.fetchedResultsController.managedObjectContext.existingObject(with: objectID) as? MastodonNotification else { return } -// let viewModel = ProfileViewModel(context: context, optionalMastodonUser: notification.account) -// coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) -// default: -// break -// } -// } -// -// func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, acceptButtonDidPressed button: UIButton) { -// viewModel.acceptFollowRequest(notification: notification) -// } -// -// func notificationTableViewCell(_ cell: NotificationStatusTableViewCell, notification: MastodonNotification, rejectButtonDidPressed button: UIButton) { -// viewModel.rejectFollowRequest(notification: notification) -// } -// -// func userNameLabelDidPressed(notification: MastodonNotification) { -// let viewModel = CachedProfileViewModel(context: context, mastodonUser: notification.account) -// DispatchQueue.main.async { -// self.coordinator.present(scene: .profile(viewModel: viewModel), from: self, transition: .show) -// } -// } -// -// func parent() -> UIViewController { -// self -// } -// -// func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, revealContentWarningButtonDidPressed button: UIButton) { -// StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell) -// } -// -// func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { -// StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell) -// } -// -// func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, playerContainerView: PlayerContainerView, contentWarningOverlayViewDidPressed contentWarningOverlayView: ContentWarningOverlayView) { -// StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, cell: cell) -// } -// -// func notificationStatusTableViewCell(_ cell: NotificationStatusTableViewCell, statusView: StatusView, metaText: MetaText, didSelectMeta meta: Meta) { -// StatusProviderFacade.responseToStatusMetaTextAction(provider: self, cell: cell, metaText: metaText, didSelectMeta: meta) -// } -//} - -// MARK: - UIScrollViewDelegate - -//extension NotificationViewController { -// func scrollViewDidScroll(_ scrollView: UIScrollView) { -// handleScrollViewDidScroll(scrollView) -// } -//} - -// MARK: - ScrollViewContainer -//extension NotificationViewController: ScrollViewContainer { -// -// var scrollView: UIScrollView { tableView } -// -// func scrollToTop(animated: Bool) { -// let indexPath = IndexPath(row: 0, section: 0) -// guard viewModel.diffableDataSource?.itemIdentifier(for: indexPath) != nil else { return } -// tableView.scrollToRow(at: indexPath, at: .top, animated: true) -// } -//} - -// MARK: - AVPlayerViewControllerDelegate -//extension NotificationViewController: AVPlayerViewControllerDelegate { -// func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// handlePlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// handlePlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -//} - -//// MARK: - statusTableViewCellDelegate -//extension NotificationViewController: StatusTableViewCellDelegate { -// var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { -// return self -// } -//} - -//extension NotificationViewController { -// -// enum CategorySwitch: String, CaseIterable { -// case showEverything -// case showMentions -// -// var title: String { -// switch self { -// case .showEverything: return L10n.Scene.Notification.Keyobard.showEverything -// case .showMentions: return L10n.Scene.Notification.Keyobard.showMentions -// } -// } -// -// // UIKeyCommand input -// var input: String { -// switch self { -// case .showEverything: return "[" // + shift + command -// case .showMentions: return "]" // + shift + command -// } -// } -// -// var modifierFlags: UIKeyModifierFlags { -// switch self { -// case .showEverything: return [.shift, .command] -// case .showMentions: return [.shift, .command] -// } -// } -// -// var propertyList: Any { -// return rawValue -// } -// } -// -// var categorySwitchKeyCommands: [UIKeyCommand] { -// CategorySwitch.allCases.map { category in -// UIKeyCommand( -// title: category.title, -// image: nil, -// action: #selector(NotificationViewController.showCategory(_:)), -// input: category.input, -// modifierFlags: category.modifierFlags, -// propertyList: category.propertyList, -// alternates: [], -// discoverabilityTitle: nil, -// attributes: [], -// state: .off -// ) -// } -// } -// -// @objc private func showCategory(_ sender: UIKeyCommand) { -// os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) -// guard let rawValue = sender.propertyList as? String, -// let category = CategorySwitch(rawValue: rawValue) else { return } -// -// switch category { -// case .showEverything: -// viewModel.selectedIndex.value = .everyThing -// case .showMentions: -// viewModel.selectedIndex.value = .mentions -// } -// } -// -// override var keyCommands: [UIKeyCommand]? { -// return categorySwitchKeyCommands + navigationKeyCommands -// } -//} - -//extension NotificationViewController: TableViewControllerNavigateable { -// -// func navigate(direction: TableViewNavigationDirection) { -// if let indexPathForSelectedRow = tableView.indexPathForSelectedRow { -// // navigate up/down on the current selected item -// navigateToStatus(direction: direction, indexPath: indexPathForSelectedRow) -// } else { -// // set first visible item selected -// navigateToFirstVisibleStatus() -// } -// } -// -// private func navigateToStatus(direction: TableViewNavigationDirection, indexPath: IndexPath) { -// guard let diffableDataSource = viewModel.diffableDataSource else { return } -// let items = diffableDataSource.snapshot().itemIdentifiers -// guard let selectedItem = diffableDataSource.itemIdentifier(for: indexPath), -// let selectedItemIndex = items.firstIndex(of: selectedItem) else { -// return -// } -// -// let _navigateToItem: NotificationItem? = { -// var index = selectedItemIndex -// while 0.. 1 { -// // drop first when visible not the first cell of table -// visibleItems.removeFirst() -// } -// guard let item = visibleItems.first, let indexPath = diffableDataSource.indexPath(for: item) else { return } -// let scrollPosition: UITableView.ScrollPosition = overrideNavigationScrollPosition ?? Self.navigateScrollPosition(tableView: tableView, indexPath: indexPath) -// tableView.selectRow(at: indexPath, animated: true, scrollPosition: scrollPosition) -// } -// -// static func validNavigateableItem(_ item: NotificationItem) -> Bool { -// switch item { -// case .notification: -// return true -// default: -// return false -// } -// } -// -// func open() { -// guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return } -// guard let diffableDataSource = viewModel.diffableDataSource else { return } -// guard let item = diffableDataSource.itemIdentifier(for: indexPathForSelectedRow) else { return } -// open(item: item) -// } -// -// func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// navigateKeyCommandHandler(sender) -// } -// -//} - // MARK: - ScrollViewContainer extension NotificationViewController: ScrollViewContainer { var scrollView: UIScrollView? { @@ -603,3 +177,73 @@ extension NotificationViewController: ScrollViewContainer { return viewController.scrollView } } + + +extension NotificationViewController { + + enum CategorySwitch: String, CaseIterable { + case everything + case mentions + + var title: String { + switch self { + case .everything: return L10n.Scene.Notification.Keyobard.showEverything + case .mentions: return L10n.Scene.Notification.Keyobard.showMentions + } + } + + // UIKeyCommand input + var input: String { + switch self { + case .everything: return "[" // + shift + command + case .mentions: return "]" // + shift + command + } + } + + var modifierFlags: UIKeyModifierFlags { + switch self { + case .everything: return [.shift, .command] + case .mentions: return [.shift, .command] + } + } + + var propertyList: Any { + return rawValue + } + } + + var categorySwitchKeyCommands: [UIKeyCommand] { + CategorySwitch.allCases.map { category in + UIKeyCommand( + title: category.title, + image: nil, + action: #selector(NotificationViewController.showCategory(_:)), + input: category.input, + modifierFlags: category.modifierFlags, + propertyList: category.propertyList, + alternates: [], + discoverabilityTitle: nil, + attributes: [], + state: .off + ) + } + } + + @objc private func showCategory(_ sender: UIKeyCommand) { + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + guard let rawValue = sender.propertyList as? String, + let category = CategorySwitch(rawValue: rawValue) + else { return } + + switch category { + case .everything: + scrollToPage(.first, animated: true, completion: nil) + case .mentions: + scrollToPage(.last, animated: true, completion: nil) + } + } + + override var keyCommands: [UIKeyCommand]? { + return categorySwitchKeyCommands + } +} diff --git a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift index d061826c..2ac1e206 100644 --- a/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift +++ b/Mastodon/Scene/Profile/Favorite/FavoriteViewController.swift @@ -73,7 +73,6 @@ extension FavoriteViewController { ]) tableView.delegate = self -// tableView.prefetchDataSource = self viewModel.setupDiffableDataSource( tableView: tableView, statusTableViewCellDelegate: self @@ -104,20 +103,6 @@ extension FavoriteViewController { } -//// MARK: - TableViewCellHeightCacheableContainer -//extension FavoriteViewController: TableViewCellHeightCacheableContainer { -// var cellFrameCache: NSCache { -// return viewModel.cellFrameCache -// } -//} - -// MARK: - UIScrollViewDelegate -//extension FavoriteViewController { -// func scrollViewDidScroll(_ scrollView: UIScrollView) { -// aspectScrollViewDidScroll(scrollView) -// } -//} - // MARK: - UITableViewDelegate extension FavoriteViewController: UITableViewDelegate, AutoGenerateTableViewDelegate { // sourcery:inline:FavoriteViewController.AutoGenerateTableViewDelegate @@ -146,83 +131,24 @@ extension FavoriteViewController: UITableViewDelegate, AutoGenerateTableViewDele // sourcery:end - -// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { -// aspectTableView(tableView, estimatedHeightForRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didSelectRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { -// return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point) -// } -// -// func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { -// return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration) -// } -// -// func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { -// return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration) -// } -// -// func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { -// aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator) -// } -// } -// MARK: - UITableViewDataSourcePrefetching -//extension FavoriteViewController: UITableViewDataSourcePrefetching { -// func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { -// aspectTableView(tableView, prefetchRowsAt: indexPaths) -// } -//} - -// MARK: - AVPlayerViewControllerDelegate -//extension FavoriteViewController: AVPlayerViewControllerDelegate { -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// aspectPlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// aspectPlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -//} - -// MARK: - TimelinePostTableViewCellDelegate -//extension FavoriteViewController: StatusTableViewCellDelegate { -// weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self } -// func parent() -> UIViewController { return self } -//} - -//extension FavoriteViewController { -// override var keyCommands: [UIKeyCommand]? { -// return navigationKeyCommands + statusNavigationKeyCommands -// } -//} -// -//// MARK: - StatusTableViewControllerNavigateable -//extension FavoriteViewController: StatusTableViewControllerNavigateable { -// @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// navigateKeyCommandHandler(sender) -// } -// -// @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// statusKeyCommandHandler(sender) -// } -//} - // MARK: - StatusTableViewCellDelegate extension FavoriteViewController: StatusTableViewCellDelegate { } + +extension FavoriteViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} + +// MARK: - StatusTableViewControllerNavigateable +extension FavoriteViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +} diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index d8e27a29..826aaba5 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -1120,3 +1120,14 @@ extension ProfileViewController: ScrollViewContainer { // } // //} + +// MARK: - SegmentedControlNavigateable +//extension ProfileViewController: SegmentedControlNavigateable { +// var navigateableSegmentedControl: UISegmentedControl { +// profileHeaderViewController.pageSegmentedControl +// } +// +// @objc func segmentedControlNavigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { +// segmentedControlNavigateKeyCommandHandler(sender) +// } +//} diff --git a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift index 3aa20541..d6bbd507 100644 --- a/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift +++ b/Mastodon/Scene/Profile/Timeline/UserTimelineViewController.swift @@ -151,3 +151,20 @@ extension UserTimelineViewController: ScrollViewContainer { // MARK: - StatusTableViewCellDelegate extension UserTimelineViewController: StatusTableViewCellDelegate { } + +extension UserTimelineViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} + +// MARK: - StatusTableViewControllerNavigateable +extension UserTimelineViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +} diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift index cfc28447..bd90fb37 100644 --- a/Mastodon/Scene/Thread/ThreadViewController.swift +++ b/Mastodon/Scene/Thread/ThreadViewController.swift @@ -92,10 +92,7 @@ extension ThreadViewController { tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) -// viewModel.tableView = tableView -// viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self tableView.delegate = self -// tableView.prefetchDataSource = self viewModel.setupDiffableDataSource( tableView: tableView, statusTableViewCellDelegate: self @@ -174,123 +171,26 @@ extension ThreadViewController: UITableViewDelegate, AutoGenerateTableViewDelega return indexPath } } - - -// func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { -// aspectTableView(tableView, estimatedHeightForRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, willDisplay: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didEndDisplaying: cell, forRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -// aspectTableView(tableView, didSelectRowAt: indexPath) -// } -// -// func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { -// guard let diffableDataSource = viewModel.diffableDataSource else { return nil } -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil } -// -// // disable root selection -// switch item { -// case .root: -// return nil -// default: -// return indexPath -// } -// } -// -// func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { -// return aspectTableView(tableView, contextMenuConfigurationForRowAt: indexPath, point: point) -// } -// -// func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { -// return aspectTableView(tableView, previewForHighlightingContextMenuWithConfiguration: configuration) -// } -// -// func tableView(_ tableView: UITableView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { -// return aspectTableView(tableView, previewForDismissingContextMenuWithConfiguration: configuration) -// } -// -// func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { -// aspectTableView(tableView, willPerformPreviewActionForMenuWith: configuration, animator: animator) -// } - } -// MARK: - UITableViewDataSourcePrefetching -//extension ThreadViewController: UITableViewDataSourcePrefetching { -// func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { -// aspectTableView(tableView, prefetchRowsAt: indexPaths) -// } -//} - -// MARK: - AVPlayerViewControllerDelegate -//extension ThreadViewController: AVPlayerViewControllerDelegate { -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// aspectPlayerViewController(playerViewController, willBeginFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -// func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { -// aspectPlayerViewController(playerViewController, willEndFullScreenPresentationWithAnimationCoordinator: coordinator) -// } -// -//} - -// MARK: - statusTableViewCellDelegate -//extension ThreadViewController: StatusTableViewCellDelegate { -// weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self } -// func parent() -> UIViewController { return self } -//} - -// MARK: - ThreadReplyLoaderTableViewCellDelegate -//extension ThreadViewController: ThreadReplyLoaderTableViewCellDelegate { -// func threadReplyLoaderTableViewCell(_ cell: ThreadReplyLoaderTableViewCell, loadMoreButtonDidPressed button: UIButton) { -// guard let diffableDataSource = viewModel.diffableDataSource else { return } -// guard let indexPath = tableView.indexPath(for: cell) else { return } -// guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return } -// guard case let .leafBottomLoader(statusObjectID) = item else { return } -// -// let nodes = viewModel.descendantNodes.value -// nodes.forEach { node in -// expandReply(node: node, statusObjectID: statusObjectID) -// } -// viewModel.descendantNodes.value = nodes -// } -// -// private func expandReply(node: ThreadViewModel.LeafNode, statusObjectID: NSManagedObjectID) { -// if node.objectID == statusObjectID { -// node.isChildrenExpanded = true -// } else { -// for child in node.children { -// expandReply(node: child, statusObjectID: statusObjectID) -// } -// } -// } -//} - -//extension ThreadViewController { -// override var keyCommands: [UIKeyCommand]? { -// return navigationKeyCommands + statusNavigationKeyCommands -// } -//} -// -//// MARK: - StatusTableViewControllerNavigateable -//extension ThreadViewController: StatusTableViewControllerNavigateable { -// @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// navigateKeyCommandHandler(sender) -// } -// -// @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { -// statusKeyCommandHandler(sender) -// } -//} // MARK: - StatusTableViewCellDelegate extension ThreadViewController: StatusTableViewCellDelegate { } + + +extension ThreadViewController { + override var keyCommands: [UIKeyCommand]? { + return navigationKeyCommands + statusNavigationKeyCommands + } +} + +// MARK: - StatusTableViewControllerNavigateable +extension ThreadViewController: StatusTableViewControllerNavigateable { + @objc func navigateKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + navigateKeyCommandHandler(sender) + } + + @objc func statusKeyCommandHandlerRelay(_ sender: UIKeyCommand) { + statusKeyCommandHandler(sender) + } +}