Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package protocol S3ClientProtocol: Sendable {

func getObject(input: GetObjectInput) async throws -> GetObjectOutput
func putObject(input: PutObjectInput) async throws -> PutObjectOutput
func deleteObject(input: DeleteObjectInput) async throws -> DeleteObjectOutput
func uploadPart(input: UploadPartInput) async throws -> UploadPartOutput
func createMultipartUpload(input: CreateMultipartUploadInput) async throws -> CreateMultipartUploadOutput
func completeMultipartUpload(input: CompleteMultipartUploadInput) async throws -> CompleteMultipartUploadOutput
Expand Down Expand Up @@ -157,6 +158,15 @@ final class AWSClient: Sendable {
}
}

func delete(node: WireDriveNodeNetworkModel) async throws {
let deleteObjectInput = DeleteObjectInput(
bucket: Constants.bucket,
key: node.path
)

try await s3.deleteObject(input: deleteObjectInput)
}

private func upload(
path: URL,
node: WireDriveNodeNetworkModel,
Expand Down Expand Up @@ -198,6 +208,7 @@ final class AWSClient: Sendable {

try await withTaskCancellationHandler {
do {

_ = try await s3.putObject(input: input)
} catch {
if Task.isCancelled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ package final actor NodesAPI: NodesAPIProtocol, WireDriveNodesRepositoryProtocol
: .success
}

package func deleteFile(nodeID: UUID) async throws {
let node = try await getNode(nodeID: nodeID)
try await awsClient.delete(node: node.toDTO())
}

package func uploadFile(
path: URL,
node: WireDriveNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public struct WireDriveDraft: Hashable, Sendable {

/// The URL of the asset that contains the file data.

package let assetURL: URL
public let assetURL: URL

/// The type of the file, represented as a Uniform Type Identifier (UTType). This value is determined locally.

Expand Down Expand Up @@ -87,6 +87,14 @@ public struct WireDriveDraft: Hashable, Sendable {

public let metadata: Metadata?

/// Optional data associated to an image.
public let data: Data?

/// Optional unique identifier from the device’s Photos library, matching `PHAsset.localIdentifier` or
/// `PHPickerResult.assetIdentifier`.
/// Used to manage drafts in conversation previews.
public let localIdentifier: String?

package init(
nodeID: UUID,
versionID: UUID,
Expand All @@ -97,7 +105,9 @@ public struct WireDriveDraft: Hashable, Sendable {
bytes: Int,
mimeType: String?,
requiresCleanup: Bool,
metadata: Metadata?
metadata: Metadata?,
data: Data?,
localIdentifier: String?
) {
self.nodeID = nodeID
self.versionID = versionID
Expand All @@ -109,5 +119,17 @@ public struct WireDriveDraft: Hashable, Sendable {
self.mimeType = mimeType
self.requiresCleanup = requiresCleanup
self.metadata = metadata
self.data = data
self.localIdentifier = localIdentifier
}
}

public extension WireDriveDraft {
var isImage: Bool {
fileType?.conforms(to: .image) ?? (data != nil)
}

var isVideo: Bool {
fileType?.conforms(to: .movie) ?? false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ package protocol NodesAPIProtocol: Sendable {

func uploadFile(path: URL, node: WireDriveNode, versionID: UUID) async throws -> AsyncThrowingStream<Int, any Error>

func deleteFile(nodeID: UUID) async throws

func deleteVersion(nodeID: UUID, versionID: UUID) async throws

func publishDraft(nodeID: UUID, versionID: UUID) async throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ public protocol WireDriveUploadDraftUseCaseProtocol: Sendable {

/// Uploads the file at `fileURL` to the drive server.

func invoke(fileURL: URL) async throws
func invoke(fileURL: URL, localIdentifier: String?) async throws

/// Creates a file using `imageData` and uploads it to the drive server.
/// When an `existingNodeID` is provided, an existing asset is being updated.

func invoke(data: Data, type: UTType) async throws
func invoke(data: Data, type: UTType, localIdentifier: String?, existingNodeID: UUID?) async throws

var charactersToReplace: [Character] { get }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ package struct UploadDraftUseCase: WireDriveUploadDraftUseCaseProtocol, WireDriv
self.filenameGenerator = filenameGenerator
}

package func invoke(fileURL: URL) async throws {
try await invoke(fileURL: fileURL, requiresCleanup: false)
package func invoke(fileURL: URL, localIdentifier: String?) async throws {
try await invoke(fileURL: fileURL, localIdentifier: localIdentifier, requiresCleanup: false)
}

/// Uploads a file using an existing draft's nodeID.
Expand Down Expand Up @@ -109,7 +109,7 @@ package struct UploadDraftUseCase: WireDriveUploadDraftUseCaseProtocol, WireDriv
}
}

package func invoke(data: Data, type: UTType) async throws {
package func invoke(data: Data, type: UTType, localIdentifier: String?, existingNodeID: UUID?) async throws {
let filename = await filenameGenerator.generateFilename(type: type)

let container = intermediaryFilesDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
Expand All @@ -118,12 +118,24 @@ package struct UploadDraftUseCase: WireDriveUploadDraftUseCaseProtocol, WireDriv
try FileManager.default.createDirectory(at: container, withIntermediateDirectories: true)

try data.write(to: url)
try await invoke(fileURL: url, requiresCleanup: true)
try await invoke(
fileURL: url,
data: data,
localIdentifier: localIdentifier,
existingNodeID: existingNodeID,
requiresCleanup: true
)
}

// MARK: - Private Methods

private func invoke(fileURL: URL, requiresCleanup: Bool) async throws {
private func invoke(
fileURL: URL,
data: Data? = nil,
localIdentifier: String? = nil,
existingNodeID: UUID? = nil,
requiresCleanup: Bool
) async throws {
let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey, .contentTypeKey])
guard let fileSize = resourceValues.fileSize, fileSize > 0 else {
throw WireDriveUploadDraftUseCaseError.missingFileSize
Expand All @@ -136,7 +148,7 @@ package struct UploadDraftUseCase: WireDriveUploadDraftUseCaseProtocol, WireDriv
}

let draft = WireDriveDraft(
nodeID: UUID(),
nodeID: existingNodeID ?? UUID(),
versionID: UUID(),
assetURL: fileURL,
fileType: resourceValues.contentType,
Expand All @@ -145,10 +157,18 @@ package struct UploadDraftUseCase: WireDriveUploadDraftUseCaseProtocol, WireDriv
bytes: fileSize,
mimeType: nil,
requiresCleanup: requiresCleanup,
metadata: try? await metadata(for: fileURL, fileType: resourceValues.contentType)
metadata: try? await metadata(for: fileURL, fileType: resourceValues.contentType),
data: data,
localIdentifier: localIdentifier
)

await draftRepository.addDraft(draft, for: cellName)
if let existingNodeID {
await draftRepository.updateDraft(draft, for: cellName)
try await nodesAPI.deleteFile(nodeID: existingNodeID)
} else {
await draftRepository.addDraft(draft, for: cellName)
}

try await invoke(nodeID: draft.nodeID)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private struct AttachmentsCarouselItemView: View {
ZStack {
ZStack {
content
.contentShape(Rectangle()) // Constrains the tappable content area of the view.
.onTapGesture(perform: onTap)
}
.aspectRatio(item.aspectRatio, contentMode: .fill)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public final class AttachmentsCarouselViewModel: ObservableObject {
private var thumbnails: [UUID: UIImage] = [:]
private var generatingThumbnailIDs: Set<UUID> = []

public var draftsLocalIdentifiers: [String] {
drafts.compactMap(\.localIdentifier)
}

@Published private(set) var items: [AttachmentsCarouselItem]

public convenience init() {
Expand Down Expand Up @@ -63,6 +67,19 @@ public final class AttachmentsCarouselViewModel: ObservableObject {
}
}

public func draft(for item: AttachmentsCarouselItem) -> WireDriveDraft? {
guard let index = items.firstIndex(of: item),
drafts.indices.contains(index) else {
return nil
}

return drafts[index]
}

public func draft(withLocalIdentifier localIdentifier: String) -> WireDriveDraft? {
drafts.first(where: { $0.localIdentifier == localIdentifier })
}

private func refreshItems() {
items = drafts.compactMap { AttachmentsCarouselItem(draft: $0, thumbnail: thumbnails[$0.versionID]) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ extension WireDriveDraft {
bytes: bytes,
mimeType: mimeType,
requiresCleanup: requiresCleanup,
metadata: metadata
metadata: metadata,
data: nil,
localIdentifier: nil
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ final class UploadDraftUseCaseTests {
// When, Then
let sut = sut
await #expect(throws: (any Error).self) {
try await sut.invoke(fileURL: url)
try await sut.invoke(fileURL: url, localIdentifier: nil)
}
}

Expand All @@ -223,7 +223,7 @@ final class UploadDraftUseCaseTests {
try data.write(to: fileURL)

// When
try await sut.invoke(fileURL: fileURL)
try await sut.invoke(fileURL: fileURL, localIdentifier: nil)

// Then
let arguments = try #require(draftsRepository.addDraftFor_Invocations.first)
Expand Down Expand Up @@ -254,7 +254,7 @@ final class UploadDraftUseCaseTests {
metadataRepository.videoMetadataFileURL_MockValue = .video(width: 10, height: 10, duration: 10)

// When
try await sut.invoke(fileURL: fileURL)
try await sut.invoke(fileURL: fileURL, localIdentifier: nil)

// Then
let arguments = try #require(draftsRepository.addDraftFor_Invocations.first)
Expand All @@ -268,7 +268,7 @@ final class UploadDraftUseCaseTests {
metadataRepository.imageMetadataFileURL_MockError = NSError(domain: "something", code: 10)

// When
try await sut.invoke(fileURL: fileURL)
try await sut.invoke(fileURL: fileURL, localIdentifier: nil)

// Then
#expect(draftsRepository.addDraftFor_Invocations.count == 1)
Expand All @@ -285,7 +285,7 @@ final class UploadDraftUseCaseTests {
let data = Data("This is a test file content.".utf8)

// When
try await sut.invoke(data: data, type: type)
try await sut.invoke(data: data, type: type, localIdentifier: nil, existingNodeID: nil)

// Then
let arguments = try #require(draftsRepository.addDraftFor_Invocations.first)
Expand All @@ -305,7 +305,7 @@ final class UploadDraftUseCaseTests {
let data = Data("This is a test file content.".utf8)

// When
try await sut.invoke(data: data, type: .plainText)
try await sut.invoke(data: data, type: .plainText, localIdentifier: nil, existingNodeID: nil)

// Then
let arguments = try #require(draftsRepository.addDraftFor_Invocations.first)
Expand Down
1 change: 1 addition & 0 deletions WireUI/Sources/WireLocators/Locators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public enum Locators {
case sketchButton
case canvas
case canvasSendButton
case canvasConfirmButton
case attachmentImagePreview
case attachmentVideoPreview
case classifiedBanner = "ClassificationBannerClassified"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ import UniformTypeIdentifiers

public struct SendableImage {

public let id: UUID
public let localIdentifier: String? // Optional unique identifier from the device’s Photos library
public let name: String
public let utType: UTType?
public let data: Data

public init(
id: UUID = UUID(),
localIdentifier: String? = nil,
name: String?,
utType: UTType?,
data: Data
Expand All @@ -43,7 +47,9 @@ public struct SendableImage {
self.name = "picture"
}

self.localIdentifier = localIdentifier
self.data = data
self.id = id
}

private static func determineUTType(from data: Data) -> UTType? {
Expand Down
15 changes: 14 additions & 1 deletion wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import AVFoundation
import Photos
import WireDesign
import WireMessagingUI
import WireTestingPackage
import XCTest

Expand All @@ -45,6 +46,7 @@
func cameraKeyboardViewController(
_ controller: CameraKeyboardViewController,
didSelectVideo: URL,
withLocalIdentifier id: String?,

Check warning on line 49 in wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "id" or name it "_".

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-ios&issues=AZ39pl7NDHsPH5Ec1Rs8&open=AZ39pl7NDHsPH5Ec1Rs8&pullRequest=4613
duration: TimeInterval
) {
cameraKeyboardDidSelectVideoHitCount += 1
Expand All @@ -58,6 +60,15 @@
) {
cameraKeyboardViewControllerDidSelectImageDataHitCount += 1
}

var cameraKeyboardViewControllerDidDeselectImageDataHitCount: UInt = 0
func cameraKeyboardViewController(
_ controller: Wire.CameraKeyboardViewController,

Check warning on line 66 in wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "controller" or name it "_".

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-ios&issues=AZ39pl7NDHsPH5Ec1Rs9&open=AZ39pl7NDHsPH5Ec1Rs9&pullRequest=4613
didDeselectImage image: PHAsset

Check warning on line 67 in wire-ios/Wire-iOS Tests/CameraKeyboardViewControllerTests.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "image" or name it "_".

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-ios&issues=AZ39pl7NDHsPH5Ec1Rs-&open=AZ39pl7NDHsPH5Ec1Rs-&pullRequest=4613
) {
cameraKeyboardViewControllerDidDeselectImageDataHitCount += 1
}

}

// MARK: - SplitLayoutObservableMock
Expand Down Expand Up @@ -188,6 +199,7 @@
sut = CameraKeyboardViewController(
splitLayoutObservable: splitView,
permissions: permissions,
attachmentsCarouselViewModel: AttachmentsCarouselViewModel(),
userSession: UserSessionMock()
)
}
Expand All @@ -199,6 +211,7 @@
sut = CallingMockCameraKeyboardViewController(
splitLayoutObservable: splitView,
permissions: permissions,
attachmentsCarouselViewModel: AttachmentsCarouselViewModel(),
userSession: UserSessionMock()
)

Expand Down Expand Up @@ -291,7 +304,7 @@
initialStateLayoutSizeCompact(with: permissions)
}

// MARK: - Tests for InitialStateLayoutSizeRegularPortrait
// MARK: - Tests for InitialStateLayoutSizeRegularPortrait#imageLiteral(resourceName: "testInitialStateLayoutSizeCompact_LibraryAccessGranted.1.png")

private func initialStateLayoutSizeRegularPortrait(
with permissions: PhotoPermissionsController,
Expand Down
Loading
Loading