mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-04 06:58:20 +02:00
22aceb6040
* Cancel automatically a Voice Broadcast from a room after an app crash * We limit for the moment the uncompleted voice broadcast cleaning to the breadcrumbs rooms list By considering potential performance issues, we decided to limit the uncompleted VB cleaning in the recents list service: - we run the process only once when the application is resumed - we run the process only on the breadcrumbs rooms list This prevent us from checking several times in parallel the same room (because a room may be listed in several recents sections) This prevent us from checking several times the same room (each room should be checked only once after the app resume) If a room is not checked from the recents list, a sanity check is still done in RoomViewController (to clean the room when the user opens it)
326 lines
14 KiB
Swift
326 lines
14 KiB
Swift
//
|
|
// Copyright 2022 New Vector Ltd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import UIKit
|
|
import WysiwygComposer
|
|
|
|
extension RoomViewController {
|
|
// MARK: - Override
|
|
open override func mention(_ roomMember: MXRoomMember) {
|
|
guard let inputToolbar = inputToolbar else {
|
|
return
|
|
}
|
|
|
|
let newAttributedString = NSMutableAttributedString(attributedString: inputToolbar.attributedTextMessage)
|
|
|
|
if inputToolbar.attributedTextMessage.length > 0 {
|
|
if #available(iOS 15.0, *) {
|
|
newAttributedString.append(PillsFormatter.mentionPill(withRoomMember: roomMember,
|
|
isHighlighted: false,
|
|
font: inputToolbar.textDefaultFont))
|
|
} else {
|
|
newAttributedString.appendString(roomMember.displayname.count > 0 ? roomMember.displayname : roomMember.userId)
|
|
}
|
|
newAttributedString.appendString(" ")
|
|
} else if roomMember.userId == self.mainSession.myUser.userId {
|
|
newAttributedString.appendString("/me ")
|
|
} else {
|
|
if #available(iOS 15.0, *) {
|
|
newAttributedString.append(PillsFormatter.mentionPill(withRoomMember: roomMember,
|
|
isHighlighted: false,
|
|
font: inputToolbar.textDefaultFont))
|
|
} else {
|
|
newAttributedString.appendString(roomMember.displayname.count > 0 ? roomMember.displayname : roomMember.userId)
|
|
}
|
|
newAttributedString.appendString(": ")
|
|
}
|
|
|
|
inputToolbar.attributedTextMessage = newAttributedString
|
|
inputToolbar.becomeFirstResponder()
|
|
}
|
|
|
|
|
|
/// Send the formatted text message and its raw counterpat to the room
|
|
///
|
|
/// - Parameter rawTextMsg: the raw text message
|
|
/// - Parameter htmlMsg: the html text message
|
|
@objc func sendFormattedTextMessage(_ rawTextMsg: String, htmlMsg: String) {
|
|
let eventModified = self.roomDataSource.event(withEventId: customizedRoomDataSource?.selectedEventId)
|
|
self.setupRoomDataSource { roomDataSource in
|
|
guard let roomDataSource = roomDataSource as? RoomDataSource else { return }
|
|
if self.wysiwygInputToolbar?.sendMode == .reply, let eventModified = eventModified {
|
|
roomDataSource.sendReply(to: eventModified, rawText: rawTextMsg, htmlText: htmlMsg) { response in
|
|
switch response {
|
|
case .success:
|
|
break
|
|
case .failure:
|
|
MXLog.error("[RoomViewController] sendFormattedTextMessage failed while updating event", context: [
|
|
"event_id": eventModified.eventId
|
|
])
|
|
}
|
|
}
|
|
} else if self.wysiwygInputToolbar?.sendMode == .edit, let eventModified = eventModified {
|
|
roomDataSource.replaceFormattedTextMessage(
|
|
for: eventModified,
|
|
rawText: rawTextMsg,
|
|
html: htmlMsg,
|
|
success: { _ in
|
|
//
|
|
},
|
|
failure: { _ in
|
|
MXLog.error("[RoomViewController] sendFormattedTextMessage failed while updating event", context: [
|
|
"event_id": eventModified.eventId
|
|
])
|
|
})
|
|
} else if !self.send(asIRCStyleCommandIfPossible: rawTextMsg) {
|
|
roomDataSource.sendFormattedTextMessage(rawTextMsg, html: htmlMsg) { response in
|
|
switch response {
|
|
case .success:
|
|
break
|
|
case .failure:
|
|
MXLog.error("[RoomViewController] sendFormattedTextMessage failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.customizedRoomDataSource?.selectedEventId != nil {
|
|
self.cancelEventSelection()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Send given attributed text message to the room
|
|
///
|
|
/// - Parameter attributedTextMsg: the attributed text message
|
|
@objc func sendAttributedTextMessage(_ attributedTextMsg: NSAttributedString) {
|
|
let eventModified = self.roomDataSource.event(withEventId: customizedRoomDataSource?.selectedEventId)
|
|
self.setupRoomDataSource { roomDataSource in
|
|
guard let roomDataSource = roomDataSource as? RoomDataSource else { return }
|
|
|
|
if self.inputToolbar?.sendMode == .reply, let eventModified = eventModified {
|
|
roomDataSource.sendReply(to: eventModified,
|
|
withAttributedTextMessage: attributedTextMsg) { response in
|
|
switch response {
|
|
case .success:
|
|
break
|
|
case .failure:
|
|
MXLog.error("[RoomViewController] sendAttributedTextMessage failed while updating event", context: [
|
|
"event_id": eventModified.eventId
|
|
])
|
|
}
|
|
}
|
|
} else if self.inputToolbar?.sendMode == .edit, let eventModified = eventModified {
|
|
roomDataSource.replaceAttributedTextMessage(
|
|
for: eventModified,
|
|
withAttributedTextMessage: attributedTextMsg,
|
|
success: { _ in
|
|
//
|
|
},
|
|
failure: { _ in
|
|
MXLog.error("[RoomViewController] sendAttributedTextMessage failed while updating event", context: [
|
|
"event_id": eventModified.eventId
|
|
])
|
|
})
|
|
} else {
|
|
roomDataSource.sendAttributedTextMessage(attributedTextMsg) { response in
|
|
switch response {
|
|
case .success:
|
|
break
|
|
case .failure:
|
|
MXLog.error("[RoomViewController] sendAttributedTextMessage failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.customizedRoomDataSource?.selectedEventId != nil {
|
|
self.cancelEventSelection()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func togglePlainTextMode() {
|
|
RiotSettings.shared.enableWysiwygTextFormatting.toggle()
|
|
wysiwygInputToolbar?.textFormattingEnabled.toggle()
|
|
}
|
|
|
|
@objc func didChangeMaximisedState(_ isMaximised: Bool) {
|
|
guard let wysiwygInputToolbar = wysiwygInputToolbar else { return }
|
|
if isMaximised {
|
|
var view: UIView!
|
|
// iPhone
|
|
if let navView = self.navigationController?.navigationController?.view {
|
|
view = navView
|
|
// iPad
|
|
} else if let navView = self.navigationController?.view {
|
|
view = navView
|
|
} else {
|
|
return
|
|
}
|
|
var originalRect = roomInputToolbarContainer.convert(roomInputToolbarContainer.frame, to: view)
|
|
var optionalTextView: UITextView?
|
|
if wysiwygInputToolbar.isFocused {
|
|
let textView = UITextView()
|
|
optionalTextView = textView
|
|
self.view.window?.addSubview(textView)
|
|
optionalTextView?.becomeFirstResponder()
|
|
originalRect = wysiwygInputToolbar.convert(wysiwygInputToolbar.frame, to: view)
|
|
}
|
|
// 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
|
|
// Same as the system dimming background color
|
|
dimmingView.backgroundColor = .black.withAlphaComponent(ThemeService.shared().isCurrentThemeDark() ? 0.29 : 0.12)
|
|
maximisedToolbarDimmingView = dimmingView
|
|
view.addSubview(dimmingView)
|
|
dimmingView.frame = view.bounds
|
|
NSLayoutConstraint.activate(
|
|
[
|
|
dimmingView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
dimmingView.leftAnchor.constraint(equalTo: view.leftAnchor),
|
|
dimmingView.rightAnchor.constraint(equalTo: view.rightAnchor),
|
|
dimmingView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
]
|
|
)
|
|
dimmingView.addSubview(self.roomInputToolbarContainer)
|
|
roomInputToolbarContainer.frame = originalRect
|
|
roomInputToolbarContainer.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
|
|
roomInputToolbarContainer.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
|
|
roomInputToolbarContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
|
|
UIView.animate(withDuration: kResizeComposerAnimationDuration, delay: 0, options: [.curveEaseInOut]) {
|
|
view.layoutIfNeeded()
|
|
}
|
|
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPanRoomToolbarContainer(_ :)))
|
|
roomInputToolbarContainer.addGestureRecognizer(panGesture)
|
|
optionalTextView?.removeFromSuperview()
|
|
} else {
|
|
let originalRect = wysiwygInputToolbar.convert(wysiwygInputToolbar.frame, to: view)
|
|
var optionalTextView: UITextView?
|
|
if wysiwygInputToolbar.isFocused {
|
|
let textView = UITextView()
|
|
optionalTextView = textView
|
|
self.view.window?.addSubview(textView)
|
|
optionalTextView?.becomeFirstResponder()
|
|
wysiwygInputToolbar.showKeyboard()
|
|
}
|
|
self.roomInputToolbarContainer.removeFromSuperview()
|
|
maximisedToolbarDimmingView?.removeFromSuperview()
|
|
maximisedToolbarDimmingView = nil
|
|
self.view.insertSubview(self.roomInputToolbarContainer, belowSubview: self.overlayContainerView)
|
|
roomInputToolbarContainer.frame = originalRect
|
|
NSLayoutConstraint.activate(self.toolbarContainerConstraints)
|
|
self.roomInputToolbarContainerBottomConstraint.isActive = true
|
|
UIView.animate(withDuration: kResizeComposerAnimationDuration, delay: 0, options: [.curveEaseInOut]) {
|
|
self.view.layoutIfNeeded()
|
|
}
|
|
roomInputToolbarContainer.gestureRecognizers?.removeAll()
|
|
optionalTextView?.removeFromSuperview()
|
|
}
|
|
}
|
|
|
|
@objc func setMaximisedToolbarIsHiddenIfNeeded(_ isHidden: Bool) {
|
|
if wysiwygInputToolbar?.isMaximised == true {
|
|
roomInputToolbarContainer.superview?.isHidden = isHidden
|
|
}
|
|
}
|
|
|
|
@objc func didSendLinkAction(_ linkAction: LinkActionWrapper) {
|
|
let presenter = ComposerLinkActionBridgePresenter(linkAction: linkAction)
|
|
presenter.delegate = self
|
|
composerLinkActionBridgePresenter = presenter
|
|
presenter.present(from: self, animated: true)
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Helpers
|
|
private extension RoomViewController {
|
|
var inputToolbar: RoomInputToolbarView? {
|
|
return self.inputToolbarView as? RoomInputToolbarView
|
|
}
|
|
|
|
var wysiwygInputToolbar: WysiwygInputToolbarView? {
|
|
return self.inputToolbarView as? WysiwygInputToolbarView
|
|
}
|
|
|
|
@objc private func didPanRoomToolbarContainer(_ sender: UIPanGestureRecognizer) {
|
|
guard let wysiwygInputToolbar = wysiwygInputToolbar else { return }
|
|
switch sender.state {
|
|
case .began:
|
|
wysiwygTranslation = wysiwygInputToolbar.maxExpandedHeight
|
|
case .changed:
|
|
let translation = sender.translation(in: view.window)
|
|
let translatedValue = wysiwygInputToolbar.maxExpandedHeight - translation.y
|
|
wysiwygTranslation = translatedValue
|
|
guard translatedValue <= wysiwygInputToolbar.maxExpandedHeight, translatedValue >= wysiwygInputToolbar.compressedHeight else { return }
|
|
wysiwygInputToolbar.idealHeight = translatedValue
|
|
case .ended:
|
|
if wysiwygTranslation <= wysiwygInputToolbar.maxCompressedHeight {
|
|
wysiwygInputToolbar.minimise()
|
|
} else {
|
|
wysiwygTranslation = wysiwygInputToolbar.maxExpandedHeight
|
|
wysiwygInputToolbar.idealHeight = wysiwygInputToolbar.maxExpandedHeight
|
|
}
|
|
case .cancelled:
|
|
wysiwygTranslation = wysiwygInputToolbar.maxExpandedHeight
|
|
wysiwygInputToolbar.idealHeight = wysiwygInputToolbar.maxExpandedHeight
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
extension RoomViewController: ComposerLinkActionBridgePresenterDelegate {
|
|
func didRequestLinkOperation(_ linkOperation: WysiwygLinkOperation) {
|
|
dismissPresenter { [weak self] in
|
|
self?.wysiwygInputToolbar?.performLinkOperation(linkOperation)
|
|
}
|
|
}
|
|
|
|
func didDismissInteractively() {
|
|
cleanup()
|
|
}
|
|
|
|
func didCancel() {
|
|
dismissPresenter(completion: nil)
|
|
}
|
|
|
|
private func dismissPresenter(completion: (() -> Void)?) {
|
|
self.composerLinkActionBridgePresenter?.dismiss(animated: true) { [weak self] in
|
|
completion?()
|
|
self?.cleanup()
|
|
}
|
|
}
|
|
|
|
private func cleanup() {
|
|
composerLinkActionBridgePresenter = nil
|
|
}
|
|
}
|
|
|
|
// MARK: - VoiceBroadcast
|
|
extension RoomViewController {
|
|
@objc func stopUncompletedVoiceBroadcastIfNeeded() {
|
|
self.roomDataSource.room.stopUncompletedVoiceBroadcastIfNeeded()
|
|
}
|
|
}
|