Voice Messages - Hold and send

- Added voice message button
- voice recording UI state
This commit is contained in:
Gil Eluard
2021-06-02 07:36:32 +02:00
committed by Stefan Ceriu
parent 8e938b196a
commit 53b2b05b15
10 changed files with 230 additions and 24 deletions

View File

@@ -58,9 +58,6 @@ typedef enum : NSUInteger
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarMinHeightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mainToolbarHeightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageComposerContainerLeadingConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *messageComposerContainerTrailingConstraint;
@property (weak, nonatomic) IBOutlet UIButton *attachMediaButton;
@property (weak, nonatomic) IBOutlet UIImageView *inputTextBackgroundView;
@@ -71,6 +68,9 @@ typedef enum : NSUInteger
@property (weak, nonatomic) IBOutlet UIButton *inputContextButton;
@property (weak, nonatomic) IBOutlet RoomActionsBar *actionsBar;
@property (weak, nonatomic) IBOutlet UIView *voiceRecorderContainerView;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *voiceRecorderContainerWidthConstraint;
/**
Tell whether the filled data will be sent encrypted. NO by default.
*/

View File

@@ -35,10 +35,12 @@ const CGFloat kActionMenuAttachButtonSpringDamping = .45;
const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2;
const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
@interface RoomInputToolbarView()
@interface RoomInputToolbarView() <VoiceRecordViewDelegate>
{
// The intermediate action sheet
UIAlertController *actionSheet;
VoiceRecordView *voiceRecordView;
}
@end
@@ -75,6 +77,13 @@ const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
[self.rightInputToolbarButton setTitle:nil forState:UIControlStateHighlighted];
self.isEncryptionEnabled = _isEncryptionEnabled;
voiceRecordView = [VoiceRecordView instanceFromNib];
voiceRecordView.delegate = self;
voiceRecordView.frame = self.voiceRecorderContainerView.bounds;
voiceRecordView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.voiceRecorderContainerView addSubview:voiceRecordView];
}
#pragma mark - Override MXKView
@@ -127,6 +136,9 @@ const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
self.inputContextLabel.textColor = ThemeService.shared.theme.textSecondaryColor;
self.inputContextButton.tintColor = ThemeService.shared.theme.textSecondaryColor;
[self.actionsBar updateWithTheme:ThemeService.shared.theme];
self.voiceRecorderContainerView.backgroundColor = ThemeService.shared.theme.backgroundColor;
[voiceRecordView updateWithTheme:ThemeService.shared.theme];
}
#pragma mark -
@@ -358,18 +370,10 @@ const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
{
self.actionMenuOpened = NO;
if (textMessage.length)
{
self.rightInputToolbarButton.alpha = 1;
self.messageComposerContainerTrailingConstraint.constant = self.frame.size.width - self.rightInputToolbarButton.frame.origin.x + 12;
}
else
{
self.rightInputToolbarButton.alpha = 0;
self.messageComposerContainerTrailingConstraint.constant = 12;
}
[self layoutIfNeeded];
[UIView animateWithDuration:.15 animations:^{
self.rightInputToolbarButton.alpha = textMessage.length ? 1 : 0;
self.voiceRecorderContainerView.alpha = textMessage.length ? 0 : 1;
}];
}
#pragma mark - properties
@@ -432,4 +436,13 @@ const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3;
[super paste:sender];
}
#pragma mark - VoiceRecordViewDelegate
- (void)voiceRecordViewExpandedStateDidChange:(VoiceRecordView * _Nonnull)voiceRecordView {
[UIView animateWithDuration:voiceRecordView.expandAnimationDuration animations:^{
self.voiceRecorderContainerWidthConstraint.constant = voiceRecordView.isExpanded ? self.bounds.size.width : 48;
[self layoutIfNeeded];
}];
}
@end

View File

@@ -4,6 +4,7 @@
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -36,25 +37,25 @@
<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"/>
<rect key="frame" x="60" y="9" width="484" height="36"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="input_text_background" translatesAutoresizingMaskIntoConstraints="NO" id="uH7-Q7-hpZ">
<rect key="frame" x="0.0" y="0.0" width="528" height="36"/>
<rect key="frame" x="0.0" y="0.0" width="484" height="36"/>
</imageView>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jXI-9E-Bgl">
<rect key="frame" x="0.0" y="0.0" width="528" height="32"/>
<rect key="frame" x="0.0" y="0.0" width="484" height="32"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="input_edit_icon" translatesAutoresizingMaskIntoConstraints="NO" id="PZ4-0Y-TmL">
<rect key="frame" x="8" y="16" width="10.5" height="10"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dVr-ZM-kkX">
<rect key="frame" x="22.5" y="13.5" width="471.5" height="14.5"/>
<rect key="frame" x="22.5" y="13.5" width="427.5" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="48y-kn-7b5">
<rect key="frame" x="498" y="6" width="30" height="30"/>
<rect key="frame" x="454" y="6" width="30" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="I17-S0-9fp"/>
<constraint firstAttribute="width" constant="30" id="cCe-RB-ET2"/>
@@ -78,7 +79,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wgb-ON-N29" customClass="KeyboardGrowingTextView">
<rect key="frame" x="5" y="33" width="518" height="4"/>
<rect key="frame" x="5" y="33" width="474" height="4"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="GrowingTextView"/>
</view>
@@ -106,21 +107,31 @@
<action selector="onTouchUpInside:" destination="iN0-l3-epB" eventType="touchUpInside" id="Y9g-uz-rAz"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zp9-EN-PLT">
<rect key="frame" x="552" y="0.0" width="48" height="58"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="width" constant="48" id="paQ-xM-7ko"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="58" id="1FO-iu-urG"/>
<constraint firstItem="Hga-l8-Wua" firstAttribute="leading" secondItem="a84-Vc-6ud" secondAttribute="leading" constant="12" id="31r-fn-347"/>
<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 firstItem="zp9-EN-PLT" firstAttribute="top" secondItem="a84-Vc-6ud" secondAttribute="top" id="OFZ-RG-O8K"/>
<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="QWp-NV-uh5" secondAttribute="trailing" constant="56" id="hXO-cY-Jgz"/>
<constraint firstAttribute="trailing" secondItem="ESv-9w-KJF" secondAttribute="trailing" id="jCS-Tf-vxr"/>
<constraint firstAttribute="bottom" secondItem="zp9-EN-PLT" secondAttribute="bottom" id="o7N-Dj-dCH"/>
<constraint firstAttribute="bottom" secondItem="ESv-9w-KJF" secondAttribute="bottom" constant="12" id="v8r-ac-MKn"/>
<constraint firstAttribute="trailing" secondItem="zp9-EN-PLT" secondAttribute="trailing" id="yEl-3y-oBb"/>
</constraints>
</view>
</subviews>
@@ -150,8 +161,9 @@
<outlet property="messageComposerContainer" destination="QWp-NV-uh5" id="APR-B5-ogC"/>
<outlet property="messageComposerContainerBottomConstraint" destination="NGr-2o-sOP" id="oez-6D-IKA"/>
<outlet property="messageComposerContainerTopConstraint" destination="WyZ-3i-OHi" id="OcO-1f-bNA"/>
<outlet property="messageComposerContainerTrailingConstraint" destination="hXO-cY-Jgz" id="lHZ-MU-vyC"/>
<outlet property="rightInputToolbarButton" destination="G8Z-CM-tGs" id="NCk-5m-aNF"/>
<outlet property="voiceRecorderContainerView" destination="zp9-EN-PLT" id="ybB-DB-qA4"/>
<outlet property="voiceRecorderContainerWidthConstraint" destination="paQ-xM-7ko" id="17V-6a-ddW"/>
</connections>
<point key="canvasLocation" x="137.59999999999999" y="151.12443778110946"/>
</view>
@@ -162,5 +174,8 @@
<image name="input_text_background" width="30" height="20"/>
<image name="send_icon" width="36" height="36"/>
<image name="upload_icon" width="36" height="36"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@@ -0,0 +1,107 @@
//
// 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
@objc protocol VoiceRecordViewDelegate: NSObjectProtocol {
func voiceRecordViewExpandedStateDidChange(_ voiceRecordView: VoiceRecordView)
}
@objcMembers
class VoiceRecordView: UIView, Themable {
@IBOutlet var voiceMessageButton: UIImageView!
@IBOutlet var voiceMessageButtonTrailingConstraint: NSLayoutConstraint!
weak var delegate: VoiceRecordViewDelegate?
var isExpanded = false {
didSet {
delegate?.voiceRecordViewExpandedStateDidChange(self)
}
}
let expandAnimationDuration = 0.3
private var firstTouchPoint: CGPoint = CGPoint.zero
private var initialVoiceMessageButtonPadding: CGFloat = 0
// MARK: - Themable
func update(theme: Theme) {
voiceMessageButton.tintColor = theme.tintColor
}
// MARK: - Instanciation
class func instanceFromNib() -> VoiceRecordView {
let nib = UINib(nibName: "VoiceRecordView", bundle: nil)
guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else {
fatalError("The nib \(nib) expected its root view to be of type \(self)")
}
return view
}
override func awakeFromNib() {
super.awakeFromNib()
initialVoiceMessageButtonPadding = voiceMessageButtonTrailingConstraint.constant
}
// MARK: - Touch management
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let point = touches.first?.location(in: self) ?? CGPoint.zero
firstTouchPoint = CGPoint(x: self.bounds.width - point.x, y: point.y)
isExpanded = true
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let point = touches.first?.location(in: self) else {
return
}
let xDelta = min(firstTouchPoint.x - (self.bounds.width - point.x), 0)
UIView.animate(withDuration: 0.001) {
self.voiceMessageButtonTrailingConstraint.constant = self.initialVoiceMessageButtonPadding - xDelta
self.layoutIfNeeded()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
isExpanded = false
UIView.animate(withDuration: expandAnimationDuration) {
self.voiceMessageButtonTrailingConstraint.constant = self.initialVoiceMessageButtonPadding
self.layoutIfNeeded()
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
isExpanded = false
UIView.animate(withDuration: expandAnimationDuration) {
self.voiceMessageButtonTrailingConstraint.constant = self.initialVoiceMessageButtonPadding
self.layoutIfNeeded()
}
}
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="VoiceRecordView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="action_voice_message" translatesAutoresizingMaskIntoConstraints="NO" id="vxl-Nq-SMz">
<rect key="frame" x="366" y="2" width="36" height="36"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="HDl-A8-XOv"/>
<constraint firstAttribute="height" constant="36" id="TTQ-3n-whY"/>
</constraints>
</imageView>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="vxl-Nq-SMz" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="RSo-K7-fpr"/>
<constraint firstAttribute="trailing" secondItem="vxl-Nq-SMz" secondAttribute="trailing" constant="12" id="USM-2w-hK0"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="voiceMessageButton" destination="vxl-Nq-SMz" id="ie8-jc-Ylg"/>
<outlet property="voiceMessageButtonTrailingConstraint" destination="USM-2w-hK0" id="Tbp-PW-1eK"/>
</connections>
<point key="canvasLocation" x="-84" y="234"/>
</view>
</objects>
<resources>
<image name="action_voice_message" width="22" height="26.5"/>
</resources>
</document>