mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Support adding a caption to media uploads. (#3531)
* Add a composer to the MediaUploadPreviewScreen. And send it's content to the media upload's caption. * Use the new compound SendButton (updating relative padding in the toolbar). * Update snapshots. * Add unit tests for sending a caption.
This commit is contained in:
@@ -689,6 +689,7 @@
|
||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||
914BDF61447C723F104BCE33 /* SessionDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2067FF58B4996323EB40C /* SessionDirectories.swift */; };
|
||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||
919BAE492CECA981009F6A5B /* TimelineProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 919BAE482CECA981009F6A5B /* TimelineProxyMock.swift */; };
|
||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
||||
91D1A46A733EC24C081DD353 /* SessionVerificationRequestDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */; };
|
||||
@@ -1900,6 +1901,7 @@
|
||||
90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
|
||||
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = "<group>"; };
|
||||
91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
919BAE482CECA981009F6A5B /* TimelineProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyMock.swift; sourceTree = "<group>"; };
|
||||
91C8BD78F7B9247AC57FA1A3 /* RedactedRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
91FFE1F410969ECB23FE9BB2 /* TimelineItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenu.swift; sourceTree = "<group>"; };
|
||||
@@ -3032,6 +3034,7 @@
|
||||
9EB9BA2F30EB8C33226D8FF1 /* UserSessionStoreMock.swift */,
|
||||
B23135B06B044CB811139D2F /* Generated */,
|
||||
E5E545F92D01588360A9BAC5 /* SDK */,
|
||||
919BAE482CECA981009F6A5B /* TimelineProxyMock.swift */,
|
||||
);
|
||||
path = Mocks;
|
||||
sourceTree = "<group>";
|
||||
@@ -7096,6 +7099,7 @@
|
||||
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */,
|
||||
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
|
||||
88CBF1595E39CE697928DE48 /* SFNumberedListView.swift in Sources */,
|
||||
919BAE492CECA981009F6A5B /* TimelineProxyMock.swift in Sources */,
|
||||
FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */,
|
||||
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,
|
||||
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */,
|
||||
|
||||
@@ -248,8 +248,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
|
||||
"state" : {
|
||||
"revision" : "7b0bbbae90c41f848f90ac7b4df6c4f50068256d",
|
||||
"version" : "1.17.5"
|
||||
"revision" : "42a086182681cf661f5c47c9b7dc3931de18c6d7",
|
||||
"version" : "1.17.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14449,15 +14449,15 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
//MARK: - sendAudio
|
||||
|
||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount: Int {
|
||||
var sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
return sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
returnValue = sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14465,27 +14465,27 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount > 0
|
||||
var sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleCallsCount > 0
|
||||
}
|
||||
|
||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
var sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
return sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<Void, TimelineProxyError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
returnValue = sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14493,35 +14493,35 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure: ((URL, AudioInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
var sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleClosure: ((URL, AudioInfo, String?, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
|
||||
func sendAudio(url: URL, audioInfo: AudioInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendAudioUrlAudioInfoProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure = sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure {
|
||||
return await sendAudioUrlAudioInfoProgressSubjectRequestHandleClosure(url, audioInfo, progressSubject, requestHandle)
|
||||
func sendAudio(url: URL, audioInfo: AudioInfo, caption: String?, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleClosure = sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleClosure {
|
||||
return await sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleClosure(url, audioInfo, caption, progressSubject, requestHandle)
|
||||
} else {
|
||||
return sendAudioUrlAudioInfoProgressSubjectRequestHandleReturnValue
|
||||
return sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendFile
|
||||
|
||||
var sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount: Int {
|
||||
var sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendFileUrlFileInfoCaptionProgressSubjectRequestHandleCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
return sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
returnValue = sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14529,27 +14529,27 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendFileUrlFileInfoProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount > 0
|
||||
var sendFileUrlFileInfoCaptionProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendFileUrlFileInfoCaptionProgressSubjectRequestHandleCallsCount > 0
|
||||
}
|
||||
|
||||
var sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendFileUrlFileInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
var sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendFileUrlFileInfoCaptionProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
return sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<Void, TimelineProxyError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
returnValue = sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14557,35 +14557,35 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendFileUrlFileInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendFileUrlFileInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendFileUrlFileInfoProgressSubjectRequestHandleClosure: ((URL, FileInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
var sendFileUrlFileInfoCaptionProgressSubjectRequestHandleClosure: ((URL, FileInfo, String?, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
|
||||
func sendFile(url: URL, fileInfo: FileInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendFileUrlFileInfoProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendFileUrlFileInfoProgressSubjectRequestHandleClosure = sendFileUrlFileInfoProgressSubjectRequestHandleClosure {
|
||||
return await sendFileUrlFileInfoProgressSubjectRequestHandleClosure(url, fileInfo, progressSubject, requestHandle)
|
||||
func sendFile(url: URL, fileInfo: FileInfo, caption: String?, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendFileUrlFileInfoCaptionProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendFileUrlFileInfoCaptionProgressSubjectRequestHandleClosure = sendFileUrlFileInfoCaptionProgressSubjectRequestHandleClosure {
|
||||
return await sendFileUrlFileInfoCaptionProgressSubjectRequestHandleClosure(url, fileInfo, caption, progressSubject, requestHandle)
|
||||
} else {
|
||||
return sendFileUrlFileInfoProgressSubjectRequestHandleReturnValue
|
||||
return sendFileUrlFileInfoCaptionProgressSubjectRequestHandleReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendImage
|
||||
|
||||
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount: Int {
|
||||
var sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
return sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
returnValue = sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14593,27 +14593,27 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount > 0
|
||||
var sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleCallsCount > 0
|
||||
}
|
||||
|
||||
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
var sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
return sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<Void, TimelineProxyError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
returnValue = sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14621,22 +14621,22 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure: ((URL, URL, ImageInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
var sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleClosure: ((URL, URL, ImageInfo, String?, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
|
||||
func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure = sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure {
|
||||
return await sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleClosure(url, thumbnailURL, imageInfo, progressSubject, requestHandle)
|
||||
func sendImage(url: URL, thumbnailURL: URL, imageInfo: ImageInfo, caption: String?, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleClosure = sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleClosure {
|
||||
return await sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleClosure(url, thumbnailURL, imageInfo, caption, progressSubject, requestHandle)
|
||||
} else {
|
||||
return sendImageUrlThumbnailURLImageInfoProgressSubjectRequestHandleReturnValue
|
||||
return sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendLocation
|
||||
@@ -14711,15 +14711,15 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
//MARK: - sendVideo
|
||||
|
||||
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount: Int {
|
||||
var sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||
var sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
return sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
returnValue = sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14727,27 +14727,27 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount > 0
|
||||
var sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleCalled: Bool {
|
||||
return sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleCallsCount > 0
|
||||
}
|
||||
|
||||
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
var sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleReturnValue: Result<Void, TimelineProxyError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
return sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<Void, TimelineProxyError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
returnValue = sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
@@ -14755,22 +14755,22 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure: ((URL, URL, VideoInfo, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
var sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleClosure: ((URL, URL, VideoInfo, String?, CurrentValueSubject<Double, Never>?, @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>)?
|
||||
|
||||
func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure = sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure {
|
||||
return await sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleClosure(url, thumbnailURL, videoInfo, progressSubject, requestHandle)
|
||||
func sendVideo(url: URL, thumbnailURL: URL, videoInfo: VideoInfo, caption: String?, progressSubject: CurrentValueSubject<Double, Never>?, requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleCallsCount += 1
|
||||
if let sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleClosure = sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleClosure {
|
||||
return await sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleClosure(url, thumbnailURL, videoInfo, caption, progressSubject, requestHandle)
|
||||
} else {
|
||||
return sendVideoUrlThumbnailURLVideoInfoProgressSubjectRequestHandleReturnValue
|
||||
return sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendVoiceMessage
|
||||
|
||||
@@ -49,22 +49,8 @@ extension JoinedRoomProxyMock {
|
||||
id = configuration.id
|
||||
isEncrypted = configuration.isEncrypted
|
||||
|
||||
let timeline = TimelineProxyMock()
|
||||
timeline.sendMessageEventContentReturnValue = .success(())
|
||||
timeline.paginateBackwardsRequestSizeReturnValue = .success(())
|
||||
timeline.paginateForwardsRequestSizeReturnValue = .success(())
|
||||
timeline.sendReadReceiptForTypeReturnValue = .success(())
|
||||
|
||||
if configuration.shouldUseAutoUpdatingTimeline {
|
||||
timeline.underlyingTimelineProvider = AutoUpdatingRoomTimelineProviderMock()
|
||||
} else {
|
||||
let timelineProvider = RoomTimelineProviderMock()
|
||||
timelineProvider.paginationState = .init(backward: configuration.timelineStartReached ? .timelineEndReached : .idle, forward: .timelineEndReached)
|
||||
timelineProvider.underlyingMembershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
|
||||
timeline.underlyingTimelineProvider = timelineProvider
|
||||
}
|
||||
|
||||
self.timeline = timeline
|
||||
timeline = TimelineProxyMock(.init(isAutoUpdating: configuration.shouldUseAutoUpdatingTimeline,
|
||||
timelineStartReached: configuration.timelineStartReached))
|
||||
|
||||
ownUserID = configuration.ownUserID
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
extension TimelineProxyMock {
|
||||
struct Configuration {
|
||||
var isAutoUpdating = false
|
||||
var timelineStartReached = false
|
||||
}
|
||||
|
||||
@MainActor
|
||||
convenience init(_ configuration: Configuration) {
|
||||
self.init()
|
||||
|
||||
sendMessageEventContentReturnValue = .success(())
|
||||
paginateBackwardsRequestSizeReturnValue = .success(())
|
||||
paginateForwardsRequestSizeReturnValue = .success(())
|
||||
sendReadReceiptForTypeReturnValue = .success(())
|
||||
|
||||
if configuration.isAutoUpdating {
|
||||
underlyingTimelineProvider = AutoUpdatingRoomTimelineProviderMock()
|
||||
} else {
|
||||
let timelineProvider = RoomTimelineProviderMock()
|
||||
timelineProvider.paginationState = .init(backward: configuration.timelineStartReached ? .timelineEndReached : .idle, forward: .timelineEndReached)
|
||||
timelineProvider.underlyingMembershipChangePublisher = PassthroughSubject().eraseToAnyPublisher()
|
||||
underlyingTimelineProvider = timelineProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ struct BigIcon: View {
|
||||
var style: Style = .defaultSolid
|
||||
|
||||
var body: some View {
|
||||
CompoundIcon(icon, size: .custom(32), relativeTo: .title)
|
||||
CompoundIcon(icon, size: .custom(32), relativeTo: .compound.headingLG)
|
||||
.modifier(BigIconModifier(style: style))
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ extension Image {
|
||||
resizable()
|
||||
.renderingMode(.template)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.scaledPadding(insets, relativeTo: .title)
|
||||
.scaledPadding(insets, relativeTo: .compound.headingLG)
|
||||
.modifier(BigIconModifier(style: style))
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ private struct BigIconModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.scaledFrame(size: 64, relativeTo: .title)
|
||||
.scaledFrame(size: 64, relativeTo: .compound.headingLG)
|
||||
.foregroundColor(style.foregroundColor)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
|
||||
@@ -15,6 +15,13 @@ struct MediaUploadPreviewScreenViewState: BindableState {
|
||||
let url: URL
|
||||
let title: String?
|
||||
var shouldDisableInteraction = false
|
||||
|
||||
var bindings = MediaUploadPreviewScreenBindings()
|
||||
}
|
||||
|
||||
struct MediaUploadPreviewScreenBindings: BindableState {
|
||||
var caption = NSAttributedString()
|
||||
var presendCallback: (() -> Void)?
|
||||
}
|
||||
|
||||
enum MediaUploadPreviewScreenViewAction {
|
||||
|
||||
+34
-6
@@ -42,6 +42,9 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
||||
}
|
||||
|
||||
override func process(viewAction: MediaUploadPreviewScreenViewAction) {
|
||||
// Get the current caption before all the processing starts.
|
||||
let caption = state.bindings.caption.nonBlankString
|
||||
|
||||
switch viewAction {
|
||||
case .send:
|
||||
Task {
|
||||
@@ -51,7 +54,7 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
||||
|
||||
switch await mediaUploadingPreprocessor.processMedia(at: url) {
|
||||
case .success(let mediaInfo):
|
||||
switch await sendAttachment(mediaInfo: mediaInfo, progressSubject: progressSubject) {
|
||||
switch await sendAttachment(mediaInfo: mediaInfo, caption: caption, progressSubject: progressSubject) {
|
||||
case .success:
|
||||
actionsSubject.send(.dismiss)
|
||||
case .failure(let error):
|
||||
@@ -75,20 +78,38 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func sendAttachment(mediaInfo: MediaInfo, progressSubject: CurrentValueSubject<Double, Never>?) async -> Result<Void, TimelineProxyError> {
|
||||
private func sendAttachment(mediaInfo: MediaInfo, caption: String?, progressSubject: CurrentValueSubject<Double, Never>?) async -> Result<Void, TimelineProxyError> {
|
||||
let requestHandle: ((SendAttachmentJoinHandleProtocol) -> Void) = { [weak self] handle in
|
||||
self?.requestHandle = handle
|
||||
}
|
||||
|
||||
switch mediaInfo {
|
||||
case let .image(imageURL, thumbnailURL, imageInfo):
|
||||
return await roomProxy.timeline.sendImage(url: imageURL, thumbnailURL: thumbnailURL, imageInfo: imageInfo, progressSubject: progressSubject, requestHandle: requestHandle)
|
||||
return await roomProxy.timeline.sendImage(url: imageURL,
|
||||
thumbnailURL: thumbnailURL,
|
||||
imageInfo: imageInfo,
|
||||
caption: caption,
|
||||
progressSubject: progressSubject,
|
||||
requestHandle: requestHandle)
|
||||
case let .video(videoURL, thumbnailURL, videoInfo):
|
||||
return await roomProxy.timeline.sendVideo(url: videoURL, thumbnailURL: thumbnailURL, videoInfo: videoInfo, progressSubject: progressSubject, requestHandle: requestHandle)
|
||||
return await roomProxy.timeline.sendVideo(url: videoURL,
|
||||
thumbnailURL: thumbnailURL,
|
||||
videoInfo: videoInfo,
|
||||
caption: caption,
|
||||
progressSubject: progressSubject,
|
||||
requestHandle: requestHandle)
|
||||
case let .audio(audioURL, audioInfo):
|
||||
return await roomProxy.timeline.sendAudio(url: audioURL, audioInfo: audioInfo, progressSubject: progressSubject, requestHandle: requestHandle)
|
||||
return await roomProxy.timeline.sendAudio(url: audioURL,
|
||||
audioInfo: audioInfo,
|
||||
caption: caption,
|
||||
progressSubject: progressSubject,
|
||||
requestHandle: requestHandle)
|
||||
case let .file(fileURL, fileInfo):
|
||||
return await roomProxy.timeline.sendFile(url: fileURL, fileInfo: fileInfo, progressSubject: progressSubject, requestHandle: requestHandle)
|
||||
return await roomProxy.timeline.sendFile(url: fileURL,
|
||||
fileInfo: fileInfo,
|
||||
caption: caption,
|
||||
progressSubject: progressSubject,
|
||||
requestHandle: requestHandle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,3 +139,10 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: label))
|
||||
}
|
||||
}
|
||||
|
||||
extension NSAttributedString {
|
||||
var nonBlankString: String? {
|
||||
guard !string.isBlank else { return nil }
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
+48
-24
@@ -5,6 +5,7 @@
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import QuickLook
|
||||
import SwiftUI
|
||||
|
||||
@@ -17,13 +18,20 @@ struct MediaUploadPreviewScreen: View {
|
||||
|
||||
var body: some View {
|
||||
mainContent
|
||||
.id(UUID())
|
||||
.id(context.viewState.url)
|
||||
.ignoresSafeArea(edges: [.horizontal])
|
||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
composer
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 16)
|
||||
.background() // Don't use compound so we match the QLPreviewController.
|
||||
}
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.disabled(context.viewState.shouldDisableInteraction)
|
||||
.ignoresSafeArea(edges: [.horizontal, .bottom])
|
||||
.toolbar { toolbar }
|
||||
.disabled(context.viewState.shouldDisableInteraction)
|
||||
.interactiveDismissDisabled()
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -38,18 +46,31 @@ struct MediaUploadPreviewScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var composer: some View {
|
||||
HStack(spacing: 12) {
|
||||
MessageComposerTextField(placeholder: L10n.richTextEditorComposerCaptionPlaceholder,
|
||||
text: $context.caption,
|
||||
presendCallback: $context.presendCallback,
|
||||
maxHeight: ComposerConstant.maxHeight,
|
||||
keyHandler: { _ in },
|
||||
pasteHandler: { _ in })
|
||||
.messageComposerStyle()
|
||||
|
||||
SendButton {
|
||||
context.send(viewAction: .send)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button { context.send(viewAction: .cancel) } label: {
|
||||
Text(L10n.actionCancel)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button { context.send(viewAction: .send) } label: {
|
||||
Text(L10n.actionSend)
|
||||
}
|
||||
.disabled(context.viewState.shouldDisableInteraction)
|
||||
// Fix a bug with the preferredColorScheme on iOS 18 where the button doesn't
|
||||
// follow the dark colour scheme on devices running with dark mode disabled.
|
||||
.tint(.compound.textActionPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,21 +132,6 @@ private class PreviewItem: NSObject, QLPreviewItem {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = MediaUploadPreviewScreenViewModel(userIndicatorController: UserIndicatorControllerMock.default,
|
||||
roomProxy: JoinedRoomProxyMock(),
|
||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings),
|
||||
title: "some random file name",
|
||||
url: URL.picturesDirectory)
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
MediaUploadPreviewScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewViewController: QLPreviewController {
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
@@ -137,3 +143,21 @@ private class PreviewViewController: QLPreviewController {
|
||||
toolbarItems?.first?.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let snapshotURL = URL.picturesDirectory
|
||||
static let testURL = Bundle.main.url(forResource: "AppIcon60x60@2x", withExtension: "png")
|
||||
|
||||
static let viewModel = MediaUploadPreviewScreenViewModel(userIndicatorController: UserIndicatorControllerMock.default,
|
||||
roomProxy: JoinedRoomProxyMock(),
|
||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings),
|
||||
title: "App Icon.png",
|
||||
url: snapshotURL)
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
MediaUploadPreviewScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ struct ComposerToolbar: View {
|
||||
if !context.composerFormattingEnabled {
|
||||
if context.viewState.isUploading {
|
||||
ProgressView()
|
||||
.scaledFrame(size: 44, relativeTo: .title)
|
||||
.scaledFrame(size: 44, relativeTo: .compound.headingLG)
|
||||
.padding(.leading, 3)
|
||||
} else if context.viewState.showSendButton {
|
||||
sendButton
|
||||
@@ -119,27 +119,29 @@ struct ComposerToolbar: View {
|
||||
Image(Asset.Images.closeRte.name)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.scaledFrame(size: 30, relativeTo: .title)
|
||||
.scaledPadding(7, relativeTo: .title)
|
||||
.scaledFrame(size: 30, relativeTo: .compound.headingLG)
|
||||
.scaledPadding(7, relativeTo: .compound.headingLG)
|
||||
}
|
||||
.accessibilityLabel(L10n.actionClose)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.composerToolbar.closeFormattingOptions)
|
||||
}
|
||||
|
||||
private var sendButton: some View {
|
||||
Button {
|
||||
sendMessage()
|
||||
} label: {
|
||||
CompoundIcon(context.viewState.composerMode.isEdit ? \.check : \.sendSolid)
|
||||
.scaledPadding(6, relativeTo: .title)
|
||||
.accessibilityLabel(context.viewState.composerMode.isEdit ? L10n.actionConfirm : L10n.actionSend)
|
||||
.foregroundColor(context.viewState.sendButtonDisabled ? .compound.iconDisabled : .white)
|
||||
.background {
|
||||
Circle()
|
||||
.foregroundColor(context.viewState.sendButtonDisabled ? .clear : .compound.iconAccentTertiary)
|
||||
Group {
|
||||
if context.viewState.composerMode.isEdit {
|
||||
Button(action: sendMessage) {
|
||||
CompoundIcon(\.check, size: .medium, relativeTo: .compound.headingLG)
|
||||
.foregroundColor(.white)
|
||||
.scaledPadding(6, relativeTo: .compound.headingLG)
|
||||
.background(.compound.iconAccentTertiary, in: Circle())
|
||||
.accessibilityLabel(L10n.actionConfirm)
|
||||
}
|
||||
.scaledPadding(4, relativeTo: .title)
|
||||
} else {
|
||||
SendButton(action: sendMessage)
|
||||
.accessibilityLabel(L10n.actionSend)
|
||||
}
|
||||
}
|
||||
.scaledPadding(4, relativeTo: .compound.headingLG)
|
||||
.disabled(context.viewState.sendButtonDisabled)
|
||||
.animation(.linear(duration: 0.1).disabledDuringTests(), value: context.viewState.sendButtonDisabled)
|
||||
.keyboardShortcut(.return, modifiers: [.command])
|
||||
@@ -271,8 +273,8 @@ struct ComposerToolbar: View {
|
||||
} label: {
|
||||
CompoundIcon(\.delete)
|
||||
.scaledToFit()
|
||||
.scaledFrame(size: 30, relativeTo: .title)
|
||||
.scaledPadding(7, relativeTo: .title)
|
||||
.scaledFrame(size: 30, relativeTo: .compound.headingLG)
|
||||
.scaledPadding(7, relativeTo: .compound.headingLG)
|
||||
}
|
||||
.buttonStyle(.compound(.plain))
|
||||
.accessibilityLabel(L10n.a11yDelete)
|
||||
@@ -291,6 +293,8 @@ struct ComposerToolbar: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
||||
static let wysiwygViewModel = WysiwygComposerViewModel()
|
||||
static let composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
@@ -330,8 +334,6 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mock
|
||||
|
||||
extension ComposerToolbar {
|
||||
static func mock(focused: Bool = true) -> ComposerToolbar {
|
||||
let wysiwygViewModel = WysiwygComposerViewModel()
|
||||
|
||||
@@ -35,17 +35,8 @@ struct MessageComposer: View {
|
||||
resizeGrabber
|
||||
}
|
||||
|
||||
mainContent
|
||||
.padding(.horizontal, 12.0)
|
||||
.clipShape(composerShape)
|
||||
.background {
|
||||
ZStack {
|
||||
composerShape
|
||||
.fill(Color.compound.bgSubtleSecondary)
|
||||
composerShape
|
||||
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 0.5)
|
||||
}
|
||||
}
|
||||
composerTextField
|
||||
.messageComposerStyle(header: header)
|
||||
// Explicitly disable all animations to fix weirdness with the header immediately
|
||||
// appearing whilst the text field and keyboard are still animating up to it.
|
||||
.animation(.noAnimation, value: mode)
|
||||
@@ -57,34 +48,27 @@ struct MessageComposer: View {
|
||||
|
||||
@State private var composerFrame = CGRect.zero
|
||||
|
||||
private var mainContent: some View {
|
||||
VStack(alignment: .leading, spacing: -6) {
|
||||
header
|
||||
|
||||
if composerFormattingEnabled {
|
||||
Color.clear
|
||||
.overlay(alignment: .top) {
|
||||
composerView
|
||||
.clipped()
|
||||
.readFrame($composerFrame)
|
||||
}
|
||||
.frame(minHeight: ComposerConstant.minHeight, maxHeight: max(composerHeight, composerFrame.height),
|
||||
alignment: .top)
|
||||
.tint(.compound.iconAccentTertiary)
|
||||
.padding(.vertical, 10)
|
||||
.onAppear {
|
||||
onAppearAction()
|
||||
}
|
||||
} else {
|
||||
MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder,
|
||||
text: $plainComposerText,
|
||||
presendCallback: $presendCallback,
|
||||
maxHeight: ComposerConstant.maxHeight,
|
||||
keyHandler: { handleKeyPress($0) },
|
||||
pasteHandler: pasteAction)
|
||||
.tint(.compound.iconAccentTertiary)
|
||||
.padding(.vertical, 10)
|
||||
}
|
||||
@ViewBuilder
|
||||
private var composerTextField: some View {
|
||||
if composerFormattingEnabled {
|
||||
Color.clear
|
||||
.overlay(alignment: .top) {
|
||||
composerView
|
||||
.clipped()
|
||||
.readFrame($composerFrame)
|
||||
}
|
||||
.frame(minHeight: ComposerConstant.minHeight, maxHeight: max(composerHeight, composerFrame.height),
|
||||
alignment: .top)
|
||||
.onAppear {
|
||||
onAppearAction()
|
||||
}
|
||||
} else {
|
||||
MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder,
|
||||
text: $plainComposerText,
|
||||
presendCallback: $presendCallback,
|
||||
maxHeight: ComposerConstant.maxHeight,
|
||||
keyHandler: { handleKeyPress($0) },
|
||||
pasteHandler: pasteAction)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +184,42 @@ private struct MessageComposerHeaderLabelStyle: LabelStyle {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Style
|
||||
|
||||
extension View {
|
||||
func messageComposerStyle(header: some View = EmptyView()) -> some View {
|
||||
modifier(MessageComposerStyleModifier(header: header))
|
||||
}
|
||||
}
|
||||
|
||||
private struct MessageComposerStyleModifier<Header: View>: ViewModifier {
|
||||
private let composerShape = RoundedRectangle(cornerRadius: 21, style: .circular)
|
||||
|
||||
let header: Header
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
VStack(alignment: .leading, spacing: -6) {
|
||||
header
|
||||
|
||||
content
|
||||
.tint(.compound.iconAccentTertiary)
|
||||
.padding(.vertical, 10)
|
||||
}
|
||||
.padding(.horizontal, 12.0)
|
||||
.clipShape(composerShape)
|
||||
.background {
|
||||
ZStack {
|
||||
composerShape
|
||||
.fill(Color.compound.bgSubtleSecondary)
|
||||
composerShape
|
||||
.stroke(Color.compound.borderInteractiveSecondary, lineWidth: 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MessageComposer_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = TimelineViewModel.mock
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ struct RoomAttachmentPicker: View {
|
||||
Menu {
|
||||
menuContent
|
||||
} label: {
|
||||
CompoundIcon(asset: Asset.Images.composerAttachment, size: .custom(30), relativeTo: .title)
|
||||
.scaledPadding(7, relativeTo: .title)
|
||||
CompoundIcon(asset: Asset.Images.composerAttachment, size: .custom(30), relativeTo: .compound.headingLG)
|
||||
.scaledPadding(7, relativeTo: .compound.headingLG)
|
||||
}
|
||||
.buttonStyle(RoomAttachmentPickerButtonStyle())
|
||||
.accessibilityLabel(L10n.actionAddToTimeline)
|
||||
|
||||
+6
-9
@@ -32,18 +32,15 @@ struct VoiceMessageRecordingButton: View {
|
||||
} label: {
|
||||
switch mode {
|
||||
case .idle:
|
||||
CompoundIcon(\.micOn, size: .medium, relativeTo: .title)
|
||||
CompoundIcon(\.micOn, size: .medium, relativeTo: .compound.headingLG)
|
||||
.foregroundColor(.compound.iconSecondary)
|
||||
.scaledPadding(10, relativeTo: .title)
|
||||
.scaledPadding(10, relativeTo: .compound.headingLG)
|
||||
case .recording:
|
||||
CompoundIcon(asset: Asset.Images.stopRecording, size: .medium, relativeTo: .title)
|
||||
CompoundIcon(asset: Asset.Images.stopRecording, size: .medium, relativeTo: .compound.headingLG)
|
||||
.foregroundColor(.compound.iconOnSolidPrimary)
|
||||
.scaledPadding(6, relativeTo: .title)
|
||||
.background(
|
||||
Circle()
|
||||
.foregroundColor(.compound.bgActionPrimaryRest)
|
||||
)
|
||||
.scaledPadding(4, relativeTo: .title)
|
||||
.scaledPadding(6, relativeTo: .compound.headingLG)
|
||||
.background(.compound.bgActionPrimaryRest, in: Circle())
|
||||
.scaledPadding(4, relativeTo: .compound.headingLG)
|
||||
}
|
||||
}
|
||||
.buttonStyle(VoiceMessageRecordingButtonStyle())
|
||||
|
||||
@@ -223,13 +223,14 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
func sendAudio(url: URL,
|
||||
audioInfo: AudioInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending audio")
|
||||
|
||||
let handle = timeline.sendAudio(url: url.path(percentEncoded: false),
|
||||
audioInfo: audioInfo,
|
||||
caption: nil,
|
||||
caption: caption,
|
||||
formattedCaption: nil,
|
||||
progressWatcher: UploadProgressListener { progress in
|
||||
progressSubject?.send(progress)
|
||||
@@ -251,13 +252,14 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
func sendFile(url: URL,
|
||||
fileInfo: FileInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending file")
|
||||
|
||||
let handle = timeline.sendFile(url: url.path(percentEncoded: false),
|
||||
fileInfo: fileInfo,
|
||||
caption: nil,
|
||||
caption: caption,
|
||||
formattedCaption: nil,
|
||||
progressWatcher: UploadProgressListener { progress in
|
||||
progressSubject?.send(progress)
|
||||
@@ -277,9 +279,11 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendImage(url: URL,
|
||||
thumbnailURL: URL,
|
||||
imageInfo: ImageInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending image")
|
||||
@@ -287,7 +291,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
let handle = timeline.sendImage(url: url.path(percentEncoded: false),
|
||||
thumbnailUrl: thumbnailURL.path(percentEncoded: false),
|
||||
imageInfo: imageInfo,
|
||||
caption: nil,
|
||||
caption: caption,
|
||||
formattedCaption: nil,
|
||||
progressWatcher: UploadProgressListener { progress in
|
||||
progressSubject?.send(progress)
|
||||
@@ -325,9 +329,11 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
return .success(())
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendVideo(url: URL,
|
||||
thumbnailURL: URL,
|
||||
videoInfo: VideoInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending video")
|
||||
@@ -335,7 +341,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
let handle = timeline.sendVideo(url: url.path(percentEncoded: false),
|
||||
thumbnailUrl: thumbnailURL.path(percentEncoded: false),
|
||||
videoInfo: videoInfo,
|
||||
caption: nil,
|
||||
caption: caption,
|
||||
formattedCaption: nil,
|
||||
progressWatcher: UploadProgressListener { progress in
|
||||
progressSubject?.send(progress)
|
||||
|
||||
@@ -51,17 +51,21 @@ protocol TimelineProxyProtocol {
|
||||
|
||||
func sendAudio(url: URL,
|
||||
audioInfo: AudioInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func sendFile(url: URL,
|
||||
fileInfo: FileInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendImage(url: URL,
|
||||
thumbnailURL: URL,
|
||||
imageInfo: ImageInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
@@ -71,9 +75,11 @@ protocol TimelineProxyProtocol {
|
||||
zoomLevel: UInt8?,
|
||||
assetType: AssetType?) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendVideo(url: URL,
|
||||
thumbnailURL: URL,
|
||||
videoInfo: VideoInfo,
|
||||
caption: String?,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ed83944ed745ce8ba0d41ee0ccc0e343b917875cb190fb83f869ca5200df8555
|
||||
size 75517
|
||||
oid sha256:888932f08b86692e4ed6d35aa438a1fa4839a0247ef4d4e8109f9f4d2902a2a7
|
||||
size 75306
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:474774d07c69a9652245a6f2e31b57fc29325592cd98f9455ddb53fca0d3c047
|
||||
size 93517
|
||||
oid sha256:3005df9a148c026be35620606d64fd479601439bf2bdda4d3d56113d121464d7
|
||||
size 93485
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:649cf6ecbb5506ff2d3d88932950bb965f5ce21529f97d10c0bf065f51931f23
|
||||
size 95810
|
||||
oid sha256:a8a8066e41d3d84e7aedc675e7991b0dd57fc1f93f363312353bf43a236e3f6a
|
||||
size 101416
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e4fd5cfb6d8191e632c5a5b0d2a9f89b50b36d1547d903971a9498ce6c6cb32a
|
||||
size 98356
|
||||
oid sha256:1b8ef13343f827c0dc3e9aaf74ce28f8a3e96bb0f4b9aa457e6a9ff40b3ad632
|
||||
size 98059
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6fd2db090826802de3831227fbb81614deea2ecd62c1e3cff9488d0e4c4c113f
|
||||
size 75899
|
||||
oid sha256:d546bdab2d2ff0d1f674bc5f5d7583f7a60c338a8ee6e94041ed4ed07a106a31
|
||||
size 75724
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a716604e488f96f610770f9b550e36ba5caf6cafaf7c1e24155cd3ff498b1a98
|
||||
size 94344
|
||||
oid sha256:b9c15c54c93c21400eb263cbc0575f86d1c47377d2f73a2fe59ec8d7886914e3
|
||||
size 94359
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ab78db432612172ab37dae1e1c2972d7ec1e8398034df87f32d97e5ccef19c5d
|
||||
size 96540
|
||||
oid sha256:8fcc46292a1139dab8462e81a1f91bc177e9a05cbcf8ff6ef5ddbe15707cfdf8
|
||||
size 102213
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9ed7b4e33493a6206a97101e5f241cb331afd24b39d60e60af8d577a30aa1cc0
|
||||
size 98738
|
||||
oid sha256:516b16553d506445d965c708a5f56e5b9c53b23488809a2513a56c2368a2d633
|
||||
size 98477
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:29c7624de3004d0a17828fd97c74b516cd420781c2c7a7c96d6383d8befcc9c5
|
||||
size 34590
|
||||
oid sha256:d4a497fc8cb28e5187c80bf8caec98e69c9faec1ed153b1c2beaa52aa8cd8ae1
|
||||
size 34435
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:34126123e98e38598b990f5a0e5c7a4f80d58b336131dcbd0646e2dbe4e8c754
|
||||
size 51216
|
||||
oid sha256:942b0ff59359bbfe7a1ae88f7acf44b856f763d91688bac8ad173a7eda405972
|
||||
size 50565
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c18ac30d254a8ab48fd16bdd213b4000944ead4e634a69cc6a808e1408805c65
|
||||
size 52052
|
||||
oid sha256:6ffa98f5f715b47251f816c0fcab5c4ff62d51d60270f8a41a34ca3835c3e0f6
|
||||
size 58631
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b1c8b56332155977848329ca750ac240b304210abef5587519f8e2e222435a42
|
||||
size 53760
|
||||
oid sha256:b4bc7dbfbb1c7c58641f7d19bcb648e792220c19919b9001b09bdc8ccda0c86f
|
||||
size 53599
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:69c9c7a7368afc683ed8ee311038cea74c1305dc96c2594e3ef1e089a5044353
|
||||
size 35006
|
||||
oid sha256:67d481416b45627802785280c9dd03bc5f634902b64a4beacd567b42fd0a444c
|
||||
size 34849
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:463bece34b226141be90906cc7aeb56f55e217034f25befa0eee5ab7fc9a9cde
|
||||
size 52029
|
||||
oid sha256:9c9ed32588b6de5c4c2c87c14a54a4d436648518cf6b7807a16a39be20332778
|
||||
size 51436
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:938c55d264a5ec64d9f982482b93c0a1f15b3454b19f7ea61b6a91a82f56fccc
|
||||
size 52835
|
||||
oid sha256:b411f75a2ce4d925ee0170cb0956129fd26b1c2d8b64b0741b82699bc058e6b6
|
||||
size 59463
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:88408dc1dd51e53dec8a4d476c4a42b7d2bdea768430be9002e48ce554ed753c
|
||||
size 54176
|
||||
oid sha256:fd4bca95fba0ed2e11a1212c1ac3d1a638815274c370a1bdb4a3dd825c89de6d
|
||||
size 54013
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:142460487dd7b40c347faeb4ce229bc900cee12d358288edec544fa7f74f26b8
|
||||
size 78954
|
||||
oid sha256:885fb08cfe301dd996fa6f1b276afa7a74f6534d692ab313ec7d64bfcb970981
|
||||
size 91980
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:740a28f97bf1d14670f314d0030a013e2aa6349551dbdc7c11e95334c05c4ac6
|
||||
size 79591
|
||||
oid sha256:ab99e9ac3233f55705f96cc0c8f1480351f0765b7b36d1e90e3262758b2ede9a
|
||||
size 93223
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b48e09d6a5605dd6e21141b176b17f7637c59be6bdee67d3650c1d61fd06493
|
||||
size 39052
|
||||
oid sha256:3cd02932f70d0c1336b295d94ca31694f569fea56b36b8cc29c448284406e8dd
|
||||
size 50622
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d24d5f89ef4e437525e82bd0aa0ab52f9f6c7463bbec799c8d5de724294d0f9c
|
||||
size 39482
|
||||
oid sha256:26109f9334be8c7452fd98c15158bea787df6b87f560f8ee6b6aea46dc6a1647
|
||||
size 51627
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:620197da87f254260dca5e965abc6e6cbd9f7743c01a236c58b8642437b92047
|
||||
size 98722
|
||||
oid sha256:1a5e7e651f73e10b1b7c5fad0a3eb631d9f50f5f42286f1faaed852316dd287d
|
||||
size 98836
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a034b807916e062e25c5fb59ecd09a47d0e1c7c822534b35e992fd9c839ab07
|
||||
size 196481
|
||||
oid sha256:71cb80dc579a03031aa4de25bf2af339ddd8670651c0e3a5b4fb5d0423592455
|
||||
size 196910
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a034b807916e062e25c5fb59ecd09a47d0e1c7c822534b35e992fd9c839ab07
|
||||
size 196481
|
||||
oid sha256:71cb80dc579a03031aa4de25bf2af339ddd8670651c0e3a5b4fb5d0423592455
|
||||
size 196910
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9a632e640aefa28698b33f87eb8a8576db9021ea3e0f6cbbddcf3d7c61f71c35
|
||||
size 99789
|
||||
oid sha256:4914f7dc335cc0333584c2ad34034ce5a28e6fb3bb52d05f27734ed2dae5e7a6
|
||||
size 99876
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ffe1ceda422b0bccc1e587036ea047bebb4ee4643f54bdf25ff2b3b530b6f7e2
|
||||
size 199662
|
||||
oid sha256:218956837978d0f8f16b8744a064f5ed78be91a8be0a712cae7536f54867319c
|
||||
size 200002
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ffe1ceda422b0bccc1e587036ea047bebb4ee4643f54bdf25ff2b3b530b6f7e2
|
||||
size 199662
|
||||
oid sha256:218956837978d0f8f16b8744a064f5ed78be91a8be0a712cae7536f54867319c
|
||||
size 200002
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bbfd12737413020fd7c0ff01c1b276f57ced10e33be2ea41977d3faf79050857
|
||||
size 54984
|
||||
oid sha256:9951096cb1ddc3050415353192108a4489908d8355b30189845a7da5fa2b5d83
|
||||
size 55103
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:91bef32e5248f8084995b237b671cea005ce0eb9253a325165dd066a43934bfa
|
||||
size 135848
|
||||
oid sha256:19d120c20ff2a374d05884e6599032a4a3d4982dd9e2d19bc4e28e95f79cb4e0
|
||||
size 136286
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:91bef32e5248f8084995b237b671cea005ce0eb9253a325165dd066a43934bfa
|
||||
size 135848
|
||||
oid sha256:19d120c20ff2a374d05884e6599032a4a3d4982dd9e2d19bc4e28e95f79cb4e0
|
||||
size 136286
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:702ac84c437121bd0c7370fc5de6928cf60894554c00dcb721924f349db4c991
|
||||
size 57150
|
||||
oid sha256:745116e5588711197b37e391b2f2ca6359c9a4cb8b3dd718ec55dd29c33d72a1
|
||||
size 57224
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c5fd3ee0cc6bb3cf124cc66877d87cde800a3da7c10f8e26d6e19d884f4ca40e
|
||||
size 139138
|
||||
oid sha256:da4f3b1174b24ae18c931db745a788e01793f73693fe132cb29836728db76172
|
||||
size 139540
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c5fd3ee0cc6bb3cf124cc66877d87cde800a3da7c10f8e26d6e19d884f4ca40e
|
||||
size 139138
|
||||
oid sha256:da4f3b1174b24ae18c931db745a788e01793f73693fe132cb29836728db76172
|
||||
size 139540
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:033f43d5be47abd91cd5f36b93a8b32b0f9800e7a8a947e0c2cd4debd8b4c694
|
||||
size 295633
|
||||
oid sha256:906409795a47057b2da67dc7a50221b6a9be839309cf434899d3ee7c19735c83
|
||||
size 295656
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc959ac1d5a22da8ca928d891e9f0a649dfa28b1cdfa7a91b0a74d2189cc38a3
|
||||
size 297711
|
||||
oid sha256:38a6eea96f81ab5acd88b75823745cc7bb71c237ff0ca181e05e0263abc487d9
|
||||
size 297761
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26de643c28b47f7fd0631c0c754d979c43fc51bc274e863fdb6acaf0ed299960
|
||||
size 180507
|
||||
oid sha256:6b8ab1d9311c44e85d8e145a9290e97aa2b37070a3104402332479ec8c2fd739
|
||||
size 180530
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:58ae4f390371ef48aca7b5faeb4c75ba7bdfad4294797fd422c0d102036ee4a8
|
||||
size 178571
|
||||
oid sha256:f5843af5ad8774b2d73bc57b18caacb79cfa46abc54c797b5c17948f961ded06
|
||||
size 178598
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:033f43d5be47abd91cd5f36b93a8b32b0f9800e7a8a947e0c2cd4debd8b4c694
|
||||
size 295633
|
||||
oid sha256:906409795a47057b2da67dc7a50221b6a9be839309cf434899d3ee7c19735c83
|
||||
size 295656
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc959ac1d5a22da8ca928d891e9f0a649dfa28b1cdfa7a91b0a74d2189cc38a3
|
||||
size 297711
|
||||
oid sha256:38a6eea96f81ab5acd88b75823745cc7bb71c237ff0ca181e05e0263abc487d9
|
||||
size 297761
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26de643c28b47f7fd0631c0c754d979c43fc51bc274e863fdb6acaf0ed299960
|
||||
size 180507
|
||||
oid sha256:6b8ab1d9311c44e85d8e145a9290e97aa2b37070a3104402332479ec8c2fd739
|
||||
size 180530
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:58ae4f390371ef48aca7b5faeb4c75ba7bdfad4294797fd422c0d102036ee4a8
|
||||
size 178571
|
||||
oid sha256:f5843af5ad8774b2d73bc57b18caacb79cfa46abc54c797b5c17948f961ded06
|
||||
size 178598
|
||||
|
||||
@@ -10,4 +10,135 @@ import XCTest
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class MediaUploadPreviewScreenViewModelTests: XCTestCase { }
|
||||
class MediaUploadPreviewScreenViewModelTests: XCTestCase {
|
||||
var timelineProxy: TimelineProxyMock!
|
||||
var viewModel: MediaUploadPreviewScreenViewModel!
|
||||
var context: MediaUploadPreviewScreenViewModel.Context { viewModel.context }
|
||||
|
||||
enum TestError: Swift.Error {
|
||||
case unexpectedParameter
|
||||
case unknown
|
||||
}
|
||||
|
||||
override func setUp() {
|
||||
AppSettings.resetAllSettings()
|
||||
let appSettings = AppSettings()
|
||||
appSettings.optimizeMediaUploads = false
|
||||
ServiceLocator.shared.register(appSettings: appSettings)
|
||||
}
|
||||
|
||||
deinit {
|
||||
AppSettings.resetAllSettings()
|
||||
}
|
||||
|
||||
func testImageUploadWithoutCaption() async throws {
|
||||
setUpViewModel(url: imageURL, expectedCaption: nil)
|
||||
context.caption = .init("")
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testImageUploadWithBlankCaption() async throws {
|
||||
setUpViewModel(url: imageURL, expectedCaption: nil)
|
||||
context.caption = .init(" ")
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testImageUploadWithCaption() async throws {
|
||||
let caption = "This is a really great image!"
|
||||
setUpViewModel(url: imageURL, expectedCaption: caption)
|
||||
context.caption = .init(string: caption)
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testVideoUploadWithoutCaption() async throws {
|
||||
setUpViewModel(url: videoURL, expectedCaption: nil)
|
||||
context.caption = .init("")
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testVideoUploadWithCaption() async throws {
|
||||
let caption = "Check out this video!"
|
||||
setUpViewModel(url: videoURL, expectedCaption: caption)
|
||||
context.caption = .init(string: caption)
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testAudioUploadWithoutCaption() async throws {
|
||||
setUpViewModel(url: audioURL, expectedCaption: nil)
|
||||
context.caption = .init("")
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testAudioUploadWithCaption() async throws {
|
||||
let caption = "Listen to this!"
|
||||
setUpViewModel(url: audioURL, expectedCaption: caption)
|
||||
context.caption = .init(string: caption)
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testFileUploadWithoutCaption() async throws {
|
||||
setUpViewModel(url: fileURL, expectedCaption: nil)
|
||||
context.caption = .init("")
|
||||
try await send()
|
||||
}
|
||||
|
||||
func testFileUploadWithCaption() async throws {
|
||||
let caption = "Please will you check my article."
|
||||
setUpViewModel(url: fileURL, expectedCaption: caption)
|
||||
context.caption = .init(string: caption)
|
||||
try await send()
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private var audioURL: URL { assertResourceURL(filename: "test_audio.mp3") }
|
||||
private var fileURL: URL { assertResourceURL(filename: "test_pdf.pdf") }
|
||||
private var imageURL: URL { assertResourceURL(filename: "test_animated_image.gif") }
|
||||
private var videoURL: URL { assertResourceURL(filename: "landscape_test_video.mov") }
|
||||
|
||||
private func assertResourceURL(filename: String) -> URL {
|
||||
guard let url = Bundle(for: Self.self).url(forResource: filename, withExtension: nil) else {
|
||||
XCTFail("Failed retrieving test asset")
|
||||
return .picturesDirectory
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
private func setUpViewModel(url: URL, expectedCaption: String?) {
|
||||
timelineProxy = TimelineProxyMock(.init())
|
||||
timelineProxy.sendAudioUrlAudioInfoCaptionProgressSubjectRequestHandleClosure = { [weak self] _, _, caption, _, _ in
|
||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||
}
|
||||
timelineProxy.sendFileUrlFileInfoCaptionProgressSubjectRequestHandleClosure = { [weak self] _, _, caption, _, _ in
|
||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||
}
|
||||
timelineProxy.sendImageUrlThumbnailURLImageInfoCaptionProgressSubjectRequestHandleClosure = { [weak self] _, _, _, caption, _, _ in
|
||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||
}
|
||||
timelineProxy.sendVideoUrlThumbnailURLVideoInfoCaptionProgressSubjectRequestHandleClosure = { [weak self] _, _, _, caption, _, _ in
|
||||
self?.verifyCaption(caption, expectedCaption: expectedCaption) ?? .failure(.sdkError(TestError.unknown))
|
||||
}
|
||||
|
||||
let roomProxy = JoinedRoomProxyMock(.init())
|
||||
roomProxy.timeline = timelineProxy
|
||||
viewModel = MediaUploadPreviewScreenViewModel(userIndicatorController: UserIndicatorControllerMock(),
|
||||
roomProxy: roomProxy,
|
||||
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings),
|
||||
title: "Some File",
|
||||
url: url)
|
||||
}
|
||||
|
||||
private func verifyCaption(_ caption: String?, expectedCaption: String?) -> Result<Void, TimelineProxyError> {
|
||||
guard caption == expectedCaption else {
|
||||
XCTFail("The sent caption '\(caption ?? "nil")' does not match the expected value '\(expectedCaption ?? "nil")'").self
|
||||
return .failure(.sdkError(TestError.unexpectedParameter))
|
||||
}
|
||||
return .success(())
|
||||
}
|
||||
|
||||
private func send() async throws {
|
||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .dismiss }
|
||||
context.send(viewAction: .send)
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user