Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rich Text Editor: Design Improvements #7127

Merged
merged 13 commits into from
Nov 30, 2022
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"images" : [
{
"filename" : "action_formatting_disabled.png",
"filename" : "Frame 143.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "action_formatting_disabled@2x.png",
"filename" : "Frame 143@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "action_formatting_disabled@3x.png",
"filename" : "Frame 143@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 9 additions & 1 deletion Riot/Modules/Room/RoomViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,15 @@ extension RoomViewController {
optionalTextView?.becomeFirstResponder()
originalRect = wysiwygInputToolbar.convert(wysiwygInputToolbar.frame, to: view)
}
wysiwygInputToolbar.showKeyboard()
// This tirggers a SwiftUI update that is handled correctly on iOS 16, but needs to be dispatchted async on older versions
// Dispatching on iOS 16 instead causes some weird SwiftUI update behaviours
if #available(iOS 16, *) {
wysiwygInputToolbar.showKeyboard()
} else {
DispatchQueue.main.async {
wysiwygInputToolbar.showKeyboard()
}
}
roomInputToolbarContainer.removeFromSuperview()
let dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
guard let self = self else { return }
self.toolbarViewDelegate?.didChangeMaximisedState(value)
self.hostingViewController.view.layer.cornerRadius = value ? 20 : 0
if !value {
self.voiceMessageBottomConstraint?.constant = 2
}
}
]

Expand Down Expand Up @@ -215,23 +218,17 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
keyboardHeight = keyboardRectangle.height
UIView.performWithoutAnimation {
if self.isMaximised {
self.voiceMessageBottomConstraint?.constant = keyboardHeight - (window?.safeAreaInsets.bottom ?? 0) + 4
} else {
self.voiceMessageBottomConstraint?.constant = 4
}
self.layoutIfNeeded()
if self.isMaximised {
self.voiceMessageBottomConstraint?.constant = keyboardHeight - (window?.safeAreaInsets.bottom ?? 0) + 2
} else {
self.voiceMessageBottomConstraint?.constant = 2
}
}
}

@objc private func keyboardWillHide(_ notification: Notification) {
if self.isMaximised {
UIView.performWithoutAnimation {
self.voiceMessageBottomConstraint?.constant = 4
self.layoutIfNeeded()
}
self.voiceMessageBottomConstraint?.constant = 2
}
}

Expand Down Expand Up @@ -277,7 +274,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
}
)
}

private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
Expand All @@ -294,7 +291,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
private func updateTextViewHeight() {
let height = UIScreen.main.bounds.height
let barOffset: CGFloat = 68
let toolbarHeight: CGFloat = 96
let toolbarHeight: CGFloat = sendMode == .send ? 96 : 110
let finalHeight = height - keyboardHeight - toolbarHeight - barOffset
wysiwygViewModel.maxExpandedHeight = finalHeight
if finalHeight < 200 {
Expand Down Expand Up @@ -339,9 +336,10 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
set {
viewModel.sendMode = ComposerSendMode(from: newValue)
updatePlaceholderText()
updateTextViewHeight()
}
}

/// Whether text formatting is currently enabled in the composer.
var textFormattingEnabled: Bool {
get {
Expand All @@ -361,7 +359,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.deactivate(voiceMessageToolbarView.containersTopConstraints)
addSubview(voiceMessageToolbarView)
let bottomConstraint = hostingViewController.view.bottomAnchor.constraint(equalTo: voiceMessageToolbarView.bottomAnchor, constant: 4)
let bottomConstraint = hostingViewController.view.bottomAnchor.constraint(equalTo: voiceMessageToolbarView.bottomAnchor, constant: 2)
voiceMessageBottomConstraint = bottomConstraint
NSLayoutConstraint.activate(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,31 @@ final class ComposerCreateActionListCoordinator: NSObject, Coordinator, Presenta
// MARK: - Setup

init(actions: [ComposerCreateAction], wysiwygEnabled: Bool, textFormattingEnabled: Bool) {
let isScrollingEnabled: Bool
if #available(iOS 16, *) {
isScrollingEnabled = false
} else {
isScrollingEnabled = true
}
viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(
actions: actions,
wysiwygEnabled: wysiwygEnabled,
isScrollingEnabled: isScrollingEnabled,
bindings: ComposerCreateActionListBindings(textFormattingEnabled: textFormattingEnabled)))
view = ComposerCreateActionList(viewModel: viewModel.context)
let hostingVC = VectorHostingController(rootView: view)
let height = hostingVC.sizeThatFits(in: CGSize(width: hostingVC.view.frame.width, height: UIView.layoutFittingCompressedSize.height)).height
hostingVC.bottomSheetPreferences = VectorHostingBottomSheetPreferences(
detents: [.custom(height: 470)],
// on iOS 15 custom will be replaced by medium which may require some scrolling
detents: [.custom(height: height)],
prefersGrabberVisible: true,
cornerRadius: 20,
prefersScrollingExpandsWhenScrolledToEdge: false
)
hostingController = hostingVC
super.init()
hostingVC.presentationController?.delegate = self
hostingVC.bottomSheetPreferences?.setup(viewController: hostingVC)
}

// MARK: - Public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum MockComposerCreateActionListScreenState: MockScreenState, CaseIterable {
let viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(
actions: actions,
wysiwygEnabled: true,
isScrollingEnabled: false,
bindings: ComposerCreateActionListBindings(textFormattingEnabled: true)))

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct ComposerCreateActionListViewState: BindableState {
/// The list of composer create actions to display to the user
let actions: [ComposerCreateAction]
let wysiwygEnabled: Bool
let isScrollingEnabled: Bool

var bindings: ComposerCreateActionListBindings
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ComposerCreateActionListTests: XCTestCase {
initialViewState: ComposerCreateActionListViewState(
actions: ComposerCreateAction.allCases,
wysiwygEnabled: true,
isScrollingEnabled: false,
bindings: ComposerCreateActionListBindings(textFormattingEnabled: true)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,56 +32,71 @@ struct ComposerCreateActionList: View {
// MARK: Public

@ObservedObject var viewModel: ComposerCreateActionListViewModel.Context

var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(viewModel.viewState.actions) { action in
HStack(spacing: 16) {
Image(action.icon)
.renderingMode(.template)
.foregroundColor(theme.colors.accent)
Text(action.title)
.foregroundColor(theme.colors.primaryContent)
.font(theme.fonts.body)
.accessibilityIdentifier(action.accessibilityIdentifier)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
viewModel.send(viewAction: .selectAction(action))
}
.padding(.horizontal, 16)
.padding(.vertical, 12)

private var internalView: some View {
VStack(alignment: .leading) {
ForEach(viewModel.viewState.actions) { action in
HStack(spacing: 16) {
Image(action.icon)
.renderingMode(.template)
.foregroundColor(theme.colors.accent)
Text(action.title)
.foregroundColor(theme.colors.primaryContent)
.font(theme.fonts.body)
.accessibilityIdentifier(action.accessibilityIdentifier)
Spacer()
}
if viewModel.viewState.wysiwygEnabled {
SeparatorLine()
HStack(spacing: 16) {
Image(textFormattingIcon)
.renderingMode(.template)
.foregroundColor(theme.colors.accent)
Text(VectorL10n.wysiwygComposerStartActionTextFormatting)
.foregroundColor(theme.colors.primaryContent)
.font(theme.fonts.body)
.accessibilityIdentifier("textFormatting")
Spacer()
Toggle("", isOn: $viewModel.textFormattingEnabled)
.toggleStyle(ComposerToggleActionStyle())
.labelsHidden()
.onChange(of: viewModel.textFormattingEnabled) { isOn in
viewModel.send(viewAction: .toggleTextFormatting(isOn))
}
}
.contentShape(Rectangle())
.padding(.horizontal, 16)
.padding(.vertical, 12)

.contentShape(Rectangle())
.onTapGesture {
viewModel.send(viewAction: .selectAction(action))
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
if viewModel.viewState.wysiwygEnabled {
SeparatorLine()
HStack(spacing: 16) {
Image(textFormattingIcon)
.renderingMode(.template)
.foregroundColor(theme.colors.accent)
Text(VectorL10n.wysiwygComposerStartActionTextFormatting)
.foregroundColor(theme.colors.primaryContent)
.font(theme.fonts.body)
.accessibilityIdentifier("textFormatting")
Spacer()
Toggle("", isOn: $viewModel.textFormattingEnabled)
.labelsHidden()
.toggleStyle(SwitchToggleStyle(tint: theme.colors.accent))
.onChange(of: viewModel.textFormattingEnabled) { isOn in
viewModel.send(viewAction: .toggleTextFormatting(isOn))
}
}
.contentShape(Rectangle())
.onTapGesture {
viewModel.textFormattingEnabled.toggle()
}
.padding(.horizontal, 16)
.padding(.vertical, 12)

}
}
}

var body: some View {
if viewModel.viewState.isScrollingEnabled {
ScrollView {
internalView
}
Spacer()
.padding(.top, 23)
.background(theme.colors.background.ignoresSafeArea())
} else {
VStack {
internalView
Spacer()
}
.padding(.top, 23)
.background(theme.colors.background.ignoresSafeArea())
}
.padding(.top, 23)
.background(theme.colors.background.ignoresSafeArea())
}
}

Expand All @@ -93,35 +108,3 @@ struct ComposerCreateActionList_Previews: PreviewProvider {
stateRenderer.screenGroup()
}
}

struct ComposerToggleActionStyle: ToggleStyle {
@Environment(\.theme) private var theme

func makeBody(configuration: Configuration) -> some View {
HStack {
Rectangle()
.foregroundColor(.clear)
.frame(width: 50, height: 30, alignment: .center)
.overlay(
Rectangle()
.foregroundColor(configuration.isOn
? theme.colors.accent.opacity(0.5)
: theme.colors.primaryContent.opacity(0.25))
.cornerRadius(7)
.padding(.all, 8)
)
.overlay(
Circle()
.foregroundColor(configuration.isOn
? theme.colors.accent
: theme.colors.background)
.padding(.all, 3)
.offset(x: configuration.isOn ? 11 : -11, y: 0)
.shadow(radius: configuration.isOn ? 0.0 : 2.0)
.animation(Animation.linear(duration: 0.1))

).cornerRadius(20)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
10 changes: 8 additions & 2 deletions RiotSwiftUI/Modules/Room/Composer/View/Composer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ struct Composer: View {
}

private var cornerRadius: CGFloat {
if viewModel.viewState.shouldDisplayContext || wysiwygViewModel.idealHeight > wysiwygViewModel.minHeight {
if shouldFixRoundCorner {
return 14
} else {
return borderHeight / 2
}
}

private var shouldFixRoundCorner: Bool {
viewModel.viewState.shouldDisplayContext || wysiwygViewModel.idealHeight > wysiwygViewModel.minHeight
}

private var actionButtonAccessibilityIdentifier: String {
viewModel.viewState.sendMode == .edit ? "editButton" : "sendButton"
}
Expand Down Expand Up @@ -103,7 +107,7 @@ struct Composer: View {
.padding(.top, 8)
.padding(.horizontal, horizontalPadding)
}
HStack(alignment: .top, spacing: 0) {
HStack(alignment: shouldFixRoundCorner ? .top : .center, spacing: 0) {
WysiwygComposerView(
focused: $viewModel.focused,
viewModel: wysiwygViewModel
Expand Down Expand Up @@ -210,10 +214,12 @@ struct Composer: View {
HStack(alignment: .bottom, spacing: 0) {
if !viewModel.viewState.textFormattingEnabled {
sendMediaButton
.padding(.bottom, 1)
}
composerContainer
if !viewModel.viewState.textFormattingEnabled {
sendButton
.padding(.bottom, 1)
}
}
if viewModel.viewState.textFormattingEnabled {
Expand Down
1 change: 1 addition & 0 deletions changelog.d/7118.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rich Text Editor: Fixed a bug that prevented fullscreen mode to work on iOS 15.
1 change: 1 addition & 0 deletions changelog.d/7130.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rich Text Composer: Fix for fullscreen mode breaking sometimes when opening it when keyboard is not showing.
1 change: 1 addition & 0 deletions changelog.d/pr-7127.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rich Text Editor: Design Improvements.