mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Rich text editor "expanded mode" (#1656)
* Fix composer icon in dark mode * Add RTE poc * Amend cornerRadius * Add snaps * Fix composer top spacing * Fix clipping * Refine UX * Fix animation * Add constants + iPad hide bars logics * Polish clamping * Fix UT * Cleanup * Add grabber color * Add UI tests * Rename handle -> grabber * Fix resize composer when RTE is off * Fix project file
This commit is contained in:
@@ -24,6 +24,8 @@ struct MessageComposer: View {
|
||||
@Binding var plainText: String
|
||||
let composerView: WysiwygComposerView
|
||||
let mode: RoomScreenComposerMode
|
||||
let showResizeGrabber: Bool
|
||||
@Binding var isExpanded: Bool
|
||||
let sendAction: EnterKeyHandler
|
||||
let pasteAction: PasteHandler
|
||||
let replyCancellationAction: () -> Void
|
||||
@@ -32,14 +34,43 @@ struct MessageComposer: View {
|
||||
@FocusState private var focused: Bool
|
||||
|
||||
@State private var isMultiline = false
|
||||
@State private var composerTranslation: CGFloat = 0
|
||||
|
||||
var body: some View {
|
||||
let roundedRectangle = RoundedRectangle(cornerRadius: borderRadius)
|
||||
VStack(spacing: 0) {
|
||||
if showResizeGrabber {
|
||||
resizeGrabber
|
||||
}
|
||||
|
||||
mainContent
|
||||
.padding(.horizontal, 12.0)
|
||||
.clipShape(RoundedRectangle(cornerRadius: borderRadius))
|
||||
.background {
|
||||
let roundedRectangle = RoundedRectangle(cornerRadius: borderRadius)
|
||||
ZStack {
|
||||
roundedRectangle
|
||||
.fill(Color.compound.bgSubtleSecondary)
|
||||
roundedRectangle
|
||||
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 1)
|
||||
.opacity(focused ? 1 : 0)
|
||||
}
|
||||
}
|
||||
// Explicitly disable all animations to fix weirdness with the header immediately
|
||||
// appearing whilst the text field and keyboard are still animating up to it.
|
||||
.animation(.noAnimation, value: mode)
|
||||
}
|
||||
.gesture(showResizeGrabber ? dragGesture : nil)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var mainContent: some View {
|
||||
VStack(alignment: .leading, spacing: -6) {
|
||||
header
|
||||
HStack(alignment: .bottom) {
|
||||
if ServiceLocator.shared.settings.richTextEditorEnabled {
|
||||
composerView
|
||||
.frame(minHeight: composerHeight, alignment: .top)
|
||||
.tint(.compound.iconAccentTertiary)
|
||||
.padding(.vertical, 10)
|
||||
.focused($focused)
|
||||
@@ -59,20 +90,11 @@ struct MessageComposer: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12.0)
|
||||
.clipped()
|
||||
.background {
|
||||
ZStack {
|
||||
roundedRectangle
|
||||
.fill(Color.compound.bgSubtleSecondary)
|
||||
roundedRectangle
|
||||
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 1)
|
||||
.opacity(focused ? 1 : 0)
|
||||
}
|
||||
}
|
||||
// Explicitly disable all animations to fix weirdness with the header immediately
|
||||
// appearing whilst the text field and keyboard are still animating up to it.
|
||||
.animation(.noAnimation, value: mode)
|
||||
}
|
||||
|
||||
private var composerHeight: CGFloat {
|
||||
let baseHeight = isExpanded ? ComposerConstant.maxHeight : ComposerConstant.minHeight
|
||||
return (baseHeight - composerTranslation).clamped(to: ComposerConstant.allowedHeightRange)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -95,6 +117,31 @@ struct MessageComposer: View {
|
||||
return 20
|
||||
}
|
||||
}
|
||||
|
||||
private var resizeGrabber: some View {
|
||||
Capsule()
|
||||
.foregroundColor(Asset.Colors.grabber.swiftUIColor)
|
||||
.frame(width: 36, height: 5)
|
||||
.padding(.vertical, 8)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
private var dragGesture: some Gesture {
|
||||
DragGesture()
|
||||
.onChanged { value in
|
||||
composerTranslation += value.translation.height
|
||||
}
|
||||
.onEnded { _ in
|
||||
withElementAnimation(.easeIn(duration: 0.3)) {
|
||||
if composerTranslation > ComposerConstant.translationThreshold {
|
||||
isExpanded = false
|
||||
} else if composerTranslation < -ComposerConstant.translationThreshold {
|
||||
isExpanded = true
|
||||
}
|
||||
composerTranslation = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MessageComposerReplyHeader: View {
|
||||
@@ -169,6 +216,8 @@ struct MessageComposer_Previews: PreviewProvider {
|
||||
return MessageComposer(plainText: .constant(content),
|
||||
composerView: composerView,
|
||||
mode: mode,
|
||||
showResizeGrabber: false,
|
||||
isExpanded: .constant(false),
|
||||
sendAction: { },
|
||||
pasteAction: { _ in },
|
||||
replyCancellationAction: { },
|
||||
|
||||
Reference in New Issue
Block a user