Composer and RTE polish (#1719)

* Move link button

* Always show indent buttons

* Fix code blocks and quote block styles

* Fix code blocks and quotes style

* Update composer attachment icon

* Fix send message icon

* Fix composer border

* Fix rte border

* Update RTE button assets

* Update close rte icon

* Refactor RTE and composer layout

* more padding fixes

* Update compound

* Update WysiwygComposer to 2.11.0

* Fix ui tests

* Fix composer bottom padding

* Update snapshot tests
This commit is contained in:
Alfonso Grillo
2023-09-18 16:11:22 +02:00
committed by GitHub
parent 38107b7b2c
commit 8ca3500981
108 changed files with 338 additions and 226 deletions
+2
View File
@@ -55,6 +55,8 @@ internal enum Asset {
internal static let locationPin = ImageAsset(name: "images/location-pin")
internal static let locationPointerFull = ImageAsset(name: "images/location-pointer-full")
internal static let locationPointer = ImageAsset(name: "images/location-pointer")
internal static let closeRte = ImageAsset(name: "images/close-rte")
internal static let composerAttachment = ImageAsset(name: "images/composer-attachment")
internal static let timelineComposerSendMessage = ImageAsset(name: "images/timeline-composer-send-message")
internal static let timelineEndedPoll = ImageAsset(name: "images/timeline-ended-poll")
internal static let timelinePollAttachment = ImageAsset(name: "images/timeline-poll-attachment")
@@ -139,7 +139,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
attributedString.enumerateAttribute(.backgroundColor, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in
if let value = value as? UIColor,
value == temporaryCodeBlockMarkingColor {
attributedString.addAttribute(.backgroundColor, value: UIColor.compound._bgCodeBlock as Any, range: range)
attributedString.addAttribute(.backgroundColor, value: UIColor(.compound._bgCodeBlock) as Any, range: range)
}
}
}
@@ -88,6 +88,7 @@ enum FormatType {
case italic
case underline
case strikeThrough
case link
case unorderedList
case orderedList
case indent
@@ -95,7 +96,6 @@ enum FormatType {
case inlineCode
case codeBlock
case quote
case link
}
extension FormatType: CaseIterable, Identifiable {
@@ -226,14 +226,4 @@ extension FormatType {
return .link
}
}
/// Return true if the format type is an indentation action.
var isIndentType: Bool {
switch self {
case .indent, .unindent:
return true
default:
return false
}
}
}
@@ -62,8 +62,6 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
.map { actions in
FormatType
.allCases
// Exclude indent type outside of lists.
.filter { wysiwygViewModel.isInList || !$0.isIndentType }
.map { type in
FormatItem(type: type,
state: actions[type.composerAction] ?? .disabled)
@@ -259,13 +257,6 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
}
}
private extension WysiwygComposerViewModel {
/// Return true if the selection of the composer is currently located in a list.
var isInList: Bool {
actionStates[.orderedList] == .reversed || actionStates[.unorderedList] == .reversed
}
}
private extension LinkAction {
var url: String? {
guard case .edit(let url) = self else {
@@ -24,6 +24,7 @@ struct ComposerToolbar: View {
@FocusState private var composerFocused: Bool
@ScaledMetric private var sendButtonIconSize = 16
@ScaledMetric(relativeTo: .title) private var closeRTEButtonSize = 30
var body: some View {
VStack(spacing: 8) {
@@ -36,25 +37,29 @@ struct ComposerToolbar: View {
}
private var topBar: some View {
HStack(alignment: .bottom, spacing: 10) {
HStack(alignment: .bottom, spacing: 5) {
if !context.composerActionsEnabled {
RoomAttachmentPicker(context: context)
.padding(.bottom, 5) // centre align with the send button
}
messageComposer
.environmentObject(context)
.onTapGesture {
guard !composerFocused else { return }
composerFocused = true
}
.padding(.leading, context.composerActionsEnabled ? 7 : 0)
.padding(.trailing, context.composerActionsEnabled ? 4 : 0)
if !context.composerActionsEnabled {
sendButton
.padding(.leading, 3)
}
}
}
private var bottomBar: some View {
HStack(alignment: .center, spacing: 10) {
HStack(alignment: .center, spacing: 9) {
closeRTEButton
FormattingToolbar(formatItems: context.formatItems) { action in
@@ -62,6 +67,7 @@ struct ComposerToolbar: View {
}
sendButton
.padding(.leading, 7)
}
}
@@ -70,9 +76,11 @@ struct ComposerToolbar: View {
context.composerActionsEnabled = false
context.composerExpanded = false
} label: {
Image(systemName: "xmark.circle.fill")
.font(.compound.headingLG)
.foregroundColor(.compound.textActionPrimary)
Image(Asset.Images.closeRte.name)
.resizable()
.scaledToFit()
.frame(width: closeRTEButtonSize, height: closeRTEButtonSize)
.padding(7)
}
.accessibilityIdentifier(A11yIdentifiers.roomScreen.composerToolbar.closeFormattingOptions)
}
@@ -89,11 +97,11 @@ struct ComposerToolbar: View {
Circle()
.foregroundColor(context.viewState.sendButtonDisabled ? .clear : .compound.iconAccentTertiary)
}
.padding(4)
}
.disabled(context.viewState.sendButtonDisabled)
.animation(.linear(duration: 0.1), value: context.viewState.sendButtonDisabled)
.keyboardShortcut(.return, modifiers: [.command])
.padding([.vertical, .trailing], 6)
}
private var messageComposer: some View {
@@ -133,6 +141,7 @@ struct ComposerToolbar: View {
private var composerView: WysiwygComposerView {
WysiwygComposerView(placeholder: placeholder,
placeholderColor: .compound.textSecondary,
viewModel: wysiwygViewModel,
itemProviderHelper: ItemProviderHelper(),
keyCommandHandler: keyCommandHandler) { provider in
@@ -151,7 +160,7 @@ struct ComposerToolbar: View {
Image(asset: Asset.Images.timelineComposerSendMessage)
.resizable()
.frame(width: sendButtonIconSize, height: sendButtonIconSize)
.padding(EdgeInsets(top: 7, leading: 8, bottom: 7, trailing: 6))
.padding(EdgeInsets(top: 10, leading: 11, bottom: 10, trailing: 9))
.opacity(context.viewState.composerMode.isEdit ? 0 : 1)
.accessibilityLabel(L10n.actionSend)
.accessibilityHidden(context.viewState.composerMode.isEdit)
@@ -22,7 +22,7 @@ struct FormattingToolbar: View {
/// The action when an item is selected
var formatAction: (FormatType) -> Void
@ScaledMetric private var toolbarButtonIconSize = 24
@ScaledMetric private var toolbarButtonIconSize = 20
var body: some View {
ScrollView(.horizontal) {
@@ -32,16 +32,16 @@ struct FormattingToolbar: View {
formatAction(item.type)
} label: {
item.icon
.renderingMode(.template)
.resizable()
.scaledToFit()
.frame(width: toolbarButtonIconSize, height: toolbarButtonIconSize)
.foregroundColor(item.foregroundColor)
.padding(10)
.padding(8)
.background(item.backgroundColor)
.cornerRadius(8)
.padding(4)
}
.disabled(item.state == .disabled)
.background(item.backgroundColor)
.cornerRadius(8)
.accessibilityIdentifier(item.accessibilityIdentifier)
.accessibilityLabel(item.accessibilityLabel)
}
@@ -54,7 +54,7 @@ private extension FormatItem {
var foregroundColor: Color {
switch state {
case .reversed:
return .compound.iconOnSolidPrimary
return .compound.iconSuccessPrimary
case .enabled:
return .compound.iconSecondary
case .disabled:
@@ -65,8 +65,8 @@ private extension FormatItem {
var backgroundColor: Color {
switch state {
case .reversed:
return .compound.bgActionPrimaryRest
default:
return .compound._bgAccentSelected
case .enabled, .disabled:
return .compound.bgCanvasDefault
}
}
@@ -50,8 +50,7 @@ struct MessageComposer: View {
roundedRectangle
.fill(Color.compound.bgSubtleSecondary)
roundedRectangle
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 1)
.opacity(focused ? 1 : 0)
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 0.5)
}
}
// Explicitly disable all animations to fix weirdness with the header immediately
@@ -20,16 +20,20 @@ import WysiwygComposer
struct RoomAttachmentPicker: View {
@ObservedObject var context: ComposerToolbarViewModel.Context
@Environment(\.isPresented) var isPresented
@ScaledMetric(relativeTo: .title) private var attachmentButtonSize: CGFloat = 30
@State private var sheetContentHeight = CGFloat(0)
var body: some View {
Button {
context.showAttachmentPopover = true
} label: {
Image(systemName: "plus.circle.fill")
.font(.compound.headingLG)
Image(Asset.Images.composerAttachment.name)
.resizable()
.scaledToFit()
.frame(width: attachmentButtonSize, height: attachmentButtonSize)
.foregroundColor(.compound.textActionPrimary)
.padding(7)
}
.accessibilityIdentifier(A11yIdentifiers.roomScreen.composerToolbar.openComposeOptions)
.popover(isPresented: $context.showAttachmentPopover) {
@@ -15,6 +15,7 @@
//
import Combine
import HTMLParser
import SwiftUI
import WysiwygComposer
@@ -63,7 +64,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
wysiwygViewModel = WysiwygComposerViewModel(minHeight: ComposerConstant.minHeight,
maxCompressedHeight: ComposerConstant.maxHeight,
maxExpandedHeight: ComposerConstant.maxHeight)
maxExpandedHeight: ComposerConstant.maxHeight,
parserStyle: .elementX)
composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel)
}
@@ -133,3 +135,20 @@ enum ComposerConstant {
static let allowedHeightRange = minHeight...maxHeight
static let translationThreshold: CGFloat = 60
}
private extension HTMLParserStyle {
static let elementX = HTMLParserStyle(textColor: UIColor.label,
linkColor: UIColor.link,
codeBlockStyle: BlockStyle(backgroundColor: UIColor(.compound._bgCodeBlock),
borderColor: UIColor(.compound.borderInteractiveSecondary),
borderWidth: 0.0,
cornerRadius: 0.0,
padding: BlockStyle.Padding(horizontal: 10, vertical: 12),
type: .background),
quoteBlockStyle: BlockStyle(backgroundColor: UIColor(.compound.iconTertiary),
borderColor: UIColor(.compound.borderInteractiveSecondary),
borderWidth: 0.0,
cornerRadius: 0.0,
padding: BlockStyle.Padding(horizontal: 25, vertical: 12),
type: .side(offset: 5, width: 4)))
}
@@ -23,8 +23,6 @@ struct RoomScreen: View {
@State private var dragOver = false
let composerToolbar: ComposerToolbar
private let attachmentButtonPadding = 10.0
init(context: RoomScreenViewModel.Context, composerToolbar: ComposerToolbar) {
self.context = context
self.composerToolbar = composerToolbar
@@ -36,13 +34,13 @@ struct RoomScreen: View {
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.safeAreaInset(edge: .bottom, spacing: 0) {
composerToolbar
.padding(.leading, attachmentButtonPadding)
.padding(.trailing, 12)
.padding(.bottom)
.padding(.leading, 5)
.padding(.trailing, 8)
.padding(.bottom, composerToolbarContext.composerActionsEnabled ? 8 : 12)
.background {
if composerToolbarContext.composerActionsEnabled {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 1)
.stroke(Color.compound.borderInteractiveSecondary, lineWidth: 0.5)
.ignoresSafeArea()
}
}