Merge branch 'nt/keyboard-shortcuts'

This commit is contained in:
Nora Trapp 2019-10-22 15:16:51 -07:00
commit b66d4da50c
11 changed files with 620 additions and 45 deletions

View File

@ -124,6 +124,38 @@ class ThreadMapping: NSObject {
return threads[indexPath.item]
}
@objc(indexPathAfterThread:)
func indexPath(after thread: TSThread?) -> IndexPath? {
guard !threads.isEmpty else { return nil }
let firstIndexPath = IndexPath(item: 0, section: kSection)
guard let thread = thread else { return firstIndexPath }
guard let index = threads.firstIndex(where: { $0.uniqueId == thread.uniqueId}) else { return firstIndexPath }
if index < (threads.count - 1) {
return IndexPath(item: index + 1, section: kSection)
} else {
return firstIndexPath
}
}
@objc(indexPathBeforeThread:)
func indexPath(before thread: TSThread?) -> IndexPath? {
guard !threads.isEmpty else { return nil }
let lastIndexPath = IndexPath(item: threads.count - 1, section: kSection)
guard let thread = thread else { return lastIndexPath }
guard let index = threads.firstIndex(where: { $0.uniqueId == thread.uniqueId}) else { return lastIndexPath }
if index > 0 {
return IndexPath(item: index - 1, section: kSection)
} else {
return lastIndexPath
}
}
let threadFinder = AnyThreadFinder()
@objc

View File

@ -226,41 +226,41 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSArray<UIKeyCommand *> *)keyCommands
{
// We're permissive about what modifier key we accept for the "send message" hotkey.
// We accept command-return, option-return.
//
// We don't support control-return because it doesn't work.
//
// We don't support shift-return because it is often used for "newline" in other
// messaging apps.
// We don't define discoverability title for these key commands as they're
// considered "default" functionality and shouldn't clutter the shortcut
// list that is rendered when you hold down the command key.
return @[
[self keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierCommand
action:@selector(modifiedReturnPressed:)
discoverabilityTitle:@"Send Message"],
// "Alternate" is option.
[self keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierAlternate
action:@selector(modifiedReturnPressed:)
discoverabilityTitle:@"Send Message"],
// An unmodified return can only be sent by a hardware keyboard,
// return on the software keyboard will not trigger this command.
// Return, send message
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(unmodifedReturnPressed:)],
// Alt + Return, inserts a new line
[UIKeyCommand keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierAlternate
action:@selector(modifiedReturnPressed:)],
// Shift + Return, inserts a new line
[UIKeyCommand keyCommandWithInput:@"\r"
modifierFlags:UIKeyModifierShift
action:@selector(modifiedReturnPressed:)],
];
}
- (UIKeyCommand *)keyCommandWithInput:(NSString *)input
modifierFlags:(UIKeyModifierFlags)modifierFlags
action:(SEL)action
discoverabilityTitle:(NSString *)discoverabilityTitle
- (void)unmodifedReturnPressed:(UIKeyCommand *)sender
{
return [UIKeyCommand keyCommandWithInput:input
modifierFlags:modifierFlags
action:action
discoverabilityTitle:discoverabilityTitle];
OWSLogInfo(@"unmodifedReturnPressed: %@", sender.input);
[self.inputTextViewDelegate inputTextViewSendMessagePressed];
}
- (void)modifiedReturnPressed:(UIKeyCommand *)sender
{
OWSLogInfo(@"modifiedReturnPressed: %@", sender.input);
[self.inputTextViewDelegate inputTextViewSendMessagePressed];
self.text = [self.text stringByAppendingString:@"\n"];
[self.inputTextViewDelegate textViewDidChange:self];
[self.textViewToolbarDelegate textViewDidChange:self];
}
@end

View File

@ -86,6 +86,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)clearTextMessageAnimated:(BOOL)isAnimated;
- (void)clearDesiredKeyboard;
- (void)toggleDefaultKeyboard;
- (void)showStickerKeyboard;
- (void)showAttachmentKeyboard;
- (void)updateFontSizes;

View File

@ -1212,6 +1212,24 @@ const CGFloat kMaxTextViewHeight = 98;
}
}
- (void)showStickerKeyboard
{
OWSAssertIsOnMainThread();
if (self.desiredKeyboardType != KeyboardType_Sticker) {
[self toggleKeyboardType:KeyboardType_Sticker];
}
}
- (void)showAttachmentKeyboard
{
OWSAssertIsOnMainThread();
if (self.desiredKeyboardType != KeyboardType_Attachment) {
[self toggleKeyboardType:KeyboardType_Attachment];
}
}
#pragma mark - ConversationTextViewToolbarDelegate
- (void)setBounds:(CGRect)bounds

View File

@ -33,6 +33,15 @@ typedef NS_ENUM(NSUInteger, ConversationViewAction) {
- (void)peekSetup;
- (void)popped;
#pragma mark - Keyboard Shortcuts
- (void)showConversationSettings;
- (void)focusInputToolbar;
- (void)openAllMedia;
- (void)openStickerKeyboard;
- (void)openAttachmentKeyboard;
- (void)openGifSearch;
@end
#pragma mark - Internal Methods. Used in extensions

View File

@ -1748,7 +1748,42 @@ typedef enum : NSUInteger {
settingsVC.conversationSettingsViewDelegate = self;
[settingsVC configureWithThread:self.thread];
settingsVC.showVerificationOnAppear = showVerification;
[self.navigationController pushViewController:settingsVC animated:YES];
[self.navigationController setViewControllers:[self.viewControllersUpToSelf arrayByAddingObject:settingsVC]
animated:YES];
}
- (void)showConversationSettingsAndShowAllMedia
{
OWSConversationSettingsViewController *settingsVC = [OWSConversationSettingsViewController new];
settingsVC.conversationSettingsViewDelegate = self;
[settingsVC configureWithThread:self.thread];
MediaTileViewController *allMedia = [[MediaTileViewController alloc] initWithThread:self.thread];
[self.navigationController
setViewControllers:[self.viewControllersUpToSelf arrayByAddingObjectsFromArray:@[ settingsVC, allMedia ]]
animated:YES];
}
- (NSArray<UIViewController *> *)viewControllersUpToSelf
{
OWSAssertIsOnMainThread();
OWSAssertDebug(self.navigationController);
if (self.navigationController.topViewController == self) {
return self.navigationController.viewControllers;
}
NSArray *viewControllers = self.navigationController.viewControllers;
NSUInteger index = [viewControllers indexOfObject:self];
if (index == NSNotFound) {
OWSFailDebug(@"Unexpectedly missing from view hierarhy");
return viewControllers;
}
return [viewControllers subarrayWithRange:NSMakeRange(0, index + 1)];
}
#pragma mark - DisappearingTimerConfigurationViewDelegate
@ -5338,6 +5373,44 @@ typedef enum : NSUInteger {
}
}
#pragma mark - Keyboard Shortcuts
- (void)focusInputToolbar
{
OWSAssertIsOnMainThread();
[self.inputToolbar clearDesiredKeyboard];
[self popKeyBoard];
}
- (void)openAllMedia
{
OWSAssertIsOnMainThread();
[self showConversationSettingsAndShowAllMedia];
}
- (void)openStickerKeyboard
{
OWSAssertIsOnMainThread();
[self.inputToolbar showStickerKeyboard];
}
- (void)openAttachmentKeyboard
{
OWSAssertIsOnMainThread();
[self.inputToolbar showAttachmentKeyboard];
}
- (void)openGifSearch
{
OWSAssertIsOnMainThread();
[self showGifPicker];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -25,8 +25,15 @@ typedef NS_ENUM(NSInteger, ConversationListViewControllerSection) {
focusMessageId:(nullable NSString *)focusMessageId
animated:(BOOL)isAnimated;
// Used by force-touch Springboard icon shortcut
// Used by force-touch Springboard icon shortcut and key commands
- (void)showNewConversationView;
- (void)showNewGroupView;
- (void)showAppSettings;
- (void)focusSearch;
- (void)selectPreviousConversation;
- (void)selectNextConversation;
- (void)archiveSelectedConversation;
- (void)unarchiveSelectedConversation;
@property (nonatomic) TSThread *lastViewedThread;

View File

@ -751,9 +751,7 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
OWSAssertDebug(avatarImage);
UIButton *avatarButton = [AvatarImageButton buttonWithType:UIButtonTypeCustom];
[avatarButton addTarget:self
action:@selector(settingsButtonPressed:)
forControlEvents:UIControlEventTouchUpInside];
[avatarButton addTarget:self action:@selector(showAppSettings) forControlEvents:UIControlEventTouchUpInside];
[avatarButton setImage:avatarImage forState:UIControlStateNormal];
[avatarButton autoSetDimension:ALDimensionWidth toSize:kAvatarSize];
[avatarButton autoSetDimension:ALDimensionHeight toSize:kAvatarSize];
@ -766,7 +764,7 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
settingsButton = [[UIBarButtonItem alloc] initWithImage:image
style:UIBarButtonItemStylePlain
target:self
action:@selector(settingsButtonPressed:)
action:@selector(showAppSettings)
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"settings")];
}
settingsButton.accessibilityLabel = CommonStrings.openSettingsButton;
@ -792,12 +790,6 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
}
}
- (void)settingsButtonPressed:(id)sender
{
OWSNavigationController *navigationController = [AppSettingsViewController inModalNavigationController];
[self presentFormSheetViewController:navigationController animated:YES completion:nil];
}
- (nullable UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
viewControllerForLocation:(CGPoint)location
{
@ -853,6 +845,143 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
}];
}
- (void)showNewGroupView
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
NewGroupViewController *viewController = [NewGroupViewController new];
[self.contactsManager requestSystemContactsOnceWithCompletion:^(NSError *_Nullable error) {
if (error) {
OWSLogError(@"Error when requesting contacts: %@", error);
}
// Even if there is an error fetching contacts we proceed to the next screen.
// As the compose view will present the proper thing depending on contact access.
//
// We just want to make sure contact access is *complete* before showing the compose
// screen to avoid flicker.
OWSNavigationController *modal = [[OWSNavigationController alloc] initWithRootViewController:viewController];
[self.navigationController presentFormSheetViewController:modal animated:YES completion:nil];
}];
}
- (void)showAppSettings
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
OWSNavigationController *navigationController = [AppSettingsViewController inModalNavigationController];
[self presentFormSheetViewController:navigationController animated:YES completion:nil];
}
- (void)focusSearch
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
// If we have presented a conversation list (the archive) search there instead.
if (self.presentedConversationListViewController) {
[self.presentedConversationListViewController focusSearch];
return;
}
[self.searchBar becomeFirstResponder];
}
- (void)selectPreviousConversation
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
// If we have presented a conversation list (the archive) navigate through that instead.
if (self.presentedConversationListViewController) {
[self.presentedConversationListViewController selectPreviousConversation];
return;
}
TSThread *_Nullable currentThread = self.conversationSplitViewController.selectedThread;
NSIndexPath *_Nullable previousIndexPath = [self.threadMapping indexPathBeforeThread:currentThread];
if (previousIndexPath) {
[self presentThread:[self threadForIndexPath:previousIndexPath] action:ConversationViewActionNone animated:YES];
[self.tableView selectRowAtIndexPath:previousIndexPath
animated:YES
scrollPosition:UITableViewScrollPositionNone];
}
}
- (void)selectNextConversation
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
// If we have presented a conversation list (the archive) navigate through that instead.
if (self.presentedConversationListViewController) {
[self.presentedConversationListViewController selectNextConversation];
return;
}
TSThread *_Nullable currentThread = self.conversationSplitViewController.selectedThread;
NSIndexPath *_Nullable nextIndexPath = [self.threadMapping indexPathAfterThread:currentThread];
if (nextIndexPath) {
[self presentThread:[self threadForIndexPath:nextIndexPath] action:ConversationViewActionNone animated:YES];
[self.tableView selectRowAtIndexPath:nextIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
}
}
- (void)archiveSelectedConversation
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
TSThread *_Nullable selectedThread = self.conversationSplitViewController.selectedThread;
if (!selectedThread) {
return;
}
if (selectedThread.isArchived) {
return;
}
[self.conversationSplitViewController closeSelectedConversationAnimated:YES];
[self.databaseStorage writeWithBlock:^(SDSAnyWriteTransaction *transaction) {
[selectedThread archiveThreadWithTransaction:transaction];
}];
[self updateViewState];
}
- (void)unarchiveSelectedConversation
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
TSThread *_Nullable selectedThread = self.conversationSplitViewController.selectedThread;
if (!selectedThread) {
return;
}
if (!selectedThread.isArchived) {
return;
}
[self.conversationSplitViewController closeSelectedConversationAnimated:YES];
[self.databaseStorage writeWithBlock:^(SDSAnyWriteTransaction *transaction) {
[selectedThread unarchiveThreadWithTransaction:transaction];
}];
[self updateViewState];
}
- (void)showCameraView
{
[self ows_askForCameraPermissions:^(BOOL cameraGranted) {
@ -1508,6 +1637,20 @@ NSString *const kArchiveButtonPseudoGroup = @"kArchiveButtonPseudoGroup";
[self showViewController:conversationList sender:self];
}
- (nullable ConversationListViewController *)presentedConversationListViewController
{
UIViewController *_Nullable topViewController = self.navigationController.topViewController;
if (topViewController == self) {
return nil;
}
if (![topViewController isKindOfClass:[ConversationListViewController class]]) {
return nil;
}
return (ConversationListViewController *)topViewController;
}
- (NSString *)currentGrouping
{
switch (self.conversationListMode) {

View File

@ -68,11 +68,6 @@ class ConversationSplitViewController: UISplitViewController {
return Theme.isDarkThemeEnabled ? .lightContent : .default
}
@objc
func showNewConversationView() {
conversationListVC.showNewConversationView()
}
@objc(closeSelectedConversationAnimated:)
func closeSelectedConversation(animated: Bool) {
guard let selectedConversationViewController = selectedConversationViewController else { return }
@ -153,6 +148,235 @@ class ConversationSplitViewController: UISplitViewController {
}
currentDetailViewController = vc
}
// MARK: - Keyboard Shortcuts
override var canBecomeFirstResponder: Bool {
return true
}
let globalKeyCommands = [
UIKeyCommand(
input: "n",
modifierFlags: .command,
action: #selector(showNewConversationView),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_NEW_MESSAGE",
comment: "A keyboard command to present the new message dialog."
)
),
UIKeyCommand(
input: "g",
modifierFlags: .command,
action: #selector(showNewGroupView),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_NEW_GROUP",
comment: "A keyboard command to present the new group dialog."
)
),
UIKeyCommand(
input: ",",
modifierFlags: .command,
action: #selector(showAppSettings),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_SETTINGS",
comment: "A keyboard command to present the application settings dialog."
)
),
UIKeyCommand(
input: "f",
modifierFlags: .command,
action: #selector(focusSearch),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_SEARCH",
comment: "A keyboard command to begin a search on the conversation list."
)
),
UIKeyCommand(
input: UIKeyCommand.inputUpArrow,
modifierFlags: .alternate,
action: #selector(selectPreviousConversation),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_PREVIOUS_CONVERSATION",
comment: "A keyboard command to jump to the previous conversation in the list."
)
),
UIKeyCommand(
input: UIKeyCommand.inputDownArrow,
modifierFlags: .alternate,
action: #selector(selectNextConversation),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_NEXT_CONVERSATION",
comment: "A keyboard command to jump to the next conversation in the list."
)
)
]
let selectedConversationKeyCommands = [
UIKeyCommand(
input: "i",
modifierFlags: [.command, .shift],
action: #selector(openConversationSettings),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_CONVERSATION_INFO",
comment: "A keyboard command to open the current conversation's settings."
)
),
UIKeyCommand(
input: "m",
modifierFlags: [.command, .shift],
action: #selector(openAllMedia),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_ALL_MEDIA",
comment: "A keyboard command to open the current conversation's all media view."
)
),
UIKeyCommand(
input: "g",
modifierFlags: [.command, .shift],
action: #selector(openGifSearch),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_GIF_SEARCH",
comment: "A keyboard command to open the current conversations GIF picker."
)
),
UIKeyCommand(
input: "u",
modifierFlags: .command,
action: #selector(openAttachmentKeyboard),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_ATTACHMENTS",
comment: "A keyboard command to open the current conversation's attachment picker."
)
),
UIKeyCommand(
input: "s",
modifierFlags: [.command, .shift],
action: #selector(openStickerKeyboard),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_STICKERS",
comment: "A keyboard command to open the current conversation's sticker picker."
)
),
UIKeyCommand(
input: "a",
modifierFlags: [.command, .shift],
action: #selector(archiveSelectedConversation),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_ARCHIVE",
comment: "A keyboard command to archive the current coversation."
)
),
UIKeyCommand(
input: "u",
modifierFlags: [.command, .shift],
action: #selector(unarchiveSelectedConversation),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_UNARCHIVE",
comment: "A keyboard command to unarchive the current coversation."
)
),
UIKeyCommand(
input: "t",
modifierFlags: [.command, .shift],
action: #selector(focusInputToolbar),
discoverabilityTitle: NSLocalizedString(
"KEY_COMMAND_FOCUS_COMPOSER",
comment: "A keyboard command to focus the current conversation's input field."
)
)
]
override var keyCommands: [UIKeyCommand]? {
// If there is a modal presented over us, or another window above us, don't respond to keyboard commands.
guard presentedViewController == nil || view.window?.isKeyWindow != true else { return nil }
if selectedThread != nil {
return selectedConversationKeyCommands + globalKeyCommands
} else {
return globalKeyCommands
}
}
@objc func showNewConversationView() {
conversationListVC.showNewConversationView()
}
@objc func showNewGroupView() {
conversationListVC.showNewGroupView()
}
@objc func showAppSettings() {
conversationListVC.showAppSettings()
}
@objc func focusSearch() {
conversationListVC.focusSearch()
}
@objc func selectPreviousConversation() {
conversationListVC.selectPreviousConversation()
}
@objc func selectNextConversation(_ sender: UIKeyCommand) {
conversationListVC.selectNextConversation()
}
@objc func archiveSelectedConversation() {
conversationListVC.archiveSelectedConversation()
}
@objc func unarchiveSelectedConversation() {
conversationListVC.unarchiveSelectedConversation()
}
@objc func openConversationSettings() {
guard let selectedConversationViewController = selectedConversationViewController else {
return owsFailDebug("unexpectedly missing selected conversation")
}
selectedConversationViewController.showConversationSettings()
}
@objc func focusInputToolbar() {
guard let selectedConversationViewController = selectedConversationViewController else {
return owsFailDebug("unexpectedly missing selected conversation")
}
selectedConversationViewController.focusInputToolbar()
}
@objc func openAllMedia() {
guard let selectedConversationViewController = selectedConversationViewController else {
return owsFailDebug("unexpectedly missing selected conversation")
}
selectedConversationViewController.openAllMedia()
}
@objc func openStickerKeyboard() {
guard let selectedConversationViewController = selectedConversationViewController else {
return owsFailDebug("unexpectedly missing selected conversation")
}
selectedConversationViewController.openStickerKeyboard()
}
@objc func openAttachmentKeyboard() {
guard let selectedConversationViewController = selectedConversationViewController else {
return owsFailDebug("unexpectedly missing selected conversation")
}
selectedConversationViewController.openAttachmentKeyboard()
}
@objc func openGifSearch() {
guard let selectedConversationViewController = selectedConversationViewController else {
return owsFailDebug("unexpectedly missing selected conversation")
}
selectedConversationViewController.openGifSearch()
}
}
extension ConversationSplitViewController: UISplitViewControllerDelegate {

View File

@ -241,6 +241,34 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Methods
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.navigationController.viewControllers.count == 1) {
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissPressed)];
}
}
- (void)dismissPressed
{
[self.groupNameTextField resignFirstResponder];
if (!self.hasUnsavedChanges) {
// If user made no changes, dismiss.
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
__weak NewGroupViewController *weakSelf = self;
[OWSAlerts showPendingChangesAlertWithDiscardAction:^{
[weakSelf dismissViewControllerAnimated:YES completion:nil];
}];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];

View File

@ -1269,6 +1269,48 @@
/* Slider label when disappearing messages is off */
"KEEP_MESSAGES_FOREVER" = "Messages do not disappear.";
/* A keyboard command to open the current conversation's all media view. */
"KEY_COMMAND_ALL_MEDIA" = "Go to All Media";
/* A keyboard command to archive the current coversation. */
"KEY_COMMAND_ARCHIVE" = "Archive Conversation";
/* A keyboard command to open the current conversation's attachment picker. */
"KEY_COMMAND_ATTACHMENTS" = "Show Attachments";
/* A keyboard command to open the current conversation's settings. */
"KEY_COMMAND_CONVERSATION_INFO" = "Go to Conversation Info";
/* A keyboard command to focus the current conversation's input field. */
"KEY_COMMAND_FOCUS_COMPOSER" = "Focus Input Bar";
/* A keyboard command to open the current conversations GIF picker. */
"KEY_COMMAND_GIF_SEARCH" = "Go to GIF Search";
/* A keyboard command to present the new group dialog. */
"KEY_COMMAND_NEW_GROUP" = "New Group";
/* A keyboard command to present the new message dialog. */
"KEY_COMMAND_NEW_MESSAGE" = "New Message";
/* A keyboard command to jump to the next conversation in the list. */
"KEY_COMMAND_NEXT_CONVERSATION" = "Go to Next Conversation";
/* A keyboard command to jump to the previous conversation in the list. */
"KEY_COMMAND_PREVIOUS_CONVERSATION" = "Go to Previous Conversation";
/* A keyboard command to begin a search on the conversation list. */
"KEY_COMMAND_SEARCH" = "Search";
/* A keyboard command to present the application settings dialog. */
"KEY_COMMAND_SETTINGS" = "Settings";
/* A keyboard command to open the current conversation's sticker picker. */
"KEY_COMMAND_STICKERS" = "Show Stickers";
/* A keyboard command to unarchive the current coversation. */
"KEY_COMMAND_UNARCHIVE" = "Unarchive Conversation";
/* Confirmation button within contextual alert */
"LEAVE_BUTTON_TITLE" = "Leave";
@ -1338,9 +1380,6 @@
/* Confirmation button text to delete selected media message from the gallery */
"MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Delete Message";
/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */
"MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@";
/* Format for the 'more items' indicator for media galleries. Embeds {{the number of additional items}}. */
"MEDIA_GALLERY_MORE_ITEMS_FORMAT" = "+%@";