feat: add keyboard shortcuts for settings and favorites

This commit is contained in:
CMK 2021-05-19 16:45:01 +08:00
parent 5cbfa28b93
commit 5c6618f13e
7 changed files with 186 additions and 17 deletions

View File

@ -78,9 +78,13 @@
"home": "Home",
"search": "Search",
"notification": "Notification",
"profile": "Profile",
"keyboard": {
"switch_to_tab": "Switch to %s"
"profile": "Profile"
},
"keyboard": {
"common": {
"switch_to_tab": "Switch to %s",
"show_favorites": "Show Favorites",
"open_settings": "Open Settings"
}
},
"status": {
@ -480,6 +484,9 @@
"clear": "Clear Media Cache",
"signout": "Sign Out"
}
},
"keyboard": {
"close_settings_window": "Close Settings Window"
}
},
"report": {

View File

@ -112,3 +112,16 @@ extension UIViewController {
}
}
extension UIViewController {
/// https://stackoverflow.com/a/27301207/3797903
var isModal: Bool {
let presentingIsModal = presentingViewController != nil
let presentingIsNavigation = navigationController != nil && navigationController?.presentingViewController?.presentedViewController == navigationController
let presentingIsTabBar = tabBarController?.presentingViewController is UITabBarController
return presentingIsModal || presentingIsNavigation || presentingIsTabBar
}
}

View File

@ -189,6 +189,18 @@ internal enum L10n {
return L10n.tr("Localizable", "Common.Controls.Firendship.UnmuteUser", String(describing: p1))
}
}
internal enum Keyboard {
internal enum Common {
/// Open Settings
internal static let openSettings = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.OpenSettings")
/// Show Favorites
internal static let showFavorites = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.ShowFavorites")
/// Switch to %@
internal static func switchToTab(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Keyboard.Common.SwitchToTab", String(describing: p1))
}
}
}
internal enum Status {
/// content warning
internal static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning")
@ -278,12 +290,6 @@ internal enum L10n {
internal static let profile = L10n.tr("Localizable", "Common.Controls.Tabs.Profile")
/// Search
internal static let search = L10n.tr("Localizable", "Common.Controls.Tabs.Search")
internal enum Keyboard {
/// Switch to %@
internal static func switchToTab(_ p1: Any) -> String {
return L10n.tr("Localizable", "Common.Controls.Tabs.Keyboard.SwitchToTab", String(describing: p1))
}
}
}
internal enum Timeline {
internal enum Accessibility {
@ -828,6 +834,10 @@ internal enum L10n {
internal enum Settings {
/// Settings
internal static let title = L10n.tr("Localizable", "Scene.Settings.Title")
internal enum Keyboard {
/// Close Settings Window
internal static let closeSettingsWindow = L10n.tr("Localizable", "Scene.Settings.Keyboard.CloseSettingsWindow")
}
internal enum Section {
internal enum Appearance {
/// Automatic

View File

@ -64,6 +64,9 @@ Please check your internet connection.";
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
"Common.Controls.Firendship.Unmute" = "Unmute";
"Common.Controls.Firendship.UnmuteUser" = "Unmute %@";
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
"Common.Controls.Status.Actions.Favorite" = "Favorite";
"Common.Controls.Status.Actions.Menu" = "Menu";
"Common.Controls.Status.Actions.Reblog" = "Reblog";
@ -91,7 +94,6 @@ Please check your internet connection.";
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
"Common.Controls.Tabs.Home" = "Home";
"Common.Controls.Tabs.Keyboard.SwitchToTab" = "Switch to %@";
"Common.Controls.Tabs.Notification" = "Notification";
"Common.Controls.Tabs.Profile" = "Profile";
"Common.Controls.Tabs.Search" = "Search";
@ -273,6 +275,7 @@ any server.";
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
"Scene.ServerRules.TermsOfService" = "terms of service";
"Scene.ServerRules.Title" = "Some ground rules.";
"Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window";
"Scene.Settings.Section.Appearance.Automatic" = "Automatic";
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
"Scene.Settings.Section.Appearance.Light" = "Always Light";

View File

@ -64,6 +64,9 @@ Please check your internet connection.";
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
"Common.Controls.Firendship.Unmute" = "Unmute";
"Common.Controls.Firendship.UnmuteUser" = "Unmute %@";
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
"Common.Controls.Status.Actions.Favorite" = "Favorite";
"Common.Controls.Status.Actions.Menu" = "Menu";
"Common.Controls.Status.Actions.Reblog" = "Reblog";
@ -91,7 +94,6 @@ Please check your internet connection.";
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
"Common.Controls.Tabs.Home" = "Home";
"Common.Controls.Tabs.Keyboard.SwitchToTab" = "Switch to %@";
"Common.Controls.Tabs.Notification" = "Notification";
"Common.Controls.Tabs.Profile" = "Profile";
"Common.Controls.Tabs.Search" = "Search";
@ -273,6 +275,7 @@ any server.";
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
"Scene.ServerRules.TermsOfService" = "terms of service";
"Scene.ServerRules.Title" = "Some ground rules.";
"Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window";
"Scene.Settings.Section.Appearance.Automatic" = "Automatic";
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
"Scene.Settings.Section.Appearance.Light" = "Always Light";

View File

@ -190,28 +190,126 @@ extension MainTabBarController {
}
// HIG: keyboard UX
// https://developer.apple.com/design/human-interface-guidelines/macos/user-interaction/keyboard/
extension MainTabBarController {
override var keyCommands: [UIKeyCommand]? {
var switchToTabKeyCommands: [UIKeyCommand] {
var commands: [UIKeyCommand] = []
for (i, tab) in Tab.allCases.enumerated() {
let title = L10n.Common.Controls.Tabs.Keyboard.switchToTab(tab.title)
let title = L10n.Common.Controls.Keyboard.Common.switchToTab(tab.title)
let input = String(i + 1)
let command = UIKeyCommand(title: title, image: nil, action: #selector(MainTabBarController.switchToTab(_:)), input: input, modifierFlags: .control, propertyList: tab.rawValue, alternates: [], discoverabilityTitle: nil, attributes: [], state: .off)
let command = UIKeyCommand(
title: title,
image: nil,
action: #selector(MainTabBarController.switchToTabKeyCommandHandler(_:)),
input: input,
modifierFlags: .command,
propertyList: tab.rawValue,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
commands.append(command)
}
return commands
}
@objc private func switchToTab(_ sender: UIKeyCommand) {
var showFavoritesKeyCommand: UIKeyCommand {
UIKeyCommand(
title: L10n.Common.Controls.Keyboard.Common.showFavorites,
image: nil,
action: #selector(MainTabBarController.showFavoritesKeyCommandHandler(_:)),
input: "f",
modifierFlags: .command,
propertyList: nil,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
var openSettingsKeyCommand: UIKeyCommand {
UIKeyCommand(
title: L10n.Common.Controls.Keyboard.Common.openSettings,
image: nil,
action: #selector(MainTabBarController.openSettingsKeyCommandHandler(_:)),
input: ",",
modifierFlags: .command,
propertyList: nil,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
override var keyCommands: [UIKeyCommand]? {
guard let topMost = self.topMost else {
return []
}
var commands: [UIKeyCommand] = []
if topMost.isModal {
} else {
// switch tabs
commands.append(contentsOf: switchToTabKeyCommands)
// show favorites
if !(self.topMost is FavoriteViewController) {
commands.append(showFavoritesKeyCommand)
}
// open settings
if context.settingService.currentSetting.value != nil {
commands.append(openSettingsKeyCommand)
}
}
return commands
}
@objc private func switchToTabKeyCommandHandler(_ sender: UIKeyCommand) {
guard let rawValue = sender.propertyList as? Int,
let tab = Tab(rawValue: rawValue) else { return }
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, tab.title)
guard let index = Tab.allCases.firstIndex(of: tab) else { return }
let previousTab = Tab(rawValue: selectedIndex)
selectedIndex = index
if let previousTab = previousTab {
switch (tab, previousTab) {
case (.home, .home):
guard let navigationController = topMost?.navigationController else { return }
if navigationController.viewControllers.count > 1 {
// pop to top when previous tab position already is home
navigationController.popToRootViewController(animated: true)
} else if let homeTimelineViewController = topMost as? HomeTimelineViewController {
// trigger scrollToTop if topMost is home timeline
homeTimelineViewController.scrollToTop(animated: true)
}
default:
break
}
}
}
@objc private func showFavoritesKeyCommandHandler(_ sender: UIKeyCommand) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let favoriteViewModel = FavoriteViewModel(context: context)
coordinator.present(scene: .favorite(viewModel: favoriteViewModel), from: nil, transition: .show)
}
@objc private func openSettingsKeyCommandHandler(_ sender: UIKeyCommand) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let setting = context.settingService.currentSetting.value else { return }
let settingsViewModel = SettingsViewModel(context: context, setting: setting)
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
}

View File

@ -439,6 +439,41 @@ extension SettingsViewController: ActiveLabelDelegate {
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension SettingsViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .formSheet
}
}
extension SettingsViewController {
var closeKeyCommand: UIKeyCommand {
UIKeyCommand(
title: L10n.Scene.Settings.Keyboard.closeSettingsWindow,
image: nil,
action: #selector(SettingsViewController.closeSettingsWindowKeyCommandHandler(_:)),
input: "w",
modifierFlags: .command,
propertyList: nil,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
override var keyCommands: [UIKeyCommand]? {
return [closeKeyCommand]
}
@objc private func closeSettingsWindowKeyCommandHandler(_ sender: UIKeyCommand) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
dismiss(animated: true, completion: nil)
}
}
#if canImport(SwiftUI) && DEBUG
import SwiftUI