mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Media upload tweaks (#3643)
* Focus the media caption composer if a hardware keyboard is available Not worrying about live connections, this screen is short lived. * Fix a bug where you could hit the send button multiple times while waiting for the media to be converted. * Begin processing media as soon as the media upload screen is shown.
This commit is contained in:
+4
@@ -52,6 +52,10 @@ final class MediaUploadPreviewScreenCoordinator: CoordinatorProtocol {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
viewModel.stopProcessing()
|
||||||
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(MediaUploadPreviewScreen(context: viewModel.context))
|
AnyView(MediaUploadPreviewScreen(context: viewModel.context))
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-14
@@ -16,11 +16,9 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
private let roomProxy: JoinedRoomProxyProtocol
|
private let roomProxy: JoinedRoomProxyProtocol
|
||||||
private let mediaUploadingPreprocessor: MediaUploadingPreprocessor
|
private let mediaUploadingPreprocessor: MediaUploadingPreprocessor
|
||||||
private let url: URL
|
private let url: URL
|
||||||
private var requestHandle: SendAttachmentJoinHandleProtocol? {
|
|
||||||
didSet {
|
private var processingTask: Task<Result<MediaInfo, MediaUploadingPreprocessorError>, Never>
|
||||||
state.shouldDisableInteraction = requestHandle != nil
|
private var requestHandle: SendAttachmentJoinHandleProtocol?
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var actionsSubject: PassthroughSubject<MediaUploadPreviewScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<MediaUploadPreviewScreenViewModelAction, Never> = .init()
|
||||||
|
|
||||||
@@ -39,6 +37,9 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
self.mediaUploadingPreprocessor = mediaUploadingPreprocessor
|
self.mediaUploadingPreprocessor = mediaUploadingPreprocessor
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
||||||
|
// Start processing the media whilst the user is reviewing it/adding a caption.
|
||||||
|
processingTask = Task { await mediaUploadingPreprocessor.processMedia(at: url) }
|
||||||
|
|
||||||
super.init(initialViewState: MediaUploadPreviewScreenViewState(url: url, title: title, shouldShowCaptionWarning: shouldShowCaptionWarning))
|
super.init(initialViewState: MediaUploadPreviewScreenViewState(url: url, title: title, shouldShowCaptionWarning: shouldShowCaptionWarning))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,10 +49,10 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
|
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .send:
|
case .send:
|
||||||
|
startLoading()
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
startLoading()
|
switch await processingTask.value {
|
||||||
|
|
||||||
switch await mediaUploadingPreprocessor.processMedia(at: url) {
|
|
||||||
case .success(let mediaInfo):
|
case .success(let mediaInfo):
|
||||||
switch await sendAttachment(mediaInfo: mediaInfo, caption: caption) {
|
switch await sendAttachment(mediaInfo: mediaInfo, caption: caption) {
|
||||||
case .success:
|
case .success:
|
||||||
@@ -75,6 +76,10 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stopProcessing() {
|
||||||
|
processingTask.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func sendAttachment(mediaInfo: MediaInfo, caption: String?) async -> Result<Void, TimelineProxyError> {
|
private func sendAttachment(mediaInfo: MediaInfo, caption: String?) async -> Result<Void, TimelineProxyError> {
|
||||||
@@ -111,16 +116,17 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
private static let loadingIndicatorIdentifier = "\(MediaUploadPreviewScreenViewModel.self)-Loading"
|
private static let loadingIndicatorIdentifier = "\(MediaUploadPreviewScreenViewModel.self)-Loading"
|
||||||
|
|
||||||
private func startLoading() {
|
private func startLoading() {
|
||||||
userIndicatorController.submitIndicator(
|
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||||
UserIndicator(id: Self.loadingIndicatorIdentifier,
|
type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true),
|
||||||
type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true),
|
title: L10n.commonSending,
|
||||||
title: L10n.commonSending,
|
persistent: true))
|
||||||
persistent: true)
|
|
||||||
)
|
state.shouldDisableInteraction = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopLoading() {
|
private func stopLoading() {
|
||||||
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||||
|
state.shouldDisableInteraction = false
|
||||||
requestHandle = nil
|
requestHandle = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -11,4 +11,7 @@ import Combine
|
|||||||
protocol MediaUploadPreviewScreenViewModelProtocol {
|
protocol MediaUploadPreviewScreenViewModelProtocol {
|
||||||
var actions: AnyPublisher<MediaUploadPreviewScreenViewModelAction, Never> { get }
|
var actions: AnyPublisher<MediaUploadPreviewScreenViewModelAction, Never> { get }
|
||||||
var context: MediaUploadPreviewScreenViewModelType.Context { get }
|
var context: MediaUploadPreviewScreenViewModelType.Context { get }
|
||||||
|
|
||||||
|
/// Stops any ongoing media processing tasks.
|
||||||
|
func stopProcessing()
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-2
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Compound
|
import Compound
|
||||||
|
import GameController
|
||||||
import QuickLook
|
import QuickLook
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
@ObservedObject var context: MediaUploadPreviewScreenViewModel.Context
|
@ObservedObject var context: MediaUploadPreviewScreenViewModel.Context
|
||||||
|
|
||||||
@State private var captionWarningFrame: CGRect = .zero
|
@State private var captionWarningFrame: CGRect = .zero
|
||||||
|
@FocusState private var isComposerFocussed
|
||||||
|
|
||||||
private var title: String { ProcessInfo.processInfo.isiOSAppOnMac ? context.viewState.title ?? "" : "" }
|
private var title: String { ProcessInfo.processInfo.isiOSAppOnMac ? context.viewState.title ?? "" : "" }
|
||||||
private var colorSchemeOverride: ColorScheme { ProcessInfo.processInfo.isiOSAppOnMac ? colorScheme : .dark }
|
private var colorSchemeOverride: ColorScheme { ProcessInfo.processInfo.isiOSAppOnMac ? colorScheme : .dark }
|
||||||
@@ -36,6 +38,7 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
.interactiveDismissDisabled()
|
.interactiveDismissDisabled()
|
||||||
.presentationBackground(.background) // Fix a bug introduced by the caption warning.
|
.presentationBackground(.background) // Fix a bug introduced by the caption warning.
|
||||||
.preferredColorScheme(colorSchemeOverride)
|
.preferredColorScheme(colorSchemeOverride)
|
||||||
|
.onAppear(perform: focusComposerIfHardwareKeyboardConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -58,8 +61,8 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
text: $context.caption,
|
text: $context.caption,
|
||||||
presendCallback: $context.presendCallback,
|
presendCallback: $context.presendCallback,
|
||||||
maxHeight: ComposerConstant.maxHeight,
|
maxHeight: ComposerConstant.maxHeight,
|
||||||
keyHandler: { _ in },
|
keyHandler: handleKeyPress) { _ in }
|
||||||
pasteHandler: { _ in })
|
.focused($isComposerFocussed)
|
||||||
|
|
||||||
if context.viewState.shouldShowCaptionWarning {
|
if context.viewState.shouldShowCaptionWarning {
|
||||||
captionWarningButton
|
captionWarningButton
|
||||||
@@ -125,6 +128,27 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
.tint(.compound.textActionPrimary)
|
.tint(.compound.textActionPrimary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleKeyPress(_ key: UIKeyboardHIDUsage) {
|
||||||
|
switch key {
|
||||||
|
case .keyboardReturnOrEnter:
|
||||||
|
context.send(viewAction: .send)
|
||||||
|
case .keyboardEscape:
|
||||||
|
context.send(viewAction: .cancel)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func focusComposerIfHardwareKeyboardConnected() {
|
||||||
|
// The simulator always detects the hardware keyboard as connected
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
if GCKeyboard.coalesced != nil {
|
||||||
|
MXLog.info("Hardware keyboard is connected")
|
||||||
|
isComposerFocussed = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct PreviewView: UIViewControllerRepresentable {
|
private struct PreviewView: UIViewControllerRepresentable {
|
||||||
|
|||||||
@@ -138,8 +138,13 @@ class MediaUploadPreviewScreenViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func send() async throws {
|
private func send() async throws {
|
||||||
|
XCTAssertFalse(context.viewState.shouldDisableInteraction, "Attempting to send when interaction is disabled.")
|
||||||
|
|
||||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .dismiss }
|
let deferred = deferFulfillment(viewModel.actions) { $0 == .dismiss }
|
||||||
context.send(viewAction: .send)
|
context.send(viewAction: .send)
|
||||||
|
|
||||||
|
XCTAssertTrue(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.")
|
||||||
|
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user