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:
Doug
2023-06-20 18:31:40 +01:00
committed by GitHub
parent 2f091cf8d1
commit ede4ab095c
30 changed files with 141 additions and 95 deletions
@@ -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)
}
@@ -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
}