mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
114 lines
4.2 KiB
Swift
114 lines
4.2 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2023-2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import Compound
|
|
import DSWaveformImage
|
|
import DSWaveformImageViews
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
struct VoiceMessagePreviewComposer: View {
|
|
@ObservedObject var playerState: AudioPlayerState
|
|
let waveform: WaveformSource
|
|
@ScaledMetric private var waveformLineWidth = 2.0
|
|
@ScaledMetric private var waveformLinePadding = 2.0
|
|
@GestureState var isDragging = false
|
|
|
|
let onPlay: () -> Void
|
|
let onPause: () -> Void
|
|
let onSeek: (Double) -> Void
|
|
let onScrubbing: (Bool) -> Void
|
|
|
|
var timeLabelContent: String {
|
|
// Display the duration if progress is 0.0
|
|
let percent = playerState.progress > 0.0 ? playerState.progress : 1.0
|
|
// If the duration is greater or equal 10 minutes, use the long format
|
|
let elapsed = Date(timeIntervalSinceReferenceDate: playerState.duration * percent)
|
|
return DateFormatter.elapsedTimeFormatter.string(from: elapsed)
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
VoiceMessageButton(state: .init(playerState.playerButtonPlaybackState),
|
|
size: .small,
|
|
action: onPlayPause)
|
|
Text(timeLabelContent)
|
|
.lineLimit(1)
|
|
.font(.compound.bodySMSemibold)
|
|
.foregroundColor(.compound.textSecondary)
|
|
.monospacedDigit()
|
|
.fixedSize(horizontal: true, vertical: true)
|
|
|
|
waveformView
|
|
.waveformInteraction(isDragging: $isDragging,
|
|
progress: playerState.progress,
|
|
showCursor: playerState.showProgressIndicator,
|
|
onSeek: onSeek)
|
|
}
|
|
.onChange(of: isDragging) { _, newValue in
|
|
onScrubbing(newValue)
|
|
}
|
|
.padding(.vertical, Compound.supportsGlass ? 7 : 4)
|
|
.padding(.horizontal, Compound.supportsGlass ? 8 : 6)
|
|
.padding(.trailing, Compound.supportsGlass ? 8 : 0)
|
|
.background {
|
|
RoundedRectangle(cornerRadius: Compound.supportsGlass ? 21 : 12)
|
|
.fill(.compound.bgSubtleSecondary)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var waveformView: some View {
|
|
let configuration: Waveform.Configuration = .init(style: .striped(.init(color: .black, width: waveformLineWidth, spacing: waveformLinePadding)),
|
|
verticalScalingFactor: 1.0)
|
|
switch waveform {
|
|
case .url(let url):
|
|
WaveformView(audioURL: url,
|
|
configuration: configuration)
|
|
.progressMask(progress: playerState.progress)
|
|
case .data(let array):
|
|
WaveformLiveCanvas(samples: array,
|
|
configuration: configuration)
|
|
.progressMask(progress: playerState.progress)
|
|
}
|
|
}
|
|
|
|
private func onPlayPause() {
|
|
if playerState.playbackState == .playing {
|
|
onPause()
|
|
} else {
|
|
onPlay()
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension DateFormatter {
|
|
static let elapsedTimeFormatter: DateFormatter = {
|
|
let dateFormatter = DateFormatter()
|
|
dateFormatter.dateFormat = "mm:ss"
|
|
return dateFormatter
|
|
}()
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
struct VoiceMessagePreviewComposer_Previews: PreviewProvider, TestablePreview {
|
|
static let playerState = AudioPlayerState(id: .recorderPreview,
|
|
title: L10n.commonVoiceMessage,
|
|
duration: 10.0,
|
|
waveform: EstimatedWaveform.mockWaveform,
|
|
progress: 0.4)
|
|
|
|
static let waveformData: [Float] = Array(repeating: 1.0, count: 1000)
|
|
|
|
static var previews: some View {
|
|
VoiceMessagePreviewComposer(playerState: playerState, waveform: .data(waveformData), onPlay: { }, onPause: { }, onSeek: { _ in }, onScrubbing: { _ in })
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|