Last active
February 25, 2023 11:48
-
-
Save saroar/e9a769cd8a42b3c226525192d0cf9dbc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // AttachmentClient.swift | |
| // | |
| // | |
| // Created by Saroar Khandoker on 27.01.2021. | |
| // | |
| import Combine | |
| import Foundation | |
| import AddaSharedModels | |
| import UIKit | |
| import InfoPlist | |
| import KeychainClient | |
| import SotoS3 | |
| import Dependencies | |
| public struct AttachmentS3Client { | |
| public static let bucket = "adda" | |
| public static var bucketWithEndpoint = "https://adda.nyc3.digitaloceanspaces.com/" | |
| static public let client = AWSClient( | |
| credentialProvider: .static( | |
| accessKeyId: EnvironmentKeys.accessKeyId, | |
| secretAccessKey: EnvironmentKeys.secretAccessKey | |
| ), | |
| httpClientProvider: .createNew | |
| ) | |
| public static let awsS3 = S3( | |
| client: client, | |
| region: .useast1, | |
| endpoint: "https://nyc3.digitaloceanspaces.com" | |
| ) | |
| public typealias UploadImageToS3Handler = @Sendable (UIImage, String?, String?) async throws -> String | |
| public let uploadImageToS3: UploadImageToS3Handler | |
| public init(uploadImageToS3: @escaping UploadImageToS3Handler) { | |
| self.uploadImageToS3 = uploadImageToS3 | |
| } | |
| } | |
| extension AttachmentS3Client { | |
| static public func buildImageKey( | |
| conversationId: String? = nil, | |
| userId: String? = nil, | |
| imageFormat: String | |
| ) -> String { | |
| let currentTime = Int64(Date().timeIntervalSince1970 * 1000) | |
| var imageKey = String(format: "%ld", currentTime) | |
| if let conversationId = conversationId { | |
| imageKey = "uploads/images/\(conversationId)/\(imageKey).\(imageFormat)" | |
| } else if let userId = userId { | |
| imageKey = "uploads/images/\(userId)/\(imageKey).\(imageFormat)" | |
| } | |
| return imageKey | |
| } | |
| // upload image to DigitalOcen Spaces | |
| static public func uploadImage( | |
| image: UIImage, | |
| conversationId: String? = nil, | |
| userId: String? = nil | |
| ) async throws -> String { | |
| return try await withCheckedThrowingContinuation { continuation in | |
| let data = image.compressImage(conversationId == nil ? .highest : .medium) | |
| let imageFormat = data.1 | |
| guard let imageData = data.0 else { | |
| return continuation.resume(throwing: "Data compressImage error") | |
| } | |
| let imageKey = buildImageKey(conversationId: conversationId, userId: userId, imageFormat: imageFormat) | |
| let body = AWSPayload.data(imageData) | |
| // Put an Object | |
| let putObjectRequest = S3.PutObjectRequest( | |
| acl: .publicRead, | |
| body: body, | |
| bucket: bucket, | |
| contentLength: Int64(imageData.count), | |
| key: imageKey | |
| ) | |
| let futureOutput = awsS3.putObject(putObjectRequest) | |
| futureOutput.whenSuccess { response in | |
| print(#line, self, response, imageKey) | |
| let finalURL = bucketWithEndpoint + imageKey | |
| return continuation.resume(returning: finalURL) | |
| } | |
| futureOutput.whenFailure { error in | |
| return continuation.resume(throwing: error.localizedDescription) | |
| } | |
| } | |
| } | |
| } | |
| extension AttachmentS3Client { | |
| public static var live: AttachmentS3Client = | |
| .init( | |
| uploadImageToS3: { image, conversationId, userId in | |
| return try await AttachmentS3Client.uploadImage( | |
| image: image, | |
| conversationId: conversationId, | |
| userId: userId | |
| ) | |
| } | |
| ) | |
| } | |
| public enum AttachmentS3ClientKey: TestDependencyKey { | |
| public static let testValue = AttachmentS3Client.happyPath | |
| } | |
| extension AttachmentS3ClientKey: DependencyKey { | |
| public static let liveValue: AttachmentS3Client = AttachmentS3Client.live | |
| } | |
| extension DependencyValues { | |
| public var attachmentS3Client: AttachmentS3Client { | |
| get { self[AttachmentS3ClientKey.self] } | |
| set { self[AttachmentS3ClientKey.self] = newValue } | |
| } | |
| } | |
| // this is how i can converted image to data | |
| // | |
| // Image+Compress.swift | |
| // AddaMeIOS | |
| // | |
| // Created by Saroar Khandoker on 19.11.2020. | |
| // | |
| import AVFoundation | |
| import SwiftUI | |
| #if os(iOS) | |
| import UIKit | |
| #elseif os(OSX) | |
| import AppKit | |
| import Cocoa | |
| typealias UIImage = NSImage | |
| extension NSImage { | |
| var cgImage: CGImage? { | |
| var proposedRect = CGRect(origin: .zero, size: size) | |
| return cgImage( | |
| forProposedRect: &proposedRect, | |
| context: nil, | |
| hints: nil) | |
| } | |
| convenience init?(named name: String) { | |
| self.init(named: Name(name)) | |
| } | |
| } | |
| #endif | |
| extension UIImage { | |
| public enum JPEGQuality: CGFloat { | |
| case lowest = 0 | |
| case low = 0.25 | |
| case medium = 0.5 | |
| case high = 0.75 | |
| case highest = 1 | |
| } | |
| private var isHeicSupported: Bool { | |
| // swiftlint:disable force_cast | |
| (CGImageDestinationCopyTypeIdentifiers() as! [String]).contains("public.heic") | |
| } | |
| public func compressImage(_ compressionQuality: JPEGQuality? = .medium) -> (Data?, String) { | |
| if isHeicSupported { | |
| do { | |
| let data = try heicData(compressionQuality: compressionQuality!) | |
| return (data, "heic") | |
| } catch { | |
| print("Error creating HEIC data: \(error.localizedDescription)") | |
| } | |
| } else { | |
| #if os(iOS) | |
| guard let data = jpegData(compressionQuality: compressionQuality!.rawValue) else { | |
| return (nil, "") | |
| } | |
| return (data, "jpeg") | |
| #elseif os(OSX) | |
| fatalError("Value of type 'NSImage' has no member 'jpegData'") | |
| #endif | |
| } | |
| return (nil, "") | |
| } | |
| } | |
| extension UIImage { | |
| public enum HEICError: Error { | |
| case heicNotSupported | |
| case cgImageMissing | |
| case couldNotFinalize | |
| } | |
| public func heicData(compressionQuality: JPEGQuality) throws -> Data { | |
| let data = NSMutableData() | |
| guard | |
| let imageDestination = | |
| CGImageDestinationCreateWithData( | |
| data, AVFileType.heic as CFString, 1, nil | |
| ) | |
| else { | |
| throw HEICError.heicNotSupported | |
| } | |
| guard let cgImage = self.cgImage else { | |
| throw HEICError.cgImageMissing | |
| } | |
| let options: NSDictionary = [ | |
| kCGImageDestinationLossyCompressionQuality: compressionQuality.rawValue | |
| ] | |
| CGImageDestinationAddImage(imageDestination, cgImage, options) | |
| guard CGImageDestinationFinalize(imageDestination) else { | |
| throw HEICError.couldNotFinalize | |
| } | |
| return data as Data | |
| } | |
| } | |
| extension UIImage { | |
| public func heicData2(compressionQuality: JPEGQuality) -> Data? { | |
| let destinationData = NSMutableData() | |
| guard | |
| let cgImage = self.cgImage, | |
| let destination = CGImageDestinationCreateWithData(destinationData, AVFileType.heic as CFString, 1, nil) | |
| else { return nil } | |
| let options = [kCGImageDestinationLossyCompressionQuality: compressionQuality] | |
| CGImageDestinationAddImage(destination, cgImage, options as CFDictionary) | |
| CGImageDestinationFinalize(destination) | |
| return destinationData as Data | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment