feat: add keyboard shortcuts for settings and favorites
This commit is contained in:
parent
5cbfa28b93
commit
5c6618f13e
|
@ -78,9 +78,13 @@
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"notification": "Notification",
|
"notification": "Notification",
|
||||||
"profile": "Profile",
|
"profile": "Profile"
|
||||||
"keyboard": {
|
},
|
||||||
"switch_to_tab": "Switch to %s"
|
"keyboard": {
|
||||||
|
"common": {
|
||||||
|
"switch_to_tab": "Switch to %s",
|
||||||
|
"show_favorites": "Show Favorites",
|
||||||
|
"open_settings": "Open Settings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
@ -480,6 +484,9 @@
|
||||||
"clear": "Clear Media Cache",
|
"clear": "Clear Media Cache",
|
||||||
"signout": "Sign Out"
|
"signout": "Sign Out"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"keyboard": {
|
||||||
|
"close_settings_window": "Close Settings Window"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"report": {
|
"report": {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -189,6 +189,18 @@ internal enum L10n {
|
||||||
return L10n.tr("Localizable", "Common.Controls.Firendship.UnmuteUser", String(describing: p1))
|
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 {
|
internal enum Status {
|
||||||
/// content warning
|
/// content warning
|
||||||
internal static let contentWarning = L10n.tr("Localizable", "Common.Controls.Status.ContentWarning")
|
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")
|
internal static let profile = L10n.tr("Localizable", "Common.Controls.Tabs.Profile")
|
||||||
/// Search
|
/// Search
|
||||||
internal static let search = L10n.tr("Localizable", "Common.Controls.Tabs.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 Timeline {
|
||||||
internal enum Accessibility {
|
internal enum Accessibility {
|
||||||
|
@ -828,6 +834,10 @@ internal enum L10n {
|
||||||
internal enum Settings {
|
internal enum Settings {
|
||||||
/// Settings
|
/// Settings
|
||||||
internal static let title = L10n.tr("Localizable", "Scene.Settings.Title")
|
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 Section {
|
||||||
internal enum Appearance {
|
internal enum Appearance {
|
||||||
/// Automatic
|
/// Automatic
|
||||||
|
|
|
@ -64,6 +64,9 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
|
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
|
||||||
"Common.Controls.Firendship.Unmute" = "Unmute";
|
"Common.Controls.Firendship.Unmute" = "Unmute";
|
||||||
"Common.Controls.Firendship.UnmuteUser" = "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.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";
|
||||||
|
@ -91,7 +94,6 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
|
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
|
||||||
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
|
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
|
||||||
"Common.Controls.Tabs.Home" = "Home";
|
"Common.Controls.Tabs.Home" = "Home";
|
||||||
"Common.Controls.Tabs.Keyboard.SwitchToTab" = "Switch to %@";
|
|
||||||
"Common.Controls.Tabs.Notification" = "Notification";
|
"Common.Controls.Tabs.Notification" = "Notification";
|
||||||
"Common.Controls.Tabs.Profile" = "Profile";
|
"Common.Controls.Tabs.Profile" = "Profile";
|
||||||
"Common.Controls.Tabs.Search" = "Search";
|
"Common.Controls.Tabs.Search" = "Search";
|
||||||
|
@ -273,6 +275,7 @@ any server.";
|
||||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||||
"Scene.ServerRules.TermsOfService" = "terms of service";
|
"Scene.ServerRules.TermsOfService" = "terms of service";
|
||||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||||
|
"Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window";
|
||||||
"Scene.Settings.Section.Appearance.Automatic" = "Automatic";
|
"Scene.Settings.Section.Appearance.Automatic" = "Automatic";
|
||||||
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
||||||
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
||||||
|
|
|
@ -64,6 +64,9 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
|
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
|
||||||
"Common.Controls.Firendship.Unmute" = "Unmute";
|
"Common.Controls.Firendship.Unmute" = "Unmute";
|
||||||
"Common.Controls.Firendship.UnmuteUser" = "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.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";
|
||||||
|
@ -91,7 +94,6 @@ Please check your internet connection.";
|
||||||
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
|
"Common.Controls.Status.UserReblogged" = "%@ reblogged";
|
||||||
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
|
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
|
||||||
"Common.Controls.Tabs.Home" = "Home";
|
"Common.Controls.Tabs.Home" = "Home";
|
||||||
"Common.Controls.Tabs.Keyboard.SwitchToTab" = "Switch to %@";
|
|
||||||
"Common.Controls.Tabs.Notification" = "Notification";
|
"Common.Controls.Tabs.Notification" = "Notification";
|
||||||
"Common.Controls.Tabs.Profile" = "Profile";
|
"Common.Controls.Tabs.Profile" = "Profile";
|
||||||
"Common.Controls.Tabs.Search" = "Search";
|
"Common.Controls.Tabs.Search" = "Search";
|
||||||
|
@ -273,6 +275,7 @@ any server.";
|
||||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||||
"Scene.ServerRules.TermsOfService" = "terms of service";
|
"Scene.ServerRules.TermsOfService" = "terms of service";
|
||||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||||
|
"Scene.Settings.Keyboard.CloseSettingsWindow" = "Close Settings Window";
|
||||||
"Scene.Settings.Section.Appearance.Automatic" = "Automatic";
|
"Scene.Settings.Section.Appearance.Automatic" = "Automatic";
|
||||||
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
"Scene.Settings.Section.Appearance.Dark" = "Always Dark";
|
||||||
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
"Scene.Settings.Section.Appearance.Light" = "Always Light";
|
||||||
|
|
|
@ -190,28 +190,126 @@ extension MainTabBarController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HIG: keyboard UX
|
||||||
|
// https://developer.apple.com/design/human-interface-guidelines/macos/user-interaction/keyboard/
|
||||||
extension MainTabBarController {
|
extension MainTabBarController {
|
||||||
|
|
||||||
override var keyCommands: [UIKeyCommand]? {
|
var switchToTabKeyCommands: [UIKeyCommand] {
|
||||||
var commands: [UIKeyCommand] = []
|
var commands: [UIKeyCommand] = []
|
||||||
for (i, tab) in Tab.allCases.enumerated() {
|
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 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)
|
commands.append(command)
|
||||||
|
|
||||||
}
|
}
|
||||||
return commands
|
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,
|
guard let rawValue = sender.propertyList as? Int,
|
||||||
let tab = Tab(rawValue: rawValue) else { return }
|
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)
|
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 }
|
guard let index = Tab.allCases.firstIndex(of: tab) else { return }
|
||||||
|
let previousTab = Tab(rawValue: selectedIndex)
|
||||||
selectedIndex = index
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
#if canImport(SwiftUI) && DEBUG
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue