Merge branch 'develop' into mauroromito/fullscreen_mode_2

# Conflicts:
#	RiotSwiftUI/Modules/Room/Composer/View/Composer.swift
This commit is contained in:
Mauro Romito
2022-11-10 14:29:08 +01:00
61 changed files with 1045 additions and 287 deletions
@@ -32,12 +32,12 @@ enum MockComposerScreenState: MockScreenState, CaseIterable {
let bindings = ComposerBindings(focused: false)
switch self {
case .send: viewModel = ComposerViewModel(initialViewState: ComposerViewState(bindings: bindings))
case .edit: viewModel = ComposerViewModel(initialViewState: ComposerViewState(sendMode: .edit, bindings: bindings))
case .reply: viewModel = ComposerViewModel(initialViewState: ComposerViewState(eventSenderDisplayName: "TestUser", sendMode: .reply, bindings: bindings))
case .send: viewModel = ComposerViewModel(initialViewState: ComposerViewState(textFormattingEnabled: true, bindings: bindings))
case .edit: viewModel = ComposerViewModel(initialViewState: ComposerViewState(sendMode: .edit, textFormattingEnabled: true, bindings: bindings))
case .reply: viewModel = ComposerViewModel(initialViewState: ComposerViewState(eventSenderDisplayName: "TestUser", sendMode: .reply, textFormattingEnabled: true, bindings: bindings))
}
let wysiwygviewModel = WysiwygComposerViewModel(minHeight: 20, maxHeight: 360)
let wysiwygviewModel = WysiwygComposerViewModel(minHeight: 20, maxCompressedHeight: 360)
viewModel.callback = { [weak viewModel, weak wysiwygviewModel] result in
guard let viewModel = viewModel else { return }
@@ -19,7 +19,7 @@ import Foundation
struct ComposerViewState: BindableState {
var eventSenderDisplayName: String?
var sendMode: ComposerSendMode = .send
var textFormattingEnabled = true
var textFormattingEnabled: Bool
var placeholder: String?
var bindings: ComposerBindings
@@ -1,4 +1,4 @@
//
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +23,8 @@ final class ComposerViewModelTests: XCTestCase {
var context: ComposerViewModel.Context!
override func setUpWithError() throws {
viewModel = ComposerViewModel(initialViewState: ComposerViewState(bindings: ComposerBindings(focused: false)))
viewModel = ComposerViewModel(initialViewState: ComposerViewState(textFormattingEnabled: true,
bindings: ComposerBindings(focused: false)))
context = viewModel.context
}
@@ -71,79 +71,6 @@ struct Composer: View {
)
}
}
// MARK: Public
@ObservedObject var viewModel: ComposerViewModelType.Context
@ObservedObject var wysiwygViewModel: WysiwygComposerViewModel
let resizeAnimationDuration: Double
let sendMessageAction: (WysiwygComposerContent) -> Void
let showSendMediaActions: () -> Void
var body: some View {
VStack(spacing: 8) {
if wysiwygViewModel.maximised {
RoundedRectangle(cornerRadius: 4)
.fill(theme.colors.quinaryContent)
.frame(width: 36, height: 5)
.padding(.top, 10)
}
if viewModel.viewState.textFormattingEnabled {
composerContainer
}
HStack(alignment: .bottom, spacing: 0) {
Button {
showSendMediaActions()
} label: {
Image(Asset.Images.startComposeModule.name)
.resizable()
.foregroundColor(theme.colors.tertiaryContent)
.frame(width: 14, height: 14)
}
.frame(width: 36, height: 36)
.background(Circle().fill(theme.colors.system))
.padding(.trailing, 8)
.accessibilityLabel(VectorL10n.create)
if viewModel.viewState.textFormattingEnabled {
FormattingToolbar(formatItems: formatItems) { type in
wysiwygViewModel.apply(type.action)
}
.frame(height: 44)
Spacer()
} else {
composerContainer
}
Button {
if wysiwygViewModel.plainTextMode {
sendMessageAction(wysiwygViewModel.plainTextModeContent)
} else {
sendMessageAction(wysiwygViewModel.content)
}
wysiwygViewModel.clearContent()
} label: {
if viewModel.viewState.sendMode == .edit {
Image(Asset.Images.saveIcon.name)
} else {
Image(Asset.Images.sendIcon.name)
}
}
.frame(width: 36, height: 36)
.padding(.leading, 8)
.isHidden(!isActionButtonShowing)
.accessibilityIdentifier(actionButtonAccessibilityIdentifier)
.accessibilityLabel(VectorL10n.send)
.onChange(of: wysiwygViewModel.isContentEmpty) { isEmpty in
viewModel.send(viewAction: .contentDidChange(isEmpty: isEmpty))
withAnimation(.easeInOut(duration: 0.15)) {
isActionButtonShowing = !isEmpty
}
}
}
.padding(.horizontal, 12)
.padding(.bottom, 4)
}
}
private var composerContainer: some View {
let rect = RoundedRectangle(cornerRadius: cornerRadius)
@@ -205,8 +132,7 @@ struct Composer: View {
}
.clipShape(rect)
.overlay(rect.stroke(borderColor, lineWidth: 1))
// .animation(.easeInOut(duration: resizeAnimationDuration), value: wysiwygViewModel.idealHeight)
.padding(.horizontal, horizontalPadding)
// .animation(.easeInOut(duration: resizeAnimationDuration), value: wysiwygViewModel.idealHeight)
.padding(.top, 8)
.onTapGesture {
if viewModel.focused {
@@ -214,6 +140,87 @@ struct Composer: View {
}
}
}
private var sendMediaButton: some View {
return Button {
showSendMediaActions()
} label: {
Image(Asset.Images.startComposeModule.name)
.resizable()
.foregroundColor(theme.colors.tertiaryContent)
.frame(width: 14, height: 14)
}
.frame(width: 36, height: 36)
.background(Circle().fill(theme.colors.system))
.padding(.trailing, 8)
.accessibilityLabel(VectorL10n.create)
}
private var sendButton: some View {
return Button {
sendMessageAction(wysiwygViewModel.content)
wysiwygViewModel.clearContent()
} label: {
if viewModel.viewState.sendMode == .edit {
Image(Asset.Images.saveIcon.name)
} else {
Image(Asset.Images.sendIcon.name)
}
}
.frame(width: 36, height: 36)
.padding(.leading, 8)
.isHidden(!isActionButtonShowing)
.accessibilityIdentifier(actionButtonAccessibilityIdentifier)
.accessibilityLabel(VectorL10n.send)
.onChange(of: wysiwygViewModel.isContentEmpty) { isEmpty in
viewModel.send(viewAction: .contentDidChange(isEmpty: isEmpty))
withAnimation(.easeInOut(duration: 0.15)) {
isActionButtonShowing = !isEmpty
}
}
}
// MARK: Public
@ObservedObject var viewModel: ComposerViewModelType.Context
@ObservedObject var wysiwygViewModel: WysiwygComposerViewModel
let resizeAnimationDuration: Double
let sendMessageAction: (WysiwygComposerContent) -> Void
let showSendMediaActions: () -> Void
var body: some View {
VStack(spacing: 8) {
if wysiwygViewModel.maximised {
RoundedRectangle(cornerRadius: 4)
.fill(theme.colors.quinaryContent)
.frame(width: 36, height: 5)
.padding(.top, 10)
}
HStack(alignment: .bottom, spacing: 0) {
if !viewModel.viewState.textFormattingEnabled {
sendMediaButton
}
composerContainer
if !viewModel.viewState.textFormattingEnabled {
sendButton
}
}
if viewModel.viewState.textFormattingEnabled {
HStack(alignment: .center, spacing: 0) {
sendMediaButton
FormattingToolbar(formatItems: formatItems) { type in
wysiwygViewModel.apply(type.action)
}
.frame(height: 44)
Spacer()
sendButton
}
}
}
.padding(.horizontal, horizontalPadding)
.padding(.bottom, 4)
}
}
// MARK: Previews