diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json index dbee5479fb..3451f0d6cc 100644 --- a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json @@ -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" } diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143.png new file mode 100644 index 0000000000..8cc5f79e86 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143@2x.png new file mode 100644 index 0000000000..1e4244a85c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143@3x.png new file mode 100644 index 0000000000..88aaee3bac Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Frame 143@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled.png deleted file mode 100644 index f7ef2b190a..0000000000 Binary files a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@2x.png deleted file mode 100644 index 270dd75d22..0000000000 Binary files a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@3x.png deleted file mode 100644 index 9394656ff4..0000000000 Binary files a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@3x.png and /dev/null differ diff --git a/Riot/Modules/Room/RoomViewController.swift b/Riot/Modules/Room/RoomViewController.swift index c3857db81d..9204eeae9c 100644 --- a/Riot/Modules/Room/RoomViewController.swift +++ b/Riot/Modules/Room/RoomViewController.swift @@ -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 diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index cee4a9c119..5b9c7d4a71 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -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 + } } ] @@ -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 } } @@ -277,7 +274,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } ) } - + private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) } @@ -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 { @@ -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 { @@ -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( [ diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift index f5adcc75ac..eebab63f0f 100644 --- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift @@ -41,14 +41,23 @@ 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 @@ -56,6 +65,7 @@ final class ComposerCreateActionListCoordinator: NSObject, Coordinator, Presenta hostingController = hostingVC super.init() hostingVC.presentationController?.delegate = self + hostingVC.bottomSheetPreferences?.setup(viewController: hostingVC) } // MARK: - Public diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift index cb1a53b886..8089ab7ae2 100644 --- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift @@ -36,6 +36,7 @@ enum MockComposerCreateActionListScreenState: MockScreenState, CaseIterable { let viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState( actions: actions, wysiwygEnabled: true, + isScrollingEnabled: false, bindings: ComposerCreateActionListBindings(textFormattingEnabled: true))) return ( diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift index 6c42041b7c..d1194e1755 100644 --- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift @@ -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 } diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift index 35532a2124..c1e158d9bb 100644 --- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift +++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift @@ -27,6 +27,7 @@ class ComposerCreateActionListTests: XCTestCase { initialViewState: ComposerCreateActionListViewState( actions: ComposerCreateAction.allCases, wysiwygEnabled: true, + isScrollingEnabled: false, bindings: ComposerCreateActionListBindings(textFormattingEnabled: true) ) ) diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift index 5da2b1d119..706c2f1d92 100644 --- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift +++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift @@ -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()) } } @@ -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() } - } - } -} diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 87c7cbe7a7..5d8ec53203 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -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" } @@ -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 @@ -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 { diff --git a/changelog.d/7118.bugfix b/changelog.d/7118.bugfix new file mode 100644 index 0000000000..af402294f7 --- /dev/null +++ b/changelog.d/7118.bugfix @@ -0,0 +1 @@ +Rich Text Editor: Fixed a bug that prevented fullscreen mode to work on iOS 15. \ No newline at end of file diff --git a/changelog.d/7130.bugfix b/changelog.d/7130.bugfix new file mode 100644 index 0000000000..a52b68f4f4 --- /dev/null +++ b/changelog.d/7130.bugfix @@ -0,0 +1 @@ +Rich Text Composer: Fix for fullscreen mode breaking sometimes when opening it when keyboard is not showing. \ No newline at end of file diff --git a/changelog.d/pr-7127.change b/changelog.d/pr-7127.change new file mode 100644 index 0000000000..0ab8abeced --- /dev/null +++ b/changelog.d/pr-7127.change @@ -0,0 +1 @@ +Rich Text Editor: Design Improvements. \ No newline at end of file