diff --git a/CHANGES.rst b/CHANGES.rst index 7c971f5c23..2309e5557c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,15 +2,22 @@ Changes to be released in next version ================================================= ✨ Features - * + * Composer Update - Typing and sending a message (#4085) + * Switching composer between text mode & action mode (#4087) 🙌 Improvements * Make the application settings more configurable (#4171) * Possibility to lock some room creation parameters from settings (#4181) * Enable / disable external friends invite (#4173) + * Composer update - UI enhancements (#4133) + * Increase grow/shrink animation speed in new composer (#4187) 🐛 Bugfix - * + * If you start typing while the new attachment sending mode is on, the send button appears (#4155) + * The final frames of the appearance animation of the new composer buttons are missing (#4160) + * Crash in [RoomViewController setupActions] (#4162) + * Too much vertical whitespace when replying (#4164) + * Black theme uses dark background for composer (#4192) ⚠️ API Changes * diff --git a/Riot/Assets/Images.xcassets/Room/Actions/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/Contents.json new file mode 100644 index 0000000000..aec39f6e42 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_camera.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_camera@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_camera@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera.png b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera.png new file mode 100644 index 0000000000..799e2c02a3 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@2x.png new file mode 100644 index 0000000000..191b388f95 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@3x.png new file mode 100644 index 0000000000..7af3968c98 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_camera.imageset/action_camera@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/Contents.json new file mode 100644 index 0000000000..63ec743931 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_file.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_file@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_file@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file.png b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file.png new file mode 100644 index 0000000000..0d944b1284 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@2x.png new file mode 100644 index 0000000000..4f4522ab7b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@3x.png new file mode 100644 index 0000000000..13ed744fc0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_file.imageset/action_file@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/Contents.json new file mode 100644 index 0000000000..7854a94236 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_media_library.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_media_library@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_media_library@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library.png b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library.png new file mode 100644 index 0000000000..0adba836c6 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@2x.png new file mode 100644 index 0000000000..aa6ddc4f99 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@3x.png new file mode 100644 index 0000000000..2dfd074ab0 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_media_library.imageset/action_media_library@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/Contents.json new file mode 100644 index 0000000000..adf12f0d69 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "action_sticker.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "action_sticker@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "action_sticker@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker.png b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker.png new file mode 100644 index 0000000000..cb7a04d3fe Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@2x.png new file mode 100644 index 0000000000..13a2b75119 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@3x.png new file mode 100644 index 0000000000..d000e8f4c8 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_sticker.imageset/action_sticker@3x.png differ diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 6f45d86687..7f973231f5 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -98,6 +98,10 @@ internal enum Asset { internal static let peopleEmptyScreenArtwork = ImageAsset(name: "people_empty_screen_artwork") internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark") internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action") + internal static let actionCamera = ImageAsset(name: "action_camera") + internal static let actionFile = ImageAsset(name: "action_file") + internal static let actionMediaLibrary = ImageAsset(name: "action_media_library") + internal static let actionSticker = ImageAsset(name: "action_sticker") internal static let error = ImageAsset(name: "error") internal static let errorMessageTick = ImageAsset(name: "error_message_tick") internal static let roomActivitiesRetry = ImageAsset(name: "room_activities_retry") diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 9bd0476432..7acb35ea10 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -127,6 +127,7 @@ #import "Riot-Swift.h" NSNotificationName const RoomCallTileTappedNotification = @"RoomCallTileTappedNotification"; +const NSTimeInterval kResizeComposerAnimationDuration = .05; @interface RoomViewController () currentAlert = nil; + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + // Show the sticker picker settings screen + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] + initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId + screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] + widgetId:nil]; + + [self presentViewController:modularVC animated:NO completion:nil]; + }]]; + + [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + } +} + +- (void)roomInputToolbarViewDidTapFileUpload +{ + MXKDocumentPickerPresenter *documentPickerPresenter = [MXKDocumentPickerPresenter new]; + documentPickerPresenter.delegate = self; + + NSArray *allowedUTIs = @[MXKUTI.data]; + [documentPickerPresenter presentDocumentPickerWith:allowedUTIs from:self animated:YES completion:nil]; + + self.documentPickerPresenter = documentPickerPresenter; +} + #pragma mark - Dialpad - (void)openDialpad @@ -3321,80 +3442,6 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; } -#pragma mark - RoomInputToolbarViewDelegate - -- (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView -{ - // Search for the sticker picker widget in the user account - Widget *widget = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.mxSession ofTypes:@[kWidgetTypeStickerPicker]].firstObject; - - if (widget) - { - // Display the widget - [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { - - StickerPickerViewController *stickerPickerVC = [[StickerPickerViewController alloc] initWithUrl:widgetUrl forWidget:widget]; - - stickerPickerVC.roomDataSource = self.roomDataSource; - - [self.navigationController pushViewController:stickerPickerVC animated:YES]; - } failure:^(NSError * _Nonnull error) { - - NSLog(@"[RoomVC] Cannot display widget %@", widget); - [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; - } - else - { - // The Sticker picker widget is not installed yet. Propose the user to install it - __weak typeof(self) weakSelf = self; - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - NSString *alertMessage = [NSString stringWithFormat:@"%@\n%@", - NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert", @"Vector", nil), - NSLocalizedStringFromTable(@"widget_sticker_picker_no_stickerpacks_alert_add_now", @"Vector", nil) - ]; - - currentAlert = [UIAlertController alertControllerWithTitle:nil message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) - { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Show the sticker picker settings screen - IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] - initForMXSession:self.roomDataSource.mxSession - inRoom:self.roomDataSource.roomId - screen:[IntegrationManagerViewController screenForWidget:kWidgetTypeStickerPicker] - widgetId:nil]; - - [self presentViewController:modularVC animated:NO completion:nil]; - } - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCStickerPickerAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } -} - #pragma mark - VoIP - (void)placeCallWithVideo:(BOOL)video @@ -3641,27 +3688,6 @@ - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView heightDidChan } } -- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView *)toolbarView -{ - MXKDocumentPickerPresenter *documentPickerPresenter = [MXKDocumentPickerPresenter new]; - documentPickerPresenter.delegate = self; - - NSArray *allowedUTIs = @[MXKUTI.data]; - [documentPickerPresenter presentDocumentPickerWith:allowedUTIs from:self animated:YES completion:nil]; - - self.documentPickerPresenter = documentPickerPresenter; -} - -- (void)roomInputToolbarViewDidTapCamera:(MXKRoomInputToolbarView*)toolbarView -{ - [self showCameraControllerAnimated:YES]; -} - -- (void)roomInputToolbarViewDidTapMediaLibrary:(MXKRoomInputToolbarView*)toolbarView -{ - [self showMediaPickerAnimated:YES]; -} - - (void)roomInputToolbarViewDidTapCancel:(MXKRoomInputToolbarView*)toolbarView { [self cancelEventSelection]; diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomActionItem.swift b/Riot/Modules/Room/Views/InputToolbar/RoomActionItem.swift new file mode 100644 index 0000000000..773e4be3df --- /dev/null +++ b/Riot/Modules/Room/Views/InputToolbar/RoomActionItem.swift @@ -0,0 +1,30 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@objcMembers +class RoomActionItem: NSObject { + let image: UIImage + let action: (() -> Void) + + init(image: UIImage, andAction action: @escaping () -> Void) { + self.image = image + self.action = action + + super.init() + } +} diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomActionsBar.swift b/Riot/Modules/Room/Views/InputToolbar/RoomActionsBar.swift new file mode 100644 index 0000000000..5482ab8330 --- /dev/null +++ b/Riot/Modules/Room/Views/InputToolbar/RoomActionsBar.swift @@ -0,0 +1,132 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit + +@objcMembers +class RoomActionsBar: UIScrollView, Themable { + // MARK: - Properties + + var itemSpacing: CGFloat = 20 { + didSet { + self.setNeedsLayout() + } + } + + var actionItems: [RoomActionItem] = [] { + didSet { + var actionButtons: [UIButton] = [] + for (index, item) in actionItems.enumerated() { + let button = UIButton(type: .custom) + button.setImage(item.image, for: .normal) + button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside) + button.tintColor = ThemeService.shared().theme.tintColor + button.tag = index + actionButtons.append(button) + addSubview(button) + } + self.actionButtons = actionButtons + self.lastBounds = .zero + self.setNeedsLayout() + } + } + + private var actionButtons: [UIButton] = [] { + willSet { + for button in actionButtons { + button.removeFromSuperview() + } + } + } + + private var lastBounds = CGRect.zero + + // MARK: - Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + override func layoutSubviews() { + super.layoutSubviews() + + guard lastBounds != self.bounds else { + return + } + + lastBounds = self.bounds + + var currentX: CGFloat = 0 + for button in actionButtons { + button.transform = CGAffineTransform.identity + button.frame = CGRect(x: currentX, y: 0, width: self.bounds.height, height: self.bounds.height) + currentX = button.frame.maxX + itemSpacing + } + + self.contentSize = CGSize(width: currentX - itemSpacing, height: self.bounds.height) + } + + // MARK: - Themable + + func update(theme: Theme) { + for button in actionButtons { + button.tintColor = theme.tintColor + } + } + + // MARK: - Business methods + + func animate(showIn: Bool, completion: ((Bool) -> Void)? = nil) { + if showIn { + for button in actionButtons { + button.transform = CGAffineTransform(translationX: 0, y: self.bounds.height) + } + for (index, button) in actionButtons.enumerated() { + UIView.animate(withDuration: 0.3, delay: 0.05 * Double(index), usingSpringWithDamping: 0.45, initialSpringVelocity: 11, options: .curveEaseInOut) { + button.transform = CGAffineTransform.identity + } completion: { (finished) in + completion?(finished) + } + } + } else { + for (index, button) in actionButtons.enumerated() { + UIView.animate(withDuration: 0.25, delay: 0.05 * Double(index), options: .curveEaseInOut) { + button.transform = CGAffineTransform(translationX: 0, y: self.bounds.height) + } completion: { (finished) in + if index == self.actionButtons.count - 1 { + completion?(finished) + } + } + } + } + } + + // MARK: - Private methods + + @objc private func buttonAction(_ sender: UIButton) { + actionItems[sender.tag].action() + } + + private func setupView() { + self.showsHorizontalScrollIndicator = false + } +} diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index e5559e9fee..b2ad883b89 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -18,6 +18,8 @@ #import "MediaPickerViewController.h" +@class RoomActionsBar; + /** Destination of the message in the composer */ @@ -31,34 +33,6 @@ typedef enum : NSUInteger @protocol RoomInputToolbarViewDelegate -/** - Tells the delegate that the user wants to display the sticker picker. - - @param toolbarView the room input toolbar view. - */ -- (void)roomInputToolbarViewPresentStickerPicker:(MXKRoomInputToolbarView*)toolbarView; - -/** - Tells the delegate that the user wants to send external files. - - @param toolbarView the room input toolbar view - */ -- (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView*)toolbarView; - -/** - Tells the delegate that the user wants to take photo or video with camera. - - @param toolbarView the room input toolbar view - */ -- (void)roomInputToolbarViewDidTapCamera:(MXKRoomInputToolbarView*)toolbarView; - -/** - Tells the delegate that the user wants to show media library. - - @param toolbarView the room input toolbar view - */ -- (void)roomInputToolbarViewDidTapMediaLibrary:(MXKRoomInputToolbarView*)toolbarView; - /** Tells the delegate that the user wants to cancel the current edition / reply. @@ -95,6 +69,7 @@ typedef enum : NSUInteger @property (weak, nonatomic) IBOutlet UIImageView *inputContextImageView; @property (weak, nonatomic) IBOutlet UILabel *inputContextLabel; @property (weak, nonatomic) IBOutlet UIButton *inputContextButton; +@property (weak, nonatomic) IBOutlet RoomActionsBar *actionsBar; /** Tell whether the filled data will be sent encrypted. NO by default. @@ -111,4 +86,9 @@ typedef enum : NSUInteger */ @property (nonatomic) RoomInputToolbarViewSendMode sendMode; +/** + YES if action menu is opened. NO otherwise + */ +@property (nonatomic, getter=isActionMenuOpened) BOOL actionMenuOpened; + @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index dfceba9784..26a2f4c69c 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -27,7 +27,13 @@ #import "WidgetManager.h" #import "IntegrationManagerViewController.h" -const double RoomInputToolbarViewContextBarHeight = 30; +const double kContextBarHeight = 24; +const NSTimeInterval kSendModeAnimationDuration = .15; +const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; +const CGFloat kActionMenuAttachButtonSpringVelocity = 7; +const CGFloat kActionMenuAttachButtonSpringDamping = .45; +const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; +const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; @interface RoomInputToolbarView() { @@ -120,6 +126,7 @@ -(void)customizeViewRendering self.inputContextImageView.tintColor = ThemeService.shared.theme.textSecondaryColor; self.inputContextLabel.textColor = ThemeService.shared.theme.textSecondaryColor; self.inputContextButton.tintColor = ThemeService.shared.theme.textSecondaryColor; + [self.actionsBar updateWithTheme:ThemeService.shared.theme]; } #pragma mark - @@ -142,6 +149,7 @@ - (void)setSendMode:(RoomInputToolbarViewSendMode)sendMode RoomInputToolbarViewSendMode previousMode = _sendMode; _sendMode = sendMode; + self.actionMenuOpened = NO; [self updatePlaceholder]; [self updateToolbarButtonLabelWithPreviousMode: previousMode]; } @@ -159,26 +167,26 @@ - (void)updateToolbarButtonLabelWithPreviousMode:(RoomInputToolbarViewSendMode)p self.inputContextImageView.image = [UIImage imageNamed:@"input_reply_icon"]; self.inputContextLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_message_replying_to", @"Vector", nil), self.eventSenderDisplayName]; - self.inputContextViewHeightConstraint.constant = RoomInputToolbarViewContextBarHeight; - updatedHeight += RoomInputToolbarViewContextBarHeight; - self->growingTextView.maxHeight -= RoomInputToolbarViewContextBarHeight; + self.inputContextViewHeightConstraint.constant = kContextBarHeight; + updatedHeight += kContextBarHeight; + self->growingTextView.maxHeight -= kContextBarHeight; break; case RoomInputToolbarViewSendModeEdit: buttonImage = [UIImage imageNamed:@"save_icon"]; self.inputContextImageView.image = [UIImage imageNamed:@"input_edit_icon"]; self.inputContextLabel.text = NSLocalizedStringFromTable(@"room_message_editing", @"Vector", nil); - self.inputContextViewHeightConstraint.constant = RoomInputToolbarViewContextBarHeight; - updatedHeight += RoomInputToolbarViewContextBarHeight; - self->growingTextView.maxHeight -= RoomInputToolbarViewContextBarHeight; + self.inputContextViewHeightConstraint.constant = kContextBarHeight; + updatedHeight += kContextBarHeight; + self->growingTextView.maxHeight -= kContextBarHeight; break; default: buttonImage = [UIImage imageNamed:@"send_icon"]; if (previousMode != _sendMode) { - updatedHeight -= RoomInputToolbarViewContextBarHeight; - self->growingTextView.maxHeight += RoomInputToolbarViewContextBarHeight; + updatedHeight -= kContextBarHeight; + self->growingTextView.maxHeight += kContextBarHeight; } self.inputContextViewHeightConstraint.constant = 0; break; @@ -199,7 +207,7 @@ - (void)updateToolbarButtonLabelWithPreviousMode:(RoomInputToolbarViewSendMode)p if (self.mainToolbarHeightConstraint.constant != updatedHeight) { - [UIView animateWithDuration:.3 animations:^{ + [UIView animateWithDuration:kSendModeAnimationDuration animations:^{ self.mainToolbarHeightConstraint.constant = updatedHeight; [self layoutIfNeeded]; @@ -329,92 +337,7 @@ - (IBAction)onTouchUpInside:(UIButton*)button { if (button == self.attachMediaButton) { - // Check whether media attachment is supported - if ([self.delegate respondsToSelector:@selector(roomInputToolbarView:presentViewController:)]) - { - // Ask the user the kind of the call: voice or video? - actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - - __weak typeof(self) weakSelf = self; - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_camera", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewDidTapCamera:self]; - } - }]]; - - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_photo_or_video", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewDidTapMediaLibrary:self]; - } - - }]]; - - if (BuildSettings.allowSendingStickers) - { - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_sticker", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewPresentStickerPicker:self]; - } - - }]]; - } - - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_file", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - - [self.delegate roomInputToolbarViewDidTapFileUpload:self]; - } - }]]; - - [actionSheet addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->actionSheet = nil; - } - - }]]; - - [actionSheet popoverPresentationController].sourceView = self.attachMediaButton; - [actionSheet popoverPresentationController].sourceRect = self.attachMediaButton.bounds; - [self.window.rootViewController presentViewController:actionSheet animated:YES completion:nil]; - } - else - { - NSLog(@"[RoomInputToolbarView] Attach media is not supported"); - } + self.actionMenuOpened = !self.isActionMenuOpened; } [super onTouchUpInside:button]; @@ -433,6 +356,8 @@ - (void)destroy - (void)updateSendButtonWithMessage:(NSString *)textMessage { + self.actionMenuOpened = NO; + if (textMessage.length) { self.rightInputToolbarButton.alpha = 1; @@ -443,9 +368,61 @@ - (void)updateSendButtonWithMessage:(NSString *)textMessage self.rightInputToolbarButton.alpha = 0; self.messageComposerContainerTrailingConstraint.constant = 12; } + [self layoutIfNeeded]; } +#pragma mark - properties + +- (void)setActionMenuOpened:(BOOL)actionMenuOpened +{ + if (_actionMenuOpened != actionMenuOpened) + { + _actionMenuOpened = actionMenuOpened; + + if (self->growingTextView.internalTextView.selectedRange.length > 0) + { + NSRange range = self->growingTextView.internalTextView.selectedRange; + range.location = range.location + range.length; + range.length = 0; + self->growingTextView.internalTextView.selectedRange = range; + } + + if (_actionMenuOpened) { + self.actionsBar.hidden = NO; + [self.actionsBar animateWithShowIn:_actionMenuOpened completion:nil]; + } + else + { + [self.actionsBar animateWithShowIn:_actionMenuOpened completion:^(BOOL finished) { + self.actionsBar.hidden = YES; + }]; + } + + [UIView animateWithDuration:kActionMenuAttachButtonAnimationDuration delay:0 usingSpringWithDamping:kActionMenuAttachButtonSpringDamping initialSpringVelocity:kActionMenuAttachButtonSpringVelocity options:UIViewAnimationOptionCurveEaseIn animations:^{ + self.attachMediaButton.transform = actionMenuOpened ? CGAffineTransformMakeRotation(M_PI * 3 / 4) : CGAffineTransformIdentity; + } completion:nil]; + + [UIView animateWithDuration:kActionMenuContentAlphaAnimationDuration delay:_actionMenuOpened ? 0 : .1 options:UIViewAnimationOptionCurveEaseIn animations:^{ + self->messageComposerContainer.alpha = actionMenuOpened ? 0 : 1; + self.rightInputToolbarButton.alpha = self->growingTextView.text.length == 0 || actionMenuOpened ? 0 : 1; + } completion:nil]; + + [UIView animateWithDuration:kActionMenuComposerHeightAnimationDuration animations:^{ + if (actionMenuOpened) + { + self.mainToolbarHeightConstraint.constant = self.mainToolbarMinHeightConstraint.constant; + } + else + { + [self->growingTextView refreshHeight]; + } + [self layoutIfNeeded]; + [self.delegate roomInputToolbarView:self heightDidChanged:self.mainToolbarHeightConstraint.constant completion:nil]; + }]; + } +} + #pragma mark - Clipboard - Handle image/data paste from general pasteboard - (void)paste:(id)sender diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib index fbc52e9f1d..aef8b2f816 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib @@ -27,6 +27,14 @@ + @@ -37,16 +45,16 @@ - +