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>
This commit is contained in:
Aaron Thornburgh
2026-03-25 18:42:10 +01:00
committed by GitHub
parent a2481b59ba
commit 96b6d69d5a
181 changed files with 790 additions and 719 deletions
@@ -17,8 +17,25 @@ enum TimelineReplyViewPlacement {
struct TimelineReplyView: View {
let placement: TimelineReplyViewPlacement
let timelineItemReplyDetails: TimelineItemReplyDetails?
var maxWidth: CGFloat?
private let backgroundShape = RoundedRectangle(cornerRadius: 6)
var body: some View {
content
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: maxWidth, alignment: .leading)
.padding(4.0)
.background {
ZStack {
backgroundShape.fill(.compound.bgCanvasDefault)
backgroundShape.stroke(.compound.borderInteractiveSecondary)
}
}
}
@ViewBuilder
private var content: some View {
if let timelineItemReplyDetails {
switch timelineItemReplyDetails {
case .loaded(let sender, _, let content):
@@ -29,7 +46,7 @@ struct TimelineReplyView: View {
ReplyView(sender: sender,
plainBody: content.caption ?? content.filename,
formattedBody: content.formattedCaption,
icon: .init(kind: .systemIcon("waveform"), cornerRadii: iconCornerRadii))
icon: .init(kind: .systemIcon("waveform")))
case .emote(let content):
ReplyView(sender: sender,
plainBody: content.body,
@@ -38,12 +55,12 @@ struct TimelineReplyView: View {
ReplyView(sender: sender,
plainBody: content.caption ?? content.filename,
formattedBody: content.formattedCaption,
icon: .init(kind: .icon(\.document), cornerRadii: iconCornerRadii))
icon: .init(kind: .icon(\.document)))
case .image(let content):
ReplyView(sender: sender,
plainBody: content.caption ?? content.filename,
formattedBody: content.formattedCaption,
icon: .init(kind: .mediaSource(content.thumbnailInfo?.source ?? content.imageInfo.source), cornerRadii: iconCornerRadii))
icon: .init(kind: .mediaSource(content.thumbnailInfo?.source ?? content.imageInfo.source)))
case .notice(let content):
ReplyView(sender: sender,
plainBody: content.body,
@@ -56,33 +73,33 @@ struct TimelineReplyView: View {
ReplyView(sender: sender,
plainBody: content.caption ?? content.filename,
formattedBody: content.formattedCaption,
icon: content.thumbnailInfo.map { .init(kind: .mediaSource($0.source), cornerRadii: iconCornerRadii) })
icon: content.thumbnailInfo.map { .init(kind: .mediaSource($0.source)) })
case .voice:
ReplyView(sender: sender,
plainBody: L10n.commonVoiceMessage,
formattedBody: nil,
icon: .init(kind: .icon(\.micOn), cornerRadii: iconCornerRadii))
icon: .init(kind: .icon(\.micOn)))
case .location:
ReplyView(sender: sender,
plainBody: L10n.commonSharedLocation,
formattedBody: nil,
icon: .init(kind: .icon(\.locationPin), cornerRadii: iconCornerRadii))
icon: .init(kind: .icon(\.locationPin)))
}
case .poll(let question):
ReplyView(sender: sender,
plainBody: question,
formattedBody: nil,
icon: .init(kind: .icon(\.polls), cornerRadii: iconCornerRadii))
icon: .init(kind: .icon(\.polls)))
case .liveLocation:
ReplyView(sender: sender,
plainBody: L10n.commonSharedLiveLocation,
formattedBody: nil,
icon: .init(kind: .icon(\.locationPin), cornerRadii: iconCornerRadii))
icon: .init(kind: .icon(\.locationPin)))
case .redacted:
ReplyView(sender: sender,
plainBody: L10n.commonMessageRemoved,
formattedBody: nil,
icon: .init(kind: .icon(\.delete), cornerRadii: iconCornerRadii))
icon: .init(kind: .icon(\.delete)))
}
default:
LoadingReplyView()
@@ -90,15 +107,6 @@ struct TimelineReplyView: View {
}
}
private var iconCornerRadii: Double {
switch placement {
case .composer:
return 9.0
case .timeline:
return 4.0
}
}
private struct LoadingReplyView: View {
var body: some View {
ReplyView(sender: .init(id: "@alice:matrix.org"), plainBody: "Hello world", formattedBody: nil)
@@ -117,7 +125,7 @@ struct TimelineReplyView: View {
}
let kind: Kind
let cornerRadii: Double
let cornerRadii = 4.0
}
@EnvironmentObject private var context: TimelineViewModel.Context
@@ -180,7 +188,7 @@ struct TimelineReplyView: View {
.aspectRatio(contentMode: .fit)
.padding(8.0)
case .icon(let keyPath):
CompoundIcon(keyPath, size: .small, relativeTo: .body)
CompoundIcon(keyPath, size: .medium, relativeTo: .body)
}
}
}