mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 19:45:20 +00:00
fix waveform alignment
This commit is contained in:
@@ -28,6 +28,7 @@ struct ImageMessageRow: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(maxWidth: 300, maxHeight: 400, alignment: .topLeading)
|
.frame(maxWidth: 300, maxHeight: 400, alignment: .topLeading)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
}
|
}
|
||||||
if message.isPrivate && message.sender == viewModel.nickname,
|
if message.isPrivate && message.sender == viewModel.nickname,
|
||||||
|
|||||||
@@ -11,44 +11,54 @@ struct VoiceMessageRow: View {
|
|||||||
@State private var player: AVAudioPlayer?
|
@State private var player: AVAudioPlayer?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(alignment: .center, spacing: 8) {
|
||||||
|
// Play / Pause control (fixed size similar to Android's 28dp)
|
||||||
Button(action: togglePlay) {
|
Button(action: togglePlay) {
|
||||||
Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
|
Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
|
||||||
.font(.system(size: 22))
|
.font(.system(size: 24))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
|
// Waveform takes remaining width, fixed height, vertically centered
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
|
let barWidth: CGFloat = 2
|
||||||
|
let barSpacing: CGFloat = 2
|
||||||
|
let step = barWidth + barSpacing
|
||||||
|
let maxBars = max(0, Int(floor(geo.size.width / step)))
|
||||||
|
let displayCount = min(maxBars, bins.count)
|
||||||
|
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
// Base waveform
|
// Base waveform (gray)
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: barSpacing) {
|
||||||
ForEach(Array(bins.enumerated()), id: \.offset) { _, v in
|
ForEach(0..<displayCount, id: \.self) { i in
|
||||||
// Apply logarithmic normalization for better visibility of quiet recordings
|
let v = bins[i]
|
||||||
let normalizedV = v <= 0 ? 0 : min(1.0, log(1.0 + v * 9.0) / log(10.0))
|
let normalizedV = v <= 0 ? 0 : min(1.0, log(1.0 + v * 9.0) / log(10.0))
|
||||||
let h = max(2, CGFloat(normalizedV) * geo.size.height)
|
let h = max(2, CGFloat(normalizedV) * geo.size.height)
|
||||||
Capsule().fill(Color.gray.opacity(0.4)).frame(width: 2, height: h)
|
VStack { Spacer(minLength: 0); Capsule().fill(Color.gray.opacity(0.4)).frame(width: barWidth, height: h); Spacer(minLength: 0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Filled progress (with normalized amplitude)
|
|
||||||
|
// Filled progress (blue) or send progress if provided
|
||||||
let activeProgress = sendProgress ?? progress
|
let activeProgress = sendProgress ?? progress
|
||||||
let filled = Int(Double(bins.count) * activeProgress)
|
let filledBars = Int(Double(displayCount) * activeProgress)
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: barSpacing) {
|
||||||
ForEach(0..<max(0, min(bins.count, filled)), id: \.self) { i in
|
ForEach(0..<max(0, min(displayCount, filledBars)), id: \.self) { i in
|
||||||
let v = bins[i]
|
let v = bins[i]
|
||||||
// Apply logarithmic normalization for better visibility of quiet recordings
|
|
||||||
let normalizedV = v <= 0 ? 0 : min(1.0, log(1.0 + v * 9.0) / log(10.0))
|
let normalizedV = v <= 0 ? 0 : min(1.0, log(1.0 + v * 9.0) / log(10.0))
|
||||||
let h = max(2, CGFloat(normalizedV) * geo.size.height)
|
let h = max(2, CGFloat(normalizedV) * geo.size.height)
|
||||||
Capsule().fill(Color.blue).frame(width: 2, height: h)
|
VStack { Spacer(minLength: 0); Capsule().fill(Color.blue).frame(width: barWidth, height: h); Spacer(minLength: 0) }
|
||||||
}
|
}
|
||||||
Spacer(minLength: 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.clipped() // prevent overflow beyond available width/height
|
||||||
}
|
}
|
||||||
.frame(height: 26)
|
.frame(height: 36)
|
||||||
|
|
||||||
|
// Reserve width for duration to avoid being pushed out by waveform
|
||||||
Text(formattedDuration)
|
Text(formattedDuration)
|
||||||
.font(.system(size: 11, design: .monospaced))
|
.font(.system(size: 12, design: .monospaced))
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
.frame(width: 44, alignment: .trailing)
|
||||||
}
|
}
|
||||||
.onAppear(perform: load)
|
.onAppear(perform: load)
|
||||||
.onDisappear { player?.stop(); isPlaying = false }
|
.onDisappear { player?.stop(); isPlaying = false }
|
||||||
|
|||||||
Reference in New Issue
Block a user