Files
letro-ios/ElementX/Sources/Other/VoiceMessage/VoiceMessageButton.swift
Aaron Thornburgh b4d6fe43c3 Update Send button bg color (#5170)
* Update Send button bg color

Change the gradient bg to accent/rest.

* Tidy-up ComposerToolbar to match iOS 18 Figma.

Also simplifies the tests a bit.

* Add a .glassEffect to Compound's SendButton.

* Add a border to TimelineReplyView.

Also use the same sizes in both the message bubbles and the composer.

* Change icon size and container in message bubbles

- Container size = 36x36px
- Icon size = 24x24px

* Update icon of reply contents to be 24x24

* Update the VoiceMessageButton to match the designs.

* Adopt Liquid Glass in the ComposerToolbar.

* Generate and fix snapshots.

---------

Co-authored-by: Doug <douglase@element.io>
2026-03-25 17:42:10 +00:00

128 lines
3.5 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 SwiftUI
struct VoiceMessageButton: View {
@ScaledMetric private var buttonSize: CGFloat
enum State {
case loading
case playing
case paused
}
enum Size {
case small
case medium
}
let state: State
let action: () -> Void
let iconSize: CompoundIcon.Size
let iconColor: Color
init(state: State, size: Size, action: @escaping () -> Void) {
switch size {
case .small:
_buttonSize = .init(wrappedValue: 30)
iconSize = .small
iconColor = .compound.iconPrimary
case .medium:
_buttonSize = .init(wrappedValue: 36)
iconSize = .medium
iconColor = .compound.iconSecondary
}
self.state = state
self.action = action
}
var body: some View {
Button(action: action) {
buttonLabel
.frame(width: buttonSize, height: buttonSize)
.overlay {
Circle().stroke(.compound.borderInteractiveSecondary)
}
}
.animation(nil, value: state)
.buttonStyle(VoiceMessageButtonStyle(color: iconColor))
.disabled(state == .loading)
.accessibilityLabel(accessibilityLabel)
}
@ViewBuilder
private var buttonLabel: some View {
switch state {
case .loading:
ProgressView()
case .playing, .paused:
CompoundIcon(state == .playing ? \.pauseSolid : \.playSolid,
size: iconSize,
relativeTo: .compound.headingLG)
}
}
private var accessibilityLabel: String {
switch state {
case .loading:
return ""
case .playing:
return L10n.a11yPause
case .paused:
return L10n.a11yPlay
}
}
}
private struct VoiceMessageButtonStyle: ButtonStyle {
@Environment(\.isEnabled) var isEnabled: Bool
let color: Color
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(isEnabled ? color.opacity(configuration.isPressed ? 0.6 : 1) : .compound.iconDisabled)
.background(Circle()
.foregroundColor(configuration.isPressed ? .compound.bgSubtlePrimary : .compound.bgCanvasDefault))
}
}
extension VoiceMessageButton.State {
init(_ state: AudioPlayerPlaybackState) {
switch state {
case .loading:
self = .loading
case .playing:
self = .playing
case .stopped, .error, .readyToPlay:
self = .paused
}
}
}
struct VoiceMessageButton_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(spacing: 8) {
HStack(spacing: 8) {
VoiceMessageButton(state: .paused, size: .small) { }
VoiceMessageButton(state: .paused, size: .medium) { }
}
HStack(spacing: 8) {
VoiceMessageButton(state: .playing, size: .small) { }
VoiceMessageButton(state: .playing, size: .medium) { }
}
}
.padding()
.background(.compound.bgSubtleSecondary)
}
}