├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Package.resolved ├── Package.swift ├── Sources └── SwiftfulFirebaseStorage │ ├── Core │ └── StorageUploader.swift │ ├── Models │ └── ImageCompressionOption.swift │ ├── Services │ ├── FirebaseStorageUploaderService.swift │ ├── MockStorageUploaderService.swift │ └── StorageUploaderService.swift │ └── SwiftfulFirebaseStorage.swift └── Tests └── SwiftfulFirebaseStorageTests └── SwiftfulFirebaseStorageTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "bdd148bd6a3b872183d5bdb2639ba3c45f65b3fa3f8891f7f2858d40ef0936c6", 3 | "pins" : [ 4 | { 5 | "identity" : "abseil-cpp-binary", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/google/abseil-cpp-binary.git", 8 | "state" : { 9 | "revision" : "748c7837511d0e6a507737353af268484e1745e2", 10 | "version" : "1.2024011601.1" 11 | } 12 | }, 13 | { 14 | "identity" : "app-check", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/google/app-check.git", 17 | "state" : { 18 | "revision" : "c218c2054299b15ae577e818bbba16084d3eabe6", 19 | "version" : "10.18.2" 20 | } 21 | }, 22 | { 23 | "identity" : "firebase-ios-sdk", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/firebase/firebase-ios-sdk.git", 26 | "state" : { 27 | "revision" : "42eae77a0af79e9c3f41df04a23c76f05cfdda77", 28 | "version" : "10.24.0" 29 | } 30 | }, 31 | { 32 | "identity" : "googleappmeasurement", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/google/GoogleAppMeasurement.git", 35 | "state" : { 36 | "revision" : "51ba746a9d51a4bd0774b68499b0c73ef6e8570d", 37 | "version" : "10.24.0" 38 | } 39 | }, 40 | { 41 | "identity" : "googledatatransport", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/google/GoogleDataTransport.git", 44 | "state" : { 45 | "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565", 46 | "version" : "9.4.0" 47 | } 48 | }, 49 | { 50 | "identity" : "googleutilities", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/google/GoogleUtilities.git", 53 | "state" : { 54 | "revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55", 55 | "version" : "7.13.1" 56 | } 57 | }, 58 | { 59 | "identity" : "grpc-binary", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/google/grpc-binary.git", 62 | "state" : { 63 | "revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359", 64 | "version" : "1.62.2" 65 | } 66 | }, 67 | { 68 | "identity" : "gtm-session-fetcher", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/google/gtm-session-fetcher.git", 71 | "state" : { 72 | "revision" : "9534039303015a84837090d20fa21cae6e5eadb6", 73 | "version" : "3.3.2" 74 | } 75 | }, 76 | { 77 | "identity" : "interop-ios-for-google-sdks", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/google/interop-ios-for-google-sdks.git", 80 | "state" : { 81 | "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", 82 | "version" : "100.0.0" 83 | } 84 | }, 85 | { 86 | "identity" : "leveldb", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/firebase/leveldb.git", 89 | "state" : { 90 | "revision" : "43aaef65e0c665daadf848761d560e446d350d3d", 91 | "version" : "1.22.4" 92 | } 93 | }, 94 | { 95 | "identity" : "nanopb", 96 | "kind" : "remoteSourceControl", 97 | "location" : "https://github.com/firebase/nanopb.git", 98 | "state" : { 99 | "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", 100 | "version" : "2.30910.0" 101 | } 102 | }, 103 | { 104 | "identity" : "promises", 105 | "kind" : "remoteSourceControl", 106 | "location" : "https://github.com/google/promises.git", 107 | "state" : { 108 | "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", 109 | "version" : "2.4.0" 110 | } 111 | }, 112 | { 113 | "identity" : "swift-protobuf", 114 | "kind" : "remoteSourceControl", 115 | "location" : "https://github.com/apple/swift-protobuf.git", 116 | "state" : { 117 | "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", 118 | "version" : "1.26.0" 119 | } 120 | } 121 | ], 122 | "version" : 3 123 | } 124 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.10 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftfulFirebaseStorage", 8 | platforms: [ 9 | .iOS(.v13) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, making them visible to other packages. 13 | .library( 14 | name: "SwiftfulFirebaseStorage", 15 | targets: ["SwiftfulFirebaseStorage"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | .package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "10.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package, defining a module or a test suite. 23 | // Targets can depend on other targets in this package and products from dependencies. 24 | .target( 25 | name: "SwiftfulFirebaseStorage", 26 | dependencies: [ 27 | .product(name: "FirebaseStorage", package: "firebase-ios-sdk"), 28 | ], 29 | resources: [ 30 | .process("Assets") 31 | ] 32 | ), 33 | .testTarget( 34 | name: "SwiftfulFirebaseStorageTests", 35 | dependencies: ["SwiftfulFirebaseStorage"]), 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /Sources/SwiftfulFirebaseStorage/Core/StorageUploader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageUploader.swift 3 | // 4 | // 5 | // Created by Nick Sarno on 4/10/24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | public struct StorageUploader: Sendable { 12 | 13 | let service: StorageUploaderService 14 | 15 | public init(configuration: StorageUploaderServiceOption) { 16 | self.service = configuration.service 17 | } 18 | 19 | public func saveImage(path: String, image: UIImage, compression: ImageCompressionOption = .jpg(compressionQuality: 1)) async throws -> URL { 20 | try await service.saveImage(path: path, image: image, compression: compression) 21 | } 22 | 23 | public func saveAudio(path: String, localFileUrl: URL) async throws -> URL { 24 | try await service.saveAudio(path: path, localFileUrl: localFileUrl) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SwiftfulFirebaseStorage/Models/ImageCompressionOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCompressionOption.swift 3 | // 4 | // 5 | // Created by Nick Sarno on 4/10/24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import FirebaseStorage 11 | 12 | public enum ImageCompressionOption { 13 | case png, jpg(compressionQuality: CGFloat) 14 | 15 | func compress(image: UIImage) -> Data? { 16 | switch self { 17 | case .png: 18 | return image.pngData() 19 | case .jpg(let compressionQuality): 20 | return image.jpegData(compressionQuality: compressionQuality) 21 | } 22 | } 23 | 24 | var contentType: String { 25 | switch self { 26 | case .png: 27 | return "image/png" 28 | case .jpg: 29 | return "image/jpeg" 30 | } 31 | } 32 | 33 | var ext: String { 34 | switch self { 35 | case .png: 36 | return "png" 37 | case .jpg: 38 | return "jpg" 39 | } 40 | } 41 | 42 | var meta: StorageMetadata { 43 | switch self { 44 | case .png: 45 | let meta = StorageMetadata() 46 | meta.contentType = "image/png" 47 | return meta 48 | case .jpg: 49 | let meta = StorageMetadata() 50 | meta.contentType = "image/jpeg" 51 | return meta 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/SwiftfulFirebaseStorage/Services/FirebaseStorageUploaderService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseStorageUploaderService.swift 3 | // AIChatTest2 4 | // 5 | // Created by Nick Sarno on 3/23/24. 6 | // 7 | 8 | import Foundation 9 | import FirebaseStorage 10 | import UIKit 11 | 12 | struct FirebaseStorageUploaderService: StorageUploaderService { 13 | 14 | func saveImage(path: String, image: UIImage, compression: ImageCompressionOption) async throws -> URL { 15 | guard let data = compression.compress(image: image) else { 16 | throw FirebaseStorageError.unableToConvertToData 17 | } 18 | 19 | let ref = referenceForPath(path: path, ext: compression.ext) 20 | return try await save(data: data, reference: ref, meta: compression.meta) 21 | } 22 | 23 | func saveAudio(path: String, localFileUrl: URL) async throws -> URL { 24 | let audioData = try Data(contentsOf: localFileUrl) 25 | let ref = referenceForPath(path: path, ext: localFileUrl.pathExtension) 26 | let meta = StorageMetadata() 27 | meta.contentType = "audio/\(localFileUrl.pathExtension)" 28 | return try await save(data: audioData, reference: ref, meta: meta) 29 | } 30 | 31 | // Add video uploader 32 | 33 | // MARK: PRIVATE 34 | 35 | private func referenceForPath(path: String, ext: String) -> StorageReference { 36 | let path = "\(path).\(ext)" 37 | return Storage.storage().reference(withPath: path) 38 | } 39 | 40 | private func save(data: Data, reference: StorageReference, meta: StorageMetadata) async throws -> URL { 41 | let _ = try await reference.putDataAsync(data, metadata: meta) 42 | return try await reference.downloadURL() 43 | } 44 | 45 | enum FirebaseStorageError: Error { 46 | case unableToFindUrl, unableToConvertToData 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/SwiftfulFirebaseStorage/Services/MockStorageUploaderService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockImageUploaderService.swift 3 | // AIChatTest2 4 | // 5 | // Created by Nick Sarno on 3/23/24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | struct MockStorageUploaderService: StorageUploaderService { 12 | 13 | func saveImage(path: String, image: UIImage, compression: ImageCompressionOption) async throws -> URL { 14 | try await Task.sleep(nanoseconds: 2_000_000_000) 15 | return URL(string: "https://picsum.photos/600/600")! 16 | } 17 | 18 | func saveAudio(path: String, localFileUrl: URL) async throws -> URL { 19 | try await Task.sleep(nanoseconds: 2_000_000_000) 20 | return URL(string: "https://commondatastorage.googleapis.com/codeskulptor-demos/DDR_assets/Kangaroo_MusiQue_-_The_Neverwritten_Role_Playing_Game.mp3")! 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiftfulFirebaseStorage/Services/StorageUploaderService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StorageUploaderService.swift 3 | // AIChatTest2 4 | // 5 | // Created by Nick Sarno on 3/23/24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | protocol StorageUploaderService: Sendable { 12 | func saveImage(path: String, image: UIImage, compression: ImageCompressionOption) async throws -> URL 13 | func saveAudio(path: String, localFileUrl: URL) async throws -> URL 14 | } 15 | 16 | public enum StorageUploaderServiceOption { 17 | case mock, firebase 18 | 19 | var service: StorageUploaderService { 20 | switch self { 21 | case .mock: 22 | return MockStorageUploaderService() 23 | case .firebase: 24 | return FirebaseStorageUploaderService() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/SwiftfulFirebaseStorage/SwiftfulFirebaseStorage.swift: -------------------------------------------------------------------------------- 1 | // The Swift Programming Language 2 | // https://docs.swift.org/swift-book 3 | -------------------------------------------------------------------------------- /Tests/SwiftfulFirebaseStorageTests/SwiftfulFirebaseStorageTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SwiftfulFirebaseStorage 3 | 4 | final class SwiftfulFirebaseStorageTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | --------------------------------------------------------------------------------