mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,12 +197,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
// The rendered reply bubble with a greedy width. The custom layout prevents
|
||||
// the infinite width from increasing the overall width of the view.
|
||||
|
||||
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: replyDetails)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(4.0)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color.compound.bgCanvasDefault)
|
||||
.cornerRadius(8)
|
||||
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: replyDetails, maxWidth: .infinity)
|
||||
.timelineBubbleLayoutSize(.bubbleWidth(mode: .rendering))
|
||||
.onTapGesture {
|
||||
if context.viewState.timelineKind != .pinned {
|
||||
@@ -212,8 +207,6 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
|
||||
// Add a fixed width reply bubble that is used for layout calculations but won't be rendered.
|
||||
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: replyDetails)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(4.0)
|
||||
.timelineBubbleLayoutSize(.bubbleWidth(mode: .layout))
|
||||
.hidden()
|
||||
}
|
||||
|
||||
@@ -90,9 +90,9 @@ struct MediaFileRoomTimelineContent: View {
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.lineLimit(2)
|
||||
} icon: {
|
||||
CompoundIcon(icon, size: .xSmall, relativeTo: .body)
|
||||
CompoundIcon(icon, size: .medium, relativeTo: .body)
|
||||
.foregroundColor(.compound.iconPrimary)
|
||||
.scaledPadding(8)
|
||||
.scaledPadding(6)
|
||||
.background(.compound.iconOnSolidPrimary,
|
||||
in: RoundedRectangle(cornerRadius: 4, style: .continuous))
|
||||
}
|
||||
|
||||
+2
-1
@@ -88,6 +88,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
|
||||
static let roomProxyMock = JoinedRoomProxyMock(.init(name: "Preview room"))
|
||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||
static let focussedEventID = "RoomTimelineItemFixtures.default.5"
|
||||
static let composerViewModel = ComposerToolbarViewModel.mock()
|
||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
focussedEventID: focussedEventID,
|
||||
timelineController: MockTimelineController(),
|
||||
@@ -105,7 +106,7 @@ struct HighlightedTimelineItemTimeline_Previews: PreviewProvider {
|
||||
ElementNavigationStack {
|
||||
RoomScreen(context: roomViewModel.context,
|
||||
timelineContext: timelineViewModel.context,
|
||||
composerToolbar: ComposerToolbar.mock())
|
||||
composerToolbar: ComposerToolbar(context: composerViewModel.context))
|
||||
}
|
||||
.previewDisplayName("Timeline")
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
|
||||
static let roomProxyMock = JoinedRoomProxyMock(.init(id: "stable_id",
|
||||
name: "Preview room"))
|
||||
static let roomViewModel = RoomScreenViewModel.mock(roomProxyMock: roomProxyMock)
|
||||
static let composerViewModel = ComposerToolbarViewModel.mock()
|
||||
static let timelineViewModel = TimelineViewModel(roomProxy: roomProxyMock,
|
||||
timelineController: MockTimelineController(),
|
||||
userSession: UserSessionMock(.init()),
|
||||
@@ -159,7 +160,7 @@ struct TimelineView_Previews: PreviewProvider { // Not testable as this preview
|
||||
ElementNavigationStack {
|
||||
RoomScreen(context: roomViewModel.context,
|
||||
timelineContext: timelineViewModel.context,
|
||||
composerToolbar: ComposerToolbar.mock())
|
||||
composerToolbar: ComposerToolbar(context: composerViewModel.context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user