Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust layout and colors of the voice recording view #704

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

# Upcoming

### ✅ Added
- Colors and images for voice recording view [#704](https://github.com/GetStream/stream-chat-swiftui/pull/704)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we forgot about this one: #688

The recording view should also be themable

- `ColorPalette.voiceMessageCurrentUserBackground` and `ColorPalette.voiceMessageOtherUserBackground`
- `ColorPalette.voiceMessageCurrentUserRecordingBackground` and `ColorPalette.voiceMessageOtherUserRecordingBackground`
- `ColorPalette.voiceMessageControlBackground`
- `Images.pauseFilled`

### 🐞 Fixed
- Use bright color for typing indicator animation in dark mode [#702](https://github.com/GetStream/stream-chat-swiftui/pull/702)
- Refresh quoted message preview when the quoted message is deleted [#705](https://github.com/GetStream/stream-chat-swiftui/pull/705)

### 🔄 Changed
- Support theming and update layout of `VoiceRecordingContainerView` [#704](https://github.com/GetStream/stream-chat-swiftui/pull/704)

# [4.69.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.69.0)
_December 18, 2024_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct AddedVoiceRecordingsView: View {
let recording = addedVoiceRecordings[i]
VoiceRecordingView(
handler: voiceRecordingHandler,
textColor: textColor(currentUser: true),
addedVoiceRecording: recording,
index: i
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct VoiceRecordingContainerView<Factory: ViewFactory>: View {
}

public var body: some View {
VStack {
VStack(spacing: 0) {
VStack {
if let quotedMessage = utils.messageCachingUtils.quotedMessage(for: message) {
factory.makeQuotedMessageView(
Expand All @@ -49,24 +49,26 @@ public struct VoiceRecordingContainerView<Factory: ViewFactory>: View {
scrolledId: $scrolledId
)
}

ForEach(message.voiceRecordingAttachments, id: \.self) { attachment in
VoiceRecordingView(
handler: handler,
addedVoiceRecording: AddedVoiceRecording(
url: attachment.payload.voiceRecordingURL,
duration: attachment.payload.duration ?? 0,
waveform: attachment.payload.waveformData ?? []
),
index: index(for: attachment)
)
VStack(spacing: 2) {
ForEach(message.voiceRecordingAttachments, id: \.self) { attachment in
VoiceRecordingView(
handler: handler,
textColor: textColor(for: message),
addedVoiceRecording: AddedVoiceRecording(
url: attachment.payload.voiceRecordingURL,
duration: attachment.payload.duration ?? 0,
waveform: attachment.payload.waveformData ?? []
),
index: index(for: attachment)
)
.padding(.all, 8)
.background(Color(recordingItemBackgroundColor))
.roundWithBorder(cornerRadius: 14)
}
}
}
.padding(.all, 8)
.background(Color(colors.background8))
.cornerRadius(16)
if !message.text.isEmpty {
AttachmentTextView(message: message)
AttachmentTextView(message: message, injectedBackgroundColor: bubbleBackgroundColor)
.frame(maxWidth: .infinity)
}
}
Expand Down Expand Up @@ -94,11 +96,28 @@ public struct VoiceRecordingContainerView<Factory: ViewFactory>: View {
}
.modifier(
factory.makeMessageViewModifier(
for: MessageModifierInfo(message: message, isFirst: isFirst)
for: MessageModifierInfo(
message: message,
isFirst: isFirst,
injectedBackgroundColor: bubbleBackgroundColor,
cornerRadius: 16
)
)
)
}

private var bubbleBackgroundColor: UIColor {
message.isSentByCurrentUser ?
colors.voiceMessageCurrentUserBackground :
colors.voiceMessageOtherUserBackground
}

private var recordingItemBackgroundColor: UIColor {
message.isSentByCurrentUser ?
colors.voiceMessageCurrentUserRecordingBackground :
colors.voiceMessageOtherUserRecordingBackground
}

private func index(for attachment: ChatMessageVoiceRecordingAttachment) -> Int {
message.voiceRecordingAttachments.firstIndex(of: attachment) ?? 0
}
Expand All @@ -114,6 +133,7 @@ struct VoiceRecordingView: View {
@State var rate: AudioPlaybackRate = .normal
@ObservedObject var handler: VoiceRecordingHandler

let textColor: Color
let addedVoiceRecording: AddedVoiceRecording
let index: Int

Expand All @@ -135,10 +155,17 @@ struct VoiceRecordingView: View {
Button(action: {
handlePlayTap()
}, label: {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.padding(.all, 8)
Image(uiImage: isPlaying ? images.pauseFilled : images.playFilled)
.frame(width: 36, height: 36)
.foregroundColor(.primary)
.modifier(ShadowViewModifier(firstRadius: 2, firstY: 4))
.modifier(
ShadowViewModifier(
backgroundColor: colors.voiceMessageControlBackground,
cornerRadius: 18,
firstRadius: 2,
firstY: 4
)
)
})
.opacity(loading ? 0 : 1)
.overlay(loading ? ProgressView() : nil)
Expand All @@ -152,6 +179,7 @@ struct VoiceRecordingView: View {
)
.bold()
.lineLimit(1)
.foregroundColor(textColor)

HStack {
RecordingDurationView(
Expand Down Expand Up @@ -199,7 +227,7 @@ struct VoiceRecordingView: View {
Image(uiImage: images.fileAac)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 36)
.frame(height: 40)
}
}
.onReceive(handler.$context, perform: { value in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,11 @@ public struct AttachmentTextView: View {
@Injected(\.fonts) private var fonts

var message: ChatMessage
let injectedBackgroundColor: UIColor?

public init(message: ChatMessage) {
public init(message: ChatMessage, injectedBackgroundColor: UIColor? = nil) {
self.message = message
self.injectedBackgroundColor = injectedBackgroundColor
}

public var body: some View {
Expand All @@ -127,6 +129,9 @@ public struct AttachmentTextView: View {
}

private var backgroundColor: UIColor {
if let injectedBackgroundColor {
return injectedBackgroundColor
}
var colors = colors
if message.isSentByCurrentUser {
if message.type == .ephemeral {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,9 @@ extension View {
return Color(colors.messageOtherUserTextColor)
}
}

func textColor(currentUser: Bool) -> Color {
@Injected(\.colors) var colors
return currentUser ? Color(colors.messageCurrentUserTextColor) : Color(colors.messageOtherUserTextColor)
}
}
6 changes: 6 additions & 0 deletions Sources/StreamChatSwiftUI/ColorPalette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ public struct ColorPalette {
public lazy var reactionCurrentUserColor: UIColor? = UIColor(tintColor)
public lazy var reactionOtherUserColor: UIColor? = textLowEmphasis
public lazy var selectedReactionBackgroundColor: UIColor? = nil
public var voiceMessageCurrentUserBackground: UIColor = .streamInnerBorder
public var voiceMessageOtherUserBackground: UIColor = .streamBarsBackground
public var voiceMessageCurrentUserRecordingBackground: UIColor = .streamBarsBackground
public var voiceMessageOtherUserRecordingBackground: UIColor = .streamBarsBackground
public var voiceMessageControlBackground: UIColor = .streamWhiteStatic
Comment on lines +82 to +86
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably could have reused some of the existing Color Pallete colors so that customers could have the styling for free 🤔 On UIKit, we are using the existing background colours in the ColorPallete. So if a customer already set their Theme, the voice recording attachment will be styled for free. In this case, they will need to set the color for this specific component. Which kinda defeats the porpose of a "Theme". That is why most of the names in the color palette don't have context (ex: Background1, Background2, etc..)

Especially for voiceMessageCurrentUserBackground, isn't this the same as messageCurrentUserBackground for example?

Copy link
Contributor Author

@laevandus laevandus Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used exactly the same colors what we have in our Figma specs. These are slightly different from what the current palette has. Not sure what is the best way here.
a) use existing colors which do not match with Figma spec
b) add background9 and background10 to replace the use of streamBarsBackground and streamInnerBorder
c) close the PR and add it for v5 (might allows making more such changes which might break existing customisations)
d) …

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For customers, it's easier if we are explicit about the names of the colors. Naming like backgroundX doesn't mean anything and it's always difficult to figure things out.
However, voiceMessageCurrentUserBackground should be the same as messageCurrentUserBackground, etc. Probably some misalignment also in the Figma.
So my suggestion would be: try to use what's already available for things that logically fit together (like the example above). Introduce new ones for the stuff we don't have with your naming convention, e.g. voiceMessageControlBackground.


// MARK: - Composer

Expand Down Expand Up @@ -114,6 +119,7 @@ private extension UIColor {
static let streamInnerBorder = mode(0xdbdde1, 0x272a30)
static let streamHighlight = mode(0xfbf4dd, 0x333024)
static let streamDisabled = mode(0xb4b7bb, 0x4c525c)
static let streamBarsBackground = mode(0xffffff, 0x17191c)

// Currently we are not using the correct shadow color from figma's color palette. This is to avoid
// an issue with snapshots inconsistency between Intel vs M1. We can't use shadows with transparency.
Expand Down
1 change: 1 addition & 0 deletions Sources/StreamChatSwiftUI/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ public class Images {
public var play: UIImage = loadImageSafely(with: "play")
public var playFilled: UIImage = UIImage(systemName: "play.fill")!
public var pause: UIImage = loadImageSafely(with: "pause")
public var pauseFilled: UIImage = loadImageSafely(with: "pause.fill")

public var checkmarkFilled: UIImage = UIImage(systemName: "checkmark.circle.fill")!

Expand Down
3 changes: 2 additions & 1 deletion Sources/StreamChatSwiftUI/Utils/Modifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import SwiftUI
struct ShadowViewModifier: ViewModifier {
@Injected(\.colors) private var colors

var backgroundColor: UIColor = .systemBackground
var cornerRadius: CGFloat = 16
var firstRadius: CGFloat = 10
var firstY: CGFloat = 12

func body(content: Content) -> some View {
content.background(Color(UIColor.systemBackground))
content.background(Color(backgroundColor))
.cornerRadius(cornerRadius)
.modifier(ShadowModifier(firstRadius: firstRadius, firstY: firstY))
.overlay(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,23 +200,28 @@ class ChatChannelTestHelpers {
return fileAttachments
}

static func voiceRecordingAttachments(count: Int) -> [AnyChatMessageAttachment] {
(0..<count).map { index in
let title = index == 0 ? "Recording" : "Recording-\(index)"
let payload = VoiceRecordingAttachmentPayload(
title: title,
voiceRecordingRemoteURL: .localYodaImage,
file: try! .init(url: .localYodaImage),
duration: Double(index) + 5.0,
waveformData: [0, 0.1, 0.5, 1],
extraData: nil
)
return ChatMessageVoiceRecordingAttachment(
id: .unique,
type: .voiceRecording,
payload: payload,
downloadingState: nil,
uploadingState: nil
).asAnyAttachment
}
}

static var voiceRecordingAttachments: [AnyChatMessageAttachment] {
let payload = VoiceRecordingAttachmentPayload(
title: "Recording",
voiceRecordingRemoteURL: .localYodaImage,
file: try! .init(url: .localYodaImage),
duration: 5,
waveformData: [0, 0.1, 0.5, 1],
extraData: nil
)
let attachment = ChatMessageVoiceRecordingAttachment(
id: .unique,
type: .voiceRecording,
payload: payload,
downloadingState: nil,
uploadingState: nil
).asAnyAttachment

return [attachment]
voiceRecordingAttachments(count: 1)
}
}
Loading
Loading