diff --git a/1.png b/1.png new file mode 100644 index 0000000..36614a6 Binary files /dev/null and b/1.png differ diff --git a/Gemfile.lock b/Gemfile.lock index 01d6b16..762932b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -233,7 +233,7 @@ GEM declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.4) + rexml (3.2.5) rouge (2.0.7) ruby-macho (1.4.0) rubyzip (1.3.0) diff --git a/Podfile b/Podfile deleted file mode 100644 index 373fed1..0000000 --- a/Podfile +++ /dev/null @@ -1,14 +0,0 @@ -platform :ios, '13.0' - -target 'Secretly' do - use_frameworks! - - # Pods for Secretly - pod 'SAMKeychain' - pod 'Alamofire', '5.0.2' - - target 'SecretlyTests' do - inherit! :search_paths - # Pods for testing - end -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 8b5c44b..0000000 --- a/Podfile.lock +++ /dev/null @@ -1,20 +0,0 @@ -PODS: - - Alamofire (5.0.2) - - SAMKeychain (1.5.3) - -DEPENDENCIES: - - Alamofire (= 5.0.2) - - SAMKeychain - -SPEC REPOS: - trunk: - - Alamofire - - SAMKeychain - -SPEC CHECKSUMS: - Alamofire: 3ba7a4db18b4f62c4a1c0e1cb39d7f3d52e10ada - SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - -PODFILE CHECKSUM: d53227b5dbed0dac69296a004ee17f10815a3552 - -COCOAPODS: 1.9.3 diff --git a/README.md b/README.md index 273864b..fb8ed7a 100644 --- a/README.md +++ b/README.md @@ -1 +1,42 @@ # Secretly + +## General Description + +This is a intermediate project of the IO's Applications Diploma. + +The original implementation app is from Luis Ezcurdia and this is the link: https://github.com/iOSLabUNAM/secretly + +## My Implementation + +My implementation is very simple, i implemented the like Button to post. + +### Files Changed/Created + +#### Models + +- Secretly/Models/Like.swift +- Secretly/Models/Post.swift + +#### Network + +- Secretly/Network/HttpResponse.swift +- Secretly/Network/RestClient.swift + +#### Services + +- Secretly/Services/LikeService.swift + +#### Views + +- Secretly/Views/PostCollectionViewCell.swift +- Secretly/Views/PostCollectionViewCell.xib +- Secretly/Views/PreviewPostView.swift + +### Videos/Photos + +If you would like to see my implementation, you can found in this video: + +**https://drive.google.com/file/d/163rqM2DQBxlpWhbHunrz1oNNwik1Hqgg/view?usp=sharing** + +![1](1.png) + diff --git a/Secretly.xcodeproj/project.pbxproj b/Secretly.xcodeproj/project.pbxproj index b949672..1e59f85 100644 --- a/Secretly.xcodeproj/project.pbxproj +++ b/Secretly.xcodeproj/project.pbxproj @@ -3,24 +3,68 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ - 77577E8C14CD01CE63615DDF /* Pods_Secretly.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E58E32D575E4ACBAC2179C3 /* Pods_Secretly.framework */; }; - B5544BF3D3901B3899D3007D /* Pods_SecretlyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B467B84EBC98A802B4B44E0F /* Pods_SecretlyTests.framework */; }; + 302B5845267E658E007133E6 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B583D267E658E007133E6 /* HttpResponse.swift */; }; + 302B5846267E658E007133E6 /* AmacaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B583E267E658E007133E6 /* AmacaConfig.swift */; }; + 302B5847267E658E007133E6 /* StatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B583F267E658E007133E6 /* StatusCode.swift */; }; + 302B5848267E658E007133E6 /* HttpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5840267E658E007133E6 /* HttpClient.swift */; }; + 302B5849267E658E007133E6 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5841267E658E007133E6 /* RequestError.swift */; }; + 302B584A267E658E007133E6 /* RestClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5842267E658E007133E6 /* RestClient.swift */; }; + 302B584B267E658E007133E6 /* RequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5843267E658E007133E6 /* RequestBuilder.swift */; }; + 302B5859267E720B007133E6 /* ResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B5850267E720B007133E6 /* ResponseError.swift */; }; + 302BB610267C658600FD74F5 /* Amaca.plist in Resources */ = {isa = PBXBuildFile; fileRef = 302BB60F267C658600FD74F5 /* Amaca.plist */; }; + 302BB61C267D7CC800FD74F5 /* PreviewPostVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302BB61B267D7CC800FD74F5 /* PreviewPostVIew.swift */; }; + 302BB61F267E316900FD74F5 /* PostInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302BB61D267E316900FD74F5 /* PostInputViewController.swift */; }; + 302BB620267E316900FD74F5 /* PostInputViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 302BB61E267E316900FD74F5 /* PostInputViewController.xib */; }; + 302BB622267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302BB621267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift */; }; + 302BB624267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302BB623267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift */; }; + 302BB626267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302BB625267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift */; }; + 30337955267536980066D94A /* PostInputViewController+CLLocationManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30337954267536980066D94A /* PostInputViewController+CLLocationManagerDelegate.swift */; }; + 30337957267536E30066D94A /* FeedCollectionViewController+UICollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30337956267536E30066D94A /* FeedCollectionViewController+UICollectionViewDelegate.swift */; }; + 303379592675371C0066D94A /* FeedCollectionViewController+UICollectionViewDataSource .swift in Sources */ = {isa = PBXBuildFile; fileRef = 303379582675371C0066D94A /* FeedCollectionViewController+UICollectionViewDataSource .swift */; }; + 3033795B267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3033795A267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift */; }; + 3033795D267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift in Sources */ = {isa = PBXBuildFile; fileRef = 3033795C267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift */; }; + 304E06C42674133D00A99128 /* String+isBlank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304E06C32674133D00A99128 /* String+isBlank.swift */; }; + 304E06C626741A5100A99128 /* UIColor+theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304E06C526741A5100A99128 /* UIColor+theme.swift */; }; + 304E06C826742BDA00A99128 /* CreatePostService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304E06C726742BDA00A99128 /* CreatePostService.swift */; }; + 304E06CA26742CC500A99128 /* FeedService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304E06C926742CC500A99128 /* FeedService.swift */; }; + 304E06CC2674442800A99128 /* UIImage+encodeBase64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304E06CB2674442800A99128 /* UIImage+encodeBase64.swift */; }; + 304E06CF267468DA00A99128 /* UIColor+Pastel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304E06CE267468DA00A99128 /* UIColor+Pastel.swift */; }; + 3072FBDF2680FA5A00B35C8C /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3072FBDE2680FA5A00B35C8C /* ImageProcessor.swift */; }; + 307A30542661ABD60020DF8B /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A30532661ABD60020DF8B /* Post.swift */; }; + 307A30562661AD480020DF8B /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A30552661AD480020DF8B /* Image.swift */; }; + 307A30582661AD540020DF8B /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A30572661AD540020DF8B /* User.swift */; }; + 307A305B2661B7A20020DF8B /* FeedCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A305A2661B7A20020DF8B /* FeedCollectionViewController.swift */; }; + 307A305E2661CD510020DF8B /* PostCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A305C2661CD510020DF8B /* PostCollectionViewCell.swift */; }; + 307A305F2661CD510020DF8B /* PostCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 307A305D2661CD510020DF8B /* PostCollectionViewCell.xib */; }; + 307A30622661E8A90020DF8B /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A30612661E8A90020DF8B /* UIColor+Hex.swift */; }; + 307A306526629B990020DF8B /* AuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A306426629B990020DF8B /* AuthorView.swift */; }; + 30B9B939268CA989007B1942 /* Nudity.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 30B9B938268CA989007B1942 /* Nudity.mlmodel */; }; + 30B9B93B268CA9E6007B1942 /* UIImage+PixelBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B9B93A268CA9E6007B1942 /* UIImage+PixelBuffer.swift */; }; + 30B9B93D268CB063007B1942 /* NudityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B9B93C268CB063007B1942 /* NudityChecker.swift */; }; + 30BC8B9D2662ACBC00F7E6A5 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BC8B9C2662ACBC00F7E6A5 /* ImageLoader.swift */; }; + 30BC8BA02662B8A700F7E6A5 /* StorageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BC8B9F2662B8A700F7E6A5 /* StorageType.swift */; }; + 30BC8BA22662BB0000F7E6A5 /* DataContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BC8BA12662BB0000F7E6A5 /* DataContainer.swift */; }; + 30BC8BA42662BDEF00F7E6A5 /* ImageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BC8BA32662BDEF00F7E6A5 /* ImageStore.swift */; }; + 30BC8BA62662C02300F7E6A5 /* CacheImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BC8BA52662C02300F7E6A5 /* CacheImage.swift */; }; + 30BC8BA82662CEBA00F7E6A5 /* Checksum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BC8BA72662CEBA00F7E6A5 /* Checksum.swift */; }; + 30C77CB0266AD69700A888DC /* CurrentUserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CAF266AD69700A888DC /* CurrentUserService.swift */; }; + 30C77CB4266AF47300A888DC /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CB3266AF47300A888DC /* Credentials.swift */; }; + 30C77CB6266AF48300A888DC /* CurrentUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CB5266AF48300A888DC /* CurrentUser.swift */; }; + 30C77CB8266BD44300A888DC /* CreatePostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CB7266BD44300A888DC /* CreatePostViewController.swift */; }; + 30FD0E722659645A006E309A /* Faker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FD0E712659645A006E309A /* Faker.swift */; }; + 66EBD99926D9B3C50045D18C /* Like.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66EBD99826D9B3C50045D18C /* Like.swift */; }; + 66EBD99B26DAB39D0045D18C /* LikeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66EBD99A26DAB39C0045D18C /* LikeService.swift */; }; E021984723FA35E00025C28E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984623FA35E00025C28E /* AppDelegate.swift */; }; E021984923FA35E00025C28E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984823FA35E00025C28E /* SceneDelegate.swift */; }; - E021984B23FA35E00025C28E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984A23FA35E00025C28E /* ViewController.swift */; }; + E021984B23FA35E00025C28E /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984A23FA35E00025C28E /* WelcomeViewController.swift */; }; E021984E23FA35E00025C28E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E021984C23FA35E00025C28E /* Main.storyboard */; }; E021985023FA35E20025C28E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E021984F23FA35E20025C28E /* Assets.xcassets */; }; E021985323FA35E20025C28E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E021985123FA35E20025C28E /* LaunchScreen.storyboard */; }; E021985E23FA35E20025C28E /* SecretlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021985D23FA35E20025C28E /* SecretlyTests.swift */; }; - E04C2BCB23FB86F7005FE010 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04C2BCA23FB86F7005FE010 /* Client.swift */; }; - E04C2BCD23FB8750005FE010 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04C2BCC23FB8750005FE010 /* HttpResponse.swift */; }; - E04C2BCF23FB8782005FE010 /* StatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04C2BCE23FB8782005FE010 /* StatusCode.swift */; }; - E04C2BD123FB87A2005FE010 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04C2BD023FB87A2005FE010 /* Credentials.swift */; }; - E04C2BD323FB88E8005FE010 /* RequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04C2BD223FB88E8005FE010 /* RequestBuilder.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,15 +78,61 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1E58E32D575E4ACBAC2179C3 /* Pods_Secretly.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Secretly.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 535CB75CE8D7BCAA112CD998 /* Pods-Secretly.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Secretly.debug.xcconfig"; path = "Target Support Files/Pods-Secretly/Pods-Secretly.debug.xcconfig"; sourceTree = ""; }; - 70DAE1AD8A20F2DE82F9BE9F /* Pods-SecretlyTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SecretlyTests.debug.xcconfig"; path = "Target Support Files/Pods-SecretlyTests/Pods-SecretlyTests.debug.xcconfig"; sourceTree = ""; }; - A426231322664F17788427AC /* Pods-SecretlyTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SecretlyTests.release.xcconfig"; path = "Target Support Files/Pods-SecretlyTests/Pods-SecretlyTests.release.xcconfig"; sourceTree = ""; }; - B467B84EBC98A802B4B44E0F /* Pods_SecretlyTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SecretlyTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 302B583D267E658E007133E6 /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = ""; }; + 302B583E267E658E007133E6 /* AmacaConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmacaConfig.swift; sourceTree = ""; }; + 302B583F267E658E007133E6 /* StatusCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusCode.swift; sourceTree = ""; }; + 302B5840267E658E007133E6 /* HttpClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpClient.swift; sourceTree = ""; }; + 302B5841267E658E007133E6 /* RequestError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestError.swift; sourceTree = ""; }; + 302B5842267E658E007133E6 /* RestClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestClient.swift; sourceTree = ""; }; + 302B5843267E658E007133E6 /* RequestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestBuilder.swift; sourceTree = ""; }; + 302B5850267E720B007133E6 /* ResponseError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseError.swift; sourceTree = ""; }; + 302BB60F267C658600FD74F5 /* Amaca.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Amaca.plist; sourceTree = ""; }; + 302BB61B267D7CC800FD74F5 /* PreviewPostVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewPostVIew.swift; sourceTree = ""; }; + 302BB61D267E316900FD74F5 /* PostInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostInputViewController.swift; sourceTree = ""; }; + 302BB61E267E316900FD74F5 /* PostInputViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PostInputViewController.xib; sourceTree = ""; }; + 302BB621267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostInputViewController+UIImagePickerControllerDelegate.swift"; sourceTree = ""; }; + 302BB623267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostInputViewController+UIColorPickerViewControllerDelegate.swift"; sourceTree = ""; }; + 302BB625267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostInputViewController+UITextFieldDelegate.swift"; sourceTree = ""; }; + 30337954267536980066D94A /* PostInputViewController+CLLocationManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostInputViewController+CLLocationManagerDelegate.swift"; sourceTree = ""; }; + 30337956267536E30066D94A /* FeedCollectionViewController+UICollectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedCollectionViewController+UICollectionViewDelegate.swift"; sourceTree = ""; }; + 303379582675371C0066D94A /* FeedCollectionViewController+UICollectionViewDataSource .swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedCollectionViewController+UICollectionViewDataSource .swift"; sourceTree = ""; }; + 3033795A267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift"; sourceTree = ""; }; + 3033795C267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift"; sourceTree = ""; }; + 304E06C32674133D00A99128 /* String+isBlank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+isBlank.swift"; sourceTree = ""; }; + 304E06C526741A5100A99128 /* UIColor+theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+theme.swift"; sourceTree = ""; }; + 304E06C726742BDA00A99128 /* CreatePostService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostService.swift; sourceTree = ""; }; + 304E06C926742CC500A99128 /* FeedService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedService.swift; sourceTree = ""; }; + 304E06CB2674442800A99128 /* UIImage+encodeBase64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+encodeBase64.swift"; sourceTree = ""; }; + 304E06CE267468DA00A99128 /* UIColor+Pastel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Pastel.swift"; sourceTree = ""; }; + 3072FBDE2680FA5A00B35C8C /* ImageProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessor.swift; sourceTree = ""; }; + 307A30532661ABD60020DF8B /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; + 307A30552661AD480020DF8B /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; + 307A30572661AD540020DF8B /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 307A305A2661B7A20020DF8B /* FeedCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCollectionViewController.swift; sourceTree = ""; }; + 307A305C2661CD510020DF8B /* PostCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCollectionViewCell.swift; sourceTree = ""; }; + 307A305D2661CD510020DF8B /* PostCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PostCollectionViewCell.xib; sourceTree = ""; }; + 307A30612661E8A90020DF8B /* UIColor+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Hex.swift"; sourceTree = ""; }; + 307A306426629B990020DF8B /* AuthorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorView.swift; sourceTree = ""; }; + 30B9B938268CA989007B1942 /* Nudity.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = Nudity.mlmodel; sourceTree = ""; }; + 30B9B93A268CA9E6007B1942 /* UIImage+PixelBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+PixelBuffer.swift"; sourceTree = ""; }; + 30B9B93C268CB063007B1942 /* NudityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NudityChecker.swift; sourceTree = ""; }; + 30BC8B9C2662ACBC00F7E6A5 /* ImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; }; + 30BC8B9F2662B8A700F7E6A5 /* StorageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageType.swift; sourceTree = ""; }; + 30BC8BA12662BB0000F7E6A5 /* DataContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataContainer.swift; sourceTree = ""; }; + 30BC8BA32662BDEF00F7E6A5 /* ImageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStore.swift; sourceTree = ""; }; + 30BC8BA52662C02300F7E6A5 /* CacheImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheImage.swift; sourceTree = ""; }; + 30BC8BA72662CEBA00F7E6A5 /* Checksum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checksum.swift; sourceTree = ""; }; + 30C77CAF266AD69700A888DC /* CurrentUserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentUserService.swift; sourceTree = ""; }; + 30C77CB3266AF47300A888DC /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; + 30C77CB5266AF48300A888DC /* CurrentUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentUser.swift; sourceTree = ""; }; + 30C77CB7266BD44300A888DC /* CreatePostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostViewController.swift; sourceTree = ""; }; + 30FD0E712659645A006E309A /* Faker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Faker.swift; sourceTree = ""; }; + 66EBD99826D9B3C50045D18C /* Like.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Like.swift; sourceTree = ""; }; + 66EBD99A26DAB39C0045D18C /* LikeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeService.swift; sourceTree = ""; }; E021984323FA35E00025C28E /* Secretly.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretly.app; sourceTree = BUILT_PRODUCTS_DIR; }; E021984623FA35E00025C28E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E021984823FA35E00025C28E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - E021984A23FA35E00025C28E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + E021984A23FA35E00025C28E /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; E021984D23FA35E00025C28E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; E021984F23FA35E20025C28E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E021985223FA35E20025C28E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -50,12 +140,6 @@ E021985923FA35E20025C28E /* SecretlyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecretlyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E021985D23FA35E20025C28E /* SecretlyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretlyTests.swift; sourceTree = ""; }; E021985F23FA35E20025C28E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E04C2BCA23FB86F7005FE010 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; - E04C2BCC23FB8750005FE010 /* HttpResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = ""; }; - E04C2BCE23FB8782005FE010 /* StatusCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCode.swift; sourceTree = ""; }; - E04C2BD023FB87A2005FE010 /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; - E04C2BD223FB88E8005FE010 /* RequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestBuilder.swift; sourceTree = ""; }; - FB507B2DA6EA68BCD7670416 /* Pods-Secretly.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Secretly.release.xcconfig"; path = "Target Support Files/Pods-Secretly/Pods-Secretly.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,7 +147,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 77577E8C14CD01CE63615DDF /* Pods_Secretly.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -71,31 +154,108 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B5544BF3D3901B3899D3007D /* Pods_SecretlyTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 61231DEC69AAA3BF634B529A /* Frameworks */ = { + 302BB614267C6BF500FD74F5 /* Frameworks */ = { isa = PBXGroup; children = ( - 1E58E32D575E4ACBAC2179C3 /* Pods_Secretly.framework */, - B467B84EBC98A802B4B44E0F /* Pods_SecretlyTests.framework */, ); name = Frameworks; sourceTree = ""; }; - 98435EFB57AE03747F167DCC /* Pods */ = { + 307A30592661B73A0020DF8B /* ViewControllers */ = { isa = PBXGroup; children = ( - 535CB75CE8D7BCAA112CD998 /* Pods-Secretly.debug.xcconfig */, - FB507B2DA6EA68BCD7670416 /* Pods-Secretly.release.xcconfig */, - 70DAE1AD8A20F2DE82F9BE9F /* Pods-SecretlyTests.debug.xcconfig */, - A426231322664F17788427AC /* Pods-SecretlyTests.release.xcconfig */, + E021984A23FA35E00025C28E /* WelcomeViewController.swift */, + 30C77CB7266BD44300A888DC /* CreatePostViewController.swift */, + 307A305A2661B7A20020DF8B /* FeedCollectionViewController.swift */, + 30337956267536E30066D94A /* FeedCollectionViewController+UICollectionViewDelegate.swift */, + 303379582675371C0066D94A /* FeedCollectionViewController+UICollectionViewDataSource .swift */, + 3033795A267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift */, + 3033795C267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift */, + 302BB61D267E316900FD74F5 /* PostInputViewController.swift */, + 302BB61E267E316900FD74F5 /* PostInputViewController.xib */, + 30337954267536980066D94A /* PostInputViewController+CLLocationManagerDelegate.swift */, + 302BB621267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift */, + 302BB623267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift */, + 302BB625267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + 307A30602661E88C0020DF8B /* Utils */ = { + isa = PBXGroup; + children = ( + 30B9B93C268CB063007B1942 /* NudityChecker.swift */, + 3072FBDE2680FA5A00B35C8C /* ImageProcessor.swift */, + 30BC8B9C2662ACBC00F7E6A5 /* ImageLoader.swift */, + 30BC8BA72662CEBA00F7E6A5 /* Checksum.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 307A306326629B590020DF8B /* Views */ = { + isa = PBXGroup; + children = ( + 307A305C2661CD510020DF8B /* PostCollectionViewCell.swift */, + 307A305D2661CD510020DF8B /* PostCollectionViewCell.xib */, + 307A306426629B990020DF8B /* AuthorView.swift */, + 302BB61B267D7CC800FD74F5 /* PreviewPostVIew.swift */, + ); + path = Views; + sourceTree = ""; + }; + 30BC8B9E2662B85200F7E6A5 /* Storage */ = { + isa = PBXGroup; + children = ( + 30BC8BA32662BDEF00F7E6A5 /* ImageStore.swift */, + 30BC8BA52662C02300F7E6A5 /* CacheImage.swift */, + 30BC8BA12662BB0000F7E6A5 /* DataContainer.swift */, + 30BC8B9F2662B8A700F7E6A5 /* StorageType.swift */, ); - path = Pods; + path = Storage; + sourceTree = ""; + }; + 30C77CAE266AD65C00A888DC /* Services */ = { + isa = PBXGroup; + children = ( + 30C77CAF266AD69700A888DC /* CurrentUserService.swift */, + 304E06C726742BDA00A99128 /* CreatePostService.swift */, + 304E06C926742CC500A99128 /* FeedService.swift */, + 66EBD99A26DAB39C0045D18C /* LikeService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 30E7709B268FC62C00EAD9A8 /* Extensions */ = { + isa = PBXGroup; + children = ( + 304E06C32674133D00A99128 /* String+isBlank.swift */, + 307A30612661E8A90020DF8B /* UIColor+Hex.swift */, + 304E06C526741A5100A99128 /* UIColor+theme.swift */, + 304E06CB2674442800A99128 /* UIImage+encodeBase64.swift */, + 30B9B93A268CA9E6007B1942 /* UIImage+PixelBuffer.swift */, + 304E06CE267468DA00A99128 /* UIColor+Pastel.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 30FD0E702659643F006E309A /* Models */ = { + isa = PBXGroup; + children = ( + 30FD0E712659645A006E309A /* Faker.swift */, + 307A30532661ABD60020DF8B /* Post.swift */, + 307A30552661AD480020DF8B /* Image.swift */, + 307A30572661AD540020DF8B /* User.swift */, + 30C77CB3266AF47300A888DC /* Credentials.swift */, + 30C77CB5266AF48300A888DC /* CurrentUser.swift */, + 66EBD99826D9B3C50045D18C /* Like.swift */, + ); + path = Models; sourceTree = ""; }; E021983A23FA35E00025C28E = { @@ -104,8 +264,7 @@ E021984523FA35E00025C28E /* Secretly */, E021985C23FA35E20025C28E /* SecretlyTests */, E021984423FA35E00025C28E /* Products */, - 98435EFB57AE03747F167DCC /* Pods */, - 61231DEC69AAA3BF634B529A /* Frameworks */, + 302BB614267C6BF500FD74F5 /* Frameworks */, ); sourceTree = ""; }; @@ -121,15 +280,22 @@ E021984523FA35E00025C28E /* Secretly */ = { isa = PBXGroup; children = ( + 30C77CAE266AD65C00A888DC /* Services */, + 307A306326629B590020DF8B /* Views */, + 307A30592661B73A0020DF8B /* ViewControllers */, + 30FD0E702659643F006E309A /* Models */, E04C2BC923FB7A5A005FE010 /* Network */, + 30BC8B9E2662B85200F7E6A5 /* Storage */, + 307A30602661E88C0020DF8B /* Utils */, + 30E7709B268FC62C00EAD9A8 /* Extensions */, E021984623FA35E00025C28E /* AppDelegate.swift */, E021984823FA35E00025C28E /* SceneDelegate.swift */, - E021984A23FA35E00025C28E /* ViewController.swift */, E021984C23FA35E00025C28E /* Main.storyboard */, E021984F23FA35E20025C28E /* Assets.xcassets */, E021985123FA35E20025C28E /* LaunchScreen.storyboard */, E021985423FA35E20025C28E /* Info.plist */, - E04C2BD023FB87A2005FE010 /* Credentials.swift */, + 302BB60F267C658600FD74F5 /* Amaca.plist */, + 30B9B938268CA989007B1942 /* Nudity.mlmodel */, ); path = Secretly; sourceTree = ""; @@ -146,10 +312,14 @@ E04C2BC923FB7A5A005FE010 /* Network */ = { isa = PBXGroup; children = ( - E04C2BCA23FB86F7005FE010 /* Client.swift */, - E04C2BCC23FB8750005FE010 /* HttpResponse.swift */, - E04C2BCE23FB8782005FE010 /* StatusCode.swift */, - E04C2BD223FB88E8005FE010 /* RequestBuilder.swift */, + 302B583E267E658E007133E6 /* AmacaConfig.swift */, + 302B5842267E658E007133E6 /* RestClient.swift */, + 302B5840267E658E007133E6 /* HttpClient.swift */, + 302B583D267E658E007133E6 /* HttpResponse.swift */, + 302B5850267E720B007133E6 /* ResponseError.swift */, + 302B5843267E658E007133E6 /* RequestBuilder.swift */, + 302B5841267E658E007133E6 /* RequestError.swift */, + 302B583F267E658E007133E6 /* StatusCode.swift */, ); path = Network; sourceTree = ""; @@ -161,17 +331,17 @@ isa = PBXNativeTarget; buildConfigurationList = E021986223FA35E20025C28E /* Build configuration list for PBXNativeTarget "Secretly" */; buildPhases = ( - 906178C7BA797602607A4AB8 /* [CP] Check Pods Manifest.lock */, E021983F23FA35E00025C28E /* Sources */, E021984023FA35E00025C28E /* Frameworks */, E021984123FA35E00025C28E /* Resources */, - A39AE415FEF9AE479FF66FB2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Secretly; + packageProductDependencies = ( + ); productName = Secretly; productReference = E021984323FA35E00025C28E /* Secretly.app */; productType = "com.apple.product-type.application"; @@ -180,7 +350,6 @@ isa = PBXNativeTarget; buildConfigurationList = E021986523FA35E20025C28E /* Build configuration list for PBXNativeTarget "SecretlyTests" */; buildPhases = ( - 7CCB06C1F7A41BB644EB8086 /* [CP] Check Pods Manifest.lock */, E021985523FA35E20025C28E /* Sources */, E021985623FA35E20025C28E /* Frameworks */, E021985723FA35E20025C28E /* Resources */, @@ -238,7 +407,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 307A305F2661CD510020DF8B /* PostCollectionViewCell.xib in Resources */, + 302BB610267C658600FD74F5 /* Amaca.plist in Resources */, E021985323FA35E20025C28E /* LaunchScreen.storyboard in Resources */, + 302BB620267E316900FD74F5 /* PostInputViewController.xib in Resources */, E021985023FA35E20025C28E /* Assets.xcassets in Resources */, E021984E23FA35E00025C28E /* Main.storyboard in Resources */, ); @@ -253,83 +425,62 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 7CCB06C1F7A41BB644EB8086 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SecretlyTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 906178C7BA797602607A4AB8 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Secretly-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - A39AE415FEF9AE479FF66FB2 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Secretly/Pods-Secretly-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Secretly/Pods-Secretly-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Secretly/Pods-Secretly-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ E021983F23FA35E00025C28E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E04C2BCD23FB8750005FE010 /* HttpResponse.swift in Sources */, - E021984B23FA35E00025C28E /* ViewController.swift in Sources */, - E04C2BCF23FB8782005FE010 /* StatusCode.swift in Sources */, - E04C2BD123FB87A2005FE010 /* Credentials.swift in Sources */, + 30C77CB8266BD44300A888DC /* CreatePostViewController.swift in Sources */, + E021984B23FA35E00025C28E /* WelcomeViewController.swift in Sources */, + 3072FBDF2680FA5A00B35C8C /* ImageProcessor.swift in Sources */, + 302BB622267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift in Sources */, + 66EBD99926D9B3C50045D18C /* Like.swift in Sources */, + 302B5845267E658E007133E6 /* HttpResponse.swift in Sources */, + 302B584A267E658E007133E6 /* RestClient.swift in Sources */, + 302B5848267E658E007133E6 /* HttpClient.swift in Sources */, + 30B9B93D268CB063007B1942 /* NudityChecker.swift in Sources */, + 30BC8B9D2662ACBC00F7E6A5 /* ImageLoader.swift in Sources */, + 302B5859267E720B007133E6 /* ResponseError.swift in Sources */, + 307A30622661E8A90020DF8B /* UIColor+Hex.swift in Sources */, + 307A30542661ABD60020DF8B /* Post.swift in Sources */, + 304E06C626741A5100A99128 /* UIColor+theme.swift in Sources */, + 304E06C826742BDA00A99128 /* CreatePostService.swift in Sources */, E021984723FA35E00025C28E /* AppDelegate.swift in Sources */, - E04C2BCB23FB86F7005FE010 /* Client.swift in Sources */, + 30337955267536980066D94A /* PostInputViewController+CLLocationManagerDelegate.swift in Sources */, + 307A30562661AD480020DF8B /* Image.swift in Sources */, + 307A30582661AD540020DF8B /* User.swift in Sources */, + 302B5849267E658E007133E6 /* RequestError.swift in Sources */, + 302B5846267E658E007133E6 /* AmacaConfig.swift in Sources */, + 302BB61C267D7CC800FD74F5 /* PreviewPostVIew.swift in Sources */, + 3033795D267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift in Sources */, + 30BC8BA82662CEBA00F7E6A5 /* Checksum.swift in Sources */, + 30FD0E722659645A006E309A /* Faker.swift in Sources */, + 30B9B939268CA989007B1942 /* Nudity.mlmodel in Sources */, + 30337957267536E30066D94A /* FeedCollectionViewController+UICollectionViewDelegate.swift in Sources */, + 307A305E2661CD510020DF8B /* PostCollectionViewCell.swift in Sources */, + 302BB626267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift in Sources */, + 30BC8BA02662B8A700F7E6A5 /* StorageType.swift in Sources */, + 307A305B2661B7A20020DF8B /* FeedCollectionViewController.swift in Sources */, + 304E06CC2674442800A99128 /* UIImage+encodeBase64.swift in Sources */, + 30BC8BA62662C02300F7E6A5 /* CacheImage.swift in Sources */, + 302B5847267E658E007133E6 /* StatusCode.swift in Sources */, + 302B584B267E658E007133E6 /* RequestBuilder.swift in Sources */, + 307A306526629B990020DF8B /* AuthorView.swift in Sources */, + 30C77CB4266AF47300A888DC /* Credentials.swift in Sources */, + 30BC8BA22662BB0000F7E6A5 /* DataContainer.swift in Sources */, E021984923FA35E00025C28E /* SceneDelegate.swift in Sources */, - E04C2BD323FB88E8005FE010 /* RequestBuilder.swift in Sources */, + 30C77CB0266AD69700A888DC /* CurrentUserService.swift in Sources */, + 30C77CB6266AF48300A888DC /* CurrentUser.swift in Sources */, + 302BB624267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift in Sources */, + 304E06CF267468DA00A99128 /* UIColor+Pastel.swift in Sources */, + 66EBD99B26DAB39D0045D18C /* LikeService.swift in Sources */, + 304E06C42674133D00A99128 /* String+isBlank.swift in Sources */, + 30BC8BA42662BDEF00F7E6A5 /* ImageStore.swift in Sources */, + 3033795B267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift in Sources */, + 30B9B93B268CA9E6007B1942 /* UIImage+PixelBuffer.swift in Sources */, + 303379592675371C0066D94A /* FeedCollectionViewController+UICollectionViewDataSource .swift in Sources */, + 304E06CA26742CC500A99128 /* FeedService.swift in Sources */, + 302BB61F267E316900FD74F5 /* PostInputViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -422,7 +573,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -477,7 +628,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -489,7 +640,6 @@ }; E021986323FA35E20025C28E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 535CB75CE8D7BCAA112CD998 /* Pods-Secretly.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; @@ -508,7 +658,6 @@ }; E021986423FA35E20025C28E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FB507B2DA6EA68BCD7670416 /* Pods-Secretly.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; @@ -527,14 +676,13 @@ }; E021986623FA35E20025C28E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 70DAE1AD8A20F2DE82F9BE9F /* Pods-SecretlyTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = D3XL2U7DQC; INFOPLIST_FILE = SecretlyTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -550,14 +698,13 @@ }; E021986723FA35E20025C28E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A426231322664F17788427AC /* Pods-SecretlyTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = D3XL2U7DQC; INFOPLIST_FILE = SecretlyTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.2; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Secretly.xcworkspace/contents.xcworkspacedata b/Secretly.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 57c163f..0000000 --- a/Secretly.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Secretly.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Secretly/Amaca.plist similarity index 73% rename from Secretly.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Secretly/Amaca.plist index 18d9810..962ad1f 100644 --- a/Secretly.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/Secretly/Amaca.plist @@ -2,7 +2,7 @@ - IDEDidComputeMac32BitWarning - + host + https://secretlyapi.herokuapp.com diff --git a/Secretly/AppDelegate.swift b/Secretly/AppDelegate.swift index 702265b..70c17d7 100644 --- a/Secretly/AppDelegate.swift +++ b/Secretly/AppDelegate.swift @@ -11,6 +11,9 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + StorageType.cache.ensureExists() + StorageType.permanent.ensureExists() + // Override point for customization after application launch. return true } diff --git a/Secretly/Assets.xcassets/Contents.json b/Secretly/Assets.xcassets/Contents.json index da4a164..73c0059 100644 --- a/Secretly/Assets.xcassets/Contents.json +++ b/Secretly/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Secretly/Assets.xcassets/logo-white.imageset/Contents.json b/Secretly/Assets.xcassets/logo-white.imageset/Contents.json new file mode 100644 index 0000000..4f332ce --- /dev/null +++ b/Secretly/Assets.xcassets/logo-white.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Icon-512.jpg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/logo-white.imageset/Icon-512.jpg b/Secretly/Assets.xcassets/logo-white.imageset/Icon-512.jpg new file mode 100644 index 0000000..c621d0f Binary files /dev/null and b/Secretly/Assets.xcassets/logo-white.imageset/Icon-512.jpg differ diff --git a/Secretly/Assets.xcassets/logo-white.imageset/logo.png b/Secretly/Assets.xcassets/logo-white.imageset/logo.png new file mode 100644 index 0000000..35ceb30 Binary files /dev/null and b/Secretly/Assets.xcassets/logo-white.imageset/logo.png differ diff --git a/Secretly/Assets.xcassets/tacocat/Contents.json b/Secretly/Assets.xcassets/tacocat/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Secretly/Assets.xcassets/tacocat/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/tacocat/animated.imageset/Contents.json b/Secretly/Assets.xcassets/tacocat/animated.imageset/Contents.json new file mode 100644 index 0000000..e8d14bf --- /dev/null +++ b/Secretly/Assets.xcassets/tacocat/animated.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "animated.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/tacocat/animated.imageset/animated.png b/Secretly/Assets.xcassets/tacocat/animated.imageset/animated.png new file mode 100644 index 0000000..18b4a61 Binary files /dev/null and b/Secretly/Assets.xcassets/tacocat/animated.imageset/animated.png differ diff --git a/Secretly/Assets.xcassets/tacocat/explosive.imageset/Contents.json b/Secretly/Assets.xcassets/tacocat/explosive.imageset/Contents.json new file mode 100644 index 0000000..2f253d1 --- /dev/null +++ b/Secretly/Assets.xcassets/tacocat/explosive.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "explosive.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/tacocat/explosive.imageset/explosive.png b/Secretly/Assets.xcassets/tacocat/explosive.imageset/explosive.png new file mode 100644 index 0000000..660f708 Binary files /dev/null and b/Secretly/Assets.xcassets/tacocat/explosive.imageset/explosive.png differ diff --git a/Secretly/Assets.xcassets/tacocat/kawai.imageset/Contents.json b/Secretly/Assets.xcassets/tacocat/kawai.imageset/Contents.json new file mode 100644 index 0000000..4a3b1c2 --- /dev/null +++ b/Secretly/Assets.xcassets/tacocat/kawai.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "kawai.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/tacocat/kawai.imageset/kawai.jpeg b/Secretly/Assets.xcassets/tacocat/kawai.imageset/kawai.jpeg new file mode 100644 index 0000000..81b7575 Binary files /dev/null and b/Secretly/Assets.xcassets/tacocat/kawai.imageset/kawai.jpeg differ diff --git a/Secretly/Assets.xcassets/tacocat/toon.imageset/Contents.json b/Secretly/Assets.xcassets/tacocat/toon.imageset/Contents.json new file mode 100644 index 0000000..0f4358d --- /dev/null +++ b/Secretly/Assets.xcassets/tacocat/toon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cute-toon.jpeg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/tacocat/toon.imageset/cute-toon.jpeg b/Secretly/Assets.xcassets/tacocat/toon.imageset/cute-toon.jpeg new file mode 100644 index 0000000..93623ca Binary files /dev/null and b/Secretly/Assets.xcassets/tacocat/toon.imageset/cute-toon.jpeg differ diff --git a/Secretly/Assets.xcassets/theme/Black chocolate.colorset/Contents.json b/Secretly/Assets.xcassets/theme/Black chocolate.colorset/Contents.json new file mode 100644 index 0000000..1bcc549 --- /dev/null +++ b/Secretly/Assets.xcassets/theme/Black chocolate.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.094", + "green" : "0.118", + "red" : "0.145" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.094", + "green" : "0.118", + "red" : "0.145" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/theme/Cadet gray.colorset/Contents.json b/Secretly/Assets.xcassets/theme/Cadet gray.colorset/Contents.json new file mode 100644 index 0000000..0f21cca --- /dev/null +++ b/Secretly/Assets.xcassets/theme/Cadet gray.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xBA", + "green" : "0xA3", + "red" : "0x88" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.729", + "green" : "0.639", + "red" : "0.533" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/theme/Champagne Pink.colorset/Contents.json b/Secretly/Assets.xcassets/theme/Champagne Pink.colorset/Contents.json new file mode 100644 index 0000000..57614f5 --- /dev/null +++ b/Secretly/Assets.xcassets/theme/Champagne Pink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xBD", + "green" : "0xCA", + "red" : "0xDB" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.741", + "green" : "0.792", + "red" : "0.859" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/theme/Contents.json b/Secretly/Assets.xcassets/theme/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Secretly/Assets.xcassets/theme/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/theme/Yale blue.colorset/Contents.json b/Secretly/Assets.xcassets/theme/Yale blue.colorset/Contents.json new file mode 100644 index 0000000..0cb476a --- /dev/null +++ b/Secretly/Assets.xcassets/theme/Yale blue.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x85", + "green" : "0x4D", + "red" : "0x19" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.522", + "green" : "0.302", + "red" : "0.098" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Base.lproj/LaunchScreen.storyboard b/Secretly/Base.lproj/LaunchScreen.storyboard index 868ada3..4d082d6 100644 --- a/Secretly/Base.lproj/LaunchScreen.storyboard +++ b/Secretly/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -15,31 +16,21 @@ - - - + + + - - - - - - - - + + - - - - @@ -48,7 +39,9 @@ - - + + + + diff --git a/Secretly/Base.lproj/Main.storyboard b/Secretly/Base.lproj/Main.storyboard index 5a5c5ec..4ed5b13 100644 --- a/Secretly/Base.lproj/Main.storyboard +++ b/Secretly/Base.lproj/Main.storyboard @@ -1,42 +1,195 @@ - + - + + + + - + - + - - + + + - + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretly/Credentials.swift b/Secretly/Credentials.swift index 08b7b52..aafbbdd 100644 --- a/Secretly/Credentials.swift +++ b/Secretly/Credentials.swift @@ -7,7 +7,6 @@ // import Foundation -import SAMKeychain enum Credentials { case userToken @@ -15,14 +14,14 @@ enum Credentials { func get() -> String? { switch self { case .userToken: - return SAMKeychain.password(forService: "mx.unam.secretly", account: "userToken") + return nil } } func set(value: String) -> Bool { switch self { case .userToken: - SAMKeychain.setPassword(value, forService: "mx.unam.secretly", account: "userToken") + return true } return true } @@ -30,7 +29,7 @@ enum Credentials { func destroy() -> Bool { switch self { case .userToken: - SAMKeychain.deletePassword(forService: "mx.unam.secretly", account: "userToken") + return true } return true } diff --git a/Secretly/Extensions/String+isBlank.swift b/Secretly/Extensions/String+isBlank.swift new file mode 100644 index 0000000..8cf8e9a --- /dev/null +++ b/Secretly/Extensions/String+isBlank.swift @@ -0,0 +1,15 @@ +// +// String+isBlank.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +extension String { + var isBlank: Bool { + return self.isEmpty || self.trimmingCharacters(in: .whitespaces) == "" + } +} diff --git a/Secretly/Extensions/UIColor+Hex.swift b/Secretly/Extensions/UIColor+Hex.swift new file mode 100644 index 0000000..5299417 --- /dev/null +++ b/Secretly/Extensions/UIColor+Hex.swift @@ -0,0 +1,50 @@ +// +// UIColor+Hex.swift +// Secretly +// +// Created by Luis Ezcurdia on 28/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension UIColor { + public convenience init?(hex: String) { + let r, g, b, a: CGFloat + + if hex.hasPrefix("#") { + let start = hex.index(hex.startIndex, offsetBy: 1) + var hexColor = String(hex[start...]) + + if hexColor.count == 6 { + hexColor = "\(hexColor)FF" + } + if hexColor.count == 8 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 + g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 + b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 + a = CGFloat(hexNumber & 0x000000ff) / 255 + + self.init(red: r, green: g, blue: b, alpha: a) + return + } + } + } + + return nil + } + + var hexString: String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + let rgb: Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 + return String(format: "#%06x", rgb) + } +} diff --git a/Secretly/Extensions/UIColor+Pastel.swift b/Secretly/Extensions/UIColor+Pastel.swift new file mode 100644 index 0000000..dade0c9 --- /dev/null +++ b/Secretly/Extensions/UIColor+Pastel.swift @@ -0,0 +1,22 @@ +// +// UIColor+Pastel.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension UIColor { + func pastel() -> UIColor { + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + + self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + return UIColor(hue: hue, saturation: CGFloat(0.70), brightness: brightness, alpha: alpha) + } +} diff --git a/Secretly/Extensions/UIColor+theme.swift b/Secretly/Extensions/UIColor+theme.swift new file mode 100644 index 0000000..339a5e4 --- /dev/null +++ b/Secretly/Extensions/UIColor+theme.swift @@ -0,0 +1,16 @@ +// +// UIColor+theme.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension UIColor { + static let text = UIColor(named: "Black chocolate")! + static let tint = UIColor(named: "Champagne pink") + static let foreground = UIColor(named: "Yale blue")! + static let background = UIColor(named: "Cadet gray")! +} diff --git a/Secretly/Extensions/UIImage+PixelBuffer.swift b/Secretly/Extensions/UIImage+PixelBuffer.swift new file mode 100644 index 0000000..78b5b28 --- /dev/null +++ b/Secretly/Extensions/UIImage+PixelBuffer.swift @@ -0,0 +1,51 @@ +// +// UIImage+PixelBuffer.swift +// Secretly +// +// Created by Luis Ezcurdia on 30/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension UIImage { + func resize(to newSize: CGSize) -> UIImage? { + guard self.size != newSize else { return self } + + UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0) + self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) + + defer { UIGraphicsEndImageContext() } + return UIGraphicsGetImageFromCurrentImageContext() + } + + func pixelBuffer() -> CVPixelBuffer? { + let width = Int(self.size.width) + let height = Int(self.size.height) + + let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary + var pixelBuffer: CVPixelBuffer? + let status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32ARGB, attrs, &pixelBuffer) + guard status == kCVReturnSuccess else { + return nil + } + + CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) + let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!) + + let rgbColorSpace = CGColorSpaceCreateDeviceRGB() + guard let context = CGContext(data: pixelData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else { + return nil + } + + context.translateBy(x: 0, y: CGFloat(height)) + context.scaleBy(x: 1.0, y: -1.0) + + UIGraphicsPushContext(context) + self.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) + UIGraphicsPopContext() + CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) + + return pixelBuffer + } +} diff --git a/Secretly/Extensions/UIImage+encodeBase64.swift b/Secretly/Extensions/UIImage+encodeBase64.swift new file mode 100644 index 0000000..3f6f801 --- /dev/null +++ b/Secretly/Extensions/UIImage+encodeBase64.swift @@ -0,0 +1,16 @@ +// +// UIImage+encodeBase64.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension UIImage { + func encodeBase64() -> String? { + guard let data = self.jpegData(compressionQuality: 0.85) else { return nil } + return "data:image/jpeg;base64,\(data.base64EncodedString())" + } +} diff --git a/Secretly/Info.plist b/Secretly/Info.plist index b5f9c07..154ea82 100644 --- a/Secretly/Info.plist +++ b/Secretly/Info.plist @@ -2,6 +2,12 @@ + NSPhotoLibraryUsageDescription + To add some context to your posts + NSCameraUsageDescription + To add some context to your posts + NSLocationWhenInUseUsageDescription + To georeference posts within a region CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/Secretly/Models/Credentials.swift b/Secretly/Models/Credentials.swift new file mode 100644 index 0000000..c5b10c4 --- /dev/null +++ b/Secretly/Models/Credentials.swift @@ -0,0 +1,44 @@ +// +// Credentials.swift +// Secretly +// +// Created by Luis Ezcurdia on 04/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct Credentials: Restable { + let username: String? + let password: String? + let token: String? + + var id: String { + get { "" } + } + + init(username: String, password: String) { + self.username = username + self.password = password + self.token = nil + } + + enum CodingKeys: String, CodingKey { + case token + case username + case password + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + token = try container.decode(String.self, forKey: .token) + username = nil + password = nil + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(password, forKey: .password) + try container.encode(username, forKey: .username) + } +} diff --git a/Secretly/Models/CurrentUser.swift b/Secretly/Models/CurrentUser.swift new file mode 100644 index 0000000..fb28cfe --- /dev/null +++ b/Secretly/Models/CurrentUser.swift @@ -0,0 +1,43 @@ +// +// CurrentUser.swift +// Secretly +// +// Created by Luis Ezcurdia on 04/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +class CurrentUser { + static func load() -> CurrentUser? { + guard let username = UserDefaults.standard.string(forKey: "secretly.username") else { + return nil + } + return CurrentUser(username: username) + } + + let username: String + + init(username: String) { + self.username = username + UserDefaults.standard.set(username, forKey: "secretly.username") + } + + func credentials() -> Credentials { + if let psswd = password() { + return Credentials(username: username, password: psswd) + } else { + return Credentials(username: username, password: genPassword()) + } + } + + private func password() -> String? { + return UserDefaults.standard.string(forKey: "secretly.password") + } + + private func genPassword() -> String { + let newPsswd = UUID().uuidString + UserDefaults.standard.set(newPsswd, forKey: "secretly.password") + return newPsswd + } +} diff --git a/Secretly/Models/Faker.swift b/Secretly/Models/Faker.swift new file mode 100644 index 0000000..92f1256 --- /dev/null +++ b/Secretly/Models/Faker.swift @@ -0,0 +1,18 @@ +// +// Faker.swift +// Secretly +// +// Created by Luis Ezcurdia on 22/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct Faker { + let username: String + let email: String +} + +extension Faker: Restable { + var id: String { "" } +} diff --git a/Secretly/Models/Image.swift b/Secretly/Models/Image.swift new file mode 100644 index 0000000..da115b5 --- /dev/null +++ b/Secretly/Models/Image.swift @@ -0,0 +1,14 @@ +// +// Image.swift +// Secretly +// +// Created by Luis Ezcurdia on 28/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct Image: Codable { + let smallUrl: String + let mediumUrl: String +} diff --git a/Secretly/Models/Like.swift b/Secretly/Models/Like.swift new file mode 100644 index 0000000..d568f84 --- /dev/null +++ b/Secretly/Models/Like.swift @@ -0,0 +1,29 @@ +// +// Like.swift +// Secretly +// +// Created by Hernán Galileo Cabrera Garibaldi on 27/08/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// +/* + [ + { + "author": { + "name": "fugiat", + "id": "8a41d660-04fc-05f4-232c-f607a462d841" + }, + "created_at": "1954-11-24T06:42:25.586Z", + "updated_at": "1970-08-07T11:31:10.325Z" + } + ] + */ + +import Foundation + +struct Like: Restable { + var id: Int + let likeableType: String + let likeableId: Int + let createdAt, updatedAt: String + var user: User +} diff --git a/Secretly/Models/Post.swift b/Secretly/Models/Post.swift new file mode 100644 index 0000000..8a5a15f --- /dev/null +++ b/Secretly/Models/Post.swift @@ -0,0 +1,66 @@ +// +// Post.swift +// Secretly +// +// Created by Luis Ezcurdia on 28/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import UIKit + +struct Post: Restable { + var id: Int? + let content: String + let backgroundColor: String + let image: Image? + var imageData: String? + let user: User? + let commentsCount: Int? + let latitude: Double? + let longitude: Double? + let createdAt: Date? + let updatedAt: Date? +// New + var likesCount: Int? + var liked: Bool? + + init(content: String, backgroundColor: String, latitude: Double? = nil, longitude: Double? = nil, image: UIImage? = nil) { + self.content = content + self.backgroundColor = backgroundColor + self.id = nil + self.image = nil + self.imageData = image?.encodeBase64() + self.latitude = latitude + self.longitude = longitude + self.user = nil + self.commentsCount = nil + self.createdAt = nil + self.updatedAt = nil +// New + self.likesCount = nil + self.liked = nil + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(content, forKey: .content) + try container.encode(backgroundColor, forKey: .backgroundColor) + try container.encode(imageData, forKey: .imageData) + try container.encode(latitude, forKey: .latitude) + try container.encode(longitude, forKey: .longitude) +// New + try container.encode(liked, forKey: .liked) + try container.encode(likesCount, forKey: .likesCount) + } +// New function to Like + mutating func newLike(state:String){ + if state == "inc"{ + likesCount = (likesCount ?? 0) + 1 + liked = true + } else if state == "dec"{ + likesCount = (likesCount ?? 0) - 1 + liked = false + } + } +} diff --git a/Secretly/Models/User.swift b/Secretly/Models/User.swift new file mode 100644 index 0000000..bf83970 --- /dev/null +++ b/Secretly/Models/User.swift @@ -0,0 +1,14 @@ +// +// User.swift +// Secretly +// +// Created by Luis Ezcurdia on 28/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct User: Codable { + let username: String + let avatarUrl: String +} diff --git a/Secretly/Network/AmacaConfig.swift b/Secretly/Network/AmacaConfig.swift new file mode 100644 index 0000000..173da18 --- /dev/null +++ b/Secretly/Network/AmacaConfig.swift @@ -0,0 +1,37 @@ +// +// AmacaConfig.swift +// Secretly +// +// Created by Luis Ezcurdia on 18/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct AmacaConfig { + static let shared = AmacaConfig() + var host: String { + values["host"] as! String + } + var httpClient: HttpClient { + HttpClient(session: URLSession.shared, baseUrl: host) + } + + var apiToken: String? { + get { + UserDefaults.standard.string(forKey: "amaca.apitoken") + } + } + + func setApiToken(_ value: String) { + UserDefaults.standard.set(value, forKey: "amaca.apitoken") + } + + private var filepath: String { + return Bundle.main.path(forResource: "Amaca", ofType: "plist")! + } + + private var values: NSDictionary { + return NSDictionary(contentsOfFile: filepath)! + } +} diff --git a/Secretly/Network/Client.swift b/Secretly/Network/Client.swift deleted file mode 100644 index 55ed6e1..0000000 --- a/Secretly/Network/Client.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// Client.swift -// Secretly -// -// Created by LuisE on 2/17/20. -// Copyright © 2020 3zcurdia. All rights reserved. -// - -import Foundation - -class Client { - static let fakestagram = Client(session: URLSession.shared, baseUrl: "https://secretlyapi.herokuapp.com") - let session: URLSession - let baseUrl: String - - init(session: URLSession, baseUrl: String) { - self.session = session - self.baseUrl = baseUrl - } - - typealias SuccessfulResponse = (Data?) -> Void - - func get(path: String, success: @escaping SuccessfulResponse) { - request(method: "get", path: path, body: nil, success: success) - } - - func post(path: String, body: Data?, success: @escaping SuccessfulResponse) { - request(method: "post", path: path, body: body, success: success) - } - - func put(path: String, body: Data?, success: @escaping SuccessfulResponse) { - request(method: "put", path: path, body: body, success: success) - } - - func delete(path: String, success: @escaping SuccessfulResponse) { - request(method: "delete", path: path, body: nil, success: success) - } - - private func request(method: String, path: String, body: Data?, success: @escaping SuccessfulResponse) { - guard let req = buildRequest(method: method, path: path, body: body) else { - debugPrint("Invalid request") - return - } - - session.dataTask(with: req) { (data, response, error) in - if let error = error { - debugPrint(error) - return - } - - let response = HttpResponse(response: response) - if response.isSuccessful() { - success(data) - } else { - #if DEBUG - debugPrint(response.status) - if let data = data { - let error = String(data: data, encoding: .utf8) - debugPrint(error) - } - #endif - } - }.resume() - } - - private func buildRequest(method: String, path: String, body: Data?) -> URLRequest? { - var builder = RequestBuilder(baseUrl: self.baseUrl) - builder.method = method - builder.path = path - builder.body = body - if let token = Credentials.userToken.get() { - builder.headers = ["Authorization": "Bearer \(token)"] - } - return builder.request() - } -} diff --git a/Secretly/Network/HttpClient.swift b/Secretly/Network/HttpClient.swift new file mode 100644 index 0000000..0554709 --- /dev/null +++ b/Secretly/Network/HttpClient.swift @@ -0,0 +1,60 @@ +// +// HttpClient.swift +// Secretly +// +// Created by LuisE on 2/17/20. +// Copyright © 2020 3zcurdia. All rights reserved. +// + +import Foundation + +struct HttpClient { + let session: URLSession + let baseUrl: String + + typealias ResultResponse = (Result) -> Void + + func get(path: String, complete: @escaping ResultResponse) { + request(method: "get", path: path, body: nil, complete: complete) + } + + func post(path: String, body: Data?, complete: @escaping ResultResponse) { + request(method: "post", path: path, body: body, complete: complete) + } + + func put(path: String, body: Data?, complete: @escaping ResultResponse) { + request(method: "put", path: path, body: body, complete: complete) + } + + func delete(path: String, complete: @escaping ResultResponse) { + request(method: "delete", path: path, body: nil, complete: complete) + } + + private func request(method: String, path: String, body: Data?, complete: @escaping ResultResponse) { + guard let req = buildRequest(method: method, path: path, body: body) else { + complete(.failure(RequestError.invalidRequest)) + return + } + + session.dataTask(with: req) { (data, response, error) in + if let error = error { + complete(.failure(error)) + return + } + let response = HttpResponse(response: response) + let result = response.result(for: data) + complete(result) + }.resume() + } + + private func buildRequest(method: String, path: String, body: Data?) -> URLRequest? { + var builder = RequestBuilder(baseUrl: self.baseUrl) + builder.method = method + builder.path = path + builder.body = body + if let token = AmacaConfig.shared.apiToken { + builder.headers = ["Authorization": "Bearer \(token)"] + } + return builder.request() + } +} diff --git a/Secretly/Network/HttpResponse.swift b/Secretly/Network/HttpResponse.swift index 755bdef..b5e78fb 100644 --- a/Secretly/Network/HttpResponse.swift +++ b/Secretly/Network/HttpResponse.swift @@ -19,7 +19,13 @@ struct HttpResponse { return StatusCode(rawValue: self.httpUrlResponse.statusCode) } - func isSuccessful() -> Bool { - return status == StatusCode.success + func result(for data: Data?) -> Result { +// return status.result().map { _ in data } +// New If Statement = if data is valid data, return this information + if let data = data, !data.isEmpty { + return status.result().map { _ in data } + } else { + return status.result().map { _ in nil } + } } } diff --git a/Secretly/Network/RequestError.swift b/Secretly/Network/RequestError.swift new file mode 100644 index 0000000..bb6b68d --- /dev/null +++ b/Secretly/Network/RequestError.swift @@ -0,0 +1,20 @@ +// +// RequestError.swift +// Secretly +// +// Created by Luis Ezcurdia on 22/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +enum RequestError: Error, Titleable { + case invalidRequest + + var title: String { + switch self { + case .invalidRequest: + return "Invalid Request" + } + } +} diff --git a/Secretly/Network/ResponseError.swift b/Secretly/Network/ResponseError.swift new file mode 100644 index 0000000..0e37350 --- /dev/null +++ b/Secretly/Network/ResponseError.swift @@ -0,0 +1,30 @@ +// +// ResponseError.swift +// Secretly +// +// Created by Luis Ezcurdia on 22/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +protocol Titleable { + var title: String { get } +} + +enum ResponseError: Error, Titleable { + case invalidResponse + case clientError + case serverError + + var title: String { + switch self { + case .invalidResponse: + return "Invalid Response" + case .clientError: + return "Client error" + case .serverError: + return "Internal Server error" + } + } +} diff --git a/Secretly/Network/RestClient.swift b/Secretly/Network/RestClient.swift new file mode 100644 index 0000000..8c79f80 --- /dev/null +++ b/Secretly/Network/RestClient.swift @@ -0,0 +1,108 @@ +// +// CodableClient.swift +// Secretly +// +// Created by Luis Ezcurdia on 22/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +// REST API -> +// GET /api/v1/posts(.:format) #list +// GET - Likes /fakestagram-api.herokuapp.com/api/posts/%7BpostId%7D/likes +// POST /api/v1/posts(.:format) #create +// GET /api/v1/posts/:id(.:format) #show +// PUT /api/v1/posts/:id(.:format) #update +// DELETE /api/v1/posts/:id(.:format) #destroy + +import Foundation + +typealias Restable = Codable & Identifiable + +struct RestClient { + let client: HttpClient + let path: String + + public var decoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .iso8601 + return decoder + }() + public var encoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + return encoder + }() + + func list(complete: @escaping (Result<[T], Error>) -> Void) { + client.get(path: path) { result in + let newResult = result.flatMap { parseList(data: $0) } + complete(newResult) + } + } + + func show(complete: @escaping (Result) -> Void) { + show("", complete: complete) + } + + func show(_ identifier: String, complete: @escaping (Result) -> Void) { + client.get(path: "\(path)/\(identifier)") { result in + let newResult = result.flatMap { parse(data: $0) } + complete(newResult) + } + } +// New Function + func create(complete: @escaping (Result)-> Void) throws { + client.post(path: path, body: nil) { result in + let result2 = result.flatMap { parse(data: $0)} + complete(result2) + } + } + + func create(model: T, complete: @escaping (Result) -> Void) throws { + let data = try encoder.encode(model) + client.post(path: path, body: data) { result in + let newResult = result.flatMap { parse(data: $0) } + complete(newResult) + } + } + + func update(model: T, complete: @escaping (Result) -> Void) throws { + let data = try encoder.encode(model) + client.put(path: "\(path)/\(model.id)", body: data) { result in + let newResult = result.flatMap { parse(data: $0) } + complete(newResult) + } + } + + func delete(model: T, complete: @escaping (Result) -> Void) { + client.delete(path: "\(path)/\(model.id)") { result in + let newResult = result.flatMap { parse(data: $0) } + complete(newResult) + } + } +// New Function + + func delete(complete: @escaping (Result) -> Void) { + client.delete(path: path) { result in + let result2 = result.flatMap { parse(data: $0) } + complete(result2) + } + } + + private func parseList(data: Data?) -> Result<[T], Error> { + if let data = data { + return Result { try self.decoder.decode([T].self, from: data) } + } else { + return .success([]) + } + } + + private func parse(data: Data?) -> Result { + if let data = data { + return Result { try self.decoder.decode(T.self, from: data) } + } else { + return .success(nil) + } + } +} diff --git a/Secretly/Network/StatusCode.swift b/Secretly/Network/StatusCode.swift index ccaf0a8..ead95c7 100644 --- a/Secretly/Network/StatusCode.swift +++ b/Secretly/Network/StatusCode.swift @@ -33,4 +33,17 @@ enum StatusCode: Int { self = .unkown } } + + func result() -> Result { + switch self { + case .success: + return .success(self.rawValue) + case .clientError: + return .failure(ResponseError.clientError) + case .serverError: + return .failure(ResponseError.serverError) + default: + return .failure(ResponseError.invalidResponse) + } + } } diff --git a/Secretly/Nudity.mlmodel b/Secretly/Nudity.mlmodel new file mode 100644 index 0000000..fd69d0c Binary files /dev/null and b/Secretly/Nudity.mlmodel differ diff --git a/Secretly/Services/CreatePostService.swift b/Secretly/Services/CreatePostService.swift new file mode 100644 index 0000000..1607aeb --- /dev/null +++ b/Secretly/Services/CreatePostService.swift @@ -0,0 +1,23 @@ +// +// CreatePostService.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct CreatePostService { + private var endpoint: RestClient + + init() { + endpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/posts") + } + + func create(_ model: Post, complete: @escaping (Result) -> Void ) { + try? endpoint.create(model: model) { result in + DispatchQueue.main.async { complete(result) } + } + } +} diff --git a/Secretly/Services/CurrentUserService.swift b/Secretly/Services/CurrentUserService.swift new file mode 100644 index 0000000..6579aa9 --- /dev/null +++ b/Secretly/Services/CurrentUserService.swift @@ -0,0 +1,52 @@ +// +// CurrentUserService.swift +// Secretly +// +// Created by Luis Ezcurdia on 04/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct CurrentUserService { + private var fakeEndpoint: RestClient + private var signInEndpoint: RestClient + private var signUpEndpoint: RestClient + + init() { + fakeEndpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/fake") + signInEndpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/sign_in") + signUpEndpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/sign_up") + } + + func auth(_ completion: @escaping (CurrentUser) -> Void) { + if let currentUser = CurrentUser.load() { + signIn(currentUser) { completion($0) } + return + } + fakeEndpoint.show { result in + guard let fake = try? result.get() else { return } + let currentUser = CurrentUser(username: fake.username) + signUp(currentUser) { completion($0) } + } + } + + private func signUp(_ currentUser: CurrentUser, completion: @escaping (CurrentUser) -> Void) { + try? signUpEndpoint.create(model: currentUser.credentials()) { result in + storeToken(result) + DispatchQueue.main.async { completion(currentUser) } + } + } + + private func signIn(_ currentUser: CurrentUser, completion: @escaping (CurrentUser) -> Void) { + try? signInEndpoint.create(model: currentUser.credentials()) { result in + storeToken(result) + DispatchQueue.main.async { completion(currentUser) } + } + } + + fileprivate func storeToken(_ result: Result) { + guard let res = try? result.get(), let token = res.token else { return } + AmacaConfig.shared.setApiToken(token) + } +} diff --git a/Secretly/Services/FeedService.swift b/Secretly/Services/FeedService.swift new file mode 100644 index 0000000..aa2b49e --- /dev/null +++ b/Secretly/Services/FeedService.swift @@ -0,0 +1,24 @@ +// +// FeedService.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct FeedService { + private var endpoint: RestClient + + init() { + endpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/posts") + } + + func load(completion: @escaping ([Post]) -> Void) { + endpoint.list { result in + guard let posts = try? result.get() else { return } + DispatchQueue.main.async { completion(posts) } + } + } +} diff --git a/Secretly/Services/LikeService.swift b/Secretly/Services/LikeService.swift new file mode 100644 index 0000000..ef0ad68 --- /dev/null +++ b/Secretly/Services/LikeService.swift @@ -0,0 +1,36 @@ +// +// LikeService.swift +// Secretly +// +// Created by Hernán Galileo Cabrera Garibaldi on 28/08/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct LikeService { + let endPoint: RestClient? + var active = false + + init(post: Post?) { + guard let post = post, let postId = post.id else { + self.endPoint = nil + return + } + self.endPoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/posts/\(postId)/likes") + self.active = post.liked ?? false + } + + mutating func action(complete: @escaping (Result)-> Void){ + if self.active { + endPoint?.delete{ result in + DispatchQueue.main.async { complete(result) } + } + } else { + try? endPoint?.create{ result in + DispatchQueue.main.async { complete(result) } + } + } + active = !active + } +} diff --git a/Secretly/Storage/CacheImage.swift b/Secretly/Storage/CacheImage.swift new file mode 100644 index 0000000..ae58a19 --- /dev/null +++ b/Secretly/Storage/CacheImage.swift @@ -0,0 +1,24 @@ +// +// CacheImage.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import UIKit + +class CacheImage { + static let shared = CacheImage() + private var memCache = NSCache() + + func read(key: String) -> UIImage? { + return memCache.object(forKey: key as NSString) + } + + func write(key: String, image: UIImage?) { + guard let image = image else { return } + memCache.setObject(image, forKey: key as NSString) + } +} diff --git a/Secretly/Storage/DataContainer.swift b/Secretly/Storage/DataContainer.swift new file mode 100644 index 0000000..884619d --- /dev/null +++ b/Secretly/Storage/DataContainer.swift @@ -0,0 +1,43 @@ +// +// DataContainer.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +enum DataContainer { + case cache + case permanent + + var baseUrl: URL { + switch self { + case .cache: + return StorageType.cache.url + case .permanent: + return StorageType.permanent.url + } + } + + private func urlFor(filename: String) -> URL { + var url = baseUrl + url.appendPathComponent(filename) + return url + } + + func read(_ filename: String) -> Data? { + return try? Data(contentsOf: urlFor(filename: filename)) + } + + func write(_ filename: String, data: Data) -> Bool { + do { + try data.write(to: urlFor(filename: filename)) + return true + } catch let err { + debugPrint("Error while writting(\(filename): \(err.localizedDescription)") + return false + } + } +} diff --git a/Secretly/Storage/ImageStore.swift b/Secretly/Storage/ImageStore.swift new file mode 100644 index 0000000..3168f9b --- /dev/null +++ b/Secretly/Storage/ImageStore.swift @@ -0,0 +1,38 @@ +// +// ImageStore.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import UIKit + +enum ImageStore { + case cache + case permanent + + var container: DataContainer { + switch self { + case .cache: + return DataContainer.cache + case .permanent: + return DataContainer.permanent + } + } + + func read(_ filename: String) -> UIImage? { + if let img = CacheImage.shared.read(key: filename) { return img } + guard let data = container.read(filename) else { + return nil + } + return UIImage(data: data) + } + + func write(_ filename: String, image: UIImage) -> Bool { + guard let data = image.pngData() else { return false } + CacheImage.shared.write(key: filename, image: image) + return container.write(filename, data: data) + } +} diff --git a/Secretly/Storage/StorageType.swift b/Secretly/Storage/StorageType.swift new file mode 100644 index 0000000..64856e0 --- /dev/null +++ b/Secretly/Storage/StorageType.swift @@ -0,0 +1,47 @@ +// +// StorageType.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +enum StorageType { + case cache + case permanent + + var searchPathDirectory: FileManager.SearchPathDirectory { + switch self { + case .cache: + return .cachesDirectory + default: + return .documentDirectory + } + } + + var url: URL { + var url = FileManager.default.urls(for: searchPathDirectory, in: .userDomainMask).first! + let applicationPath = "mx.unam.ioslab.secretly.storage" + url.appendPathComponent(applicationPath) + return url + } + + var path: String { + return url.path + } + + func clear() { + try? FileManager.default.removeItem(at: url) + } + + func ensureExists() { + var isDir: ObjCBool = false + if FileManager.default.fileExists(atPath: path, isDirectory: &isDir) { + if isDir.boolValue { return } + clear() + } + try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil) + } +} diff --git a/Secretly/Utils/Checksum.swift b/Secretly/Utils/Checksum.swift new file mode 100644 index 0000000..4f9113c --- /dev/null +++ b/Secretly/Utils/Checksum.swift @@ -0,0 +1,18 @@ +// +// Checksum.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import CryptoKit + +struct Checksum { + static func sha256(_ content: String) -> String { + let data = content.data(using: .utf8)! + let digest = SHA256.hash(data: data) + return digest.description.components(separatedBy: " ")[2] + } +} diff --git a/Secretly/Utils/ImageLoader.swift b/Secretly/Utils/ImageLoader.swift new file mode 100644 index 0000000..2e2cc90 --- /dev/null +++ b/Secretly/Utils/ImageLoader.swift @@ -0,0 +1,28 @@ +// +// ImageLoader.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import UIKit + +struct ImageLoader { + static func load(_ stringUrl: String, completion: @escaping (UIImage) -> Void) { + let filename = Checksum.sha256(stringUrl) + if let img = ImageStore.cache.read(filename) { + completion(img) + return + } + + guard let url = URL(string: stringUrl) else { return } + DispatchQueue.global(qos: .background).async { + if let data = try? Data(contentsOf: url), let img = UIImage(data: data) { + DispatchQueue.main.async { completion(img) } + _ = ImageStore.cache.write(filename, image: img) + } + } + } +} diff --git a/Secretly/Utils/ImageProcessor.swift b/Secretly/Utils/ImageProcessor.swift new file mode 100644 index 0000000..7963805 --- /dev/null +++ b/Secretly/Utils/ImageProcessor.swift @@ -0,0 +1,50 @@ +// +// ImageProcessor.swift +// Secretly +// +// Created by Luis Ezcurdia on 21/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit +import CoreImage + +struct ImageProcessor { + let width: CGFloat + let heigth: CGFloat + + func process(_ image: UIImage, completion: @escaping (UIImage) -> Void) { + DispatchQueue.global(qos: .userInitiated).async { + guard let resized = resize(image, for: CGSize(width: width, height: heigth)), + let blured = blur(resized, radius: 30) + else { return } + DispatchQueue.main.async { + completion(UIImage(cgImage: blured)) + } + } + } + + private func resize(_ image: UIImage, for size: CGSize) -> CIImage? { + let renderer = UIGraphicsImageRenderer(size: size) + let resized = renderer.image { (_) in + image.draw(in: CGRect(origin: .zero, size: size)) + } + return CIImage(image: resized) + } + + private func blur(_ image: UIImage?, radius: CGFloat) -> CGImage? { + guard let image = image else { return nil } + return blur(CIImage(image: image), radius: radius) + } + + private func blur(_ image: CIImage?, radius: CGFloat) -> CGImage? { + guard let input = image, + let filter = CIFilter(name: "CIHexagonalPixellate") else { return nil } + let context = CIContext() + filter.setValue(input, forKey: kCIInputImageKey) + filter.setValue(CIVector(x: width/2, y: heigth/2), forKey: kCIInputCenterKey) + filter.setValue(radius, forKey: kCIInputScaleKey) + guard let result = filter.outputImage else { return nil } + return context.createCGImage(result, from: result.extent) + } +} diff --git a/Secretly/Utils/NudityChecker.swift b/Secretly/Utils/NudityChecker.swift new file mode 100644 index 0000000..04d7789 --- /dev/null +++ b/Secretly/Utils/NudityChecker.swift @@ -0,0 +1,43 @@ +// +// NudityChecker.swift +// Secretly +// +// Created by Luis Ezcurdia on 30/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +struct NSFWContentError: Error, Titleable { + var title: String { + get { "The image contains nudity" } + } + + var localizedDescription: String { + get { "The image has a \(prob)% of NSFW contnet." } + } + let prob: Double + + init(_ prob: Double?) { + self.prob = (prob ?? 0.0) * 100.0 + } +} + +struct NudityChecker { + let model = Nudity() + private let size = CGSize(width: 224, height: 224) + + func validate(_ image: UIImage) throws { + guard let buffer = image.resize(to: size)?.pixelBuffer() else { return } + + let result = try model.prediction(data: buffer) + #if DEBUG + debugPrint(result.classLabel) + debugPrint(result.prob) + #endif + + if result.classLabel == "NSFW" || (result.prob["NSFW"] ?? 0) > 0.55 { + throw NSFWContentError(result.prob["NSFW"]) + } + } +} diff --git a/Secretly/ViewController.swift b/Secretly/ViewController.swift deleted file mode 100644 index 9024806..0000000 --- a/Secretly/ViewController.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// ViewController.swift -// Secretly -// -// Created by LuisE on 2/16/20. -// Copyright © 2020 3zcurdia. All rights reserved. -// - -import UIKit -import Alamofire - -struct Faker: Decodable { - let email: String - let username: String -} - -class ViewController: UIViewController { - @IBOutlet weak var helloLbl: UILabel! - override func viewDidLoad() { - super.viewDidLoad() - AF.request("https://secretlyapi.herokuapp.com/api/v1/fake").responseDecodable(of: Faker.self) { [unowned self] response in - let fake = try? response.result.get() - DispatchQueue.main.async { - self.helloLbl.text = "Hello \(fake?.username ?? "Joe.Doe")!" - } - } - } -} diff --git a/Secretly/ViewControllers/CreatePostViewController.swift b/Secretly/ViewControllers/CreatePostViewController.swift new file mode 100644 index 0000000..4091252 --- /dev/null +++ b/Secretly/ViewControllers/CreatePostViewController.swift @@ -0,0 +1,59 @@ +// +// CreatePostViewController.swift +// Secretly +// +// Created by Luis Ezcurdia on 05/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +class CreatePostViewController: UIViewController { + @IBOutlet weak var contentField: UITextField! + @IBOutlet weak var colorField: UITextField! + + override func viewDidLoad() { + super.viewDidLoad() + colorField.text = "#3366CC" + } + + @IBAction + func createPost(_ sender: Any?) { + let post = Post(content: contentField.text!, backgroundColor: colorField.text!) + let postsEndpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/posts") + + do { + try postsEndpoint.create(model: post) { [unowned self] result in + switch result { + case .success(let post): + print("there is a new post \(post?.id ?? 0)") + case .failure(let err): + DispatchQueue.main.async { + self.errorAlert(err) + } + } + } + } catch let err { + self.errorAlert(err) + } + } + + func errorAlert(_ error: Error) { + let err = error as? Titleable + let alert = UIAlertController(title: (err?.title ?? "Error"), message: error.localizedDescription, preferredStyle: .alert) + let okAction = UIAlertAction(title: "Ok", style: .default) + alert.addAction(okAction) + self.present(alert, animated: true, completion: nil) + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift new file mode 100644 index 0000000..15100dd --- /dev/null +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift @@ -0,0 +1,26 @@ +// +// FeedCollectionViewController+UICollectionViewDataSource .swift +// Secretly +// +// Created by Luis Ezcurdia on 12/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension FeedCollectionViewController: UICollectionViewDataSource { + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return posts?.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PostCollectionViewCell.reuseIdentifier, for: indexPath) as! PostCollectionViewCell + + cell.post = self.posts?[indexPath.row] + return cell + } +} diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift new file mode 100644 index 0000000..2fb0c29 --- /dev/null +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift @@ -0,0 +1,16 @@ +// +// FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift +// Secretly +// +// Created by Luis Ezcurdia on 12/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension FeedCollectionViewController: UICollectionViewDataSourcePrefetching { + func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { + guard let indexPath = indexPaths.last else { return } + print("================\(indexPath.row)=================") + } +} diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegate.swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegate.swift new file mode 100644 index 0000000..2b373e7 --- /dev/null +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegate.swift @@ -0,0 +1,40 @@ +// +// FeedCollectionViewController+UICollectionViewDelegate.swift +// Secretly +// +// Created by Luis Ezcurdia on 12/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension FeedCollectionViewController: UICollectionViewDelegate { + /* + // Uncomment this method to specify if the specified item should be highlighted during tracking + override func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { + return true + } + */ + + /* + // Uncomment this method to specify if the specified item should be selected + override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + return true + } + */ + + /* + // Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item + override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool { + return false + } + + override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool { + return false + } + + override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) { + + } + */ +} diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift new file mode 100644 index 0000000..e6c7ccc --- /dev/null +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift @@ -0,0 +1,15 @@ +// +// FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift +// Secretly +// +// Created by Luis Ezcurdia on 12/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension FeedCollectionViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: collectionView.bounds.width, height: 300) + } +} diff --git a/Secretly/ViewControllers/FeedCollectionViewController.swift b/Secretly/ViewControllers/FeedCollectionViewController.swift new file mode 100644 index 0000000..9180d42 --- /dev/null +++ b/Secretly/ViewControllers/FeedCollectionViewController.swift @@ -0,0 +1,61 @@ +// +// FeedCollectionViewController.swift +// Secretly +// +// Created by Luis Ezcurdia on 28/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit +import CoreLocation + +class FeedCollectionViewController: UIViewController { + let feedService = FeedService() + var posts: [Post]? { + didSet { + self.collectionView.reloadData() + self.refreshControl.endRefreshing() + } + } + + let refreshControl = UIRefreshControl() + let postInputView = PostInputViewController() + + @IBOutlet weak var collectionView: UICollectionView! + + override func viewDidLoad() { + super.viewDidLoad() + setupCollectionView() + loadPosts() + } + + func setupCollectionView() { + postInputView.delegate = self + collectionView.delegate = self + collectionView.dataSource = self + collectionView.prefetchDataSource = self + let nib = UINib(nibName: String(describing: PostCollectionViewCell.self), bundle: nil) + collectionView.register(nib, forCellWithReuseIdentifier: PostCollectionViewCell.reuseIdentifier) + collectionView.addSubview(refreshControl) + + refreshControl.addTarget(self, action: #selector(self.loadPosts), for: UIControl.Event.valueChanged) + } + + @objc + func loadPosts() { + feedService.load { [unowned self] posts in self.posts = posts } + } + + @IBAction + func onTapAdd(_ sender: Any) { + postInputView.clear() + present(postInputView, animated: true) + } +} + +extension FeedCollectionViewController: PostInputViewDelegate { + func didCreatePost(post: Post?) { + guard let upost = post else { return } + self.posts?.insert(upost, at: 0) + } +} diff --git a/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift b/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift new file mode 100644 index 0000000..119d7c9 --- /dev/null +++ b/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift @@ -0,0 +1,17 @@ +// +// PostInputViewController+UICollectionViewDataSource.swift +// Secretly +// +// Created by Luis Ezcurdia on 12/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit +import CoreLocation + +extension PostInputViewController: CLLocationManagerDelegate { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let lastLocation = locations.last else { return } + self.currentLocation = lastLocation.coordinate + } +} diff --git a/Secretly/ViewControllers/PostInputViewController+UIColorPickerViewControllerDelegate.swift b/Secretly/ViewControllers/PostInputViewController+UIColorPickerViewControllerDelegate.swift new file mode 100644 index 0000000..33a10dd --- /dev/null +++ b/Secretly/ViewControllers/PostInputViewController+UIColorPickerViewControllerDelegate.swift @@ -0,0 +1,15 @@ +// +// PostInputViewController+UIColorPickerViewControllerDelegate.swift +// Secretly +// +// Created by Luis Ezcurdia on 19/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension PostInputViewController: UIColorPickerViewControllerDelegate { + func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) { + previewPost.backgroundColor = viewController.selectedColor.pastel() + } +} diff --git a/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift b/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift new file mode 100644 index 0000000..e6ac6cb --- /dev/null +++ b/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift @@ -0,0 +1,29 @@ +// +// PostInputViewController+UIImagePickerControllerDelegate.swift +// Secretly +// +// Created by Luis Ezcurdia on 19/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit +import AVFoundation + +extension PostInputViewController: UIImagePickerControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + if let img = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { + do { + try NudityChecker().validate(img) + self.previewPost.image = img + ImageProcessor(width: 512, heigth: 512).process(img) { + self.previewPost.image = $0 + self.previewPost.backgroundColor = .black + } + } catch let err { + self.previewPost.image = nil + DispatchQueue.main.async { self.errorAlert(err) } + } + picker.dismiss(animated: true) + } + } +} diff --git a/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift b/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift new file mode 100644 index 0000000..5ed93a9 --- /dev/null +++ b/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift @@ -0,0 +1,16 @@ +// +// PostInputViewController+UITextFieldDelegate.swift +// Secretly +// +// Created by Luis Ezcurdia on 19/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +extension PostInputViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + self.previewPost.content = textField.text + return true + } +} diff --git a/Secretly/ViewControllers/PostInputViewController.swift b/Secretly/ViewControllers/PostInputViewController.swift new file mode 100644 index 0000000..676ce35 --- /dev/null +++ b/Secretly/ViewControllers/PostInputViewController.swift @@ -0,0 +1,165 @@ +// +// PostInputViewController.swift +// Secretly +// +// Created by Luis Ezcurdia on 19/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit +import CoreLocation + +struct EmptyPostError: Error {} + +protocol PostInputViewDelegate { + func didCreatePost(post: Post?) +} + +class PostInputViewController: UIViewController, UINavigationControllerDelegate { + let createPostService = CreatePostService() + @IBOutlet weak var contentTxt: UITextField! + @IBOutlet weak var imgPickerButton: UIButton! + @IBOutlet weak var colorPickerButton: UIButton! + @IBOutlet weak var previewPost: PreviewPostView! + var source: Post? + var delegate: PostInputViewDelegate? + + let locationManager = CLLocationManager() + var currentLocation: CLLocationCoordinate2D? + + let imgPicker: UIImagePickerController = { + let view = UIImagePickerController() + view.mediaTypes = ["public.image"] + return view + }() + let colorPicker = UIColorPickerViewController() + + override func viewDidLoad() { + super.viewDidLoad() + imgPickerButton.layer.cornerRadius = 8 + colorPickerButton.layer.cornerRadius = 8 + colorPicker.delegate = self + imgPicker.delegate = self + contentTxt.delegate = self + enableBasicLocationServices() + } + + func enableBasicLocationServices() { + locationManager.delegate = self + switch locationManager.authorizationStatus { + case .notDetermined: + locationManager.requestWhenInUseAuthorization() + case .restricted, .denied: + print("Disable location features") + case .authorizedWhenInUse, .authorizedAlways: + print("Enable location features") + @unknown default: + fatalError() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + locationManager.startUpdatingLocation() + } + + override func viewWillDisappear(_ animated: Bool) { + locationManager.stopUpdatingLocation() + super.viewWillDisappear(animated) + } + + @IBAction + func didTapPublish(_ sender: Any) { + do { + try self.createPost() + self.dismiss(animated: true) + } catch let err { + self.errorAlert(err) + } + } + + @IBAction + func didTapCancel(_ sender: Any) { + self.dismiss(animated: true) + } + + @IBAction + func didTapImagePicker(_ sender: Any) { + present(imageSourceSelectAlert, animated: true) + } + + private lazy var imageSourceSelectAlert: UIAlertController = { + let alert = UIAlertController(title: "Select source", message: "Select the image source for your post.", preferredStyle: .actionSheet) + let cameraAction = UIAlertAction(title: "Camera", style: .default, handler: openCamera) + let libraryAction = UIAlertAction(title: "Library", style: .default, handler: openLibrary) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) + + alert.addAction(cameraAction) + alert.addAction(libraryAction) + alert.addAction(cancelAction) + + return alert + }() + + private func openCamera(_ sender: Any) { + if UIImagePickerController.isSourceTypeAvailable(.camera) { + self.imgPicker.sourceType = .camera + } else { + self.imgPicker.sourceType = .photoLibrary + } + self.present(imgPicker, animated: true) + } + + private func openLibrary(_ sender: Any) { + self.imgPicker.sourceType = .savedPhotosAlbum + self.present(imgPicker, animated: true) + } + + @IBAction + func didTapColorPicker(_ sender: Any) { + colorPicker.selectedColor = previewPost.backgroundColor ?? .clear + present(colorPicker, animated: true) + } + + private func createPost() throws { + let post = try self.buildPost() + createPostService.create(post) { [unowned self] result in + switch result { + case .success(let post): + delegate?.didCreatePost(post: post) + case .failure(let err): + self.errorAlert(err) + } + } + } + + private func buildPost() throws -> Post { + guard let postText = contentTxt.text else { + throw EmptyPostError() + } + var post = Post( + content: postText, + backgroundColor: previewPost.backgroundColor?.hexString ?? "#3366CC", + latitude: currentLocation?.latitude, + longitude: currentLocation?.longitude + ) + if let uimage = previewPost.image { + post.imageData = uimage.encodeBase64() + } + return post + } + + func errorAlert(_ error: Error) { + let err = error as? Titleable + let alert = UIAlertController(title: (err?.title ?? "Server Error"), message: error.localizedDescription, preferredStyle: .alert) + let okAction = UIAlertAction(title: "Ok", style: .default) + alert.addAction(okAction) + self.present(alert, animated: true, completion: nil) + } + + public func clear() { + source = nil + contentTxt?.text = "" + previewPost?.clear() + } +} diff --git a/Secretly/ViewControllers/PostInputViewController.xib b/Secretly/ViewControllers/PostInputViewController.xib new file mode 100644 index 0000000..7fd920c --- /dev/null +++ b/Secretly/ViewControllers/PostInputViewController.xib @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretly/ViewControllers/WelcomeViewController.swift b/Secretly/ViewControllers/WelcomeViewController.swift new file mode 100644 index 0000000..e27862b --- /dev/null +++ b/Secretly/ViewControllers/WelcomeViewController.swift @@ -0,0 +1,22 @@ +// +// ViewController.swift +// Secretly +// +// Created by LuisE on 2/16/20. +// Copyright © 2020 3zcurdia. All rights reserved. +// + +import UIKit + +class WelcomeViewController: UIViewController { + @IBOutlet weak var helloLbl: UILabel! + let service = CurrentUserService() + + override func viewDidLoad() { + super.viewDidLoad() + service.auth { [unowned self] currentUser in + self.helloLbl.text = "Hello \(currentUser.username)" + self.performSegue(withIdentifier: "showFeedSegue", sender: self) + } + } +} diff --git a/Secretly/Views/AuthorView.swift b/Secretly/Views/AuthorView.swift new file mode 100644 index 0000000..9d32ae3 --- /dev/null +++ b/Secretly/Views/AuthorView.swift @@ -0,0 +1,93 @@ +// +// AuthorView.swift +// Secretly +// +// Created by Luis Ezcurdia on 29/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +class AuthorView: UIView { + var author: User? { + didSet { + updateContent() + } + } + + let stack: UIStackView = { + let sv = UIStackView() + sv.alignment = .fill + sv.distribution = .fill + sv.axis = .horizontal + sv.spacing = CGFloat(10.0) + sv.translatesAutoresizingMaskIntoConstraints = false + return sv + }() + + let avatarImg: UIImageView = { + let iv = UIImageView() + iv.image = UIImage(systemName: "person.fill") + iv.contentMode = .scaleAspectFill + iv.clipsToBounds = true + iv.translatesAutoresizingMaskIntoConstraints = false + return iv + }() + + let usernameLbl: UILabel = { + let lbl = UILabel() + lbl.text = "Jane Doe" + lbl.font = UIFont.systemFont(ofSize: 18, weight: .semibold) + lbl.textColor = .white + lbl.translatesAutoresizingMaskIntoConstraints = false + return lbl + }() + + convenience init() { + self.init(frame: .zero) + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupConstraints() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupConstraints() + } + + /* + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + + func setupConstraints() { + self.backgroundColor = .clear + addSubview(stack) + stack.addArrangedSubview(avatarImg) + stack.addArrangedSubview(usernameLbl) + + NSLayoutConstraint.activate([ + stack.topAnchor.constraint(equalTo: self.topAnchor, constant: 3), + stack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 3), + stack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 3), + stack.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 3) + ]) + + NSLayoutConstraint.activate([ + avatarImg.widthAnchor.constraint(equalTo: avatarImg.heightAnchor) + ]) + avatarImg.layer.cornerRadius = self.frame.height / 2.0 + } + + func updateContent() { + guard let author = author else { return } + usernameLbl.text = author.username + ImageLoader.load(author.avatarUrl) { [unowned self] img in self.avatarImg.image = img } + } + +} diff --git a/Secretly/Views/PostCollectionViewCell.swift b/Secretly/Views/PostCollectionViewCell.swift new file mode 100644 index 0000000..99992bc --- /dev/null +++ b/Secretly/Views/PostCollectionViewCell.swift @@ -0,0 +1,68 @@ +// +// PostCollectionViewCell.swift +// Secretly +// +// Created by Luis Ezcurdia on 28/05/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +class PostCollectionViewCell: UICollectionViewCell { + static let reuseIdentifier = "feedPostCell" +// New var from Like + var likeService: LikeService? + var post: Post? { + didSet { + updateView() + likeService = LikeService(post: post) + } + } +// New IBOutlet from like's +// +// + @IBOutlet weak var likeButton: UIButton! + @IBOutlet weak var labelLikeButton: UILabel! + + @IBOutlet weak var authorView: AuthorView! + @IBOutlet weak var contentLabel: UILabel! + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var commentCounter: UILabel! + override func awakeFromNib() { + super.awakeFromNib() + } + + @IBAction func likePress(_ sender: UIButton){ + var likeNumber = self.post?.likesCount ?? 0 + likeService?.action() { [unowned self] result in + switch result { + case .success(nil): + likeNumber = likeNumber - 1 + self.labelLikeButton.text = "\(likeNumber) likes" + self.likeButton.setImage(UIImage(systemName: "heart"), for: .normal) + case .success( _): + likeNumber = likeNumber + 1 + self.labelLikeButton.text = "\(likeNumber) likes" + self.likeButton.setImage(UIImage(systemName: "heart.fill"), for: .normal) + case .failure(_): + print("Fail Operation") + } + print("Numero de likes: ", likeNumber) + } + } + + func updateView() { + + imageView.image = nil + guard let post = post else { return } + if let color = UIColor(hex: post.backgroundColor) { + self.backgroundColor = color + } + self.contentLabel.text = post.content + self.commentCounter.text = String(describing: post.commentsCount ?? 0) + if let postImg = post.image { + ImageLoader.load(postImg.mediumUrl) { img in self.imageView.image = img } + } + self.authorView.author = post.user + } +} diff --git a/Secretly/Views/PostCollectionViewCell.xib b/Secretly/Views/PostCollectionViewCell.xib new file mode 100644 index 0000000..d9a8fbd --- /dev/null +++ b/Secretly/Views/PostCollectionViewCell.xib @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Secretly/Views/PreviewPostView.swift b/Secretly/Views/PreviewPostView.swift new file mode 100644 index 0000000..8bfc869 --- /dev/null +++ b/Secretly/Views/PreviewPostView.swift @@ -0,0 +1,79 @@ +// +// PreviewPostVIew.swift +// Secretly +// +// Created by Luis Ezcurdia on 18/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +class PreviewPostView: UIView { + public var content: String? { + didSet { + self.contentLbl.text = content + } + } + public var image: UIImage? { + didSet { + self.imgView.image = image + } + } + let imgView: UIImageView = { + let iv = UIImageView() + iv.backgroundColor = .clear + iv.contentMode = .scaleToFill + iv.translatesAutoresizingMaskIntoConstraints = false + return iv + }() + + let imgView2: UIImageView = { + let iv2 = UIImageView() + iv2.backgroundColor = .clear + iv2.contentMode = .scaleToFill + iv2.translatesAutoresizingMaskIntoConstraints = false + return iv2 + }() + + let contentLbl: UILabel = { + let lbl = UILabel() + lbl.font = UIFont.systemFont(ofSize: 22, weight: .semibold) + lbl.textColor = .white + lbl.textAlignment = .center + lbl.translatesAutoresizingMaskIntoConstraints = false + return lbl + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupLayout() + } + + func clear() { + self.imgView.image = nil + self.image = nil + self.contentLbl.text = nil + self.content = nil + self.imgView2.image = nil + } + + private func setupLayout() { + addSubview(imgView) + addSubview(contentLbl) + addSubview(imgView2) + NSLayoutConstraint.activate([ + imgView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4), + imgView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 4), + imgView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 4), + imgView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 4), + contentLbl.centerYAnchor.constraint(equalTo: self.centerYAnchor), + contentLbl.leadingAnchor.constraint(equalTo: self.leadingAnchor), + contentLbl.trailingAnchor.constraint(equalTo: self.trailingAnchor) + ]) + } +} diff --git a/arq.md b/arq.md new file mode 100644 index 0000000..1724fe6 --- /dev/null +++ b/arq.md @@ -0,0 +1,29 @@ +## Arquitecture + +- Modelos + - User + - Post + - Comment +- Vistas / Controllers + - Vista de feed + - Preloading (infinite scroll) + - Single post: DRY + - Vista de coment + - Creacion de post (validaciones -> Combine?) + - Creacion de comment (validaciones???) + +- Network layer (fault tolerant , codable) +- Persitencia de datos en disco + - Credenciales seguras(keychain) + - Manejo de cache +- Geolocation + - Obtener lat-long por sensores + - Envia en request de post +- Modulo de seguridad + - Obtener y enviar un identificador de dispositivo unico + - Bloquear acciones de usuario si ha sido bloqueado (403) +- Image processing + - Access to camera or lib + - Take pictures + - Send image over the wire + - Aplicarle filtro de blur gausiano