diff --git a/Secretly.xcodeproj/project.pbxproj b/Secretly.xcodeproj/project.pbxproj index 8dd4829..1703c1a 100644 --- a/Secretly.xcodeproj/project.pbxproj +++ b/Secretly.xcodeproj/project.pbxproj @@ -56,6 +56,8 @@ 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 */; }; + 88FFFB2726E7207000373DEE /* Like.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88FFFB2626E7207000373DEE /* Like.swift */; }; + 88FFFB2926E7219600373DEE /* LikeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88FFFB2826E7219600373DEE /* 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 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984A23FA35E00025C28E /* WelcomeViewController.swift */; }; @@ -125,6 +127,8 @@ 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 = ""; }; + 88FFFB2626E7207000373DEE /* Like.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Like.swift; sourceTree = ""; }; + 88FFFB2826E7219600373DEE /* 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 = ""; }; @@ -222,6 +226,7 @@ 30C77CAF266AD69700A888DC /* CurrentUserService.swift */, 304E06C726742BDA00A99128 /* CreatePostService.swift */, 304E06C926742CC500A99128 /* FeedService.swift */, + 88FFFB2826E7219600373DEE /* LikeService.swift */, ); path = Services; sourceTree = ""; @@ -248,6 +253,7 @@ 307A30572661AD540020DF8B /* User.swift */, 30C77CB3266AF47300A888DC /* Credentials.swift */, 30C77CB5266AF48300A888DC /* CurrentUser.swift */, + 88FFFB2626E7207000373DEE /* Like.swift */, ); path = Models; sourceTree = ""; @@ -428,6 +434,7 @@ E021984B23FA35E00025C28E /* WelcomeViewController.swift in Sources */, 3072FBDF2680FA5A00B35C8C /* ImageProcessor.swift in Sources */, 302BB622267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift in Sources */, + 88FFFB2726E7207000373DEE /* Like.swift in Sources */, 302B5845267E658E007133E6 /* HttpResponse.swift in Sources */, 302B584A267E658E007133E6 /* RestClient.swift in Sources */, 302B5848267E658E007133E6 /* HttpClient.swift in Sources */, @@ -466,6 +473,7 @@ 30C77CB6266AF48300A888DC /* CurrentUser.swift in Sources */, 302BB624267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift in Sources */, 304E06CF267468DA00A99128 /* UIColor+Pastel.swift in Sources */, + 88FFFB2926E7219600373DEE /* LikeService.swift in Sources */, 304E06C42674133D00A99128 /* String+isBlank.swift in Sources */, 30BC8BA42662BDEF00F7E6A5 /* ImageStore.swift in Sources */, 3033795B267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift in Sources */, diff --git a/Secretly/Models/Like.swift b/Secretly/Models/Like.swift new file mode 100644 index 0000000..c99a9f9 --- /dev/null +++ b/Secretly/Models/Like.swift @@ -0,0 +1,26 @@ +// +// Like.swift +// Secretly +// +// Created by Emanuel Flores Martínez on 06/09/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct Like: Restable { + let id: Int? + let createdAt: String? + let updatedAt: String? + + init() { + self.id = nil + self.createdAt = nil + self.updatedAt = nil + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + } +} diff --git a/Secretly/Models/Post.swift b/Secretly/Models/Post.swift index eba5ff0..6a655d7 100644 --- a/Secretly/Models/Post.swift +++ b/Secretly/Models/Post.swift @@ -21,6 +21,8 @@ struct Post: Restable { let longitude: Double? let createdAt: Date? let updatedAt: Date? + var likesCount: Int? + var liked: Bool? init(content: String, backgroundColor: String, latitude: Double? = nil, longitude: Double? = nil, image: UIImage? = nil) { self.content = content @@ -34,6 +36,8 @@ struct Post: Restable { self.commentsCount = nil self.createdAt = nil self.updatedAt = nil + self.likesCount = nil + self.liked = nil } func encode(to encoder: Encoder) throws { @@ -43,5 +47,7 @@ struct Post: Restable { try container.encode(imageData, forKey: .imageData) try container.encode(latitude, forKey: .latitude) try container.encode(longitude, forKey: .longitude) + try container.encode(likesCount, forKey: .likesCount) + try container.encode(liked, forKey: .liked) } } diff --git a/Secretly/Network/RestClient.swift b/Secretly/Network/RestClient.swift index 3048ccf..4acdbfa 100644 --- a/Secretly/Network/RestClient.swift +++ b/Secretly/Network/RestClient.swift @@ -58,6 +58,13 @@ struct RestClient { complete(newResult) } } + + func create(complete: @escaping (Result)-> Void) throws { + client.post(path: path, body: nil) { 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) @@ -73,6 +80,13 @@ struct RestClient { complete(newResult) } } + + func delete(complete: @escaping (Result) -> Void) { + client.delete(path: path) { result in + let newResult = result.flatMap { parse(data: $0) } + complete(newResult) + } + } private func parseList(data: Data?) -> Result<[T], Error> { if let data = data { diff --git a/Secretly/Services/LikeService.swift b/Secretly/Services/LikeService.swift new file mode 100644 index 0000000..326312b --- /dev/null +++ b/Secretly/Services/LikeService.swift @@ -0,0 +1,33 @@ +// +// LikeService.swift +// Secretly +// +// Created by Emanuel Flores Martínez on 06/09/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct LikeService { + let endpoint: RestClient? + + 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") + } + + func likePost(complete: @escaping(Result) -> Void) { + try? endpoint?.create { result in + DispatchQueue.main.async { complete(result) } + } + } + + func dislikePost(complete: @escaping(Result) -> Void) { + endpoint?.delete { result in + DispatchQueue.main.async { complete(result) } + } + } +} diff --git a/Secretly/Views/PostCollectionViewCell.swift b/Secretly/Views/PostCollectionViewCell.swift index ee08d53..b809b38 100644 --- a/Secretly/Views/PostCollectionViewCell.swift +++ b/Secretly/Views/PostCollectionViewCell.swift @@ -9,22 +9,56 @@ import UIKit class PostCollectionViewCell: UICollectionViewCell { + // MARK: - Properties static let reuseIdentifier = "feedPostCell" + var likeService: LikeService? var post: Post? { didSet { - updateView() + updateView() + likeService = LikeService(post: post) } } @IBOutlet weak var authorView: AuthorView! @IBOutlet weak var contentLabel: UILabel! @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var likeState: UIImageView! + @IBOutlet weak var likesCounter: UILabel! @IBOutlet weak var commentCounter: UILabel! override func awakeFromNib() { super.awakeFromNib() + + let tapLike = UITapGestureRecognizer(target: self, action: #selector(handleLike)) + self.likeState.addGestureRecognizer(tapLike) + + let doubleTapImage = UITapGestureRecognizer(target: self, action: #selector(handleLike)) + doubleTapImage.numberOfTapsRequired = 2 + self.imageView.addGestureRecognizer(doubleTapImage) + } + + // MARK: - Actions + @objc func handleLike() { + post?.liked?.toggle() + guard let postLiked = post?.liked, let postLikesCount = post?.likesCount else { return } + if postLiked { + self.likeService?.likePost { [weak self] _ in + guard let self = self else { return } + if postLikesCount > 0 { + self.post?.likesCount! += 1 + } + self.setupLike() + } + } else { + self.likeService?.dislikePost { [weak self] _ in + guard let self = self else { return } + self.post?.likesCount! -= 1 + self.setupLike() + } + } + } + // MARK: - Helper functions func updateView() { imageView.image = nil guard let post = post else { return } @@ -37,5 +71,16 @@ class PostCollectionViewCell: UICollectionViewCell { ImageLoader.load(postImg.mediumUrl) { img in self.imageView.image = img } } self.authorView.author = post.user + setupLike() + } + + private func setupLike() { + let imageName = post?.liked ?? false ? "heart.fill" : "heart" + likeState.image = UIImage(systemName: imageName) + if post?.likesCount == 1 { + likesCounter.text = "\(post?.likesCount ?? 1) like" + } else { + likesCounter.text = "\(post?.likesCount ?? 0) likes" + } } } diff --git a/Secretly/Views/PostCollectionViewCell.xib b/Secretly/Views/PostCollectionViewCell.xib index 7b3a4e2..e568d99 100644 --- a/Secretly/Views/PostCollectionViewCell.xib +++ b/Secretly/Views/PostCollectionViewCell.xib @@ -17,7 +17,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -62,6 +62,15 @@ + @@ -69,6 +78,7 @@ + @@ -77,6 +87,7 @@ + @@ -90,6 +101,7 @@ +