mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 08:03:50 +02:00
Switching composer between text mode & action mode
This commit is contained in:
@@ -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
|
||||
@objc class RoomActionItem: NSObject {
|
||||
var image: UIImage!
|
||||
var action: (() -> Void)!
|
||||
|
||||
init(image: UIImage, andAction action: @escaping () -> Void) {
|
||||
super.init()
|
||||
|
||||
self.image = image
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// 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
|
||||
@objc class RoomActionsBar: UIScrollView {
|
||||
// MARK: - Properties
|
||||
|
||||
var itemSpacing: CGFloat = 20 {
|
||||
didSet {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
var actionItems: Array<RoomActionItem> = [] {
|
||||
didSet {
|
||||
var actionButtons: Array<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: Array<UIButton> = [] {
|
||||
willSet {
|
||||
for button in actionButtons {
|
||||
button.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var lastBounds = CGRect.zero
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.showsHorizontalScrollIndicator = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
self.showsHorizontalScrollIndicator = false
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
guard lastBounds != self.bounds else {
|
||||
return
|
||||
}
|
||||
|
||||
lastBounds = self.bounds
|
||||
|
||||
var currentX: CGFloat = 0
|
||||
for button in actionButtons {
|
||||
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: - Business methods
|
||||
|
||||
func customizeViewRendering() {
|
||||
for button in actionButtons {
|
||||
button.tintColor = ThemeService.shared().theme.tintColor
|
||||
}
|
||||
}
|
||||
|
||||
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.32, delay: 0.05 * Double(index), usingSpringWithDamping: 0.5, initialSpringVelocity: 7, options: .curveEaseInOut) {
|
||||
button.transform = CGAffineTransform.identity
|
||||
} completion: { (finished) in
|
||||
completion?(finished)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (index, button) in actionButtons.enumerated() {
|
||||
UIView.animate(withDuration: 0.2, delay: 0.05 * Double(index), options: .curveEaseInOut) {
|
||||
button.transform = CGAffineTransform(translationX: 0, y: self.bounds.height)
|
||||
} completion: { (finished) in
|
||||
completion?(finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
@objc private func buttonAction(_ sender: UIButton) {
|
||||
if let action = actionItems[sender.tag].action {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
self.showsHorizontalScrollIndicator = false
|
||||
}
|
||||
}
|
||||
@@ -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 <MXKRoomInputToolbarViewDelegate>
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
@@ -120,6 +120,7 @@ const double RoomInputToolbarViewContextBarHeight = 30;
|
||||
self.inputContextImageView.tintColor = ThemeService.shared.theme.textSecondaryColor;
|
||||
self.inputContextLabel.textColor = ThemeService.shared.theme.textSecondaryColor;
|
||||
self.inputContextButton.tintColor = ThemeService.shared.theme.textSecondaryColor;
|
||||
[self.actionsBar customizeViewRendering];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
@@ -142,6 +143,7 @@ const double RoomInputToolbarViewContextBarHeight = 30;
|
||||
RoomInputToolbarViewSendMode previousMode = _sendMode;
|
||||
_sendMode = sendMode;
|
||||
|
||||
self.actionMenuOpened = NO;
|
||||
[self updatePlaceholder];
|
||||
[self updateToolbarButtonLabelWithPreviousMode: previousMode];
|
||||
}
|
||||
@@ -329,92 +331,7 @@ const double RoomInputToolbarViewContextBarHeight = 30;
|
||||
{
|
||||
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];
|
||||
@@ -443,9 +360,55 @@ const double RoomInputToolbarViewContextBarHeight = 30;
|
||||
self.rightInputToolbarButton.alpha = 0;
|
||||
self.messageComposerContainerTrailingConstraint.constant = 12;
|
||||
}
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark - properties
|
||||
|
||||
- (void)setActionMenuOpened:(BOOL)actionMenuOpened
|
||||
{
|
||||
if (_actionMenuOpened != actionMenuOpened)
|
||||
{
|
||||
_actionMenuOpened = actionMenuOpened;
|
||||
|
||||
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:.4 delay:0 usingSpringWithDamping:0.45 initialSpringVelocity:5 options:UIViewAnimationOptionCurveEaseIn animations:^{
|
||||
self.attachMediaButton.transform = actionMenuOpened ? CGAffineTransformMakeRotation(M_PI * 3 / 4) : CGAffineTransformIdentity;
|
||||
} completion:^(BOOL finished) {
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:.2 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:^(BOOL finished) {
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:.3 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
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
<action selector="onTouchUpInside:" destination="iN0-l3-epB" eventType="touchUpInside" id="WbU-WH-gwL"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView hidden="YES" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ESv-9w-KJF" customClass="RoomActionsBar" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="60" y="8" width="540" height="38"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="38" id="i6C-gL-ADZ"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="contentLayoutGuide" id="F6O-76-cZl"/>
|
||||
<viewLayoutGuide key="frameLayoutGuide" id="rZR-Bv-AqG"/>
|
||||
</scrollView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QWp-NV-uh5" userLabel="Message Composer Container">
|
||||
<rect key="frame" x="60" y="9" width="528" height="36"/>
|
||||
<subviews>
|
||||
@@ -105,11 +113,14 @@
|
||||
<constraint firstItem="QWp-NV-uh5" firstAttribute="leading" secondItem="Hga-l8-Wua" secondAttribute="trailing" constant="12" id="M9f-je-3zO"/>
|
||||
<constraint firstAttribute="bottom" secondItem="QWp-NV-uh5" secondAttribute="bottom" constant="13" id="NGr-2o-sOP"/>
|
||||
<constraint firstAttribute="trailing" secondItem="G8Z-CM-tGs" secondAttribute="trailing" constant="12" id="Sua-LC-3yW"/>
|
||||
<constraint firstItem="ESv-9w-KJF" firstAttribute="leading" secondItem="Hga-l8-Wua" secondAttribute="trailing" constant="12" id="TIe-py-lFJ"/>
|
||||
<constraint firstItem="QWp-NV-uh5" firstAttribute="top" secondItem="a84-Vc-6ud" secondAttribute="top" constant="9" id="WyZ-3i-OHi"/>
|
||||
<constraint firstAttribute="bottom" secondItem="G8Z-CM-tGs" secondAttribute="bottom" constant="12" id="Yam-dS-zwr"/>
|
||||
<constraint firstAttribute="height" constant="58" id="Yjj-ua-rbe"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Hga-l8-Wua" secondAttribute="bottom" constant="12" id="b0G-CY-AmP"/>
|
||||
<constraint firstAttribute="trailing" secondItem="QWp-NV-uh5" secondAttribute="trailing" constant="12" id="hXO-cY-Jgz"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ESv-9w-KJF" secondAttribute="trailing" id="jCS-Tf-vxr"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ESv-9w-KJF" secondAttribute="bottom" constant="12" id="v8r-ac-MKn"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
@@ -125,6 +136,7 @@
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="actionsBar" destination="ESv-9w-KJF" id="h7H-vz-yzO"/>
|
||||
<outlet property="attachMediaButton" destination="Hga-l8-Wua" id="Osr-ek-c91"/>
|
||||
<outlet property="growingTextView" destination="wgb-ON-N29" id="nwF-uV-Ng9"/>
|
||||
<outlet property="inputContextButton" destination="48y-kn-7b5" id="yRn-1S-96w"/>
|
||||
|
||||
Reference in New Issue
Block a user