mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
App polish part 2 (#1118)
- Fix sheet backgrounds in dark mode. - Fix background colours for reaction buttons. - Reply view layout. - Update separator colours. - Don't show sender name/avatar in DMs. - Fix inconsistent line heights with formatted body text. - Use plain text for reply - Increase tappable size of collapsed state changes button. - Blockquote layout and text colour. - Tap to expand the topic in room details. - Change the topic and security font size in room details. - Add cancel button when inviting someone to an existing room. - Reword Add Friends to Add People in start chat screen. - Update compound.
This commit is contained in:
@@ -101,29 +101,41 @@ struct TimelineReplyView: View {
|
||||
|
||||
var icon: Icon?
|
||||
|
||||
var isTextOnly: Bool {
|
||||
icon?.mediaSource == nil && icon?.systemIconName == nil
|
||||
}
|
||||
|
||||
/// The string shown as the message preview.
|
||||
///
|
||||
/// This converts the formatted body to a plain string to remove formatting
|
||||
/// and render with a consistent font size. This conversion is done to avoid
|
||||
/// showing markdown characters in the preview for messages with formatting.
|
||||
var messagePreview: String {
|
||||
guard let formattedBody else { return plainBody }
|
||||
return String(formattedBody.characters)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(spacing: 8) {
|
||||
iconView
|
||||
.frame(width: imageContainerSize, height: imageContainerSize)
|
||||
.foregroundColor(.compound.iconPrimary)
|
||||
.background(Color.compound.bgSubtlePrimary)
|
||||
.cornerRadius(icon?.cornerRadii ?? 0.0, corners: .allCorners)
|
||||
|
||||
if icon?.mediaSource == nil, icon?.systemIconName == nil {
|
||||
Spacer().frame(width: 4.0)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(sender.displayName ?? sender.id)
|
||||
.font(.compound.bodySMSemibold)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
|
||||
Text(formattedBody ?? AttributedString(plainBody))
|
||||
Text(messagePreview)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.tint(.compound.textLinkExternal)
|
||||
.lineLimit(2)
|
||||
}
|
||||
.padding(.leading, isTextOnly ? 8 : 0)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +166,7 @@ struct TimelineReplyView_Previews: PreviewProvider {
|
||||
static let viewModel = RoomScreenViewModel.mock
|
||||
|
||||
static var previews: some View {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: ""))
|
||||
|
||||
TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loading(eventID: ""))
|
||||
|
||||
@@ -65,8 +65,8 @@ struct RoomAttachmentPicker: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.presentationDetents([.height(sheetContentHeight)])
|
||||
.presentationBackground(Color.compound.bgCanvasDefault)
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,13 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
|
||||
@State private var showItemActionMenu = false
|
||||
|
||||
private var isTextItem: Bool {
|
||||
timelineItem is TextBasedRoomTimelineItem
|
||||
}
|
||||
private var isTextItem: Bool { timelineItem is TextBasedRoomTimelineItem }
|
||||
private var isEncryptedOneToOneRoom: Bool { context.viewState.isEncryptedOneToOneRoom }
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .trailingFirstTextBaseline) {
|
||||
VStack(alignment: alignment, spacing: -12) {
|
||||
if !timelineItem.isOutgoing {
|
||||
if !timelineItem.isOutgoing, !isEncryptedOneToOneRoom {
|
||||
header
|
||||
.zIndex(1)
|
||||
}
|
||||
@@ -213,7 +212,6 @@ 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)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(4.0)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -235,7 +233,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
}
|
||||
|
||||
private var messageBubbleTopPadding: CGFloat {
|
||||
guard timelineItem.isOutgoing else { return 0 }
|
||||
guard timelineItem.isOutgoing || isEncryptedOneToOneRoom else { return 0 }
|
||||
return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0
|
||||
}
|
||||
|
||||
@@ -298,13 +296,14 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider {
|
||||
}
|
||||
|
||||
static var mockTimeline: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(viewModel.state.items) { item in
|
||||
item.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(viewModel.state.items) { item in
|
||||
item.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells
|
||||
}
|
||||
}
|
||||
}
|
||||
.environment(\.timelineStyle, .bubbles)
|
||||
.previewLayout(.sizeThatFits)
|
||||
.environmentObject(viewModel.context)
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ struct TimelineReactionButton: View {
|
||||
.background(
|
||||
backgroundShape
|
||||
.strokeBorder(reaction.isHighlighted ? Color.compound.textSecondary : .compound.bgCanvasDefault, lineWidth: 2)
|
||||
.background(reaction.isHighlighted ? Color.compound.textPrimary.opacity(0.1) : .compound.bgSubtleSecondary, in: backgroundShape)
|
||||
.background(reaction.isHighlighted ? Color.compound.textPrimary.opacity(0.1) : .compound._bgReactionButton, in: backgroundShape)
|
||||
)
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
+25
-22
@@ -42,33 +42,25 @@ struct CollapsibleRoomTimelineView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct CollapsibleRoomTimelineView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let item = CollapsibleTimelineItem(items: [SeparatorRoomTimelineItem(id: "First separator", text: "This is a separator"),
|
||||
SeparatorRoomTimelineItem(id: "Second separator", text: "This is another separator")])
|
||||
CollapsibleRoomTimelineView(timelineItem: item)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CollapsibleRoomTimelineItemDisclosureGroupStyle: DisclosureGroupStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
VStack(spacing: 0.0) {
|
||||
HStack(alignment: .center) {
|
||||
configuration.label
|
||||
Text(Image(systemName: "chevron.forward"))
|
||||
.rotationEffect(.degrees(configuration.isExpanded ? 90 : 0))
|
||||
.animation(.elementDefault, value: configuration.isExpanded)
|
||||
Button { configuration.isExpanded.toggle() } label: {
|
||||
HStack(alignment: .center) {
|
||||
configuration.label
|
||||
Text(Image(systemName: "chevron.forward"))
|
||||
.rotationEffect(.degrees(configuration.isExpanded ? 90 : 0))
|
||||
.animation(.elementDefault, value: configuration.isExpanded)
|
||||
}
|
||||
.font(.compound.bodySM)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.padding(.horizontal, 36.0)
|
||||
.padding(.vertical, 12.0)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(maxWidth: .infinity)
|
||||
.font(.compound.bodySM)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.padding(.horizontal, 36.0)
|
||||
.padding(.top, 20.0)
|
||||
.padding(.bottom, 12.0)
|
||||
|
||||
.onTapGesture {
|
||||
configuration.isExpanded.toggle()
|
||||
}
|
||||
.padding(.top, 8.0)
|
||||
|
||||
if configuration.isExpanded {
|
||||
configuration.content
|
||||
@@ -76,3 +68,14 @@ private struct CollapsibleRoomTimelineItemDisclosureGroupStyle: DisclosureGroupS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CollapsibleRoomTimelineView_Previews: PreviewProvider {
|
||||
static let item = CollapsibleTimelineItem(items: [
|
||||
SeparatorRoomTimelineItem(id: "First separator", text: "This is a separator"),
|
||||
SeparatorRoomTimelineItem(id: "Second separator", text: "This is another separator")
|
||||
])
|
||||
|
||||
static var previews: some View {
|
||||
CollapsibleRoomTimelineView(timelineItem: item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,16 +57,17 @@ struct FormattedBodyText: View {
|
||||
// The rendered blockquote with a greedy width. The custom layout prevents the
|
||||
// infinite width from increasing the overall width of the view.
|
||||
Text(component.attributedString.mergingAttributes(blockquoteAttributes))
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 12.0)
|
||||
.overlay(alignment: .leading) {
|
||||
// User an overlay here so that the rectangle's infinite height doesn't take priority
|
||||
Rectangle()
|
||||
Capsule()
|
||||
.frame(width: 2.0)
|
||||
.padding(.leading, 6.0)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
.padding(.leading, 5.0)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
|
||||
} else {
|
||||
@@ -152,6 +153,12 @@ struct FormattedBodyText_Previews: PreviewProvider {
|
||||
<blockquote>Third blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||
""",
|
||||
"""
|
||||
<blockquote>A blockquote that is long and goes onto multiple lines as the first item in the message</blockquote>
|
||||
<p>Then another line of text here to reply to the blockquote, which is also a multiline component.</p>
|
||||
<blockquote>Short line here.</blockquote>
|
||||
<p>And a simple reply here.</p>
|
||||
""",
|
||||
"""
|
||||
<code>Hello world</code>
|
||||
<p>Text</p>
|
||||
<code><b>Hello</b> <i>world</i></code>
|
||||
|
||||
@@ -86,8 +86,8 @@ public struct TimelineItemMenu: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationBackground(Color.compound.bgCanvasDefault)
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public struct TimelineItemMenu: View {
|
||||
|
||||
private func reactionBackgroundColor(for emoji: String) -> Color {
|
||||
if item.properties.reactions.first(where: { $0.key == emoji }) != nil {
|
||||
return .compound._bgReactionButton
|
||||
return .compound.bgActionPrimaryRest
|
||||
} else {
|
||||
return .clear
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user