feat: add keyboard shortcuts for compose scene

This commit is contained in:
CMK 2021-05-21 19:12:01 +08:00
parent 443c863465
commit 0bcfc14aa6
7 changed files with 218 additions and 15 deletions

View File

@ -87,10 +87,9 @@
"keyboard": {
"common": {
"switch_to_tab": "Switch to %s",
"compose_new_post": "Compose New Post",
"show_favorites": "Show Favorites",
"open_settings": "Open Settings",
"previous_section": "Previous Section",
"next_section": "Next Section"
"open_settings": "Open Settings"
},
"timeline": {
"previous_status": "Previous Status",
@ -103,6 +102,10 @@
"toggle_favorite": "Toggle Status Favorite",
"toggle_content_warning": "Toggle Content Warning",
"preview_image": "Preview Image"
},
"segmented_control": {
"previous_section": "Previous Section",
"next_section": "Next Section"
}
},
"status": {
@ -379,6 +382,14 @@
"post_visibility_menu": "Post visibility menu",
"input_limit_remains_count": "Input limit remains %ld",
"input_limit_exceeds_count": "Input limit exceeds %ld"
},
"keyboard": {
"discard_post": "Discard Post",
"publish_post": "Publish Post",
"toggle_poll": "Toggle Poll",
"toggle_content_warning": "Toggle Content Warning",
"append_attachment_entry": "Append Attachment - %s",
"select_visibility_entry": "Select Visibility - %s"
}
},
"profile": {

View File

@ -199,12 +199,10 @@ internal enum L10n {
}
internal enum Keyboard {
internal enum Common {
/// Next Section
internal static let nextSection = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.NextSection")
/// Compose New Post
internal static let composeNewPost = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.ComposeNewPost")
/// Open Settings
internal static let openSettings = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.OpenSettings")
/// Previous Section
internal static let previousSection = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.PreviousSection")
/// Show Favorites
internal static let showFavorites = L10n.tr("Localizable", "Common.Controls.Keyboard.Common.ShowFavorites")
/// Switch to %@
@ -212,6 +210,12 @@ internal enum L10n {
return L10n.tr("Localizable", "Common.Controls.Keyboard.Common.SwitchToTab", String(describing: p1))
}
}
internal enum SegmentedControl {
/// Next Section
internal static let nextSection = L10n.tr("Localizable", "Common.Controls.Keyboard.SegmentedControl.NextSection")
/// Previous Section
internal static let previousSection = L10n.tr("Localizable", "Common.Controls.Keyboard.SegmentedControl.PreviousSection")
}
internal enum Timeline {
/// Next Status
internal static let nextStatus = L10n.tr("Localizable", "Common.Controls.Keyboard.Timeline.NextStatus")
@ -436,6 +440,24 @@ internal enum L10n {
/// Write an accurate warning here...
internal static let placeholder = L10n.tr("Localizable", "Scene.Compose.ContentWarning.Placeholder")
}
internal enum Keyboard {
/// Append Attachment - %@
internal static func appendAttachmentEntry(_ p1: Any) -> String {
return L10n.tr("Localizable", "Scene.Compose.Keyboard.AppendAttachmentEntry", String(describing: p1))
}
/// Discard Post
internal static let discardPost = L10n.tr("Localizable", "Scene.Compose.Keyboard.DiscardPost")
/// Publish Post
internal static let publishPost = L10n.tr("Localizable", "Scene.Compose.Keyboard.PublishPost")
/// Select Visibility - %@
internal static func selectVisibilityEntry(_ p1: Any) -> String {
return L10n.tr("Localizable", "Scene.Compose.Keyboard.SelectVisibilityEntry", String(describing: p1))
}
/// Toggle Content Warning
internal static let toggleContentWarning = L10n.tr("Localizable", "Scene.Compose.Keyboard.ToggleContentWarning")
/// Toggle Poll
internal static let togglePoll = L10n.tr("Localizable", "Scene.Compose.Keyboard.TogglePoll")
}
internal enum MediaSelection {
/// Browse
internal static let browse = L10n.tr("Localizable", "Scene.Compose.MediaSelection.Browse")

View File

@ -27,8 +27,8 @@ enum SegmentedControlNavigationDirection: String, CaseIterable {
var title: String {
switch self {
case .previous: return L10n.Common.Controls.Keyboard.Common.previousSection
case .next: return L10n.Common.Controls.Keyboard.Common.nextSection
case .previous: return L10n.Common.Controls.Keyboard.SegmentedControl.previousSection
case .next: return L10n.Common.Controls.Keyboard.SegmentedControl.nextSection
}
}

View File

@ -68,11 +68,12 @@ Please check your internet connection.";
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
"Common.Controls.Firendship.Unmute" = "Unmute";
"Common.Controls.Firendship.UnmuteUser" = "Unmute %@";
"Common.Controls.Keyboard.Common.NextSection" = "Next Section";
"Common.Controls.Keyboard.Common.ComposeNewPost" = "Compose New Post";
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
"Common.Controls.Keyboard.Common.PreviousSection" = "Previous Section";
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Next Section";
"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Previous Section";
"Common.Controls.Keyboard.Timeline.NextStatus" = "Next Status";
"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Open Author Profile";
"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Open Reblogger Profile";
@ -149,6 +150,12 @@ uploaded to Mastodon.";
"Scene.Compose.ComposeAction" = "Publish";
"Scene.Compose.ContentInputPlaceholder" = "Type or paste what's on your mind";
"Scene.Compose.ContentWarning.Placeholder" = "Write an accurate warning here...";
"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Append Attachment - %@";
"Scene.Compose.Keyboard.DiscardPost" = "Discard Post";
"Scene.Compose.Keyboard.PublishPost" = "Publish Post";
"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Select Visibility - %@";
"Scene.Compose.Keyboard.ToggleContentWarning" = "Toggle Content Warning";
"Scene.Compose.Keyboard.TogglePoll" = "Toggle Poll";
"Scene.Compose.MediaSelection.Browse" = "Browse";
"Scene.Compose.MediaSelection.Camera" = "Take Photo";
"Scene.Compose.MediaSelection.PhotoLibrary" = "Photo Library";

View File

@ -68,11 +68,12 @@ Please check your internet connection.";
"Common.Controls.Firendship.UnblockUser" = "Unblock %@";
"Common.Controls.Firendship.Unmute" = "Unmute";
"Common.Controls.Firendship.UnmuteUser" = "Unmute %@";
"Common.Controls.Keyboard.Common.NextSection" = "Next Section";
"Common.Controls.Keyboard.Common.ComposeNewPost" = "Compose New Post";
"Common.Controls.Keyboard.Common.OpenSettings" = "Open Settings";
"Common.Controls.Keyboard.Common.PreviousSection" = "Previous Section";
"Common.Controls.Keyboard.Common.ShowFavorites" = "Show Favorites";
"Common.Controls.Keyboard.Common.SwitchToTab" = "Switch to %@";
"Common.Controls.Keyboard.SegmentedControl.NextSection" = "Next Section";
"Common.Controls.Keyboard.SegmentedControl.PreviousSection" = "Previous Section";
"Common.Controls.Keyboard.Timeline.NextStatus" = "Next Status";
"Common.Controls.Keyboard.Timeline.OpenAuthorProfile" = "Open Author Profile";
"Common.Controls.Keyboard.Timeline.OpenRebloggerProfile" = "Open Reblogger Profile";
@ -149,6 +150,12 @@ uploaded to Mastodon.";
"Scene.Compose.ComposeAction" = "Publish";
"Scene.Compose.ContentInputPlaceholder" = "Type or paste what's on your mind";
"Scene.Compose.ContentWarning.Placeholder" = "Write an accurate warning here...";
"Scene.Compose.Keyboard.AppendAttachmentEntry" = "Append Attachment - %@";
"Scene.Compose.Keyboard.DiscardPost" = "Discard Post";
"Scene.Compose.Keyboard.PublishPost" = "Publish Post";
"Scene.Compose.Keyboard.SelectVisibilityEntry" = "Select Visibility - %@";
"Scene.Compose.Keyboard.ToggleContentWarning" = "Toggle Content Warning";
"Scene.Compose.Keyboard.TogglePoll" = "Toggle Poll";
"Scene.Compose.MediaSelection.Browse" = "Browse";
"Scene.Compose.MediaSelection.Camera" = "Take Photo";
"Scene.Compose.MediaSelection.PhotoLibrary" = "Photo Library";

View File

@ -37,6 +37,8 @@ final class ComposeViewController: UIViewController, NeedsDependency {
button.adjustsImageWhenHighlighted = false
return button
}()
private(set) lazy var cancelBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
private(set) lazy var publishBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(customView: publishButton)
return barButtonItem
@ -138,7 +140,7 @@ extension ComposeViewController {
}
.store(in: &disposeBag)
view.backgroundColor = Asset.Scene.Compose.background.color
navigationItem.leftBarButtonItem = UIBarButtonItem(title: L10n.Common.Controls.Actions.cancel, style: .plain, target: self, action: #selector(ComposeViewController.cancelBarButtonItemPressed(_:)))
navigationItem.leftBarButtonItem = cancelBarButtonItem
navigationItem.rightBarButtonItem = publishBarButtonItem
publishButton.addTarget(self, action: #selector(ComposeViewController.publishBarButtonItemPressed(_:)), for: .touchUpInside)
@ -247,7 +249,8 @@ extension ComposeViewController {
// adjust inset for auto-complete
let autoCompleteTableViewBottomInset: CGFloat = {
let tableViewFrameInWindow = self.autoCompleteViewController.tableView.superview!.convert(self.autoCompleteViewController.tableView.frame, to: nil)
guard let superview = self.autoCompleteViewController.tableView.superview else { return .zero }
let tableViewFrameInWindow = superview.convert(self.autoCompleteViewController.tableView.frame, to: nil)
let padding = tableViewFrameInWindow.maxY + self.composeToolbarView.frame.height + AutoCompleteViewController.chevronViewHeight - endFrame.minY
return max(0, padding)
}()
@ -1257,3 +1260,130 @@ extension ComposeViewController: AutoCompleteViewControllerDelegate {
}
}
}
extension ComposeViewController {
override var keyCommands: [UIKeyCommand]? {
composeKeyCommands
}
}
extension ComposeViewController {
enum ComposeKeyCommand: String, CaseIterable {
case discardPost
case publishPost
case mediaBrowse
case mediaPhotoLibrary
case mediaCamera
case togglePoll
case toggleContentWarning
case selectVisibilityPublic
case selectVisibilityUnlisted
case selectVisibilityPrivate
case selectVisibilityDirect
var title: String {
switch self {
case .discardPost: return L10n.Scene.Compose.Keyboard.discardPost
case .publishPost: return L10n.Scene.Compose.Keyboard.publishPost
case .mediaBrowse: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.browse)
case .mediaPhotoLibrary: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.photoLibrary)
case .mediaCamera: return L10n.Scene.Compose.Keyboard.appendAttachmentEntry(L10n.Scene.Compose.MediaSelection.camera)
case .togglePoll: return L10n.Scene.Compose.Keyboard.togglePoll
case .toggleContentWarning: return L10n.Scene.Compose.Keyboard.toggleContentWarning
case .selectVisibilityPublic: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.public)
case .selectVisibilityUnlisted: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.unlisted)
case .selectVisibilityPrivate: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.private)
case .selectVisibilityDirect: return L10n.Scene.Compose.Keyboard.selectVisibilityEntry(L10n.Scene.Compose.Visibility.direct)
}
}
// UIKeyCommand input
var input: String {
switch self {
case .discardPost: return "w" // + command
case .publishPost: return "\r" // (enter) + command
case .mediaBrowse: return "b" // + option + command
case .mediaPhotoLibrary: return "p" // + option + command
case .mediaCamera: return "c" // + option + command
case .togglePoll: return "p" // + shift + command
case .toggleContentWarning: return "c" // + shift + command
case .selectVisibilityPublic: return "1" // + command
case .selectVisibilityUnlisted: return "2" // + command
case .selectVisibilityPrivate: return "3" // + command
case .selectVisibilityDirect: return "4" // + command
}
}
var modifierFlags: UIKeyModifierFlags {
switch self {
case .discardPost: return [.command]
case .publishPost: return [.command]
case .mediaBrowse: return [.alternate, .command]
case .mediaPhotoLibrary: return [.alternate, .command]
case .mediaCamera: return [.alternate, .command]
case .togglePoll: return [.shift, .command]
case .toggleContentWarning: return [.shift, .command]
case .selectVisibilityPublic: return [.command]
case .selectVisibilityUnlisted: return [.command]
case .selectVisibilityPrivate: return [.command]
case .selectVisibilityDirect: return [.command]
}
}
var propertyList: Any {
return rawValue
}
}
var composeKeyCommands: [UIKeyCommand]? {
ComposeKeyCommand.allCases.map { command in
UIKeyCommand(
title: command.title,
image: nil,
action: #selector(Self.composeKeyCommandHandler(_:)),
input: command.input,
modifierFlags: command.modifierFlags,
propertyList: command.propertyList,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
}
@objc private func composeKeyCommandHandler(_ sender: UIKeyCommand) {
guard let rawValue = sender.propertyList as? String,
let command = ComposeKeyCommand(rawValue: rawValue) else { return }
switch command {
case .discardPost:
cancelBarButtonItemPressed(cancelBarButtonItem)
case .publishPost:
publishBarButtonItemPressed(publishBarButtonItem)
case .mediaBrowse:
present(documentPickerController, animated: true, completion: nil)
case .mediaPhotoLibrary:
present(imagePicker, animated: true, completion: nil)
case .mediaCamera:
guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
return
}
present(imagePickerController, animated: true, completion: nil)
case .togglePoll:
composeToolbarView.pollButton.sendActions(for: .touchUpInside)
case .toggleContentWarning:
composeToolbarView.contentWarningButton.sendActions(for: .touchUpInside)
case .selectVisibilityPublic:
viewModel.selectedStatusVisibility.value = .public
case .selectVisibilityUnlisted:
viewModel.selectedStatusVisibility.value = .unlisted
case .selectVisibilityPrivate:
viewModel.selectedStatusVisibility.value = .private
case .selectVisibilityDirect:
viewModel.selectedStatusVisibility.value = .direct
}
}
}

View File

@ -246,6 +246,21 @@ extension MainTabBarController {
)
}
var composeNewPostKeyCommand: UIKeyCommand {
UIKeyCommand(
title: L10n.Common.Controls.Keyboard.Common.composeNewPost,
image: nil,
action: #selector(MainTabBarController.composeNewPostKeyCommandHandler(_:)),
input: "n",
modifierFlags: .command,
propertyList: nil,
alternates: [],
discoverabilityTitle: nil,
attributes: [],
state: .off
)
}
override var keyCommands: [UIKeyCommand]? {
guard let topMost = self.topMost else {
return []
@ -259,6 +274,11 @@ extension MainTabBarController {
// switch tabs
commands.append(contentsOf: switchToTabKeyCommands)
// show compose
if !(self.topMost is ComposeViewController) {
commands.append(composeNewPostKeyCommand)
}
// show favorites
if !(self.topMost is FavoriteViewController) {
commands.append(showFavoritesKeyCommand)
@ -312,4 +332,10 @@ extension MainTabBarController {
coordinator.present(scene: .settings(viewModel: settingsViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
@objc private func composeNewPostKeyCommandHandler(_ sender: UIKeyCommand) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let composeViewModel = ComposeViewModel(context: context, composeKind: .post)
coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
}