-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into issue-618-cont-voice-recording
Signed-off-by: Julius Linus <69230048+rapterjet2004@users.noreply.github.com>
- Loading branch information
Showing
117 changed files
with
2,065 additions
and
1,261 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,4 @@ nextcloud | |
CallKit | ||
Unban | ||
unban | ||
Zammad |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// | ||
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// | ||
|
||
import UIKit | ||
|
||
protocol AudioPlayerViewDelegate: AnyObject { | ||
|
||
func audioPlayerPlayButtonPressed() | ||
func audioPlayerPauseButtonPressed() | ||
func audioPlayerProgressChanged(progress: CGFloat) | ||
} | ||
|
||
class AudioPlayerView: UIView { | ||
|
||
@IBOutlet var contentView: UIView! | ||
@IBOutlet weak var playPauseButton: UIButton! | ||
@IBOutlet weak var slider: UISlider! | ||
@IBOutlet weak var durationLabel: UILabel! | ||
|
||
var isPlaying: Bool = false | ||
|
||
public weak var delegate: AudioPlayerViewDelegate? | ||
|
||
override init(frame: CGRect) { | ||
super.init(frame: frame) | ||
commonInit() | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
super.init(coder: aDecoder) | ||
commonInit() | ||
} | ||
|
||
func commonInit() { | ||
Bundle.main.loadNibNamed("AudioPlayerView", owner: self, options: nil) | ||
|
||
contentView.frame = self.bounds | ||
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] | ||
|
||
// Play/Pause button | ||
playPauseButton.addAction(for: .touchUpInside) { [unowned self] in | ||
if isPlaying { | ||
delegate?.audioPlayerPauseButtonPressed() | ||
} else { | ||
delegate?.audioPlayerPlayButtonPressed() | ||
} | ||
} | ||
|
||
// Slider | ||
let thumbImage = UIImage(systemName: "circle.fill")? | ||
.withConfiguration(UIImage.SymbolConfiguration(pointSize: 16)) | ||
.withTintColor(.label, renderingMode: .alwaysOriginal) | ||
slider.setThumbImage(thumbImage, for: .normal) | ||
slider.semanticContentAttribute = .forceLeftToRight | ||
slider.addAction(for: .valueChanged) { [unowned self] in | ||
delegate?.audioPlayerProgressChanged(progress: CGFloat(slider.value)) | ||
} | ||
|
||
// Duration label | ||
hideDurationLabel() | ||
|
||
backgroundColor = .secondarySystemBackground | ||
layer.cornerRadius = 8.0 | ||
layer.masksToBounds = true | ||
|
||
self.addSubview(contentView) | ||
} | ||
|
||
func setPlayerProgress(_ progress: CGFloat, isPlaying playing: Bool, maximumValue maxValue: CGFloat) { | ||
setPlayPauseButton(playing: playing) | ||
slider.isEnabled = true | ||
slider.value = Float(progress) | ||
slider.maximumValue = Float(maxValue) | ||
setDurationLabel(progress: progress, duration: maxValue) | ||
slider.setNeedsLayout() | ||
} | ||
|
||
func resetPlayer() { | ||
setPlayPauseButton(playing: false) | ||
slider.isEnabled = false | ||
slider.value = 0 | ||
hideDurationLabel() | ||
slider.setNeedsLayout() | ||
} | ||
|
||
func setPlayPauseButton(playing: Bool) { | ||
isPlaying = playing | ||
|
||
if isPlaying { | ||
playPauseButton.setImage(UIImage(systemName: "pause.fill"), for: .normal) | ||
} else { | ||
playPauseButton.setImage(UIImage(systemName: "play.fill"), for: .normal) | ||
} | ||
} | ||
|
||
func setDurationLabel(progress: CGFloat, duration: CGFloat) { | ||
let dateComponentsFormatter = DateComponentsFormatter() | ||
dateComponentsFormatter.allowedUnits = [.minute, .second] | ||
dateComponentsFormatter.zeroFormattingBehavior = [] | ||
|
||
let progressTime = dateComponentsFormatter.string(from: TimeInterval(progress)) ?? "0:00" | ||
let durationTime = dateComponentsFormatter.string(from: TimeInterval(duration)) ?? "0:00" | ||
|
||
let playerTimeString = "\(progressTime)".withTextColor(.label).withFont(.systemFont(ofSize: 13, weight: .medium)) | ||
playerTimeString.append(" / \(durationTime)".withTextColor(.secondaryLabel).withFont(.systemFont(ofSize: 13))) | ||
|
||
durationLabel.attributedText = playerTimeString | ||
} | ||
|
||
func hideDurationLabel() { | ||
durationLabel.text = "" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> | ||
<device id="retina6_12" orientation="portrait" appearance="light"/> | ||
<dependencies> | ||
<deployment identifier="iOS"/> | ||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/> | ||
<capability name="Safe area layout guides" minToolsVersion="9.0"/> | ||
<capability name="System colors in document resources" minToolsVersion="11.0"/> | ||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||
</dependencies> | ||
<objects> | ||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="AudioPlayerView" customModule="NextcloudTalk" customModuleProvider="target"> | ||
<connections> | ||
<outlet property="contentView" destination="iN0-l3-epB" id="WHP-2s-6Il"/> | ||
<outlet property="durationLabel" destination="7vy-DL-v2I" id="gLr-z8-lYD"/> | ||
<outlet property="playPauseButton" destination="weF-VJ-kQg" id="6O8-WK-cv9"/> | ||
<outlet property="slider" destination="Cgz-Rx-FyJ" id="TME-tZ-DL1"/> | ||
</connections> | ||
</placeholder> | ||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> | ||
<view contentMode="scaleToFill" id="iN0-l3-epB"> | ||
<rect key="frame" x="0.0" y="0.0" width="441" height="52"/> | ||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||
<subviews> | ||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="weF-VJ-kQg"> | ||
<rect key="frame" x="8" y="4" width="44" height="44"/> | ||
<constraints> | ||
<constraint firstAttribute="width" constant="44" id="TwB-9N-gEK"/> | ||
<constraint firstAttribute="height" constant="44" id="XIa-Qd-GZD"/> | ||
</constraints> | ||
<color key="tintColor" systemColor="labelColor"/> | ||
<state key="normal" title="Button"/> | ||
<buttonConfiguration key="configuration" style="plain" image="play.fill" catalog="system"/> | ||
</button> | ||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="0:00 / 0:02" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7vy-DL-v2I"> | ||
<rect key="frame" x="366" y="0.0" width="67" height="52"/> | ||
<fontDescription key="fontDescription" type="system" pointSize="13"/> | ||
<nil key="textColor"/> | ||
<nil key="highlightedColor"/> | ||
</label> | ||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="Cgz-Rx-FyJ"> | ||
<rect key="frame" x="58" y="11" width="302" height="31"/> | ||
</slider> | ||
</subviews> | ||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/> | ||
<constraints> | ||
<constraint firstItem="Cgz-Rx-FyJ" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="0pD-pS-YhM"/> | ||
<constraint firstAttribute="bottom" secondItem="7vy-DL-v2I" secondAttribute="bottom" id="1PB-ja-hxc"/> | ||
<constraint firstItem="Cgz-Rx-FyJ" firstAttribute="leading" secondItem="weF-VJ-kQg" secondAttribute="trailing" constant="8" symbolic="YES" id="H3b-Y3-6vI"/> | ||
<constraint firstItem="weF-VJ-kQg" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="4" id="MAo-xn-Ott"/> | ||
<constraint firstAttribute="bottom" secondItem="weF-VJ-kQg" secondAttribute="bottom" constant="4" id="Ms8-VQ-WhR"/> | ||
<constraint firstItem="weF-VJ-kQg" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="QBV-dj-HsA"/> | ||
<constraint firstItem="7vy-DL-v2I" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="QFI-XE-9Fd"/> | ||
<constraint firstAttribute="trailing" secondItem="7vy-DL-v2I" secondAttribute="trailing" constant="8" id="VnI-wM-6MZ"/> | ||
<constraint firstItem="7vy-DL-v2I" firstAttribute="leading" secondItem="Cgz-Rx-FyJ" secondAttribute="trailing" constant="8" id="nHt-Db-jET"/> | ||
</constraints> | ||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> | ||
<point key="canvasLocation" x="130.53435114503816" y="-233.09859154929578"/> | ||
</view> | ||
</objects> | ||
<resources> | ||
<image name="play.fill" catalog="system" width="120" height="128"/> | ||
<systemColor name="labelColor"> | ||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> | ||
</systemColor> | ||
</resources> | ||
</document> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// | ||
// SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
// | ||
|
||
extension BaseChatTableViewCell { | ||
|
||
func setupForAudioCell(with message: NCChatMessage) { | ||
if self.audioPlayerView == nil { | ||
// Audio player view | ||
let audioPlayerView = AudioPlayerView(frame: CGRect(x: 0, y: 0, width: 0, height: voiceMessageCellPlayerHeight)) | ||
self.audioPlayerView = audioPlayerView | ||
self.audioPlayerView?.delegate = self | ||
|
||
audioPlayerView.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
self.messageBodyView.addSubview(audioPlayerView) | ||
|
||
NSLayoutConstraint.activate([ | ||
audioPlayerView.leftAnchor.constraint(equalTo: self.messageBodyView.leftAnchor), | ||
audioPlayerView.rightAnchor.constraint(equalTo: self.messageBodyView.rightAnchor), | ||
audioPlayerView.topAnchor.constraint(equalTo: self.messageBodyView.topAnchor) | ||
]) | ||
} | ||
} | ||
|
||
func prepareForReuseAudioCell() { | ||
self.audioPlayerView?.resetPlayer() | ||
self.clearFileStatusView() | ||
} | ||
|
||
func audioPlayerPlayButtonPressed() { | ||
guard let audioFileParameter = message?.file() else { | ||
return | ||
} | ||
|
||
self.delegate?.cellWants(toPlayAudioFile: audioFileParameter) | ||
} | ||
|
||
func audioPlayerPauseButtonPressed() { | ||
guard let audioFileParameter = message?.file() else { | ||
return | ||
} | ||
|
||
self.delegate?.cellWants(toPauseAudioFile: audioFileParameter) | ||
} | ||
|
||
func audioPlayerProgressChanged(progress: CGFloat) { | ||
guard let audioFileParameter = message?.file() else { | ||
return | ||
} | ||
|
||
self.delegate?.cellWants(toChangeProgress: progress, fromAudioFile: audioFileParameter) | ||
} | ||
} |
Oops, something went wrong.