mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge pull request #7103 from vector-im/phlpro/voicebroadcast_left_time
Add the left time in the Voice Broadcast tile recorder
This commit is contained in:
commit
11e39db7ab
15 changed files with 118 additions and 14 deletions
|
@ -409,7 +409,7 @@ final class BuildSettings: NSObject {
|
|||
|
||||
// MARK: - Voice Broadcast
|
||||
static let voiceBroadcastChunkLength: Int = 120
|
||||
static let voiceBroadcastMaxLength: UInt64 = 144000
|
||||
static let voiceBroadcastMaxLength: UInt = 14400 // 240min.
|
||||
|
||||
// MARK: - MXKAppSettings
|
||||
static let enableBotCreation: Bool = false
|
||||
|
|
12
Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/Contents.json
vendored
Normal file
12
Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "voice_broadcast_time_left.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 1H6V2.33333H10V1ZM7.33333 9.66667H8.66667V5.66667H7.33333V9.66667ZM12.6867 5.26L13.6333 4.31333C13.3467 3.97333 13.0333 3.65333 12.6933 3.37333L11.7467 4.32C10.7133 3.49333 9.41333 3 8 3C4.68667 3 2 5.68667 2 9C2 12.3133 4.68 15 8 15C11.32 15 14 12.3133 14 9C14 7.58667 13.5067 6.28667 12.6867 5.26ZM8 13.6667C5.42 13.6667 3.33333 11.58 3.33333 9C3.33333 6.42 5.42 4.33333 8 4.33333C10.58 4.33333 12.6667 6.42 12.6667 9C12.6667 11.58 10.58 13.6667 8 13.6667Z" fill="#737D8C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 593 B |
|
@ -2202,6 +2202,7 @@ Tap the + to start adding people.";
|
|||
"voice_broadcast_playback_loading_error" = "Unable to play this voice broadcast.";
|
||||
"voice_broadcast_live" = "Live";
|
||||
"voice_broadcast_tile" = "Voice broadcast";
|
||||
"voice_broadcast_time_left" = "%@ left";
|
||||
|
||||
// Mark: - Version check
|
||||
|
||||
|
|
|
@ -347,6 +347,7 @@ internal class Asset: NSObject {
|
|||
internal static let voiceBroadcastStop = ImageAsset(name: "voice_broadcast_stop")
|
||||
internal static let voiceBroadcastTileLive = ImageAsset(name: "voice_broadcast_tile_live")
|
||||
internal static let voiceBroadcastTileMic = ImageAsset(name: "voice_broadcast_tile_mic")
|
||||
internal static let voiceBroadcastTimeLeft = ImageAsset(name: "voice_broadcast_time_left")
|
||||
internal static let launchScreenLogo = ImageAsset(name: "launch_screen_logo")
|
||||
}
|
||||
@objcMembers
|
||||
|
|
|
@ -9155,6 +9155,10 @@ public class VectorL10n: NSObject {
|
|||
public static var voiceBroadcastTile: String {
|
||||
return VectorL10n.tr("Vector", "voice_broadcast_tile")
|
||||
}
|
||||
/// %@ left
|
||||
public static func voiceBroadcastTimeLeft(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "voice_broadcast_time_left", p1)
|
||||
}
|
||||
/// Can't start a new voice broadcast
|
||||
public static var voiceBroadcastUnauthorizedTitle: String {
|
||||
return VectorL10n.tr("Vector", "voice_broadcast_unauthorized_title")
|
||||
|
|
|
@ -66,6 +66,12 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType {
|
|||
return self.height(for: roomBubbleCellData, fitting: maxWidth)
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
cleanContentVC()
|
||||
|
||||
super.prepareForReuse()
|
||||
}
|
||||
|
||||
// MARK - SizableBaseRoomCellType
|
||||
|
||||
// Each sublcass should override this method, to indicate a unique identifier for a view height.
|
||||
|
@ -175,9 +181,20 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType {
|
|||
return height
|
||||
}
|
||||
|
||||
private func cleanContentVC() {
|
||||
contentVC?.removeFromParent()
|
||||
contentVC?.view.removeFromSuperview()
|
||||
contentVC?.didMove(toParent: nil)
|
||||
contentVC = nil
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func addContentViewController(_ controller: UIViewController, on contentView: UIView) {
|
||||
controller.view.invalidateIntrinsicContentSize()
|
||||
|
||||
cleanContentVC()
|
||||
|
||||
let parent = vc_parentViewController
|
||||
parent?.addChild(controller)
|
||||
contentView.vc_addSubViewMatchingParent(controller.view)
|
||||
|
@ -185,13 +202,4 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType {
|
|||
|
||||
contentVC = controller
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
contentVC?.removeFromParent()
|
||||
contentVC?.view.removeFromSuperview()
|
||||
contentVC?.didMove(toParent: nil)
|
||||
contentVC = nil
|
||||
|
||||
super.prepareForReuse()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,12 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
private var chunkFrames: AVAudioFrameCount = 0
|
||||
private var chunkFileNumber: Int = 0
|
||||
|
||||
private var currentElapsedTime: UInt = 0 // Time in seconds.
|
||||
private var currentRemainingTime: UInt { // Time in seconds.
|
||||
BuildSettings.voiceBroadcastMaxLength - currentElapsedTime
|
||||
}
|
||||
private var elapsedTimeTimer: Timer?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
weak var serviceDelegate: VoiceBroadcastRecorderServiceDelegate?
|
||||
|
@ -67,12 +73,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
}
|
||||
|
||||
try audioEngine.start()
|
||||
startTimer()
|
||||
|
||||
// Disable the sleep mode during the recording until we are able to handle it
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
} catch {
|
||||
MXLog.debug("[VoiceBroadcastRecorderService] startRecordingVoiceBroadcast error", context: error)
|
||||
stopRecordingVoiceBroadcast()
|
||||
invalidateTimer()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,6 +89,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
audioEngine.stop()
|
||||
audioEngine.inputNode.removeTap(onBus: audioNodeBus)
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
invalidateTimer()
|
||||
|
||||
voiceBroadcastService?.stopVoiceBroadcast(success: { [weak self] _ in
|
||||
MXLog.debug("[VoiceBroadcastRecorderService] Stopped")
|
||||
|
@ -110,6 +119,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
func pauseRecordingVoiceBroadcast() {
|
||||
audioEngine.pause()
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
invalidateTimer()
|
||||
|
||||
voiceBroadcastService?.pauseVoiceBroadcast(success: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
@ -126,6 +136,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
|
||||
func resumeRecordingVoiceBroadcast() {
|
||||
try? audioEngine.start()
|
||||
startTimer()
|
||||
|
||||
voiceBroadcastService?.resumeVoiceBroadcast(success: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
@ -143,12 +154,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
private func resetValues() {
|
||||
chunkFrames = 0
|
||||
chunkFileNumber = 0
|
||||
currentElapsedTime = 0
|
||||
}
|
||||
|
||||
/// Release the service
|
||||
private func tearDownVoiceBroadcastService() {
|
||||
resetValues()
|
||||
session.tearDownVoiceBroadcastService()
|
||||
invalidateTimer()
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(false)
|
||||
|
@ -157,6 +170,31 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
/// Start ElapsedTimeTimer.
|
||||
private func startTimer() {
|
||||
elapsedTimeTimer = Timer.scheduledTimer(timeInterval: 1.0,
|
||||
target: self,
|
||||
selector: #selector(updateCurrentElapsedTimeValue),
|
||||
userInfo: nil,
|
||||
repeats: true)
|
||||
}
|
||||
|
||||
/// Invalidate ElapsedTimeTimer.
|
||||
private func invalidateTimer() {
|
||||
elapsedTimeTimer?.invalidate()
|
||||
elapsedTimeTimer = nil
|
||||
}
|
||||
|
||||
/// Update currentElapsedTime value.
|
||||
@objc private func updateCurrentElapsedTimeValue() {
|
||||
guard currentRemainingTime > 0 else {
|
||||
stopRecordingVoiceBroadcast()
|
||||
return
|
||||
}
|
||||
currentElapsedTime += 1
|
||||
serviceDelegate?.voiceBroadcastRecorderService(self, didUpdateRemainingTime: self.currentRemainingTime)
|
||||
}
|
||||
|
||||
/// Write audio buffer to chunk file.
|
||||
private func writeBuffer(_ buffer: AVAudioPCMBuffer) {
|
||||
let sampleRate = buffer.format.sampleRate
|
||||
|
|
|
@ -18,6 +18,7 @@ import Foundation
|
|||
|
||||
protocol VoiceBroadcastRecorderServiceDelegate: AnyObject {
|
||||
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState)
|
||||
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt)
|
||||
}
|
||||
|
||||
protocol VoiceBroadcastRecorderServiceProtocol {
|
||||
|
|
|
@ -53,6 +53,14 @@ struct VoiceBroadcastRecorderView: View {
|
|||
} icon: {
|
||||
Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
|
||||
}
|
||||
|
||||
Label {
|
||||
Text(viewModel.viewState.currentRecordingState.remainingTimeLabel)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.font(theme.fonts.caption1)
|
||||
} icon: {
|
||||
Image(uiImage: Asset.Images.voiceBroadcastTimeLeft.image)
|
||||
}
|
||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Label {
|
||||
|
|
|
@ -35,9 +35,15 @@ struct VoiceBroadcastRecorderDetails {
|
|||
let avatarData: AvatarInputProtocol
|
||||
}
|
||||
|
||||
struct VoiceBroadcastRecordingState {
|
||||
var remainingTime: UInt
|
||||
var remainingTimeLabel: String
|
||||
}
|
||||
|
||||
struct VoiceBroadcastRecorderViewState: BindableState {
|
||||
var details: VoiceBroadcastRecorderDetails
|
||||
var recordingState: VoiceBroadcastRecorderState
|
||||
var currentRecordingState: VoiceBroadcastRecordingState
|
||||
var bindings: VoiceBroadcastRecorderViewStateBindings
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ enum MockVoiceBroadcastRecorderScreenState: MockScreenState, CaseIterable {
|
|||
|
||||
var screenView: ([Any], AnyView) {
|
||||
let details = VoiceBroadcastRecorderDetails(senderDisplayName: "", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room"))
|
||||
let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, bindings: VoiceBroadcastRecorderViewStateBindings()))
|
||||
let recordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength, remainingTimeLabel: "1h 20m 47s left")
|
||||
let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, currentRecordingState: recordingState, bindings: VoiceBroadcastRecorderViewStateBindings()))
|
||||
|
||||
return (
|
||||
[false, viewModel],
|
||||
|
|
|
@ -34,8 +34,10 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic
|
|||
init(details: VoiceBroadcastRecorderDetails,
|
||||
recorderService: VoiceBroadcastRecorderServiceProtocol) {
|
||||
self.voiceBroadcastRecorderService = recorderService
|
||||
let currentRecordingState = VoiceBroadcastRecorderViewModel.currentRecordingState(from: BuildSettings.voiceBroadcastMaxLength)
|
||||
super.init(initialViewState: VoiceBroadcastRecorderViewState(details: details,
|
||||
recordingState: .stopped,
|
||||
currentRecordingState: currentRecordingState,
|
||||
bindings: VoiceBroadcastRecorderViewStateBindings()))
|
||||
|
||||
self.voiceBroadcastRecorderService.serviceDelegate = self
|
||||
|
@ -77,10 +79,27 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic
|
|||
self.state.recordingState = .resumed
|
||||
voiceBroadcastRecorderService.resumeRecordingVoiceBroadcast()
|
||||
}
|
||||
|
||||
private func updateRemainingTime(_ remainingTime: UInt) {
|
||||
state.currentRecordingState = VoiceBroadcastRecorderViewModel.currentRecordingState(from: remainingTime)
|
||||
}
|
||||
|
||||
private static func currentRecordingState(from remainingTime: UInt) -> VoiceBroadcastRecordingState {
|
||||
let time = TimeInterval(Double(remainingTime))
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .abbreviated
|
||||
|
||||
return VoiceBroadcastRecordingState(remainingTime: remainingTime,
|
||||
remainingTimeLabel: VectorL10n.voiceBroadcastTimeLeft(formatter.string(from: time) ?? "0s"))
|
||||
}
|
||||
}
|
||||
|
||||
extension VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderServiceDelegate {
|
||||
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) {
|
||||
self.state.recordingState = state
|
||||
}
|
||||
|
||||
func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt) {
|
||||
self.updateRemainingTime(remainingTime)
|
||||
}
|
||||
}
|
||||
|
|
1
changelog.d/pr-7103.feature
Normal file
1
changelog.d/pr-7103.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add the left time in the Voice Broadcast tile recorder.
|
1
changelog.d/pr-7105.bugfix
Normal file
1
changelog.d/pr-7105.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix scroll issues with VoiceBroadcast and Poll cells
|
Loading…
Reference in a new issue