forked from zelo72/mastodon-ios
feat: add keyboard navigate for timeline
This commit is contained in:
parent
cd2a509905
commit
c44ced7501
|
@ -68,6 +68,7 @@
|
||||||
"find_people": "Find people to follow",
|
"find_people": "Find people to follow",
|
||||||
"manually_search": "Manually search instead",
|
"manually_search": "Manually search instead",
|
||||||
"skip": "Skip",
|
"skip": "Skip",
|
||||||
|
"reply": "Reply",
|
||||||
"report_user": "Report %s",
|
"report_user": "Report %s",
|
||||||
"block_domain": "Block %s",
|
"block_domain": "Block %s",
|
||||||
"unblock_domain": "Unblock %s",
|
"unblock_domain": "Unblock %s",
|
||||||
|
@ -85,6 +86,18 @@
|
||||||
"switch_to_tab": "Switch to %s",
|
"switch_to_tab": "Switch to %s",
|
||||||
"show_favorites": "Show Favorites",
|
"show_favorites": "Show Favorites",
|
||||||
"open_settings": "Open Settings"
|
"open_settings": "Open Settings"
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"previous_status": "Previous Status",
|
||||||
|
"next_status": "Next Status",
|
||||||
|
"open_status": "Open Status",
|
||||||
|
"open_author_profile": "Open Author Profile",
|
||||||
|
"open_reblogger_profile": "Open Reblogger Profile",
|
||||||
|
"reply_status": "Reply Status",
|
||||||
|
"toggle_reblog": "Toggle Status Reblog",
|
||||||
|
"toggle_favorite": "Toggle Status Favorite",
|
||||||
|
"toggle_content_warning": "Toggle Content Warning",
|
||||||
|
"preview_image": "Preview Image"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
@ -498,6 +511,13 @@
|
||||||
"send": "Send Report",
|
"send": "Send Report",
|
||||||
"skip_to_send": "Send without comment",
|
"skip_to_send": "Send without comment",
|
||||||
"text_placeholder": "Type or paste additional comments"
|
"text_placeholder": "Type or paste additional comments"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"keyboard": {
|
||||||
|
"close_preview": "Close Preview",
|
||||||
|
"show_next": "Show Next",
|
||||||
|
"show_previous": "Show Previous"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -197,6 +197,9 @@
|
||||||
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
DB118A8225E4B6E600FAB162 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */; };
|
||||||
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; };
|
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; };
|
||||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */; };
|
||||||
|
DB1D842C26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */; };
|
||||||
|
DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */; };
|
||||||
|
DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1D842F26566512000346B3 /* KeyboardPreference.swift */; };
|
||||||
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; };
|
DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */; };
|
||||||
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; };
|
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1E347725F519300079D7DF /* PickServerItem.swift */; };
|
||||||
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; };
|
DB1FD43625F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */; };
|
||||||
|
@ -752,6 +755,9 @@
|
||||||
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
DB118A8125E4B6E600FAB162 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
|
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
|
||||||
DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = "<group>"; };
|
DB1D186B25EF5BA7003F1F23 /* PollTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollTableView.swift; sourceTree = "<group>"; };
|
||||||
|
DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+StatusTableViewKeyCommandNavigateable.swift"; sourceTree = "<group>"; };
|
||||||
|
DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewControllerNavigateable.swift; sourceTree = "<group>"; };
|
||||||
|
DB1D842F26566512000346B3 /* KeyboardPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPreference.swift; sourceTree = "<group>"; };
|
||||||
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
|
DB1E346725F518E20079D7DF /* CategoryPickerSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
|
||||||
DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = "<group>"; };
|
DB1E347725F519300079D7DF /* PickServerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickServerItem.swift; sourceTree = "<group>"; };
|
||||||
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = "<group>"; };
|
DB1FD43525F26899004CFCFC /* MastodonPickServerViewModel+LoadIndexedServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonPickServerViewModel+LoadIndexedServerState.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1241,6 +1247,7 @@
|
||||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
||||||
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
||||||
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */,
|
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */,
|
||||||
|
DB1D842B26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift */,
|
||||||
);
|
);
|
||||||
path = StatusProvider;
|
path = StatusProvider;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1341,6 +1348,7 @@
|
||||||
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
|
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
|
||||||
5DF1058425F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */,
|
5DF1058425F88AE500D6C0D4 /* NeedsDependency+AVPlayerViewControllerDelegate.swift */,
|
||||||
DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */,
|
DBE3CE12261D7D4200430CC6 /* StatusTableViewControllerAspect.swift */,
|
||||||
|
DB1D842D26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift */,
|
||||||
);
|
);
|
||||||
path = Protocol;
|
path = Protocol;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1789,6 +1797,7 @@
|
||||||
DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */,
|
DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */,
|
||||||
DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */,
|
DB6D1B3C2636857500ACB481 /* AppearancePreference.swift */,
|
||||||
DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */,
|
DBE54AC52636C89F004E7C0B /* NotificationPreference.swift */,
|
||||||
|
DB1D842F26566512000346B3 /* KeyboardPreference.swift */,
|
||||||
);
|
);
|
||||||
path = Preference;
|
path = Preference;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3054,6 +3063,7 @@
|
||||||
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
|
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */,
|
||||||
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
|
5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */,
|
||||||
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */,
|
2D38F1DF25CD46A400561493 /* HomeTimelineViewController+Provider.swift in Sources */,
|
||||||
|
DB1D843026566512000346B3 /* KeyboardPreference.swift in Sources */,
|
||||||
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
|
2D206B9225F60EA700143C56 /* UIControl.swift in Sources */,
|
||||||
2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */,
|
2D9DB96B263A91D1007C1D71 /* APIService+DomainBlock.swift in Sources */,
|
||||||
DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */,
|
DBBF1DC92652538500E5B703 /* AutoCompleteSection.swift in Sources */,
|
||||||
|
@ -3205,6 +3215,7 @@
|
||||||
0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */,
|
0F202227261411BB000C64BF /* HashtagTimelineViewController+Provider.swift in Sources */,
|
||||||
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
|
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
|
||||||
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */,
|
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */,
|
||||||
|
DB1D842E26552C4D000346B3 /* StatusTableViewControllerNavigateable.swift in Sources */,
|
||||||
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */,
|
DB938F1F2624382F00E5B6C1 /* ThreadViewModel+Diffable.swift in Sources */,
|
||||||
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */,
|
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */,
|
||||||
DB040ECD26526EA600BEE9D8 /* ComposeCollectionView.swift in Sources */,
|
DB040ECD26526EA600BEE9D8 /* ComposeCollectionView.swift in Sources */,
|
||||||
|
@ -3243,6 +3254,7 @@
|
||||||
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */,
|
DB66729C25F9F91F00D60309 /* ComposeStatusItem.swift in Sources */,
|
||||||
DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */,
|
DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */,
|
||||||
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
|
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
|
||||||
|
DB1D842C26551A1C000346B3 /* StatusProvider+StatusTableViewKeyCommandNavigateable.swift in Sources */,
|
||||||
5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */,
|
5DF1057925F88A1D00D6C0D4 /* PlayerContainerView.swift in Sources */,
|
||||||
DB6D9F4926353FD7008423CD /* Subscription.swift in Sources */,
|
DB6D9F4926353FD7008423CD /* Subscription.swift in Sources */,
|
||||||
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */,
|
DB45FB0F25CA87D0005A8AC7 /* AuthenticationService.swift in Sources */,
|
||||||
|
|
|
@ -7,15 +7,6 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UITableView {
|
|
||||||
|
|
||||||
// static let groupedTableViewPaddingHeaderViewHeight: CGFloat = 16
|
|
||||||
// static var groupedTableViewPaddingHeaderView: UIView {
|
|
||||||
// return UIView(frame: CGRect(x: 0, y: 0, width: 100, height: groupedTableViewPaddingHeaderViewHeight))
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UITableView {
|
extension UITableView {
|
||||||
|
|
||||||
func deselectRow(with transitionCoordinator: UIViewControllerTransitionCoordinator?, animated: Bool) {
|
func deselectRow(with transitionCoordinator: UIViewControllerTransitionCoordinator?, animated: Bool) {
|
||||||
|
|
|
@ -110,6 +110,8 @@ internal enum L10n {
|
||||||
internal static let preview = L10n.tr("Localizable", "Common.Controls.Actions.Preview")
|
internal static let preview = L10n.tr("Localizable", "Common.Controls.Actions.Preview")
|
||||||
/// Remove
|
/// Remove
|
||||||
internal static let remove = L10n.tr("Localizable", "Common.Controls.Actions.Remove")
|
internal static let remove = L10n.tr("Localizable", "Common.Controls.Actions.Remove")
|
||||||
|
/// Reply
|
||||||
|
internal static let reply = L10n.tr("Localizable", "Common.Controls.Actions.Reply")
|
||||||
/// Report %@
|
/// Report %@
|
||||||
internal static func reportUser(_ p1: Any) -> String {
|
internal static func reportUser(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "Common.Controls.Actions.ReportUser", String(describing: p1))
|
return L10n.tr("Localizable", "Common.Controls.Actions.ReportUser", String(describing: p1))
|
||||||
|
@ -200,6 +202,28 @@ internal enum L10n {
|
||||||
return L10n.tr("Localizable", "Common.Controls.Keyboard.Common.SwitchToTab", String(describing: p1))
|
return L10n.tr("Localizable", "Common.Controls.Keyboard.Common.SwitchToTab", String(describing: p1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal enum Timeline {
|
||||||
|
/// Next Status
|
||||||
|
internal static let nextStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.NextStatus")
|
||||||
|
/// Open Author Profile
|
||||||
|
internal static let openAuthorProfile = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenAuthorProfile")
|
||||||
|
/// Open Reblogger Profile
|
||||||
|
internal static let openRebloggerProfile = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenRebloggerProfile")
|
||||||
|
/// Open Status
|
||||||
|
internal static let openStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.OpenStatus")
|
||||||
|
/// Preview Image
|
||||||
|
internal static let previewImage = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.PreviewImage")
|
||||||
|
/// Previous Status
|
||||||
|
internal static let previousStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.PreviousStatus")
|
||||||
|
/// Reply Status
|
||||||
|
internal static let replyStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ReplyStatus")
|
||||||
|
/// Toggle Content Warning
|
||||||
|
internal static let toggleContentWarning = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleContentWarning")
|
||||||
|
/// Toggle Status Favorite
|
||||||
|
internal static let toggleFavorite = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleFavorite")
|
||||||
|
/// Toggle Status Reblog
|
||||||
|
internal static let toggleReblog = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.ToggleReblog")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
internal enum Status {
|
internal enum Status {
|
||||||
/// content warning
|
/// content warning
|
||||||
|
@ -527,6 +551,16 @@ internal enum L10n {
|
||||||
internal static let mentions = L10n.tr("Localizable", "Scene.Notification.Title.Mentions")
|
internal static let mentions = L10n.tr("Localizable", "Scene.Notification.Title.Mentions")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal enum Preview {
|
||||||
|
internal enum Keyboard {
|
||||||
|
/// Close Preview
|
||||||
|
internal static let closePreview = L10n.tr("Localizable", "Scene.Preview.Keyboard.ClosePreview")
|
||||||
|
/// Show Next
|
||||||
|
internal static let showNext = L10n.tr("Localizable", "Scene.Preview.Keyboard.ShowNext")
|
||||||
|
/// Show Previous
|
||||||
|
internal static let showPrevious = L10n.tr("Localizable", "Scene.Preview.Keyboard.ShowPrevious")
|
||||||
|
}
|
||||||
|
}
|
||||||
internal enum Profile {
|
internal enum Profile {
|
||||||
/// %@ posts
|
/// %@ posts
|
||||||
internal static func subtitle(_ p1: Any) -> String {
|
internal static func subtitle(_ p1: Any) -> String {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// KeyboardPreference.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-5-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UserDefaults {
|
||||||
|
|
||||||
|
@objc dynamic var backKeyCommandPressDate: Date? {
|
||||||
|
get {
|
||||||
|
register(defaults: [#function: Date().timeIntervalSinceReferenceDate])
|
||||||
|
return Date(timeIntervalSinceReferenceDate: double(forKey: #function))
|
||||||
|
}
|
||||||
|
set { self[#function] = newValue?.timeIntervalSinceReferenceDate }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
//
|
||||||
|
// StatusProvider+KeyCommands.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-5-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension StatusTableViewCellDelegate where Self: StatusProvider & StatusTableViewControllerNavigateable {
|
||||||
|
|
||||||
|
func keyCommandHandler(_ 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)
|
||||||
|
switch navigation {
|
||||||
|
case .up: navigateStatus(direction: .up)
|
||||||
|
case .down: navigateStatus(direction: .down)
|
||||||
|
case .back: backTimeline()
|
||||||
|
case .openStatus: openStatus()
|
||||||
|
case .openAuthorProfile: openAuthorProfile()
|
||||||
|
case .openRebloggerProfile: openRebloggerProfile()
|
||||||
|
case .replyStatus: replyStatus()
|
||||||
|
case .toggleReblog: toggleReblog()
|
||||||
|
case .toggleFavorite: toggleFavorite()
|
||||||
|
case .toggleContentWarning: toggleContentWarning()
|
||||||
|
case .previewImage: previewImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// navigate status up/down
|
||||||
|
extension StatusTableViewCellDelegate where Self: StatusProvider & StatusTableViewControllerNavigateable {
|
||||||
|
|
||||||
|
private func navigateStatus(direction: StatusTableViewNavigationDirection) {
|
||||||
|
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: StatusTableViewNavigationDirection, indexPath: IndexPath) {
|
||||||
|
guard let diffableDataSource = tableViewDiffableDataSource else { return }
|
||||||
|
let items = diffableDataSource.snapshot().itemIdentifiers
|
||||||
|
guard let selectedItem = diffableDataSource.itemIdentifier(for: indexPath),
|
||||||
|
let selectedItemIndex = items.firstIndex(of: selectedItem) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _navigateToItem: Item? = {
|
||||||
|
var index = selectedItemIndex
|
||||||
|
while 0..<items.count ~= index {
|
||||||
|
index = {
|
||||||
|
switch direction {
|
||||||
|
case .up: return index - 1
|
||||||
|
case .down: return index + 1
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
guard 0..<items.count ~= index else { return nil }
|
||||||
|
let item = items[index]
|
||||||
|
|
||||||
|
guard Self.validNavigateableItem(item) else { continue }
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
guard let item = _navigateToItem, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func navigateToFirstVisibleStatus() {
|
||||||
|
guard let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows else { return }
|
||||||
|
guard let diffableDataSource = tableViewDiffableDataSource else { return }
|
||||||
|
|
||||||
|
var visibleItems: [Item] = indexPathsForVisibleRows.sorted().compactMap { indexPath in
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil }
|
||||||
|
guard Self.validNavigateableItem(item) else { return nil }
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
if indexPathsForVisibleRows.first?.row != 0, visibleItems.count > 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: Item) -> Bool {
|
||||||
|
switch item {
|
||||||
|
case .homeTimelineIndex,
|
||||||
|
.status,
|
||||||
|
.root, .leaf, .reply:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// status coordinate
|
||||||
|
extension StatusTableViewCellDelegate where Self: StatusProvider & StatusTableViewControllerNavigateable {
|
||||||
|
|
||||||
|
private func openStatus() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.coordinateToStatusThreadScene(for: .primary, provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func backTimeline() {
|
||||||
|
UserDefaults.shared.backKeyCommandPressDate = Date()
|
||||||
|
navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openAuthorProfile() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.coordinateToStatusAuthorProfileScene(for: .primary, provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openRebloggerProfile() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.coordinateToStatusAuthorProfileScene(for: .secondary, provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func replyStatus() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.responseToStatusReplyAction(provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func previewImage() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
guard let provider = self as? (StatusProvider & MediaPreviewableViewController) else { return }
|
||||||
|
guard let cell = tableView.cellForRow(at: indexPathForSelectedRow),
|
||||||
|
let presentable = cell as? MosaicImageViewContainerPresentable else { return }
|
||||||
|
let mosaicImageView = presentable.mosaicImageViewContainer
|
||||||
|
guard let imageView = mosaicImageView.imageViews.first else { return }
|
||||||
|
StatusProviderFacade.coordinateToStatusMediaPreviewScene(provider: provider, cell: cell, mosaicImageView: mosaicImageView, didTapImageView: imageView, atIndex: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle
|
||||||
|
extension StatusTableViewCellDelegate where Self: StatusProvider & StatusTableViewControllerNavigateable {
|
||||||
|
|
||||||
|
private func toggleReblog() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.responseToStatusReblogAction(provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func toggleFavorite() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.responseToStatusLikeAction(provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func toggleContentWarning() {
|
||||||
|
guard let indexPathForSelectedRow = tableView.indexPathForSelectedRow else { return }
|
||||||
|
StatusProviderFacade.responseToStatusContentWarningRevealAction(provider: self, indexPath: indexPathForSelectedRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusTableViewCellDelegate where Self: StatusProvider & StatusTableViewControllerNavigateable {
|
||||||
|
|
||||||
|
var statusNavigationKeyCommands: [UIKeyCommand] {
|
||||||
|
StatusTableViewNavigation.allCases.map { navigation in
|
||||||
|
UIKeyCommand(
|
||||||
|
title: navigation.title,
|
||||||
|
image: nil,
|
||||||
|
action: #selector(Self.keyCommandHandlerRelay(_:)),
|
||||||
|
input: navigation.input,
|
||||||
|
modifierFlags: navigation.modifierFlags,
|
||||||
|
propertyList: navigation.propertyList,
|
||||||
|
alternates: [],
|
||||||
|
discoverabilityTitle: nil,
|
||||||
|
attributes: [],
|
||||||
|
state: .off
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,14 @@ extension StatusProviderFacade {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func coordinateToStatusAuthorProfileScene(for target: Target, provider: StatusProvider, indexPath: IndexPath) {
|
||||||
|
_coordinateToStatusAuthorProfileScene(
|
||||||
|
for: target,
|
||||||
|
provider: provider,
|
||||||
|
status: provider.status(for: nil, indexPath: indexPath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
static func coordinateToStatusAuthorProfileScene(for target: Target, provider: StatusProvider, cell: UITableViewCell) {
|
static func coordinateToStatusAuthorProfileScene(for target: Target, provider: StatusProvider, cell: UITableViewCell) {
|
||||||
_coordinateToStatusAuthorProfileScene(
|
_coordinateToStatusAuthorProfileScene(
|
||||||
for: target,
|
for: target,
|
||||||
|
@ -189,6 +197,13 @@ extension StatusProviderFacade {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func responseToStatusLikeAction(provider: StatusProvider, indexPath: IndexPath) {
|
||||||
|
_responseToStatusLikeAction(
|
||||||
|
provider: provider,
|
||||||
|
status: provider.status(for: nil, indexPath: indexPath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private static func _responseToStatusLikeAction(provider: StatusProvider, status: Future<Status?, Never>) {
|
private static func _responseToStatusLikeAction(provider: StatusProvider, status: Future<Status?, Never>) {
|
||||||
// prepare authentication
|
// prepare authentication
|
||||||
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||||
|
@ -292,6 +307,13 @@ extension StatusProviderFacade {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func responseToStatusReblogAction(provider: StatusProvider, indexPath: IndexPath) {
|
||||||
|
_responseToStatusReblogAction(
|
||||||
|
provider: provider,
|
||||||
|
status: provider.status(for: nil, indexPath: indexPath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private static func _responseToStatusReblogAction(provider: StatusProvider, status: Future<Status?, Never>) {
|
private static func _responseToStatusReblogAction(provider: StatusProvider, status: Future<Status?, Never>) {
|
||||||
// prepare authentication
|
// prepare authentication
|
||||||
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
guard let activeMastodonAuthenticationBox = provider.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||||
|
@ -400,6 +422,13 @@ extension StatusProviderFacade {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func responseToStatusReplyAction(provider: StatusProvider, indexPath: IndexPath) {
|
||||||
|
_responseToStatusReplyAction(
|
||||||
|
provider: provider,
|
||||||
|
status: provider.status(for: nil, indexPath: indexPath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private static func _responseToStatusReplyAction(provider: StatusProvider, status: Future<Status?, Never>) {
|
private static func _responseToStatusReplyAction(provider: StatusProvider, status: Future<Status?, Never>) {
|
||||||
status
|
status
|
||||||
.sink { [weak provider] status in
|
.sink { [weak provider] status in
|
||||||
|
@ -450,6 +479,13 @@ extension StatusProviderFacade {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func responseToStatusContentWarningRevealAction(provider: StatusProvider, indexPath: IndexPath) {
|
||||||
|
_responseToStatusContentWarningRevealAction(
|
||||||
|
dependency: provider,
|
||||||
|
status: provider.status(for: nil, indexPath: indexPath)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private static func _responseToStatusContentWarningRevealAction(dependency: NeedsDependency, status: Future<Status?, Never>) {
|
private static func _responseToStatusContentWarningRevealAction(dependency: NeedsDependency, status: Future<Status?, Never>) {
|
||||||
status
|
status
|
||||||
.compactMap { [weak dependency] status -> AnyPublisher<Status?, Never>? in
|
.compactMap { [weak dependency] status -> AnyPublisher<Status?, Never>? in
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import AVKit
|
import AVKit
|
||||||
|
import GameController
|
||||||
|
|
||||||
// Check List Last Updated
|
// Check List Last Updated
|
||||||
// - HomeViewController: 2021/4/30
|
// - HomeViewController: 2021/4/30
|
||||||
// - FavoriteViewController: 2021/4/30
|
// - FavoriteViewController: 2021/4/30
|
||||||
// - HashtagTimelineViewController: 2021/4/30
|
// - HashtagTimelineViewController: 2021/4/30
|
||||||
|
@ -34,6 +35,12 @@ protocol StatusTableViewControllerAspect: UIViewController {
|
||||||
extension StatusTableViewControllerAspect {
|
extension StatusTableViewControllerAspect {
|
||||||
/// [UI] hook to deselect row in the transitioning for the table view
|
/// [UI] hook to deselect row in the transitioning for the table view
|
||||||
func aspectViewWillAppear(_ animated: Bool) {
|
func aspectViewWillAppear(_ animated: Bool) {
|
||||||
|
if GCKeyboard.coalesced != nil, let backKeyCommandPressDate = UserDefaults.shared.backKeyCommandPressDate {
|
||||||
|
guard backKeyCommandPressDate.timeIntervalSinceNow <= -0.5 else {
|
||||||
|
// break if interval greater than 0.5s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
tableView.deselectRow(with: transitionCoordinator, animated: animated)
|
tableView.deselectRow(with: transitionCoordinator, animated: animated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
//
|
||||||
|
// StatusTableViewControllerNavigateable.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-5-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
typealias StatusTableViewControllerNavigateable = StatusTableViewControllerNavigateableCore & StatusTableViewControllerNavigateableRelay
|
||||||
|
|
||||||
|
protocol StatusTableViewControllerNavigateableCore: AnyObject {
|
||||||
|
var tableView: UITableView { get }
|
||||||
|
var overrideNavigationScrollPosition: UITableView.ScrollPosition? { get set }
|
||||||
|
func keyCommandHandler(_ sender: UIKeyCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusTableViewControllerNavigateableCore {
|
||||||
|
var overrideNavigationScrollPosition: UITableView.ScrollPosition? {
|
||||||
|
get { return nil }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc protocol StatusTableViewControllerNavigateableRelay: AnyObject {
|
||||||
|
func keyCommandHandlerRelay(_ sender: UIKeyCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StatusTableViewNavigationDirection {
|
||||||
|
case up
|
||||||
|
case down
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum StatusTableViewNavigation: String, CaseIterable {
|
||||||
|
case up
|
||||||
|
case down
|
||||||
|
case back // pop
|
||||||
|
case openStatus
|
||||||
|
case openAuthorProfile
|
||||||
|
case openRebloggerProfile
|
||||||
|
case replyStatus
|
||||||
|
case toggleReblog
|
||||||
|
case toggleFavorite
|
||||||
|
case toggleContentWarning
|
||||||
|
case previewImage
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .up: return L10n.Common.Controls.Keyboard.Timeline.previousStatus
|
||||||
|
case .down: return L10n.Common.Controls.Keyboard.Timeline.nextStatus
|
||||||
|
case .back: return L10n.Common.Controls.Actions.back
|
||||||
|
case .openStatus: return L10n.Common.Controls.Keyboard.Timeline.openStatus
|
||||||
|
case .openAuthorProfile: return L10n.Common.Controls.Keyboard.Timeline.openAuthorProfile
|
||||||
|
case .openRebloggerProfile: return L10n.Common.Controls.Keyboard.Timeline.openRebloggerProfile
|
||||||
|
case .replyStatus: return L10n.Common.Controls.Keyboard.Timeline.replyStatus
|
||||||
|
case .toggleReblog: return L10n.Common.Controls.Keyboard.Timeline.toggleReblog
|
||||||
|
case .toggleFavorite: return L10n.Common.Controls.Keyboard.Timeline.toggleFavorite
|
||||||
|
case .toggleContentWarning: return L10n.Common.Controls.Keyboard.Timeline.toggleContentWarning
|
||||||
|
case .previewImage: return L10n.Common.Controls.Keyboard.Timeline.previewImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIKeyCommand input
|
||||||
|
var input: String {
|
||||||
|
switch self {
|
||||||
|
case .up: return "k"
|
||||||
|
case .down: return "j"
|
||||||
|
case .back: return "h"
|
||||||
|
case .openStatus: return "l" // little "L"
|
||||||
|
case .openAuthorProfile: return "p"
|
||||||
|
case .openRebloggerProfile: return "p" // + option
|
||||||
|
case .replyStatus: return "n" // + shift + command
|
||||||
|
case .toggleReblog: return "r"
|
||||||
|
case .toggleFavorite: return "f"
|
||||||
|
case .toggleContentWarning: return "o"
|
||||||
|
case .previewImage: return "i"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var modifierFlags: UIKeyModifierFlags {
|
||||||
|
switch self {
|
||||||
|
case .up: return []
|
||||||
|
case .down: return []
|
||||||
|
case .back: return []
|
||||||
|
case .openStatus: return []
|
||||||
|
case .openAuthorProfile: return []
|
||||||
|
case .openRebloggerProfile: return [.alternate]
|
||||||
|
case .replyStatus: return [.shift, .alternate]
|
||||||
|
case .toggleReblog: return []
|
||||||
|
case .toggleFavorite: return []
|
||||||
|
case .toggleContentWarning: return []
|
||||||
|
case .previewImage: return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyList: Any {
|
||||||
|
return rawValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";
|
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";
|
||||||
"Common.Controls.Actions.Preview" = "Preview";
|
"Common.Controls.Actions.Preview" = "Preview";
|
||||||
"Common.Controls.Actions.Remove" = "Remove";
|
"Common.Controls.Actions.Remove" = "Remove";
|
||||||
|
"Common.Controls.Actions.Reply" = "Reply";
|
||||||
"Common.Controls.Actions.ReportUser" = "Report %@";
|
"Common.Controls.Actions.ReportUser" = "Report %@";
|
||||||
"Common.Controls.Actions.Save" = "Save";
|
"Common.Controls.Actions.Save" = "Save";
|
||||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||||
|
@ -67,6 +68,16 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
|
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
|
||||||
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
|
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
|
||||||
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
|
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
|
||||||
|
"Common.Controls.Keyboard.Timeline.NextStatus" = "Next Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Open Author Profile";
|
||||||
|
"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Open Reblogger Profile";
|
||||||
|
"Common.Controls.Keyboard.Timeline.OpenStatus" = "Open Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.PreviewImage" = "Preview Image";
|
||||||
|
"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Previous Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Reply Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Toggle Content Warning";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Toggle Status Favorite";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Status Reblog";
|
||||||
"Common.Controls.Status.Actions.Favorite" = "Favorite";
|
"Common.Controls.Status.Actions.Favorite" = "Favorite";
|
||||||
"Common.Controls.Status.Actions.Menu" = "Menu";
|
"Common.Controls.Status.Actions.Menu" = "Menu";
|
||||||
"Common.Controls.Status.Actions.Reblog" = "Reblog";
|
"Common.Controls.Status.Actions.Reblog" = "Reblog";
|
||||||
|
@ -178,6 +189,9 @@ tap the link to confirm your account.";
|
||||||
"Scene.Notification.Action.Reblog" = "rebloged your post";
|
"Scene.Notification.Action.Reblog" = "rebloged your post";
|
||||||
"Scene.Notification.Title.Everything" = "Everything";
|
"Scene.Notification.Title.Everything" = "Everything";
|
||||||
"Scene.Notification.Title.Mentions" = "Mentions";
|
"Scene.Notification.Title.Mentions" = "Mentions";
|
||||||
|
"Scene.Preview.Keyboard.ClosePreview" = "Close Preview";
|
||||||
|
"Scene.Preview.Keyboard.ShowNext" = "Show Next";
|
||||||
|
"Scene.Preview.Keyboard.ShowPrevious" = "Show Previous";
|
||||||
"Scene.Profile.Dashboard.Accessibility.CountFollowers" = "%ld followers";
|
"Scene.Profile.Dashboard.Accessibility.CountFollowers" = "%ld followers";
|
||||||
"Scene.Profile.Dashboard.Accessibility.CountFollowing" = "%ld following";
|
"Scene.Profile.Dashboard.Accessibility.CountFollowing" = "%ld following";
|
||||||
"Scene.Profile.Dashboard.Accessibility.CountPosts" = "%ld posts";
|
"Scene.Profile.Dashboard.Accessibility.CountPosts" = "%ld posts";
|
||||||
|
|
|
@ -34,6 +34,7 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";
|
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";
|
||||||
"Common.Controls.Actions.Preview" = "Preview";
|
"Common.Controls.Actions.Preview" = "Preview";
|
||||||
"Common.Controls.Actions.Remove" = "Remove";
|
"Common.Controls.Actions.Remove" = "Remove";
|
||||||
|
"Common.Controls.Actions.Reply" = "Reply";
|
||||||
"Common.Controls.Actions.ReportUser" = "Report %@";
|
"Common.Controls.Actions.ReportUser" = "Report %@";
|
||||||
"Common.Controls.Actions.Save" = "Save";
|
"Common.Controls.Actions.Save" = "Save";
|
||||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||||
|
@ -67,6 +68,16 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
|
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
|
||||||
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
|
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
|
||||||
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
|
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
|
||||||
|
"Common.Controls.Keyboard.Timeline.NextStatus" = "Next Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Open Author Profile";
|
||||||
|
"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Open Reblogger Profile";
|
||||||
|
"Common.Controls.Keyboard.Timeline.OpenStatus" = "Open Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.PreviewImage" = "Preview Image";
|
||||||
|
"Common.Controls.Keyboard.Timeline.PreviousStatus" = "Previous Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ReplyStatus" = "Reply Status";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ToggleContentWarning" = "Toggle Content Warning";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ToggleFavorite" = "Toggle Status Favorite";
|
||||||
|
"Common.Controls.Keyboard.Timeline.ToggleReblog" = "Toggle Status Reblog";
|
||||||
"Common.Controls.Status.Actions.Favorite" = "Favorite";
|
"Common.Controls.Status.Actions.Favorite" = "Favorite";
|
||||||
"Common.Controls.Status.Actions.Menu" = "Menu";
|
"Common.Controls.Status.Actions.Menu" = "Menu";
|
||||||
"Common.Controls.Status.Actions.Reblog" = "Reblog";
|
"Common.Controls.Status.Actions.Reblog" = "Reblog";
|
||||||
|
@ -178,6 +189,9 @@ tap the link to confirm your account.";
|
||||||
"Scene.Notification.Action.Reblog" = "rebloged your post";
|
"Scene.Notification.Action.Reblog" = "rebloged your post";
|
||||||
"Scene.Notification.Title.Everything" = "Everything";
|
"Scene.Notification.Title.Everything" = "Everything";
|
||||||
"Scene.Notification.Title.Mentions" = "Mentions";
|
"Scene.Notification.Title.Mentions" = "Mentions";
|
||||||
|
"Scene.Preview.Keyboard.ClosePreview" = "Close Preview";
|
||||||
|
"Scene.Preview.Keyboard.ShowNext" = "Show Next";
|
||||||
|
"Scene.Preview.Keyboard.ShowPrevious" = "Show Previous";
|
||||||
"Scene.Profile.Dashboard.Accessibility.CountFollowers" = "%ld followers";
|
"Scene.Profile.Dashboard.Accessibility.CountFollowers" = "%ld followers";
|
||||||
"Scene.Profile.Dashboard.Accessibility.CountFollowing" = "%ld following";
|
"Scene.Profile.Dashboard.Accessibility.CountFollowing" = "%ld following";
|
||||||
"Scene.Profile.Dashboard.Accessibility.CountPosts" = "%ld posts";
|
"Scene.Profile.Dashboard.Accessibility.CountPosts" = "%ld posts";
|
||||||
|
|
|
@ -339,3 +339,16 @@ extension HashtagTimelineViewController: StatusTableViewCellDelegate {
|
||||||
weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self }
|
weak var playerViewControllerDelegate: AVPlayerViewControllerDelegate? { return self }
|
||||||
func parent() -> UIViewController { return self }
|
func parent() -> UIViewController { return self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension HashtagTimelineViewController {
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
return statusNavigationKeyCommands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - StatusTableViewControllerNavigateable
|
||||||
|
extension HashtagTimelineViewController: StatusTableViewControllerNavigateable {
|
||||||
|
@objc func keyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||||
|
keyCommandHandler(sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -537,3 +537,16 @@ extension HomeTimelineViewController: HomeTimelineNavigationBarTitleViewDelegate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension HomeTimelineViewController {
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
return statusNavigationKeyCommands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - StatusTableViewControllerNavigateable
|
||||||
|
extension HomeTimelineViewController: StatusTableViewControllerNavigateable {
|
||||||
|
@objc func keyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||||
|
keyCommandHandler(sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -240,3 +240,74 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MediaPreviewViewController {
|
||||||
|
|
||||||
|
var closeKeyCommand: UIKeyCommand {
|
||||||
|
UIKeyCommand(
|
||||||
|
title: L10n.Scene.Preview.Keyboard.closePreview,
|
||||||
|
image: nil,
|
||||||
|
action: #selector(MediaPreviewViewController.closePreviewKeyCommandHandler(_:)),
|
||||||
|
input: "i",
|
||||||
|
modifierFlags: [],
|
||||||
|
propertyList: nil,
|
||||||
|
alternates: [],
|
||||||
|
discoverabilityTitle: nil,
|
||||||
|
attributes: [],
|
||||||
|
state: .off
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var showNextKeyCommand: UIKeyCommand {
|
||||||
|
UIKeyCommand(
|
||||||
|
title: L10n.Scene.Preview.Keyboard.closePreview,
|
||||||
|
image: nil,
|
||||||
|
action: #selector(MediaPreviewViewController.showNextKeyCommandHandler(_:)),
|
||||||
|
input: "j",
|
||||||
|
modifierFlags: [],
|
||||||
|
propertyList: nil,
|
||||||
|
alternates: [],
|
||||||
|
discoverabilityTitle: nil,
|
||||||
|
attributes: [],
|
||||||
|
state: .off
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var showPreviousKeyCommand: UIKeyCommand {
|
||||||
|
UIKeyCommand(
|
||||||
|
title: L10n.Scene.Preview.Keyboard.closePreview,
|
||||||
|
image: nil,
|
||||||
|
action: #selector(MediaPreviewViewController.showPreviousKeyCommandHandler(_:)),
|
||||||
|
input: "k",
|
||||||
|
modifierFlags: [],
|
||||||
|
propertyList: nil,
|
||||||
|
alternates: [],
|
||||||
|
discoverabilityTitle: nil,
|
||||||
|
attributes: [],
|
||||||
|
state: .off
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override var keyCommands: [UIKeyCommand] {
|
||||||
|
return [
|
||||||
|
closeKeyCommand,
|
||||||
|
showNextKeyCommand,
|
||||||
|
showPreviousKeyCommand,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func closePreviewKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||||
|
dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func showNextKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||||
|
pagingViewConttroller.scrollToPage(.next, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func showPreviousKeyCommandHandler(_ sender: UIKeyCommand) {
|
||||||
|
pagingViewConttroller.scrollToPage(.previous, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,3 +173,16 @@ extension FavoriteViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.stateMachine }
|
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.stateMachine }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension FavoriteViewController {
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
return statusNavigationKeyCommands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - StatusTableViewControllerNavigateable
|
||||||
|
extension FavoriteViewController: StatusTableViewControllerNavigateable {
|
||||||
|
@objc func keyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||||
|
keyCommandHandler(sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -440,6 +440,13 @@ extension ProfileViewController {
|
||||||
viewModel.isEditing
|
viewModel.isEditing
|
||||||
.handleEvents(receiveOutput: { [weak self] isEditing in
|
.handleEvents(receiveOutput: { [weak self] isEditing in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
// set firset responder for key command
|
||||||
|
if !isEditing {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
|
self.profileSegmentedViewController.pagingViewController.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dismiss keyboard if needs
|
// dismiss keyboard if needs
|
||||||
if !isEditing { self.view.endEditing(true) }
|
if !isEditing { self.view.endEditing(true) }
|
||||||
|
|
||||||
|
@ -860,7 +867,6 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed dwersDashboardMeterView: ProfileStatusDashboardMeterView) {
|
func profileHeaderView(_ profileHeaderView: ProfileHeaderView, profileStatusDashboardView: ProfileStatusDashboardView, followersDashboardMeterViewDidPressed dwersDashboardMeterView: ProfileStatusDashboardMeterView) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -869,3 +875,4 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||||
extension ProfileViewController: ScrollViewContainer {
|
extension ProfileViewController: ScrollViewContainer {
|
||||||
var scrollView: UIScrollView { return overlayScrollView }
|
var scrollView: UIScrollView { return overlayScrollView }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,15 @@ final class ProfilePagingViewController: TabmanViewController {
|
||||||
super.pageboyViewController(pageboyViewController, didScrollToPageAt: index, direction: direction, animated: animated)
|
super.pageboyViewController(pageboyViewController, didScrollToPageAt: index, direction: direction, animated: animated)
|
||||||
|
|
||||||
let viewController = viewModel.viewControllers[index]
|
let viewController = viewModel.viewControllers[index]
|
||||||
|
(viewController as? StatusTableViewControllerNavigateable)?.overrideNavigationScrollPosition = .top
|
||||||
pagingDelegate?.profilePagingViewController(self, didScrollToPostCustomScrollViewContainerController: viewController, atIndex: index)
|
pagingDelegate?.profilePagingViewController(self, didScrollToPostCustomScrollViewContainerController: viewController, atIndex: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make key commands works
|
||||||
|
override var canBecomeFirstResponder: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
}
|
}
|
||||||
|
@ -43,5 +49,23 @@ extension ProfilePagingViewController {
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
dataSource = viewModel
|
dataSource = viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ProfilePagingViewController {
|
||||||
|
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
return currentViewController?.keyCommands
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func keyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||||
|
(currentViewController as? StatusTableViewControllerNavigateable)?.keyCommandHandlerRelay(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ final class ProfileSegmentedViewController: UIViewController {
|
||||||
deinit {
|
deinit {
|
||||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileSegmentedViewController {
|
extension ProfileSegmentedViewController {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import GameplayKit
|
||||||
|
|
||||||
// TODO: adopt MediaPreviewableViewController
|
// TODO: adopt MediaPreviewableViewController
|
||||||
final class UserTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
final class UserTimelineViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ final class UserTimelineViewController: UIViewController, NeedsDependency, Media
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var overrideNavigationScrollPosition: UITableView.ScrollPosition? = nil
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log("%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
}
|
}
|
||||||
|
@ -185,3 +187,22 @@ extension UserTimelineViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
var loadMoreConfigurableTableView: UITableView { return tableView }
|
var loadMoreConfigurableTableView: UITableView { return tableView }
|
||||||
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.stateMachine }
|
var loadMoreConfigurableStateMachine: GKStateMachine { return viewModel.stateMachine }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension UserTimelineViewController {
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
return [
|
||||||
|
UIKeyCommand(title: "Test", image: nil, action: #selector(UserTimelineViewController.test(_:)), input: "t", modifierFlags: [], propertyList: nil, alternates: [], discoverabilityTitle: nil, attributes: [], state: .off)
|
||||||
|
] + statusNavigationKeyCommands
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func test(_ sender: UIKeyCommand) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - StatusTableViewControllerNavigateable
|
||||||
|
extension UserTimelineViewController: StatusTableViewControllerNavigateable {
|
||||||
|
@objc func keyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||||
|
keyCommandHandler(sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -56,7 +56,6 @@ final class StatusTableViewCell: UITableViewCell, StatusCell {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
var pollCountdownSubscription: AnyCancellable?
|
var pollCountdownSubscription: AnyCancellable?
|
||||||
var observations = Set<NSKeyValueObservation>()
|
var observations = Set<NSKeyValueObservation>()
|
||||||
private var selectionBackgroundViewObservation: NSKeyValueObservation?
|
|
||||||
|
|
||||||
let statusView = StatusView()
|
let statusView = StatusView()
|
||||||
let threadMetaStackView = UIStackView()
|
let threadMetaStackView = UIStackView()
|
||||||
|
|
|
@ -227,3 +227,16 @@ extension ThreadViewController: ThreadReplyLoaderTableViewCellDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ThreadViewController {
|
||||||
|
override var keyCommands: [UIKeyCommand]? {
|
||||||
|
return statusNavigationKeyCommands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - StatusTableViewControllerNavigateable
|
||||||
|
extension ThreadViewController: StatusTableViewControllerNavigateable {
|
||||||
|
@objc func keyCommandHandlerRelay(_ sender: UIKeyCommand) {
|
||||||
|
keyCommandHandler(sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue