From eecdb897e5b1f6477b86a8b65de0f4b3b64432c8 Mon Sep 17 00:00:00 2001 From: dan gullis Date: Tue, 20 Feb 2024 12:15:57 +0000 Subject: [PATCH] cloudinary setup --- .DS_Store | Bin 6148 -> 6148 bytes ImagePicker.swift | 70 +++++++++++++++ MobileAcebook.xcodeproj/project.pbxproj | 43 ++++++++++ .../xcshareddata/swiftpm/Package.resolved | 14 +++ MobileAcebook/CloudinaryConfig.swift | 17 ++++ MobileAcebook/CloudinaryImageView.swift | 34 ++++++++ MobileAcebook/UserPageView.swift | 40 +++++++++ MobileAcebook/WelcomePageView.swift | 81 +++++++++++------- 8 files changed, 270 insertions(+), 29 deletions(-) create mode 100644 ImagePicker.swift create mode 100644 MobileAcebook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 MobileAcebook/CloudinaryConfig.swift create mode 100644 MobileAcebook/CloudinaryImageView.swift create mode 100644 MobileAcebook/UserPageView.swift diff --git a/.DS_Store b/.DS_Store index 7fa1be84d357e72af5ed943ecd6fe9ee00e303cc..ff57f3846d7487706752138bc8e2304db79570ca 100644 GIT binary patch delta 179 zcmZoMXfc=|#>B!ku~2NHo}wrx0|Nsi1A_nqLk>eKgCj#SkWAXl$g-T7k#lkzQ}^V< zO!@*j>4w3{`MCu^wLsO4I+G7D8HwcPySOCfIjuc!79ODkb!K> qg~`0kyEgx4Ud6bXor9kP=p>-E-kYjcFi8fF0cPcDf7 delta 265 zcmZoMXfc=|#>B)qu~2NHo}wrR0|Nsi1A_nqLkNRELp*~skaXNwxSX*bBqPY+0py1R z#gSxzYCIV97%G4`mm$S7CqFqUCqD_O8fXZE9T4mN2Lm9Bfq{p?o52&P!y9Oh8xZ2s zw;rh01xX+8WC50TMxn_HtkRPYv(D1wVF+SyWpHG0VekdgdJL%yDOl}azuAv%C*#J3 iIHt|)9Q+(Wk8T#^_|80;U&ImQA&_5LHb;o8VFmyO-Z|U= diff --git a/ImagePicker.swift b/ImagePicker.swift new file mode 100644 index 00000000..b18c327e --- /dev/null +++ b/ImagePicker.swift @@ -0,0 +1,70 @@ +// +// ImagePicker.swift +// +// Created by Dan Gullis on 19/02/2024. +// + +//ObservableObject: A protocol that allows SwiftUI to observe changes to a class's properties. When a property marked with @Published changes, any views observing the object will be notified and re-rendered with the new data. + +//@Published: A property wrapper that marks a property as observable. When the value of a @Published property changes, it notifies any views that are observing the object to update their state. + +//@StateObject: A property wrapper used in SwiftUI to create and manage a reference to an observable object. It ensures that the object it wraps remains alive for use in the view where it is declared and in any views that share it. This is particularly important for objects that conform to the ObservableObject protocol. + +//The use of ObservableObject, @StateObject, and @Published in SwiftUI allows for a reactive programming model where the UI automatically updates in response to changes in the underlying data model. + +// this class uses SwiftUI and PhotosUI framework to select images form the user photo library +// uses cloudinary framework to upload selected image to cloudinary account + +import SwiftUI +import PhotosUI +import Cloudinary + +class ImagePicker: ObservableObject { + // variable to store image data once its loaded + @Published var imageData: Data? + // varible to represent the selected item - when this changes the didset block is executed + @Published var imageSelection: PhotosPickerItem? { + didSet { + if let imageSelection { + Task { + try await loadTransferable(from: imageSelection) + } + } + } + } + // asynchronous function loads image data from selected photoPickerItem + // if sucessful assigns image data to imageData variable + func loadTransferable(from imageSelection: PhotosPickerItem?) async throws { + do { + if let imageData = try await imageSelection?.loadTransferable(type: Data.self) { + self.imageData = imageData + } + } catch { + print(error.localizedDescription) + imageData=nil + } + } + + // uploads image data to cloudinary returns public ID of image on completion + func uploadToCloudinary(data: Data, completion: @escaping (String?) -> Void){ + let config = CLDConfiguration(cloudName: "dbhtb2iqe", secure: true) + let cloudinary = CLDCloudinary(configuration: config) + let uploader = cloudinary.createUploader() + + let uploadParams = CLDUploadRequestParams().setFolder("acebook-mobile") + + uploader.upload(data: data, uploadPreset: "acebook-user-image", params: uploadParams, completionHandler: {result, error in + if let error = error { + print("upload error \(error)") + completion(nil) + } else if let result = result, let imagePublicId = result.publicId { + completion(imagePublicId) + } else { + completion(nil) + } + + }) + + } + +} diff --git a/MobileAcebook.xcodeproj/project.pbxproj b/MobileAcebook.xcodeproj/project.pbxproj index 5506db3b..1a23c667 100644 --- a/MobileAcebook.xcodeproj/project.pbxproj +++ b/MobileAcebook.xcodeproj/project.pbxproj @@ -19,6 +19,11 @@ AE5D85E32AC9AFD2009680C6 /* MockAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5D85E22AC9AFD2009680C6 /* MockAuthenticationService.swift */; }; AE5D85E62AC9B077009680C6 /* AuthenticationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5D85E52AC9B077009680C6 /* AuthenticationServiceProtocol.swift */; }; AE5D85E82AC9B29A009680C6 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5D85E72AC9B29A009680C6 /* User.swift */; }; + F390892E2B83ABEA000D4C06 /* Cloudinary in Frameworks */ = {isa = PBXBuildFile; productRef = F390892D2B83ABEA000D4C06 /* Cloudinary */; }; + F39089572B84B5C3000D4C06 /* UserPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39089562B84B5C3000D4C06 /* UserPageView.swift */; }; + F39089592B84B5D9000D4C06 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39089582B84B5D9000D4C06 /* ImagePicker.swift */; }; + F390895B2B84CCEF000D4C06 /* CloudinaryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F390895A2B84CCEF000D4C06 /* CloudinaryConfig.swift */; }; + F390895D2B84CDCB000D4C06 /* CloudinaryImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F390895C2B84CDCB000D4C06 /* CloudinaryImageView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -54,6 +59,10 @@ AE5D85E22AC9AFD2009680C6 /* MockAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationService.swift; sourceTree = ""; }; AE5D85E52AC9B077009680C6 /* AuthenticationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProtocol.swift; sourceTree = ""; }; AE5D85E72AC9B29A009680C6 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + F39089562B84B5C3000D4C06 /* UserPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPageView.swift; sourceTree = ""; }; + F39089582B84B5D9000D4C06 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; + F390895A2B84CCEF000D4C06 /* CloudinaryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudinaryConfig.swift; sourceTree = ""; }; + F390895C2B84CDCB000D4C06 /* CloudinaryImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudinaryImageView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -61,6 +70,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F390892E2B83ABEA000D4C06 /* Cloudinary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,6 +94,7 @@ AE5D85A32AC8A221009680C6 = { isa = PBXGroup; children = ( + F39089582B84B5D9000D4C06 /* ImagePicker.swift */, AE5D85AE2AC8A221009680C6 /* MobileAcebook */, AE5D85BF2AC8A224009680C6 /* MobileAcebookTests */, AE5D85C92AC8A224009680C6 /* MobileAcebookUITests */, @@ -111,6 +122,9 @@ AE5D85B32AC8A224009680C6 /* Assets.xcassets */, AE5D85B52AC8A224009680C6 /* Preview Content */, AE5D85D92AC8A337009680C6 /* WelcomePageView.swift */, + F390895A2B84CCEF000D4C06 /* CloudinaryConfig.swift */, + F39089562B84B5C3000D4C06 /* UserPageView.swift */, + F390895C2B84CDCB000D4C06 /* CloudinaryImageView.swift */, ); path = MobileAcebook; sourceTree = ""; @@ -190,6 +204,9 @@ dependencies = ( ); name = MobileAcebook; + packageProductDependencies = ( + F390892D2B83ABEA000D4C06 /* Cloudinary */, + ); productName = MobileAcebook; productReference = AE5D85AC2AC8A221009680C6 /* MobileAcebook.app */; productType = "com.apple.product-type.application"; @@ -262,6 +279,9 @@ Base, ); mainGroup = AE5D85A32AC8A221009680C6; + packageReferences = ( + F390892C2B83ABEA000D4C06 /* XCRemoteSwiftPackageReference "cloudinary_ios" */, + ); productRefGroup = AE5D85AD2AC8A221009680C6 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -305,9 +325,13 @@ buildActionMask = 2147483647; files = ( AE5D85E12AC9AFA9009680C6 /* AuthenticationService.swift in Sources */, + F390895B2B84CCEF000D4C06 /* CloudinaryConfig.swift in Sources */, AE5D85E62AC9B077009680C6 /* AuthenticationServiceProtocol.swift in Sources */, + F39089572B84B5C3000D4C06 /* UserPageView.swift in Sources */, AE5D85B02AC8A221009680C6 /* MobileAcebookApp.swift in Sources */, + F39089592B84B5D9000D4C06 /* ImagePicker.swift in Sources */, AE5D85E82AC9B29A009680C6 /* User.swift in Sources */, + F390895D2B84CDCB000D4C06 /* CloudinaryImageView.swift in Sources */, AE5D85DA2AC8A337009680C6 /* WelcomePageView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -629,6 +653,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + F390892C2B83ABEA000D4C06 /* XCRemoteSwiftPackageReference "cloudinary_ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/cloudinary/cloudinary_ios.git"; + requirement = { + branch = master; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + F390892D2B83ABEA000D4C06 /* Cloudinary */ = { + isa = XCSwiftPackageProductDependency; + package = F390892C2B83ABEA000D4C06 /* XCRemoteSwiftPackageReference "cloudinary_ios" */; + productName = Cloudinary; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = AE5D85A42AC8A221009680C6 /* Project object */; } diff --git a/MobileAcebook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MobileAcebook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..a7bedbd5 --- /dev/null +++ b/MobileAcebook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "cloudinary_ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/cloudinary/cloudinary_ios.git", + "state" : { + "branch" : "master", + "revision" : "26d2c43324c623d2495a6c127d1a8da1549370ce" + } + } + ], + "version" : 2 +} diff --git a/MobileAcebook/CloudinaryConfig.swift b/MobileAcebook/CloudinaryConfig.swift new file mode 100644 index 00000000..1eacebbb --- /dev/null +++ b/MobileAcebook/CloudinaryConfig.swift @@ -0,0 +1,17 @@ +// +// CloudinaryConfig.swift +// MobileAcebook +// +// Created by Dan Gullis on 20/02/2024. +// + +import Cloudinary + +struct CloudinaryConfig { + static let cloudName = "dbhtb2iqe" + static let secure = true + + static var configuration: CLDConfiguration { + return CLDConfiguration(cloudName: cloudName, secure: secure) + } +} diff --git a/MobileAcebook/CloudinaryImageView.swift b/MobileAcebook/CloudinaryImageView.swift new file mode 100644 index 00000000..e495fd94 --- /dev/null +++ b/MobileAcebook/CloudinaryImageView.swift @@ -0,0 +1,34 @@ +// +// CloudinaryImageView.swift +// MobileAcebook +// +// Created by Dan Gullis on 20/02/2024. +// + +import SwiftUI +import Cloudinary + +func cloudinaryImageView(cloudinary: CLDCloudinary, imagePath: String) -> some View { + VStack { + if let cloudinaryURL = cloudinary.createUrl().generate(imagePath) { + if let url = URL(string: cloudinaryURL) { + AsyncImage(url: url) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 100, height: 100) + .clipped() + .clipShape(Circle()) + + } placeholder: { + ProgressView() // Show a progress view while the image is loading + } + } else { + // Placeholder view if the URL is not valid + Text("Invalid URL") + } + } else { + Text("Image not found") + } + } +} diff --git a/MobileAcebook/UserPageView.swift b/MobileAcebook/UserPageView.swift new file mode 100644 index 00000000..f0adde5f --- /dev/null +++ b/MobileAcebook/UserPageView.swift @@ -0,0 +1,40 @@ +// +// UserPageView.swift +// MobileAcebook +// +// Created by Dan Gullis on 20/02/2024. +// + +import SwiftUI +import Cloudinary + + +struct UserPageView: View { + let cloudinary = CLDCloudinary(configuration: CloudinaryConfig.configuration) + + var body: some View { + NavigationView{ + VStack { + HStack { + // replace image path with publicId of user image from sign up + cloudinaryImageView(cloudinary: cloudinary, imagePath: "acebook-mobile/Screenshot_2024-02-20_at_11.23.41_qd0jaw") + + Text("username") + } + + Spacer () + } + } + } + +} + + + +struct UserPageView_Previews: PreviewProvider { + static var previews: some View { + UserPageView() + } +} + + diff --git a/MobileAcebook/WelcomePageView.swift b/MobileAcebook/WelcomePageView.swift index 96006af9..176e81fa 100644 --- a/MobileAcebook/WelcomePageView.swift +++ b/MobileAcebook/WelcomePageView.swift @@ -6,41 +6,64 @@ // import SwiftUI +import PhotosUI +import Cloudinary + struct WelcomePageView: View { + @StateObject var imagePicker = ImagePicker() + @State private var uploadedImagePublicId: String? var body: some View { - ZStack { - VStack { - Spacer() - - Text("Welcome to Acebook!") - .font(.largeTitle) - .padding(.bottom, 20) - .accessibilityIdentifier("welcomeText") - - Spacer() - - Image("makers-logo") - .resizable() - .scaledToFit() - .frame(width: 200, height: 200) - .accessibilityIdentifier("makers-logo") - - Spacer() - - Button("Sign Up") { - // TODO: sign up logic + NavigationView { + ZStack { + VStack { + Spacer() + + Text("Welcome to Acebook!") + .font(.largeTitle) + .padding(.bottom, 20) + .accessibilityIdentifier("welcomeText") + + Spacer() + + Image("makers-logo") + .resizable() + .scaledToFit() + .frame(width: 200, height: 200) + .accessibilityIdentifier("makers-logo") + + Spacer() + + Button("Sign Up") { + // TODO: sign up logic + } + .accessibilityIdentifier("signUpButton") + + + NavigationLink("user page", destination: UserPageView()) + + PhotosPicker(selection: $imagePicker.imageSelection) { + Text("upload a photo") + } + .onChange(of: imagePicker.imageData) { imageData in + // This block is executed when image data is set in the ImagePicker + // You can update the uploadedImagePublicId here + if let imageData = imageData { + imagePicker.uploadToCloudinary(data: imageData) { imagePublicId in + uploadedImagePublicId = imagePublicId + } + } + } + Spacer() + } - .accessibilityIdentifier("signUpButton") - - Spacer() } } } -} - -struct WelcomePageView_Previews: PreviewProvider { - static var previews: some View { - WelcomePageView() + + struct WelcomePageView_Previews: PreviewProvider { + static var previews: some View { + WelcomePageView() + } } }