mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-18 23:48:29 +02:00
Voice Messages - Hold and send
- Added voice message button - voice recording UI state
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
107
Riot/Modules/Room/Views/InputToolbar/VoiceRecordView.swift
Normal file
107
Riot/Modules/Room/Views/InputToolbar/VoiceRecordView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Riot/Modules/Room/Views/InputToolbar/VoiceRecordView.xib
Normal file
44
Riot/Modules/Room/Views/InputToolbar/VoiceRecordView.xib
Normal 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>
|
||||
Reference in New Issue
Block a user