mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Revert "Support sending gallery messages behind a feature flag"
This reverts commit 6a2ed331fa.
This commit is contained in:
@@ -7610,16 +7610,16 @@
|
|||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
C0C687DE1D270F9895FEE186 /* AccessibilityTests */,
|
|
||||||
C0FAEB81CFD9776CD78CE489 /* ElementX */,
|
C0FAEB81CFD9776CD78CE489 /* ElementX */,
|
||||||
D3DB351B7FBE0F49649171FC /* IntegrationTests */,
|
|
||||||
676A87694F0F247B2AF1A142 /* MatrixRustSDKMocks */,
|
|
||||||
FEB53A5BC378C913769656D8 /* NSE */,
|
FEB53A5BC378C913769656D8 /* NSE */,
|
||||||
F8E276FD6DC43EADB85241BC /* Periphery */,
|
|
||||||
7A17BE29BAC81ADBAC6349D9 /* PreviewTests */,
|
|
||||||
19F0C845D67E9BEA4BE7133E /* ShareExtension */,
|
19F0C845D67E9BEA4BE7133E /* ShareExtension */,
|
||||||
0E28CD62691FDBC63147D5E3 /* UITests */,
|
676A87694F0F247B2AF1A142 /* MatrixRustSDKMocks */,
|
||||||
32C23C8D224D46EFE62AFAD0 /* UnitTests */,
|
32C23C8D224D46EFE62AFAD0 /* UnitTests */,
|
||||||
|
7A17BE29BAC81ADBAC6349D9 /* PreviewTests */,
|
||||||
|
0E28CD62691FDBC63147D5E3 /* UITests */,
|
||||||
|
D3DB351B7FBE0F49649171FC /* IntegrationTests */,
|
||||||
|
C0C687DE1D270F9895FEE186 /* AccessibilityTests */,
|
||||||
|
F8E276FD6DC43EADB85241BC /* Periphery */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|||||||
@@ -416,11 +416,6 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
|||||||
@UserPreference(defaultValue: false)
|
@UserPreference(defaultValue: false)
|
||||||
var linkPreviewsEnabled: Bool
|
var linkPreviewsEnabled: Bool
|
||||||
|
|
||||||
/// Enables *sending* gallery messages (multiple media in a single message). Received galleries
|
|
||||||
/// are always rendered regardless of this flag, matching Element X Android.
|
|
||||||
@UserPreference(defaultValue: false)
|
|
||||||
var galleryEnabled: Bool
|
|
||||||
|
|
||||||
@UserPreference(defaultValue: false)
|
@UserPreference(defaultValue: false)
|
||||||
var jumpToReadMarkerEnabled: Bool
|
var jumpToReadMarkerEnabled: Bool
|
||||||
|
|
||||||
|
|||||||
@@ -1123,8 +1123,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: flowParameters.appSettings),
|
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: flowParameters.appSettings),
|
||||||
timelineController: timelineController,
|
timelineController: timelineController,
|
||||||
clientProxy: userSession.clientProxy,
|
clientProxy: userSession.clientProxy,
|
||||||
userIndicatorController: flowParameters.userIndicatorController,
|
userIndicatorController: flowParameters.userIndicatorController)
|
||||||
galleryEnabled: flowParameters.appSettings.galleryEnabled)
|
|
||||||
|
|
||||||
let mediaUploadPreviewScreenCoordinator = MediaUploadPreviewScreenCoordinator(parameters: parameters)
|
let mediaUploadPreviewScreenCoordinator = MediaUploadPreviewScreenCoordinator(parameters: parameters)
|
||||||
|
|
||||||
|
|||||||
@@ -12267,48 +12267,6 @@ nonisolated class TimelineControllerMock: TimelineControllerProtocol, @unchecked
|
|||||||
return sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue
|
return sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - sendGallery
|
|
||||||
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount = 0
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCount: Int {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount = newValue } }
|
|
||||||
}
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCalled: Bool {
|
|
||||||
return sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCount > 0
|
|
||||||
}
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArgumentsLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedArguments: (itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)?
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArguments: (itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)? {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArgumentsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedArguments } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArgumentsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedArguments = newValue } }
|
|
||||||
}
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations: [(itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)] = []
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocations: [(itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)] {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations = newValue } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValueLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReturnValue: Result<Void, TimelineControllerError>!
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValue: Result<Void, TimelineControllerError>! {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValueLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReturnValue } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValueLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReturnValue = newValue } }
|
|
||||||
}
|
|
||||||
nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure: (([GalleryItemInfo], String?, String?, String?) async -> Result<Void, TimelineControllerError>)?
|
|
||||||
|
|
||||||
@concurrent func sendGallery(itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?) async -> Result<Void, TimelineControllerError> {
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount += 1 }
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArguments = (itemInfos: itemInfos, caption: caption, formattedCaption: formattedCaption, inReplyToEventID: inReplyToEventID)
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations.append((itemInfos: itemInfos, caption: caption, formattedCaption: formattedCaption, inReplyToEventID: inReplyToEventID)) }
|
|
||||||
if let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure = sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure {
|
|
||||||
return await sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure(itemInfos, caption, formattedCaption, inReplyToEventID)
|
|
||||||
} else {
|
|
||||||
return sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - createPoll
|
//MARK: - createPoll
|
||||||
|
|
||||||
private let createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
|
private let createPollQuestionAnswersMaxSelectionsPollKindCallsCountLock = NSLock()
|
||||||
@@ -13064,48 +13022,6 @@ nonisolated class TimelineProxyMock: TimelineProxyProtocol, @unchecked Sendable
|
|||||||
return sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue
|
return sendVoiceMessageUrlAudioInfoWaveformRequestHandleReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//MARK: - sendGallery
|
|
||||||
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount = 0
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCount: Int {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount = newValue } }
|
|
||||||
}
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCalled: Bool {
|
|
||||||
return sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCount > 0
|
|
||||||
}
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArgumentsLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedArguments: (itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)?
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArguments: (itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)? {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArgumentsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedArguments } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArgumentsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedArguments = newValue } }
|
|
||||||
}
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations: [(itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)] = []
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocations: [(itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?)] {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations = newValue } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValueLock = NSLock()
|
|
||||||
private nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
|
||||||
var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValue: Result<Void, TimelineProxyError>! {
|
|
||||||
get { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValueLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReturnValue } }
|
|
||||||
set { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValueLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReturnValue = newValue } }
|
|
||||||
}
|
|
||||||
nonisolated(unsafe) var sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure: (([GalleryItemInfo], String?, String?, String?) async -> Result<Void, TimelineProxyError>)?
|
|
||||||
|
|
||||||
@concurrent func sendGallery(itemInfos: [GalleryItemInfo], caption: String?, formattedCaption: String?, inReplyToEventID: String?) async -> Result<Void, TimelineProxyError> {
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCountLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingCallsCount += 1 }
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArguments = (itemInfos: itemInfos, caption: caption, formattedCaption: formattedCaption, inReplyToEventID: inReplyToEventID)
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedInvocationsLock.withLock { sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDUnderlyingReceivedInvocations.append((itemInfos: itemInfos, caption: caption, formattedCaption: formattedCaption, inReplyToEventID: inReplyToEventID)) }
|
|
||||||
if let sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure = sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure {
|
|
||||||
return await sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure(itemInfos, caption, formattedCaption, inReplyToEventID)
|
|
||||||
} else {
|
|
||||||
return sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//MARK: - sendReadReceipt
|
//MARK: - sendReadReceipt
|
||||||
|
|
||||||
private let sendReadReceiptForTypeCallsCountLock = NSLock()
|
private let sendReadReceiptForTypeCallsCountLock = NSLock()
|
||||||
|
|||||||
@@ -162,17 +162,6 @@ struct TimelineControllerMockConfiguration {
|
|||||||
return .success(())
|
return .success(())
|
||||||
}
|
}
|
||||||
|
|
||||||
sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure = { [weak self, timelineProxy] itemInfos, caption, formattedCaption, inReplyToEventID in
|
|
||||||
self?.callbacks.send(.messageSentOrEdited)
|
|
||||||
if let timelineProxy {
|
|
||||||
return await timelineProxy.sendGallery(itemInfos: itemInfos,
|
|
||||||
caption: caption,
|
|
||||||
formattedCaption: formattedCaption,
|
|
||||||
inReplyToEventID: inReplyToEventID).mapError(TimelineControllerError.timelineProxyError)
|
|
||||||
}
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
|
|
||||||
sendVoiceMessageUrlAudioInfoWaveformRequestHandleClosure = { [weak self, timelineProxy] url, audioInfo, waveform, requestHandle in
|
sendVoiceMessageUrlAudioInfoWaveformRequestHandleClosure = { [weak self, timelineProxy] url, audioInfo, waveform, requestHandle in
|
||||||
self?.callbacks.send(.messageSentOrEdited)
|
self?.callbacks.send(.messageSentOrEdited)
|
||||||
if let timelineProxy {
|
if let timelineProxy {
|
||||||
|
|||||||
+1
-5
@@ -18,9 +18,6 @@ struct MediaUploadPreviewScreenCoordinatorParameters {
|
|||||||
let timelineController: TimelineControllerProtocol
|
let timelineController: TimelineControllerProtocol
|
||||||
let clientProxy: ClientProxyProtocol
|
let clientProxy: ClientProxyProtocol
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
/// Whether sending as a gallery is enabled. When `false`, multiple attachments are sent as
|
|
||||||
/// individual messages (the previous behaviour).
|
|
||||||
let galleryEnabled: Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MediaUploadPreviewScreenCoordinatorAction {
|
enum MediaUploadPreviewScreenCoordinatorAction {
|
||||||
@@ -44,8 +41,7 @@ final class MediaUploadPreviewScreenCoordinator: CoordinatorProtocol {
|
|||||||
mediaUploadingPreprocessor: parameters.mediaUploadingPreprocessor,
|
mediaUploadingPreprocessor: parameters.mediaUploadingPreprocessor,
|
||||||
timelineController: parameters.timelineController,
|
timelineController: parameters.timelineController,
|
||||||
clientProxy: parameters.clientProxy,
|
clientProxy: parameters.clientProxy,
|
||||||
userIndicatorController: parameters.userIndicatorController,
|
userIndicatorController: parameters.userIndicatorController)
|
||||||
galleryEnabled: parameters.galleryEnabled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
|
|||||||
+6
-54
@@ -22,7 +22,6 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
private var processingTask: Task<Result<[MediaInfo], MediaUploadingPreprocessorError>, Never>
|
private var processingTask: Task<Result<[MediaInfo], MediaUploadingPreprocessorError>, Never>
|
||||||
private var requestHandle: SendAttachmentJoinHandleProtocol?
|
private var requestHandle: SendAttachmentJoinHandleProtocol?
|
||||||
private let clientProxy: ClientProxyProtocol
|
private let clientProxy: ClientProxyProtocol
|
||||||
private let galleryEnabled: Bool
|
|
||||||
|
|
||||||
private var actionsSubject: PassthroughSubject<MediaUploadPreviewScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<MediaUploadPreviewScreenViewModelAction, Never> = .init()
|
||||||
|
|
||||||
@@ -37,14 +36,12 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor,
|
mediaUploadingPreprocessor: MediaUploadingPreprocessor,
|
||||||
timelineController: TimelineControllerProtocol,
|
timelineController: TimelineControllerProtocol,
|
||||||
clientProxy: ClientProxyProtocol,
|
clientProxy: ClientProxyProtocol,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||||
galleryEnabled: Bool) {
|
|
||||||
self.mediaURLs = mediaURLs
|
self.mediaURLs = mediaURLs
|
||||||
self.mediaUploadingPreprocessor = mediaUploadingPreprocessor
|
self.mediaUploadingPreprocessor = mediaUploadingPreprocessor
|
||||||
self.timelineController = timelineController
|
self.timelineController = timelineController
|
||||||
self.clientProxy = clientProxy
|
self.clientProxy = clientProxy
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.galleryEnabled = galleryEnabled
|
|
||||||
|
|
||||||
// Start processing the media whilst the user is reviewing it/adding a caption.
|
// Start processing the media whilst the user is reviewing it/adding a caption.
|
||||||
processingTask = Self.processMedia(at: mediaURLs, preprocessor: mediaUploadingPreprocessor, clientProxy: clientProxy)
|
processingTask = Self.processMedia(at: mediaURLs, preprocessor: mediaUploadingPreprocessor, clientProxy: clientProxy)
|
||||||
@@ -57,7 +54,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
|
|
||||||
override func process(viewAction: MediaUploadPreviewScreenViewAction) {
|
override func process(viewAction: MediaUploadPreviewScreenViewAction) {
|
||||||
// Get the current caption before all the processing starts.
|
// Get the current caption before all the processing starts.
|
||||||
let caption = state.bindings.caption.nonBlankString
|
var caption = state.bindings.caption.nonBlankString
|
||||||
|
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .send:
|
case .send:
|
||||||
@@ -68,25 +65,14 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
|
|
||||||
switch await processingTask.value {
|
switch await processingTask.value {
|
||||||
case .success(let mediaInfos):
|
case .success(let mediaInfos):
|
||||||
if galleryEnabled, mediaInfos.count > 1 {
|
for mediaInfo in mediaInfos {
|
||||||
switch await sendGallery(mediaInfos: mediaInfos, caption: caption) {
|
switch await sendAttachment(mediaInfo: mediaInfo, caption: caption) {
|
||||||
case .success:
|
case .success:
|
||||||
break
|
caption = nil // Set the caption only on the first uploaded file.
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Failed sending gallery with error: \(error)")
|
MXLog.error("Failed sending media with error: \(error)")
|
||||||
showError(label: L10n.screenMediaUploadPreviewErrorFailedSending)
|
showError(label: L10n.screenMediaUploadPreviewErrorFailedSending)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
var perItemCaption = caption
|
|
||||||
for mediaInfo in mediaInfos {
|
|
||||||
switch await sendAttachment(mediaInfo: mediaInfo, caption: perItemCaption) {
|
|
||||||
case .success:
|
|
||||||
perItemCaption = nil // Set the caption only on the first uploaded file.
|
|
||||||
case .failure(let error):
|
|
||||||
MXLog.error("Failed sending media with error: \(error)")
|
|
||||||
showError(label: L10n.screenMediaUploadPreviewErrorFailedSending)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
@@ -121,40 +107,6 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendGallery(mediaInfos: [MediaInfo], caption: String?) async -> Result<Void, TimelineControllerError> {
|
|
||||||
let itemInfos: [GalleryItemInfo] = mediaInfos.map { mediaInfo in
|
|
||||||
switch mediaInfo {
|
|
||||||
case let .image(imageURL, thumbnailURL, imageInfo):
|
|
||||||
return .image(imageInfo: imageInfo,
|
|
||||||
source: .file(filename: imageURL.path(percentEncoded: false)),
|
|
||||||
caption: nil,
|
|
||||||
formattedCaption: nil,
|
|
||||||
thumbnailSource: .file(filename: thumbnailURL.path(percentEncoded: false)))
|
|
||||||
case let .video(videoURL, thumbnailURL, videoInfo):
|
|
||||||
return .video(videoInfo: videoInfo,
|
|
||||||
source: .file(filename: videoURL.path(percentEncoded: false)),
|
|
||||||
caption: nil,
|
|
||||||
formattedCaption: nil,
|
|
||||||
thumbnailSource: .file(filename: thumbnailURL.path(percentEncoded: false)))
|
|
||||||
case let .audio(audioURL, audioInfo):
|
|
||||||
return .audio(audioInfo: audioInfo,
|
|
||||||
source: .file(filename: audioURL.path(percentEncoded: false)),
|
|
||||||
caption: nil,
|
|
||||||
formattedCaption: nil)
|
|
||||||
case let .file(fileURL, fileInfo):
|
|
||||||
return .file(fileInfo: fileInfo,
|
|
||||||
source: .file(filename: fileURL.path(percentEncoded: false)),
|
|
||||||
caption: nil,
|
|
||||||
formattedCaption: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await timelineController.sendGallery(itemInfos: itemInfos,
|
|
||||||
caption: caption,
|
|
||||||
formattedCaption: nil,
|
|
||||||
inReplyToEventID: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopProcessing() {
|
func stopProcessing() {
|
||||||
processingTask.cancel()
|
processingTask.cancel()
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-148
@@ -6,7 +6,6 @@
|
|||||||
// Please see LICENSE files in the repository root for full details.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AVFoundation
|
|
||||||
import Combine
|
import Combine
|
||||||
import Compound
|
import Compound
|
||||||
import GameController
|
import GameController
|
||||||
@@ -36,7 +35,17 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
mainContent
|
mainContent
|
||||||
.id(context.viewState.mediaURLs)
|
.id(context.viewState.mediaURLs)
|
||||||
.ignoresSafeArea(edges: [.horizontal])
|
.ignoresSafeArea(edges: [.horizontal])
|
||||||
.overlay(alignment: .top) { galleryBadge }
|
.safeAreaInset(edge: .top) {
|
||||||
|
if context.viewState.mediaURLs.count > 1 {
|
||||||
|
Text(L10n.screenMediaUploadPreviewItemCount(currentIndex + 1, context.viewState.mediaURLs.count))
|
||||||
|
.font(.compound.bodyMD)
|
||||||
|
.foregroundColor(.compound.textPrimary)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.background(.compound.bgBadgeDefault)
|
||||||
|
.clipShape(.capsule)
|
||||||
|
}
|
||||||
|
}
|
||||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||||
composer
|
composer
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 12)
|
||||||
@@ -65,20 +74,6 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var galleryBadge: some View {
|
|
||||||
if context.viewState.mediaURLs.count > 1 {
|
|
||||||
Text(L10n.screenMediaUploadPreviewItemCount(currentIndex + 1, context.viewState.mediaURLs.count))
|
|
||||||
.font(.compound.bodySMSemibold)
|
|
||||||
.foregroundStyle(.compound.textPrimary)
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.padding(.vertical, 6)
|
|
||||||
.background(Color.compound.bgCanvasDefault.opacity(0.85),
|
|
||||||
in: Capsule())
|
|
||||||
.padding(.top, 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
if ProcessInfo.processInfo.isiOSAppOnMac {
|
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
@@ -86,9 +81,6 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
.font(.compound.headingMD)
|
.font(.compound.headingMD)
|
||||||
.foregroundColor(.compound.textSecondary)
|
.foregroundColor(.compound.textSecondary)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
} else if context.viewState.mediaURLs.count > 1 {
|
|
||||||
UploadMediaPeekCarousel(mediaURLs: context.viewState.mediaURLs,
|
|
||||||
currentIndex: $currentIndex)
|
|
||||||
} else {
|
} else {
|
||||||
PreviewView(mediaURLs: context.viewState.mediaURLs,
|
PreviewView(mediaURLs: context.viewState.mediaURLs,
|
||||||
title: context.viewState.title,
|
title: context.viewState.title,
|
||||||
@@ -222,133 +214,6 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct UploadMediaPeekCarousel: View {
|
|
||||||
let mediaURLs: [URL]
|
|
||||||
@Binding var currentIndex: Int
|
|
||||||
|
|
||||||
@State private var scrolledID: Int?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
LazyHStack(spacing: 8) {
|
|
||||||
ForEach(Array(mediaURLs.enumerated()), id: \.offset) { index, url in
|
|
||||||
UploadMediaThumbnail(url: url)
|
|
||||||
.containerRelativeFrame(.horizontal)
|
|
||||||
.id(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scrollTargetLayout()
|
|
||||||
}
|
|
||||||
.contentMargins(.horizontal, 24, for: .scrollContent)
|
|
||||||
.scrollTargetBehavior(.viewAligned)
|
|
||||||
.scrollPosition(id: $scrolledID)
|
|
||||||
.onAppear {
|
|
||||||
if scrolledID == nil {
|
|
||||||
scrolledID = currentIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: scrolledID) { _, newValue in
|
|
||||||
if let newValue, newValue != currentIndex {
|
|
||||||
currentIndex = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct UploadMediaThumbnail: View {
|
|
||||||
let url: URL
|
|
||||||
|
|
||||||
private var contentType: UTType? {
|
|
||||||
UTType(filenameExtension: url.pathExtension)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isImageOrVideo: Bool {
|
|
||||||
guard let contentType else { return false }
|
|
||||||
return contentType.conforms(to: .image) || contentType.conforms(to: .movie) || contentType.conforms(to: .audiovisualContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Group {
|
|
||||||
if isImageOrVideo {
|
|
||||||
UploadMediaImageThumbnail(url: url)
|
|
||||||
} else {
|
|
||||||
UploadMediaFilePreview(url: url, title: url.lastPathComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct UploadMediaImageThumbnail: View {
|
|
||||||
let url: URL
|
|
||||||
@State private var image: UIImage?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
Color.black
|
|
||||||
if let image {
|
|
||||||
Image(uiImage: image)
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
} else {
|
|
||||||
ProgressView().tint(.white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.task(id: url) { await load() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private func load() async {
|
|
||||||
if let image = UIImage(contentsOfFile: url.path(percentEncoded: false)) {
|
|
||||||
self.image = image
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to a video frame thumbnail.
|
|
||||||
let asset = AVURLAsset(url: url)
|
|
||||||
let generator = AVAssetImageGenerator(asset: asset)
|
|
||||||
generator.appliesPreferredTrackTransform = true
|
|
||||||
do {
|
|
||||||
let cgImage = try await generator.image(at: .zero).image
|
|
||||||
image = UIImage(cgImage: cgImage)
|
|
||||||
} catch {
|
|
||||||
image = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct UploadMediaFilePreview: UIViewControllerRepresentable {
|
|
||||||
let url: URL
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> QLPreviewController {
|
|
||||||
let controller = QLPreviewController()
|
|
||||||
controller.dataSource = context.coordinator
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: QLPreviewController, context: Context) { }
|
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
|
||||||
Coordinator(url: url, title: title)
|
|
||||||
}
|
|
||||||
|
|
||||||
final class Coordinator: NSObject, QLPreviewControllerDataSource {
|
|
||||||
private let item: PreviewItem
|
|
||||||
|
|
||||||
init(url: URL, title: String) {
|
|
||||||
item = PreviewItem(previewItemURL: url, previewItemTitle: title)
|
|
||||||
}
|
|
||||||
|
|
||||||
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
|
|
||||||
item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct PreviewView: UIViewControllerRepresentable {
|
private struct PreviewView: UIViewControllerRepresentable {
|
||||||
let mediaURLs: [URL]
|
let mediaURLs: [URL]
|
||||||
let title: String?
|
let title: String?
|
||||||
@@ -531,8 +396,7 @@ struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: .volatile()),
|
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: .volatile()),
|
||||||
timelineController: TimelineControllerMock(.init()),
|
timelineController: TimelineControllerMock(.init()),
|
||||||
clientProxy: ClientProxyMock(.init()),
|
clientProxy: ClientProxyMock(.init()),
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
galleryEnabled: true)
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ElementNavigationStack {
|
ElementNavigationStack {
|
||||||
MediaUploadPreviewScreen(context: viewModel.context)
|
MediaUploadPreviewScreen(context: viewModel.context)
|
||||||
|
|||||||
-2
@@ -67,8 +67,6 @@ protocol DeveloperOptionsProtocol: AnyObject {
|
|||||||
|
|
||||||
var linkPreviewsEnabled: Bool { get set }
|
var linkPreviewsEnabled: Bool { get set }
|
||||||
|
|
||||||
var galleryEnabled: Bool { get set }
|
|
||||||
|
|
||||||
var jumpToReadMarkerEnabled: Bool { get set }
|
var jumpToReadMarkerEnabled: Bool { get set }
|
||||||
|
|
||||||
var linkNewDeviceEnabled: Bool { get set }
|
var linkNewDeviceEnabled: Bool { get set }
|
||||||
|
|||||||
-5
@@ -97,11 +97,6 @@ struct DeveloperOptionsScreen: View {
|
|||||||
.foregroundStyle(.compound.textCriticalPrimary)
|
.foregroundStyle(.compound.textCriticalPrimary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle(isOn: $context.galleryEnabled) {
|
|
||||||
Text("Gallery messages")
|
|
||||||
Text("Allows sending multiple media in a single message. Received galleries always render regardless of this setting.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $context.jumpToReadMarkerEnabled) {
|
Toggle(isOn: $context.jumpToReadMarkerEnabled) {
|
||||||
Text("Jump to unread")
|
Text("Jump to unread")
|
||||||
Text("Adds a button to jump to the read marker, plus a presence dot on the scroll-to-bottom button when new messages arrive while scrolled away.")
|
Text("Adds a button to jump to the read marker, plus a presence dot on the scroll-to-bottom button when new messages arrive while scrolled away.")
|
||||||
|
|||||||
@@ -402,16 +402,6 @@ class TimelineController: TimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendGallery(itemInfos: [GalleryItemInfo],
|
|
||||||
caption: String?,
|
|
||||||
formattedCaption: String?,
|
|
||||||
inReplyToEventID: String?) async -> Result<Void, TimelineControllerError> {
|
|
||||||
await activeTimeline.sendGallery(itemInfos: itemInfos,
|
|
||||||
caption: caption,
|
|
||||||
formattedCaption: formattedCaption,
|
|
||||||
inReplyToEventID: inReplyToEventID).mapError(TimelineControllerError.timelineProxyError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Polls
|
// MARK: - Polls
|
||||||
|
|
||||||
func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
|
func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError> {
|
||||||
|
|||||||
@@ -134,11 +134,6 @@ protocol TimelineControllerProtocol: Sendable {
|
|||||||
waveform: [Float],
|
waveform: [Float],
|
||||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError>
|
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineControllerError>
|
||||||
|
|
||||||
func sendGallery(itemInfos: [GalleryItemInfo],
|
|
||||||
caption: String?,
|
|
||||||
formattedCaption: String?,
|
|
||||||
inReplyToEventID: String?) async -> Result<Void, TimelineControllerError>
|
|
||||||
|
|
||||||
// MARK: - Poll
|
// MARK: - Poll
|
||||||
|
|
||||||
func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError>
|
func createPoll(question: String, answers: [String], maxSelections: Int, pollKind: Poll.Kind) async -> Result<Void, TimelineControllerError>
|
||||||
|
|||||||
@@ -374,30 +374,6 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
return .success(())
|
return .success(())
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendGallery(itemInfos: [GalleryItemInfo],
|
|
||||||
caption: String?,
|
|
||||||
formattedCaption: String?,
|
|
||||||
inReplyToEventID: String?) async -> Result<Void, TimelineProxyError> {
|
|
||||||
MXLog.info("Sending gallery with \(itemInfos.count) items")
|
|
||||||
|
|
||||||
do {
|
|
||||||
let formatted = formattedCaption.map { FormattedBody(format: .html, body: $0) }
|
|
||||||
let params = GalleryUploadParameters(caption: caption,
|
|
||||||
formattedCaption: formatted,
|
|
||||||
mentions: nil,
|
|
||||||
inReplyTo: inReplyToEventID)
|
|
||||||
|
|
||||||
let handle = try timeline.sendGallery(params: params, itemInfos: itemInfos)
|
|
||||||
try await handle.join()
|
|
||||||
MXLog.info("Finished sending gallery")
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed sending gallery with error: \(error)")
|
|
||||||
return .failure(.sdkError(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
return .success(())
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendVoiceMessage(url: URL,
|
func sendVoiceMessage(url: URL,
|
||||||
audioInfo: AudioInfo,
|
audioInfo: AudioInfo,
|
||||||
waveform: [Float],
|
waveform: [Float],
|
||||||
|
|||||||
@@ -110,11 +110,6 @@ protocol TimelineProxyProtocol: Sendable {
|
|||||||
waveform: [Float],
|
waveform: [Float],
|
||||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
||||||
|
|
||||||
func sendGallery(itemInfos: [GalleryItemInfo],
|
|
||||||
caption: String?,
|
|
||||||
formattedCaption: String?,
|
|
||||||
inReplyToEventID: String?) async -> Result<Void, TimelineProxyError>
|
|
||||||
|
|
||||||
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError>
|
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError>
|
||||||
func markAsRead(receiptType: ReceiptType) async -> Result<Void, TimelineProxyError>
|
func markAsRead(receiptType: ReceiptType) async -> Result<Void, TimelineProxyError>
|
||||||
|
|
||||||
|
|||||||
@@ -190,12 +190,10 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
#expect(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.")
|
#expect(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.")
|
||||||
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 1) // Loading indicator
|
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 1) // Loading indicator
|
||||||
|
|
||||||
// Then the screen should be dismissed once the gallery has been sent in a single request.
|
// Then the screen should be dismissed once all of the files have been sent.
|
||||||
try await deferredDismiss.fulfill()
|
try await deferredDismiss.fulfill()
|
||||||
#expect(timelineProxy.sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCount == 1)
|
#expect(timelineProxy.sendImageUrlThumbnailURLImageInfoCaptionRequestHandleCallsCount == 1)
|
||||||
#expect(timelineProxy.sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDReceivedArguments?.itemInfos.count == 3)
|
#expect(timelineProxy.sendFileUrlFileInfoCaptionRequestHandleCallsCount == 2)
|
||||||
#expect(timelineProxy.sendImageUrlThumbnailURLImageInfoCaptionRequestHandleCallsCount == 0)
|
|
||||||
#expect(timelineProxy.sendFileUrlFileInfoCaptionRequestHandleCallsCount == 0)
|
|
||||||
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 1, "Only a loading indicator should be shown.")
|
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 1, "Only a loading indicator should be shown.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,8 +218,8 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func multipleFilesWithSendFailure() async throws {
|
func multipleFilesWithSendFailure() async throws {
|
||||||
// Given an upload screen with multiple media files where the gallery send fails.
|
// Given an upload screen with multiple media files where one of the files will fail to send.
|
||||||
setUpViewModel(urls: [fileURL, imageURL, imageURL, fileURL], expectedCaption: nil, simulateGallerySendFailure: true)
|
setUpViewModel(urls: [fileURL, imageURL, imageURL, fileURL], expectedCaption: nil, simulateImageSendFailures: true)
|
||||||
#expect(!context.viewState.shouldDisableInteraction)
|
#expect(!context.viewState.shouldDisableInteraction)
|
||||||
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 0)
|
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 0)
|
||||||
|
|
||||||
@@ -232,10 +230,11 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
#expect(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.")
|
#expect(context.viewState.shouldDisableInteraction, "The interaction should be disabled while sending.")
|
||||||
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 1) // Loading indicator
|
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 1) // Loading indicator
|
||||||
|
|
||||||
// Then the screen should still be dismissed and an error indicator surfaced.
|
// Then the screen should be dismissed so the user can see which files made it into the timeline.
|
||||||
try await deferredDismiss.fulfill()
|
try await deferredDismiss.fulfill()
|
||||||
#expect(timelineProxy.sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDCallsCount == 1)
|
#expect(timelineProxy.sendImageUrlThumbnailURLImageInfoCaptionRequestHandleCallsCount == 2)
|
||||||
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 2, "An error indicator should be shown.")
|
#expect(timelineProxy.sendFileUrlFileInfoCaptionRequestHandleCallsCount == 2)
|
||||||
|
#expect(userIndicatorController.submitIndicatorDelayCallsCount == 3, "Error indicators for each failure should be shown.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
@@ -269,8 +268,7 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
private func setUpViewModel(urls: [URL],
|
private func setUpViewModel(urls: [URL],
|
||||||
expectedCaption: String?,
|
expectedCaption: String?,
|
||||||
maxUploadSizeResult: Result<UInt, ClientProxyError>? = nil,
|
maxUploadSizeResult: Result<UInt, ClientProxyError>? = nil,
|
||||||
simulateGallerySendFailure: Bool = false,
|
simulateImageSendFailures: Bool = false) {
|
||||||
galleryEnabled: Bool = true) {
|
|
||||||
timelineProxy = TimelineProxyMock(.init())
|
timelineProxy = TimelineProxyMock(.init())
|
||||||
timelineProxy.sendAudioUrlAudioInfoCaptionRequestHandleClosure = { [weak self] _, _, caption, _ in
|
timelineProxy.sendAudioUrlAudioInfoCaptionRequestHandleClosure = { [weak self] _, _, caption, _ in
|
||||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||||
@@ -279,15 +277,12 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||||
}
|
}
|
||||||
timelineProxy.sendImageUrlThumbnailURLImageInfoCaptionRequestHandleClosure = { [weak self] _, _, _, caption, _ in
|
timelineProxy.sendImageUrlThumbnailURLImageInfoCaptionRequestHandleClosure = { [weak self] _, _, _, caption, _ in
|
||||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
guard !simulateImageSendFailures else { return .failure(.sdkError(TestError.unknown)) }
|
||||||
|
return self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||||
}
|
}
|
||||||
timelineProxy.sendVideoUrlThumbnailURLVideoInfoCaptionRequestHandleClosure = { [weak self] _, _, _, caption, _ in
|
timelineProxy.sendVideoUrlThumbnailURLVideoInfoCaptionRequestHandleClosure = { [weak self] _, _, _, caption, _ in
|
||||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||||
}
|
}
|
||||||
timelineProxy.sendGalleryItemInfosCaptionFormattedCaptionInReplyToEventIDClosure = { [weak self] _, caption, _, _ in
|
|
||||||
guard !simulateGallerySendFailure else { return .failure(.sdkError(TestError.unknown)) }
|
|
||||||
return self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
|
||||||
}
|
|
||||||
|
|
||||||
clientProxy = ClientProxyMock(.init())
|
clientProxy = ClientProxyMock(.init())
|
||||||
if let maxUploadSizeResult {
|
if let maxUploadSizeResult {
|
||||||
@@ -301,8 +296,7 @@ final class MediaUploadPreviewScreenViewModelTests {
|
|||||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings),
|
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: appSettings),
|
||||||
timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
|
timelineController: TimelineControllerMock(.init(timelineProxy: timelineProxy)),
|
||||||
clientProxy: clientProxy,
|
clientProxy: clientProxy,
|
||||||
userIndicatorController: userIndicatorController,
|
userIndicatorController: userIndicatorController)
|
||||||
galleryEnabled: galleryEnabled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func verifyCaption(_ caption: String?, expectedCaption: String?) -> Result<Void, TimelineProxyError> {
|
private func verifyCaption(_ caption: String?, expectedCaption: String?) -> Result<Void, TimelineProxyError> {
|
||||||
|
|||||||
Reference in New Issue
Block a user