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

Merged
merged 7 commits into from
Jan 14, 2025
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,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)
nuno-vieira marked this conversation as resolved.
Show resolved Hide resolved
- `ColorPalette.voiceMessageControlBackground`
- `Images.pauseFilled`
- Exposes all the default message actions [#711](https://github.com/GetStream/stream-chat-swiftui/pull/711)
### 🐞 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)
- Fix composer command view not Themable [#710](https://github.com/GetStream/stream-chat-swiftui/pull/710)
- Fix reactions users view not paginating results [#712](https://github.com/GetStream/stream-chat-swiftui/pull/712)

### 🔄 Changed
- Support theming and update layout of `VoiceRecordingContainerView` [#704](https://github.com/GetStream/stream-chat-swiftui/pull/704)
- Use `ColorPalette.highlightedAccentBackground` for `AudioVisualizationView.highlightedBarColor` [#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 @@ -44,7 +44,7 @@ open class AudioVisualizationView: UIView {
open var barColor: UIColor { colors.textLowEmphasis }

/// The colour of the waveform bar that is part of the "played" duration.
open var highlightedBarColor: UIColor { .blue }
open var highlightedBarColor: UIColor { colors.highlightedAccentBackground }

/// The colour of the waveform bar's background.
open var barBackgroundColor: UIColor { colors.background }
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,22 +49,24 @@ 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(colors.background8))
.roundWithBorder(cornerRadius: 14)
}
}
}
.padding(.all, 8)
.background(Color(colors.background8))
.cornerRadius(16)
if !message.text.isEmpty {
AttachmentTextView(message: message)
.frame(maxWidth: .infinity)
Expand Down Expand Up @@ -94,7 +96,11 @@ public struct VoiceRecordingContainerView<Factory: ViewFactory>: View {
}
.modifier(
factory.makeMessageViewModifier(
for: MessageModifierInfo(message: message, isFirst: isFirst)
for: MessageModifierInfo(
message: message,
isFirst: isFirst,
cornerRadius: 16
)
)
)
}
Expand All @@ -114,6 +120,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 +142,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 +166,7 @@ struct VoiceRecordingView: View {
)
.bold()
.lineLimit(1)
.foregroundColor(textColor)

HStack {
RecordingDurationView(
Expand Down Expand Up @@ -199,7 +214,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)
}
}
1 change: 1 addition & 0 deletions Sources/StreamChatSwiftUI/ColorPalette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public struct ColorPalette {
public lazy var reactionCurrentUserColor: UIColor? = UIColor(tintColor)
public lazy var reactionOtherUserColor: UIColor? = textLowEmphasis
public lazy var selectedReactionBackgroundColor: UIColor? = nil
public var voiceMessageControlBackground: UIColor = .streamWhiteStatic

// MARK: - Composer

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)
}
}
101 changes: 97 additions & 4 deletions StreamChatSwiftUITests/Tests/ChatChannel/MessageView_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ class MessageView_Tests: StreamChatTestCase {
// Then
AssertSnapshot(
view,
variants: .onlyUserInterfaceStyles,
size: CGSize(width: defaultScreenSize.width, height: 130)
)
}
Expand Down Expand Up @@ -348,7 +347,6 @@ class MessageView_Tests: StreamChatTestCase {
// Then
AssertSnapshot(
view,
variants: .onlyUserInterfaceStyles,
size: CGSize(width: defaultScreenSize.width, height: 130)
)
}
Expand Down Expand Up @@ -378,7 +376,6 @@ class MessageView_Tests: StreamChatTestCase {
// Then
AssertSnapshot(
view,
variants: .onlyUserInterfaceStyles,
size: CGSize(width: defaultScreenSize.width, height: 130)
)
}
Expand Down Expand Up @@ -408,10 +405,106 @@ class MessageView_Tests: StreamChatTestCase {
// Then
AssertSnapshot(
view,
variants: .onlyUserInterfaceStyles,
size: CGSize(width: defaultScreenSize.width, height: 130)
)
}

func test_messageViewVoiceRecordingWithTextFromParticipantMultiple_snapshot() {
// Given
let voiceMessage = ChatMessage.mock(
id: .unique,
cid: .unique,
text: "Hello",
author: .mock(id: .unique),
attachments: ChatChannelTestHelpers.voiceRecordingAttachments(count: 2),
isSentByCurrentUser: false
)

// When
let view = MessageView(
factory: DefaultViewFactory.shared,
message: voiceMessage,
contentWidth: defaultScreenSize.width,
isFirst: true,
scrolledId: .constant(nil)
)
.frame(width: defaultScreenSize.width, height: 250)
.padding()

// Then
AssertSnapshot(
view,
size: CGSize(width: defaultScreenSize.width, height: 250)
)
}

func test_messageViewVoiceRecordingWithTextFromMeMultiple_snapshot() {
nuno-vieira marked this conversation as resolved.
Show resolved Hide resolved
// Given
let voiceMessage = ChatMessage.mock(
id: .unique,
cid: .unique,
text: "Hello",
author: .mock(id: .unique),
attachments: ChatChannelTestHelpers.voiceRecordingAttachments(count: 2),
isSentByCurrentUser: true
)

// When
let view = MessageView(
factory: DefaultViewFactory.shared,
message: voiceMessage,
contentWidth: defaultScreenSize.width,
isFirst: true,
scrolledId: .constant(nil)
)
.frame(width: defaultScreenSize.width, height: 250)
.padding()

// Then
AssertSnapshot(
view,
size: CGSize(width: defaultScreenSize.width, height: 250)
)
}

func test_messageViewVoiceRecordingFromMeTheming_snapshot() {
// Given
let voiceMessage = ChatMessage.mock(
id: .unique,
cid: .unique,
text: "Hello",
author: .mock(id: .unique),
attachments: ChatChannelTestHelpers.voiceRecordingAttachments(count: 2),
isSentByCurrentUser: true
)

// When
adjustAppearance() { appearance in
appearance.colors.messageCurrentUserBackground = [.orange]
appearance.colors.background8 = .yellow
appearance.colors.voiceMessageControlBackground = .cyan
appearance.colors.messageCurrentUserTextColor = .blue
appearance.colors.textLowEmphasis = .red
appearance.images.playFilled = UIImage(systemName: "star")!
appearance.images.fileAac = UIImage(systemName: "scribble")!
}
let view = MessageView(
factory: DefaultViewFactory.shared,
message: voiceMessage,
contentWidth: defaultScreenSize.width,
isFirst: true,
scrolledId: .constant(nil)
)
.frame(width: defaultScreenSize.width, height: 250)
.padding()

// Then
AssertSnapshot(
view,
variants: [.defaultLight],
size: CGSize(width: defaultScreenSize.width, height: 250)
)
}

func test_messageViewFileText_snapshot() {
// Given
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Diff not rendered.
7 changes: 7 additions & 0 deletions StreamChatSwiftUITests/Tests/StreamChatTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ open class StreamChatTestCase: XCTestCase {
super.setUp()
streamChat = StreamChat(chatClient: chatClient)
}

func adjustAppearance(_ block: (inout Appearance) -> Void) {
guard let streamChat else { return }
var appearance = streamChat.appearance
block(&appearance)
streamChat.appearance = appearance
}
}
Loading