Skip to content
This repository was archived by the owner on Jun 7, 2020. It is now read-only.

Commit a3487cc

Browse files
authored
Merge pull request #1977 from RocketChat/feat/hw_keyboard_shortcuts
[NEW] Keyboard shortcuts
2 parents 48fd897 + 2226e12 commit a3487cc

20 files changed

Lines changed: 673 additions & 29 deletions

Rocket.Chat.xcodeproj/project.pbxproj

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@
417417
8013F88C1FD6B7D400EE1A4E /* RunCommandRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8013F88B1FD6B7D400EE1A4E /* RunCommandRequestSpec.swift */; };
418418
8013F88E1FD6B83C00EE1A4E /* CommandModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8013F88D1FD6B83C00EE1A4E /* CommandModelMapping.swift */; };
419419
8018B5701FBE070000C3CC25 /* PushManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8018B56F1FBE070000C3CC25 /* PushManagerSpec.swift */; };
420+
801B0EF42114B2A300C0972E /* ShortcutHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 801B0EF32114B2A300C0972E /* ShortcutHelpers.swift */; };
421+
801B0EF7211895E900C0972E /* MainSplitViewControllerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 801B0EF6211895E900C0972E /* MainSplitViewControllerShortcuts.swift */; };
420422
801DF8151FD7172500302CC8 /* SubscriptionUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 801DF8141FD7172500302CC8 /* SubscriptionUserView.swift */; };
421423
8020CCF01FEAB4A7003424F4 /* EmojiPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8020CCEF1FEAB4A7003424F4 /* EmojiPicker.swift */; };
422424
8020CCF21FEAB4C3003424F4 /* EmojiPicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8020CCF11FEAB4C3003424F4 /* EmojiPicker.xib */; };
@@ -1270,6 +1272,8 @@
12701272
8013F88B1FD6B7D400EE1A4E /* RunCommandRequestSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RunCommandRequestSpec.swift; sourceTree = "<group>"; };
12711273
8013F88D1FD6B83C00EE1A4E /* CommandModelMapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandModelMapping.swift; sourceTree = "<group>"; };
12721274
8018B56F1FBE070000C3CC25 /* PushManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushManagerSpec.swift; sourceTree = "<group>"; };
1275+
801B0EF32114B2A300C0972E /* ShortcutHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutHelpers.swift; sourceTree = "<group>"; };
1276+
801B0EF6211895E900C0972E /* MainSplitViewControllerShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewControllerShortcuts.swift; sourceTree = "<group>"; };
12731277
801DF8141FD7172500302CC8 /* SubscriptionUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUserView.swift; sourceTree = "<group>"; };
12741278
8020CCEF1FEAB4A7003424F4 /* EmojiPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPicker.swift; sourceTree = "<group>"; };
12751279
8020CCF11FEAB4C3003424F4 /* EmojiPicker.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPicker.xib; sourceTree = "<group>"; };
@@ -2278,9 +2282,9 @@
22782282
4174CB111D2D99840086DAC8 /* Base */ = {
22792283
isa = PBXGroup;
22802284
children = (
2285+
801B0EF5211895D800C0972E /* Main */,
22812286
4174CB141D2D99A30086DAC8 /* BaseNavigationController.swift */,
22822287
4174CB121D2D99960086DAC8 /* BaseViewController.swift */,
2283-
414B3B24203E2F2C0078D3D9 /* MainSplitViewController.swift */,
22842288
99F530A020D0F08A004EC060 /* BaseTableViewController.swift */,
22852289
);
22862290
path = Base;
@@ -2585,6 +2589,7 @@
25852589
8052F75620B4ADC400B3B0CF /* Debounce.swift */,
25862590
80CC78D020DD7F99002FBEBC /* RealmAssorter.swift */,
25872591
80B2D99020E549CC002F4149 /* StoryboardInitializable.swift */,
2592+
801B0EF32114B2A300C0972E /* ShortcutHelpers.swift */,
25882593
);
25892594
path = Helpers;
25902595
sourceTree = "<group>";
@@ -2869,6 +2874,15 @@
28692874
path = Command;
28702875
sourceTree = "<group>";
28712876
};
2877+
801B0EF5211895D800C0972E /* Main */ = {
2878+
isa = PBXGroup;
2879+
children = (
2880+
414B3B24203E2F2C0078D3D9 /* MainSplitViewController.swift */,
2881+
801B0EF6211895E900C0972E /* MainSplitViewControllerShortcuts.swift */,
2882+
);
2883+
path = Main;
2884+
sourceTree = "<group>";
2885+
};
28722886
8020CCF31FEAD8AA003424F4 /* EmojiPicker */ = {
28732887
isa = PBXGroup;
28742888
children = (
@@ -4393,8 +4407,10 @@
43934407
4156250820BEDCD100D20576 /* ServersListViewModel.swift in Sources */,
43944408
8076FDB52048571200114F28 /* AuthUser.swift in Sources */,
43954409
80B2D98B20E5496E002F4149 /* UserDetailTableView.swift in Sources */,
4410+
801B0EF42114B2A300C0972E /* ShortcutHelpers.swift in Sources */,
43964411
4159720E1D3DB882002258F4 /* Mention.swift in Sources */,
43974412
806401311FB09DE800990572 /* Permission.swift in Sources */,
4413+
801B0EF7211895E900C0972E /* MainSplitViewControllerShortcuts.swift in Sources */,
43984414
806DB94E20698C25004ED8ED /* ChatMessageCellAccessibility.swift in Sources */,
43994415
41B96362207E48260068F1A6 /* MessageTextValidator.swift in Sources */,
44004416
809B530E1FE2EFA100833DD2 /* ReactionListView.swift in Sources */,

Rocket.Chat/Controllers/Base/MainSplitViewController.swift renamed to Rocket.Chat/Controllers/Base/Main/MainSplitViewController.swift

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,30 @@ final class MainSplitViewController: UISplitViewController {
1616
SocketManager.removeConnectionHandler(token: socketHandlerToken)
1717
}
1818

19+
static var current: MainSplitViewController? {
20+
return (UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController as? MainSplitViewController
21+
}
22+
1923
static var chatViewController: ChatViewController? {
20-
guard
21-
let appDelegate = UIApplication.shared.delegate as? AppDelegate,
22-
let mainViewController = appDelegate.window?.rootViewController as? MainSplitViewController
23-
else {
24-
return nil
25-
}
24+
return current?.chatViewController
25+
}
2626

27-
var controller: ChatViewController?
27+
static var subscriptionsViewController: SubscriptionsViewController? {
28+
return current?.subscriptionsViewController
29+
}
2830

29-
if let nav = mainViewController.detailViewController as? UINavigationController {
30-
if let chatController = nav.viewControllers.first as? ChatViewController {
31-
controller = chatController
32-
}
33-
} else if let nav = mainViewController.viewControllers.first as? UINavigationController, nav.viewControllers.count >= 2 {
34-
if let chatController = nav.viewControllers[1] as? ChatViewController {
35-
controller = chatController
36-
}
31+
var chatViewController: ChatViewController? {
32+
if let nav = detailViewController as? UINavigationController {
33+
return nav.viewControllers.first as? ChatViewController
34+
} else if let nav = viewControllers.first as? UINavigationController, nav.viewControllers.count >= 2 {
35+
return nav.viewControllers[1] as? ChatViewController
3736
}
3837

39-
return controller
38+
return nil
39+
}
40+
41+
var subscriptionsViewController: SubscriptionsViewController? {
42+
return (viewControllers.first as? UINavigationController)?.viewControllers.first as? SubscriptionsViewController
4043
}
4144

4245
override func awakeFromNib() {
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//
2+
// MainSplitViewControllerShortcuts.swift
3+
// Rocket.Chat
4+
//
5+
// Created by Matheus Cardoso on 8/6/18.
6+
// Copyright © 2018 Rocket.Chat. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
// MARK: Keyboard Shortcuts
12+
13+
extension MainSplitViewController {
14+
override var keyCommands: [UIKeyCommand]? {
15+
return [
16+
UIKeyCommand(input: "\t", modifierFlags: [], action: #selector(shortcutFocusOnComposer(_:)), discoverabilityTitle: localized("shortcuts.type_message")),
17+
UIKeyCommand(input: "p", modifierFlags: .command, action: #selector(shortcutTogglePreferences(_:)), discoverabilityTitle: localized("shortcuts.preferences")),
18+
UIKeyCommand(input: "f", modifierFlags: [.command, .alternate], action: #selector(shortcutRoomSearch(_:)), discoverabilityTitle: localized("shortcuts.room_search")),
19+
UIKeyCommand(input: "1...9", modifierFlags: .command, action: #selector(shortcutSelectRoom(_:)), discoverabilityTitle: localized("shortcuts.room_selection")),
20+
UIKeyCommand(input: "]", modifierFlags: .command, action: #selector(shortcutSelectRoom(_:)), discoverabilityTitle: localized("shortcuts.next_room")),
21+
UIKeyCommand(input: "[", modifierFlags: .command, action: #selector(shortcutSelectRoom(_:)), discoverabilityTitle: localized("shortcuts.previous_room")),
22+
UIKeyCommand(input: "n", modifierFlags: .command, action: #selector(shortcutSelectRoom(_:)), discoverabilityTitle: localized("shortcuts.new_room")),
23+
UIKeyCommand(input: "i", modifierFlags: .command, action: #selector(shortcutRoomActions(_:)), discoverabilityTitle: localized("shortcuts.room_actions")),
24+
UIKeyCommand(input: "u", modifierFlags: .command, action: #selector(shortcutUpload(_:)), discoverabilityTitle: localized("shortcuts.upload_room")),
25+
UIKeyCommand(input: "f", modifierFlags: .command, action: #selector(shortcutRoomMessageSearch(_:)), discoverabilityTitle: localized("shortcuts.search_messages")),
26+
UIKeyCommand(input: "↑ ↓", modifierFlags: .alternate, action: #selector(shortcutScrollMessages(_:)), discoverabilityTitle: localized("shortcuts.scroll_messages")),
27+
UIKeyCommand(input: UIKeyInputUpArrow, modifierFlags: .alternate, action: #selector(shortcutScrollMessages(_:))),
28+
UIKeyCommand(input: UIKeyInputDownArrow, modifierFlags: .alternate, action: #selector(shortcutScrollMessages(_:))),
29+
UIKeyCommand(input: "r", modifierFlags: .command, action: #selector(shortcutReplyLatest(_:)), discoverabilityTitle: localized("shortcuts.reply_latest")),
30+
UIKeyCommand(input: "`", modifierFlags: [.command, .alternate], action: #selector(shortcutSelectServer(_:)), discoverabilityTitle: localized("shortcuts.server_selection")),
31+
UIKeyCommand(input: "1...9", modifierFlags: [.command, .alternate], action: #selector(shortcutSelectServer(_:)), discoverabilityTitle: localized("shortcuts.server_selection_numbers")),
32+
UIKeyCommand(input: "n", modifierFlags: [.command, .alternate], action: #selector(shortcutSelectServer(_:)), discoverabilityTitle: localized("shortcuts.add_server"))
33+
] + ((0...9).map({ "\($0)" })).map { (input: String) -> UIKeyCommand in
34+
UIKeyCommand(input: input, modifierFlags: .command, action: #selector(shortcutSelectRoom(_:)))
35+
} + ((0...9).map({ "\($0)" })).map { (input: String) -> UIKeyCommand in
36+
UIKeyCommand(input: input, modifierFlags: [.command, .alternate], action: #selector(shortcutSelectServer(_:)))
37+
} + [
38+
UIKeyCommand(input: "t", modifierFlags: .command, action: #selector(shortcutChangeTheme(_:)), discoverabilityTitle: localized("shortcuts.change_theme"))
39+
]
40+
}
41+
42+
@objc func shortcutFocusOnComposer(_ command: UIKeyCommand) {
43+
guard
44+
!isPresenting,
45+
let textView = chatViewController?.textInputbar.textView
46+
else {
47+
return
48+
}
49+
50+
if textView.isFirstResponder {
51+
textView.resignFirstResponder()
52+
} else {
53+
textView.becomeFirstResponder()
54+
subscriptionsViewController?.searchController?.dismiss(animated: true) { [weak self] in
55+
self?.chatViewController?.keyboardFrame?.updateFrame()
56+
}
57+
}
58+
}
59+
60+
@objc func shortcutTogglePreferences(_ command: UIKeyCommand) {
61+
subscriptionsViewController?.togglePreferences()
62+
}
63+
64+
@objc func shortcutRoomSearch(_ command: UIKeyCommand) {
65+
guard !isPresenting else {
66+
return
67+
}
68+
69+
if subscriptionsViewController?.searchBar?.isFirstResponder ?? false {
70+
subscriptionsViewController?.searchController?.dismiss(animated: true, completion: nil)
71+
} else {
72+
subscriptionsViewController?.searchBar?.becomeFirstResponder()
73+
}
74+
}
75+
76+
@objc func shortcutSelectServer(_ command: UIKeyCommand) {
77+
guard let input = command.input, !isPresenting else {
78+
return
79+
}
80+
81+
switch input {
82+
case "`":
83+
subscriptionsViewController?.toggleServersList()
84+
case "n":
85+
AppManager.addServer(serverUrl: "")
86+
default:
87+
guard let position = Int(input), position > 0 else {
88+
break
89+
}
90+
91+
let index = position - 1
92+
93+
if index < DatabaseManager.servers?.count ?? 0 {
94+
AppManager.changeSelectedServer(index: index)
95+
} else {
96+
subscriptionsViewController?.openServersList()
97+
}
98+
}
99+
}
100+
101+
@objc func shortcutSelectRoom(_ command: UIKeyCommand) {
102+
guard
103+
let viewController = subscriptionsViewController,
104+
let input = command.input
105+
else {
106+
return
107+
}
108+
109+
if input != "n" && isPresenting {
110+
return
111+
}
112+
113+
switch input {
114+
case "n":
115+
viewController.toggleNewRoom()
116+
case "]":
117+
viewController.selectNextRoom()
118+
case "[":
119+
viewController.selectPreviousRoom()
120+
default:
121+
guard let position = Int(input), position > 0 else {
122+
break
123+
}
124+
125+
viewController.selectRoomAt(position - 1)
126+
}
127+
}
128+
129+
@objc func shortcutUpload(_ command: UIKeyCommand) {
130+
guard chatViewController?.subscription?.validated() != nil else {
131+
return
132+
}
133+
134+
chatViewController?.toggleUpload()
135+
}
136+
137+
@objc func shortcutRoomActions(_ command: UIKeyCommand) {
138+
guard chatViewController?.subscription?.validated() != nil else {
139+
return
140+
}
141+
142+
chatViewController?.toggleActions()
143+
}
144+
145+
@objc func shortcutRoomMessageSearch(_ command: UIKeyCommand) {
146+
guard chatViewController?.subscription?.validated() != nil else {
147+
return
148+
}
149+
150+
chatViewController?.toggleSearchMessages()
151+
}
152+
153+
@objc func shortcutScrollMessages(_ command: UIKeyCommand) {
154+
guard
155+
!isPresenting,
156+
let input = command.input,
157+
let offset = chatViewController?.collectionView?.contentOffset,
158+
let maxHeight = chatViewController?.collectionView?.contentSize.height,
159+
let collectionView = chatViewController?.collectionView
160+
else {
161+
return
162+
}
163+
164+
let heightDelta: CGFloat = input == UIKeyInputUpArrow ? -50.0 : 50.0
165+
166+
UIView.animate(withDuration: 0.1, animations: { [weak collectionView] in
167+
let offset = min(offset.y + heightDelta, maxHeight)
168+
collectionView?.contentOffset = CGPoint(x: 0, y: offset)
169+
})
170+
}
171+
172+
@objc func shortcutReplyLatest(_ command: UIKeyCommand) {
173+
guard !isPresenting else {
174+
return
175+
}
176+
177+
if let message = chatViewController?.subscription?.roomLastMessage {
178+
chatViewController?.reply(to: message)
179+
}
180+
}
181+
182+
@objc func shortcutChangeTheme(_ command: UIKeyCommand) {
183+
if let currentThemeIndex = ThemeManager.themes.index(where: { $0.title == ThemeManager.themeTitle }) {
184+
if ThemeManager.themes.count > currentThemeIndex + 1 {
185+
ThemeManager.theme = ThemeManager.themes[currentThemeIndex + 1].theme
186+
} else {
187+
ThemeManager.theme = ThemeManager.themes.first?.theme ?? .light
188+
}
189+
}
190+
}
191+
}

Rocket.Chat/Controllers/Chat/ChatViewController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ final class ChatViewController: SLKTextViewController {
206206
ThemeManager.addObserver(navigationController?.navigationBar)
207207
setupAutoCompletionSeparator()
208208
textInputbar.applyTheme()
209+
210+
textInputbar.textView.inputAssistantItem.leadingBarButtonGroups = []
211+
textInputbar.textView.inputAssistantItem.trailingBarButtonGroups = []
212+
209213
updateEmptyState()
210214

211215
chatTitleView?.state = SocketManager.sharedInstance.state

Rocket.Chat/Controllers/Subscriptions/SubscriptionsList/SubscriptionsViewController.swift

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,16 +328,32 @@ extension SubscriptionsViewController: UISearchBarDelegate {
328328
}
329329

330330
func toggleServersList() {
331+
if serversView != nil {
332+
closeServersList()
333+
} else {
334+
openServersList()
335+
}
336+
}
337+
338+
func openServersList() {
339+
guard serversView == nil else {
340+
return
341+
}
342+
331343
sortingView?.close()
332344

333-
if let serversView = serversView {
334-
titleView?.updateTitleImage(reverse: false)
335-
serversView.close()
336-
} else {
337-
titleView?.updateTitleImage(reverse: true)
338-
serversView = ServersListView.showIn(view, frame: frameForDropDownOverlay)
339-
serversView?.delegate = self
345+
titleView?.updateTitleImage(reverse: true)
346+
serversView = ServersListView.showIn(view, frame: frameForDropDownOverlay)
347+
serversView?.delegate = self
348+
}
349+
350+
func closeServersList() {
351+
guard let serversView = serversView else {
352+
return
340353
}
354+
355+
titleView?.updateTitleImage(reverse: false)
356+
serversView.close()
341357
}
342358

343359
private var frameForDropDownOverlay: CGRect {
@@ -469,6 +485,10 @@ extension SubscriptionsViewController: UITableViewDelegate {
469485
}
470486

471487
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
488+
onSelectRowAt(indexPath)
489+
}
490+
491+
func onSelectRowAt(_ indexPath: IndexPath) {
472492
guard let subscription = viewModel.subscriptionForRowAt(indexPath: indexPath)?.managedObject else { return }
473493

474494
searchController?.searchBar.resignFirstResponder()
@@ -614,3 +634,34 @@ extension SubscriptionsViewController {
614634
}
615635
}
616636
}
637+
638+
// MARK: Room Selection Helpers
639+
640+
extension SubscriptionsViewController {
641+
func selectRoomAt(_ index: Int) {
642+
guard
643+
let indexPath = viewModel.indexPathForAbsoluteIndex(index),
644+
indexPath.row >= 0 && indexPath.section >= 0
645+
else {
646+
return
647+
}
648+
649+
onSelectRowAt(indexPath)
650+
651+
DispatchQueue.main.async { [weak self] in
652+
self?.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
653+
}
654+
}
655+
656+
func selectNextRoom() {
657+
if let indexPath = tableView.indexPathsForSelectedRows?.first {
658+
selectRoomAt(viewModel.absoluteIndexForIndexPath(indexPath) + 1)
659+
}
660+
}
661+
662+
func selectPreviousRoom() {
663+
if let indexPath = tableView.indexPathsForSelectedRows?.first {
664+
selectRoomAt(viewModel.absoluteIndexForIndexPath(indexPath) - 1)
665+
}
666+
}
667+
}

0 commit comments

Comments
 (0)