diff --git a/Secretly.xcodeproj/project.pbxproj b/Secretly.xcodeproj/project.pbxproj index 8dd4829..712cae6 100644 --- a/Secretly.xcodeproj/project.pbxproj +++ b/Secretly.xcodeproj/project.pbxproj @@ -63,6 +63,11 @@ 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 */; }; + F17F4C76268650A60070BB2A /* LikeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17F4C75268650A60070BB2A /* LikeService.swift */; }; + F17F4C7A2686521C0070BB2A /* Like.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17F4C792686521C0070BB2A /* Like.swift */; }; + F17F4C7E268654160070BB2A /* Author.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17F4C7D268654160070BB2A /* Author.swift */; }; + F1A9B26E269EA0FB001BD470 /* UserProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A9B26D269EA0FB001BD470 /* UserProfileService.swift */; }; + F1A9B272269EA50D001BD470 /* UserProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A9B271269EA50D001BD470 /* UserProfileViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -136,6 +141,11 @@ 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 = ""; }; + F17F4C75268650A60070BB2A /* LikeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeService.swift; sourceTree = ""; }; + F17F4C792686521C0070BB2A /* Like.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Like.swift; sourceTree = ""; }; + F17F4C7D268654160070BB2A /* Author.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Author.swift; sourceTree = ""; }; + F1A9B26D269EA0FB001BD470 /* UserProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileService.swift; sourceTree = ""; }; + F1A9B271269EA50D001BD470 /* UserProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -179,6 +189,7 @@ 302BB621267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift */, 302BB623267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift */, 302BB625267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift */, + F1A9B271269EA50D001BD470 /* UserProfileViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -222,6 +233,8 @@ 30C77CAF266AD69700A888DC /* CurrentUserService.swift */, 304E06C726742BDA00A99128 /* CreatePostService.swift */, 304E06C926742CC500A99128 /* FeedService.swift */, + F17F4C75268650A60070BB2A /* LikeService.swift */, + F1A9B26D269EA0FB001BD470 /* UserProfileService.swift */, ); path = Services; sourceTree = ""; @@ -248,6 +261,8 @@ 307A30572661AD540020DF8B /* User.swift */, 30C77CB3266AF47300A888DC /* Credentials.swift */, 30C77CB5266AF48300A888DC /* CurrentUser.swift */, + F17F4C792686521C0070BB2A /* Like.swift */, + F17F4C7D268654160070BB2A /* Author.swift */, ); path = Models; sourceTree = ""; @@ -437,12 +452,14 @@ 307A30622661E8A90020DF8B /* UIColor+Hex.swift in Sources */, 307A30542661ABD60020DF8B /* Post.swift in Sources */, 304E06C626741A5100A99128 /* UIColor+theme.swift in Sources */, + F17F4C76268650A60070BB2A /* LikeService.swift in Sources */, 304E06C826742BDA00A99128 /* CreatePostService.swift in Sources */, E021984723FA35E00025C28E /* AppDelegate.swift in Sources */, 30337955267536980066D94A /* PostInputViewController+CLLocationManagerDelegate.swift in Sources */, 307A30562661AD480020DF8B /* Image.swift in Sources */, 307A30582661AD540020DF8B /* User.swift in Sources */, 302B5849267E658E007133E6 /* RequestError.swift in Sources */, + F17F4C7E268654160070BB2A /* Author.swift in Sources */, 302B5846267E658E007133E6 /* AmacaConfig.swift in Sources */, 302BB61C267D7CC800FD74F5 /* PreviewPostVIew.swift in Sources */, 3033795D267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift in Sources */, @@ -458,15 +475,18 @@ 30BC8BA62662C02300F7E6A5 /* CacheImage.swift in Sources */, 302B5847267E658E007133E6 /* StatusCode.swift in Sources */, 302B584B267E658E007133E6 /* RequestBuilder.swift in Sources */, + F1A9B26E269EA0FB001BD470 /* UserProfileService.swift in Sources */, 307A306526629B990020DF8B /* AuthorView.swift in Sources */, 30C77CB4266AF47300A888DC /* Credentials.swift in Sources */, 30BC8BA22662BB0000F7E6A5 /* DataContainer.swift in Sources */, E021984923FA35E00025C28E /* SceneDelegate.swift in Sources */, 30C77CB0266AD69700A888DC /* CurrentUserService.swift in Sources */, 30C77CB6266AF48300A888DC /* CurrentUser.swift in Sources */, + F1A9B272269EA50D001BD470 /* UserProfileViewController.swift in Sources */, 302BB624267E3A8700FD74F5 /* PostInputViewController+UIColorPickerViewControllerDelegate.swift in Sources */, 304E06CF267468DA00A99128 /* UIColor+Pastel.swift in Sources */, 304E06C42674133D00A99128 /* String+isBlank.swift in Sources */, + F17F4C7A2686521C0070BB2A /* Like.swift in Sources */, 30BC8BA42662BDEF00F7E6A5 /* ImageStore.swift in Sources */, 3033795B267537490066D94A /* FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift in Sources */, 30B9B93B268CA9E6007B1942 /* UIImage+PixelBuffer.swift in Sources */, diff --git a/Secretly/AppDelegate.swift b/Secretly/AppDelegate.swift index 70c17d7..80c638d 100644 --- a/Secretly/AppDelegate.swift +++ b/Secretly/AppDelegate.swift @@ -10,7 +10,7 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { StorageType.cache.ensureExists() StorageType.permanent.ensureExists() @@ -20,13 +20,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: UISceneSession Lifecycle - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + func application(_: UIApplication, didDiscardSceneSessions _: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. diff --git a/Secretly/Assets.xcassets/background-green.imageset/Contents.json b/Secretly/Assets.xcassets/background-green.imageset/Contents.json new file mode 100644 index 0000000..898a762 --- /dev/null +++ b/Secretly/Assets.xcassets/background-green.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "c7804fc1d8c88e040ea41715a77f7b85.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/background-green.imageset/c7804fc1d8c88e040ea41715a77f7b85.jpg b/Secretly/Assets.xcassets/background-green.imageset/c7804fc1d8c88e040ea41715a77f7b85.jpg new file mode 100644 index 0000000..b354276 Binary files /dev/null and b/Secretly/Assets.xcassets/background-green.imageset/c7804fc1d8c88e040ea41715a77f7b85.jpg differ diff --git a/Secretly/Assets.xcassets/background.imageset/Contents.json b/Secretly/Assets.xcassets/background.imageset/Contents.json new file mode 100644 index 0000000..ed74c4e --- /dev/null +++ b/Secretly/Assets.xcassets/background.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "a6728fe45010243d0736d9601d316115.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/background.imageset/a6728fe45010243d0736d9601d316115.jpg b/Secretly/Assets.xcassets/background.imageset/a6728fe45010243d0736d9601d316115.jpg new file mode 100644 index 0000000..fa18d14 Binary files /dev/null and b/Secretly/Assets.xcassets/background.imageset/a6728fe45010243d0736d9601d316115.jpg differ diff --git a/Secretly/Assets.xcassets/user.imageset/Contents.json b/Secretly/Assets.xcassets/user.imageset/Contents.json new file mode 100644 index 0000000..800ede3 --- /dev/null +++ b/Secretly/Assets.xcassets/user.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "aadb8ffd3640a9b7bf9bf84421ebc763.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Secretly/Assets.xcassets/user.imageset/aadb8ffd3640a9b7bf9bf84421ebc763.jpg b/Secretly/Assets.xcassets/user.imageset/aadb8ffd3640a9b7bf9bf84421ebc763.jpg new file mode 100644 index 0000000..b97c67e Binary files /dev/null and b/Secretly/Assets.xcassets/user.imageset/aadb8ffd3640a9b7bf9bf84421ebc763.jpg differ diff --git a/Secretly/Base.lproj/Main.storyboard b/Secretly/Base.lproj/Main.storyboard index 4ed5b13..177cc35 100644 --- a/Secretly/Base.lproj/Main.storyboard +++ b/Secretly/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -56,18 +56,36 @@ - + + + + + + + + + + + + - + @@ -173,6 +191,7 @@ + diff --git a/Secretly/Credentials.swift b/Secretly/Credentials.swift index aafbbdd..0aefcfa 100644 --- a/Secretly/Credentials.swift +++ b/Secretly/Credentials.swift @@ -18,7 +18,7 @@ enum Credentials { } } - func set(value: String) -> Bool { + func set(value _: String) -> Bool { switch self { case .userToken: return true diff --git a/Secretly/Extensions/String+isBlank.swift b/Secretly/Extensions/String+isBlank.swift index 8cf8e9a..a184ccf 100644 --- a/Secretly/Extensions/String+isBlank.swift +++ b/Secretly/Extensions/String+isBlank.swift @@ -10,6 +10,6 @@ import Foundation extension String { var isBlank: Bool { - return self.isEmpty || self.trimmingCharacters(in: .whitespaces) == "" + return isEmpty || trimmingCharacters(in: .whitespaces) == "" } } diff --git a/Secretly/Extensions/UIColor+Hex.swift b/Secretly/Extensions/UIColor+Hex.swift index 5299417..da087b4 100644 --- a/Secretly/Extensions/UIColor+Hex.swift +++ b/Secretly/Extensions/UIColor+Hex.swift @@ -24,10 +24,10 @@ extension UIColor { 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 + r = CGFloat((hexNumber & 0xFF00_0000) >> 24) / 255 + g = CGFloat((hexNumber & 0x00FF_0000) >> 16) / 255 + b = CGFloat((hexNumber & 0x0000_FF00) >> 8) / 255 + a = CGFloat(hexNumber & 0x0000_00FF) / 255 self.init(red: r, green: g, blue: b, alpha: a) return @@ -44,7 +44,7 @@ extension UIColor { 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 + 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 index dade0c9..2bf152b 100644 --- a/Secretly/Extensions/UIColor+Pastel.swift +++ b/Secretly/Extensions/UIColor+Pastel.swift @@ -15,7 +15,7 @@ extension UIColor { var brightness: CGFloat = 0 var alpha: CGFloat = 0 - self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + 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/UIImage+PixelBuffer.swift b/Secretly/Extensions/UIImage+PixelBuffer.swift index 78b5b28..7183053 100644 --- a/Secretly/Extensions/UIImage+PixelBuffer.swift +++ b/Secretly/Extensions/UIImage+PixelBuffer.swift @@ -10,18 +10,18 @@ import UIKit extension UIImage { func resize(to newSize: CGSize) -> UIImage? { - guard self.size != newSize else { return self } + guard size != newSize else { return self } UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0) - self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) + 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 width = Int(size.width) + let height = Int(size.height) let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary var pixelBuffer: CVPixelBuffer? @@ -42,7 +42,7 @@ extension UIImage { context.scaleBy(x: 1.0, y: -1.0) UIGraphicsPushContext(context) - self.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) + draw(in: CGRect(x: 0, y: 0, width: width, height: height)) UIGraphicsPopContext() CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) diff --git a/Secretly/Extensions/UIImage+encodeBase64.swift b/Secretly/Extensions/UIImage+encodeBase64.swift index 3f6f801..37ef380 100644 --- a/Secretly/Extensions/UIImage+encodeBase64.swift +++ b/Secretly/Extensions/UIImage+encodeBase64.swift @@ -10,7 +10,7 @@ import UIKit extension UIImage { func encodeBase64() -> String? { - guard let data = self.jpegData(compressionQuality: 0.85) else { return nil } + guard let data = jpegData(compressionQuality: 0.85) else { return nil } return "data:image/jpeg;base64,\(data.base64EncodedString())" } } diff --git a/Secretly/Models/Author.swift b/Secretly/Models/Author.swift new file mode 100644 index 0000000..4a701ab --- /dev/null +++ b/Secretly/Models/Author.swift @@ -0,0 +1,20 @@ +// +// Author.swift +// Secretly +// +// Created by Yocelin Garcia Romero on 25/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import UIKit + +struct Author: Restable { + let name: String? + let id: String? + + init(id: String, name: String) { + self.name = name + self.id = id + } +} diff --git a/Secretly/Models/Credentials.swift b/Secretly/Models/Credentials.swift index c5b10c4..be54e63 100644 --- a/Secretly/Models/Credentials.swift +++ b/Secretly/Models/Credentials.swift @@ -13,14 +13,12 @@ struct Credentials: Restable { let password: String? let token: String? - var id: String { - get { "" } - } + var id: String { "" } init(username: String, password: String) { self.username = username self.password = password - self.token = nil + token = nil } enum CodingKeys: String, CodingKey { diff --git a/Secretly/Models/Like.swift b/Secretly/Models/Like.swift new file mode 100644 index 0000000..6925685 --- /dev/null +++ b/Secretly/Models/Like.swift @@ -0,0 +1,18 @@ +// +// Like.swift +// Secretly +// +// Created by Yocelin Garcia Romero on 25/06/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation +import UIKit + +struct Like: Restable { + let id: Int + let likeableType: String + let likeableId: Int + let createdAt, updatedAt: String + let user: User +} diff --git a/Secretly/Models/Post.swift b/Secretly/Models/Post.swift index eba5ff0..c0a27ac 100644 --- a/Secretly/Models/Post.swift +++ b/Secretly/Models/Post.swift @@ -21,19 +21,23 @@ 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 self.backgroundColor = backgroundColor - self.id = nil + id = nil self.image = nil - self.imageData = image?.encodeBase64() + imageData = image?.encodeBase64() self.latitude = latitude self.longitude = longitude - self.user = nil - self.commentsCount = nil - self.createdAt = nil - self.updatedAt = nil + user = nil + commentsCount = nil + createdAt = nil + updatedAt = nil + likesCount = nil + liked = nil } func encode(to encoder: Encoder) throws { @@ -44,4 +48,14 @@ struct Post: Restable { try container.encode(latitude, forKey: .latitude) try container.encode(longitude, forKey: .longitude) } + + mutating func like() { + likesCount = (likesCount ?? 0) + 1 + liked = true + } + + mutating func unlike() { + likesCount = (likesCount ?? 0) - 1 + liked = false + } } diff --git a/Secretly/Models/User.swift b/Secretly/Models/User.swift index bf83970..2ec46f7 100644 --- a/Secretly/Models/User.swift +++ b/Secretly/Models/User.swift @@ -8,7 +8,8 @@ import Foundation -struct User: Codable { +struct User: Restable { + let id: Int let username: String let avatarUrl: String } diff --git a/Secretly/Network/AmacaConfig.swift b/Secretly/Network/AmacaConfig.swift index 173da18..30a55d0 100644 --- a/Secretly/Network/AmacaConfig.swift +++ b/Secretly/Network/AmacaConfig.swift @@ -13,14 +13,13 @@ struct 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") - } + UserDefaults.standard.string(forKey: "amaca.apitoken") } func setApiToken(_ value: String) { diff --git a/Secretly/Network/HttpClient.swift b/Secretly/Network/HttpClient.swift index 0554709..5c0347f 100644 --- a/Secretly/Network/HttpClient.swift +++ b/Secretly/Network/HttpClient.swift @@ -31,30 +31,33 @@ struct HttpClient { } private func request(method: String, path: String, body: Data?, complete: @escaping ResultResponse) { - guard let req = buildRequest(method: method, path: path, body: body) else { + guard let req = RequestBuilder.build(method: method, baseUrl: baseUrl, path: path, body: body) else { complete(.failure(RequestError.invalidRequest)) return } - session.dataTask(with: req) { (data, response, error) in + 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) + 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() - } + /* 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)"] + } + #if DEBUD + debugPrint(builder.debugDescription) + #endif + return builder.request() + } */ } diff --git a/Secretly/Network/HttpResponse.swift b/Secretly/Network/HttpResponse.swift index eb0543a..840836e 100644 --- a/Secretly/Network/HttpResponse.swift +++ b/Secretly/Network/HttpResponse.swift @@ -12,14 +12,27 @@ struct HttpResponse { let httpUrlResponse: HTTPURLResponse init(response: URLResponse?) { - self.httpUrlResponse = (response as? HTTPURLResponse) ?? HTTPURLResponse() + httpUrlResponse = (response as? HTTPURLResponse) ?? HTTPURLResponse() } var status: StatusCode { - return StatusCode(rawValue: self.httpUrlResponse.statusCode) + return StatusCode(rawValue: httpUrlResponse.statusCode) } func result(for data: Data?) -> Result { - return status.result().map { _ in data } + // #if DEGUB + if let udata = data, !udata.isEmpty { + let currentData = String(data: udata, encoding: .utf8) + + debugPrint("Response: \(status) \(httpUrlResponse.statusCode) \(httpUrlResponse.url!) -d \(String(describing: currentData))") + } else { + debugPrint("Response: \(status) \(httpUrlResponse.statusCode) \(httpUrlResponse.url!)") + } + // #endif + if let udata = data, !udata.isEmpty { + return status.result().map { _ in data } + } else { + return status.result().map { _ in nil } + } } } diff --git a/Secretly/Network/RequestBuilder.swift b/Secretly/Network/RequestBuilder.swift index c29e13e..4c9a000 100644 --- a/Secretly/Network/RequestBuilder.swift +++ b/Secretly/Network/RequestBuilder.swift @@ -8,7 +8,7 @@ import Foundation -struct RequestBuilder { +struct RequestBuilder: CustomDebugStringConvertible { enum ContentMode { case jsonApp @@ -26,6 +26,7 @@ struct RequestBuilder { } } } + private let urlComponents: URLComponents public var scheme: String = "https" public var method: String = "get" @@ -34,12 +35,36 @@ struct RequestBuilder { public var headers: [String: String]? public var contentMode: ContentMode = .jsonApp + var debugDescription: String { + let currentUrl = url()?.debugDescription ?? "Not valid URL" + let currentHeaders = headers?.debugDescription ?? "" + if let ubody = body, let currentBody = String(data: ubody, encoding: .utf8) { + return "Request to: \(method.uppercased()) - \(currentUrl) -H \(currentHeaders) -d \(currentBody)" + } else { + return "Request to: \(method.uppercased()) - \(currentUrl) -H \(currentHeaders)" + } + } + + static func build(method: String, baseUrl: String, path: String, body: Data?) -> URLRequest? { + var builder = RequestBuilder(baseUrl: baseUrl) + builder.method = method + builder.path = path + builder.body = body + if let token = AmacaConfig.shared.apiToken { + builder.headers = ["Authorization": "Bearer \(token)"] + } + #if DEBUD + debugPrint(builder.debugDescription) + #endif + return builder.request() + } + init(baseUrl: String) { - self.urlComponents = URLComponents(string: baseUrl)! + urlComponents = URLComponents(string: baseUrl)! } func url() -> URL? { - var comps = self.urlComponents + var comps = urlComponents comps.scheme = scheme comps.path = path return comps.url diff --git a/Secretly/Network/RestClient.swift b/Secretly/Network/RestClient.swift index 3048ccf..6e712e3 100644 --- a/Secretly/Network/RestClient.swift +++ b/Secretly/Network/RestClient.swift @@ -27,6 +27,7 @@ struct RestClient { decoder.dateDecodingStrategy = .iso8601 return decoder }() + public var encoder: JSONEncoder = { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase @@ -51,6 +52,13 @@ struct RestClient { } } + 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 create(model: T, complete: @escaping (Result) -> Void) throws { let data = try encoder.encode(model) client.post(path: path, body: data) { result in @@ -67,6 +75,13 @@ struct RestClient { } } + func delete(complete: @escaping (Result) -> Void) { + client.delete(path: path) { 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) } diff --git a/Secretly/Network/StatusCode.swift b/Secretly/Network/StatusCode.swift index ead95c7..9647fc4 100644 --- a/Secretly/Network/StatusCode.swift +++ b/Secretly/Network/StatusCode.swift @@ -37,7 +37,7 @@ enum StatusCode: Int { func result() -> Result { switch self { case .success: - return .success(self.rawValue) + return .success(rawValue) case .clientError: return .failure(ResponseError.clientError) case .serverError: diff --git a/Secretly/SceneDelegate.swift b/Secretly/SceneDelegate.swift index acef9b4..7bf4840 100644 --- a/Secretly/SceneDelegate.swift +++ b/Secretly/SceneDelegate.swift @@ -9,42 +9,40 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let _ = (scene as? UIWindowScene) else { return } } - func sceneDidDisconnect(_ scene: UIScene) { + func sceneDidDisconnect(_: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). } - func sceneDidBecomeActive(_ scene: UIScene) { + func sceneDidBecomeActive(_: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - func sceneWillResignActive(_ scene: UIScene) { + func sceneWillResignActive(_: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - func sceneWillEnterForeground(_ scene: UIScene) { + func sceneWillEnterForeground(_: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - func sceneDidEnterBackground(_ scene: UIScene) { + func sceneDidEnterBackground(_: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } - } diff --git a/Secretly/Services/CreatePostService.swift b/Secretly/Services/CreatePostService.swift index 1607aeb..305ac9a 100644 --- a/Secretly/Services/CreatePostService.swift +++ b/Secretly/Services/CreatePostService.swift @@ -15,7 +15,7 @@ struct CreatePostService { endpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/posts") } - func create(_ model: Post, complete: @escaping (Result) -> Void ) { + 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 index 6579aa9..d5b9c6f 100644 --- a/Secretly/Services/CurrentUserService.swift +++ b/Secretly/Services/CurrentUserService.swift @@ -34,14 +34,14 @@ struct CurrentUserService { private func signUp(_ currentUser: CurrentUser, completion: @escaping (CurrentUser) -> Void) { try? signUpEndpoint.create(model: currentUser.credentials()) { result in storeToken(result) - DispatchQueue.main.async { completion(currentUser) } + 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) } + DispatchQueue.main.async { completion(currentUser) } } } diff --git a/Secretly/Services/LikeService.swift b/Secretly/Services/LikeService.swift new file mode 100644 index 0000000..494d7e3 --- /dev/null +++ b/Secretly/Services/LikeService.swift @@ -0,0 +1,37 @@ +// +// LikeService.swift +// Secretly +// +// Created by Luis Ezcurdia on 11/06/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 { + endpoint = nil + return + } + endpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/posts/\(postId)/likes") + active = post.liked ?? false + } + + mutating func action(complete: @escaping (Result) -> Void) { + if active { + active = !active + endpoint?.delete { result in + DispatchQueue.main.async { complete(result) } + } + } else { + active = !active + try? endpoint?.create { result in + DispatchQueue.main.async { complete(result) } + } + } + } +} diff --git a/Secretly/Services/UserProfileService.swift b/Secretly/Services/UserProfileService.swift new file mode 100644 index 0000000..6fa86e8 --- /dev/null +++ b/Secretly/Services/UserProfileService.swift @@ -0,0 +1,24 @@ +// +// ProfileService.swift +// Secretly +// +// Created by Yocelin Garcia Romero on 13/07/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +struct UserProfileService { + let endpoint: RestClient? + + init() { + endpoint = RestClient(client: AmacaConfig.shared.httpClient, path: "/api/v1/profile") + } + + func showProfile(completion: @escaping (User) -> Void) { + endpoint?.show { result in + guard let userProfile = try? result.get() else { return } + DispatchQueue.main.async { completion(userProfile) } + } + } +} diff --git a/Secretly/Utils/Checksum.swift b/Secretly/Utils/Checksum.swift index 4f9113c..b19115e 100644 --- a/Secretly/Utils/Checksum.swift +++ b/Secretly/Utils/Checksum.swift @@ -6,8 +6,8 @@ // Copyright © 2021 3zcurdia. All rights reserved. // -import Foundation import CryptoKit +import Foundation struct Checksum { static func sha256(_ content: String) -> String { diff --git a/Secretly/Utils/ImageProcessor.swift b/Secretly/Utils/ImageProcessor.swift index 7963805..c7922b9 100644 --- a/Secretly/Utils/ImageProcessor.swift +++ b/Secretly/Utils/ImageProcessor.swift @@ -6,8 +6,8 @@ // Copyright © 2021 3zcurdia. All rights reserved. // -import UIKit import CoreImage +import UIKit struct ImageProcessor { let width: CGFloat @@ -16,8 +16,8 @@ struct ImageProcessor { 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 } + let blured = blur(resized, radius: 30) + else { return } DispatchQueue.main.async { completion(UIImage(cgImage: blured)) } @@ -26,7 +26,7 @@ struct ImageProcessor { private func resize(_ image: UIImage, for size: CGSize) -> CIImage? { let renderer = UIGraphicsImageRenderer(size: size) - let resized = renderer.image { (_) in + let resized = renderer.image { _ in image.draw(in: CGRect(origin: .zero, size: size)) } return CIImage(image: resized) @@ -42,7 +42,7 @@ struct ImageProcessor { 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(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 index 04d7789..77d3bf7 100644 --- a/Secretly/Utils/NudityChecker.swift +++ b/Secretly/Utils/NudityChecker.swift @@ -9,13 +9,10 @@ import UIKit struct NSFWContentError: Error, Titleable { - var title: String { - get { "The image contains nudity" } - } + var title: String { "The image contains nudity" } + + var localizedDescription: String { "The image has a \(prob)% of NSFW contnet." } - var localizedDescription: String { - get { "The image has a \(prob)% of NSFW contnet." } - } let prob: Double init(_ prob: Double?) { @@ -32,8 +29,8 @@ struct NudityChecker { let result = try model.prediction(data: buffer) #if DEBUG - debugPrint(result.classLabel) - debugPrint(result.prob) + debugPrint(result.classLabel) + debugPrint(result.prob) #endif if result.classLabel == "NSFW" || (result.prob["NSFW"] ?? 0) > 0.55 { diff --git a/Secretly/ViewControllers/CreatePostViewController.swift b/Secretly/ViewControllers/CreatePostViewController.swift index 4091252..27a4035 100644 --- a/Secretly/ViewControllers/CreatePostViewController.swift +++ b/Secretly/ViewControllers/CreatePostViewController.swift @@ -9,8 +9,8 @@ import UIKit class CreatePostViewController: UIViewController { - @IBOutlet weak var contentField: UITextField! - @IBOutlet weak var colorField: UITextField! + @IBOutlet var contentField: UITextField! + @IBOutlet var colorField: UITextField! override func viewDidLoad() { super.viewDidLoad() @@ -18,16 +18,16 @@ class CreatePostViewController: UIViewController { } @IBAction - func createPost(_ sender: Any?) { + func createPost(_: 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): + case let .success(post): print("there is a new post \(post?.id ?? 0)") - case .failure(let err): + case let .failure(err): DispatchQueue.main.async { self.errorAlert(err) } @@ -40,20 +40,19 @@ class CreatePostViewController: UIViewController { func errorAlert(_ error: Error) { let err = error as? Titleable - let alert = UIAlertController(title: (err?.title ?? "Error"), message: error.localizedDescription, preferredStyle: .alert) + 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) + 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. - } - */ - + // 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 index 15100dd..0859eef 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift @@ -9,18 +9,18 @@ import UIKit extension FeedCollectionViewController: UICollectionViewDataSource { - func numberOfSections(in collectionView: UICollectionView) -> Int { + func numberOfSections(in _: UICollectionView) -> Int { return 1 } - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + func collectionView(_: UICollectionView, numberOfItemsInSection _: 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] + cell.post = posts?[indexPath.row] return cell } } diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift index 2fb0c29..838822e 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSourcePrefetching.swift @@ -9,7 +9,7 @@ import UIKit extension FeedCollectionViewController: UICollectionViewDataSourcePrefetching { - func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { + func 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 index 2b373e7..2a235f3 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegate.swift +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegate.swift @@ -10,31 +10,31 @@ 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 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 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 - } + // 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, 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?) { - - } - */ + 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 index e6c7ccc..130c315 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift @@ -9,7 +9,7 @@ import UIKit extension FeedCollectionViewController: UICollectionViewDelegateFlowLayout { - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + func collectionView(_ collectionView: UICollectionView, layout _: UICollectionViewLayout, sizeForItemAt _: IndexPath) -> CGSize { return CGSize(width: collectionView.bounds.width, height: 300) } } diff --git a/Secretly/ViewControllers/FeedCollectionViewController.swift b/Secretly/ViewControllers/FeedCollectionViewController.swift index 9180d42..93cd225 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController.swift +++ b/Secretly/ViewControllers/FeedCollectionViewController.swift @@ -6,22 +6,22 @@ // Copyright © 2021 3zcurdia. All rights reserved. // -import UIKit import CoreLocation +import UIKit class FeedCollectionViewController: UIViewController { let feedService = FeedService() var posts: [Post]? { didSet { - self.collectionView.reloadData() - self.refreshControl.endRefreshing() + collectionView.reloadData() + refreshControl.endRefreshing() } } let refreshControl = UIRefreshControl() let postInputView = PostInputViewController() - @IBOutlet weak var collectionView: UICollectionView! + @IBOutlet var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() @@ -38,7 +38,7 @@ class FeedCollectionViewController: UIViewController { collectionView.register(nib, forCellWithReuseIdentifier: PostCollectionViewCell.reuseIdentifier) collectionView.addSubview(refreshControl) - refreshControl.addTarget(self, action: #selector(self.loadPosts), for: UIControl.Event.valueChanged) + refreshControl.addTarget(self, action: #selector(loadPosts), for: UIControl.Event.valueChanged) } @objc @@ -47,7 +47,7 @@ class FeedCollectionViewController: UIViewController { } @IBAction - func onTapAdd(_ sender: Any) { + func onTapAdd(_: Any) { postInputView.clear() present(postInputView, animated: true) } @@ -56,6 +56,6 @@ class FeedCollectionViewController: UIViewController { extension FeedCollectionViewController: PostInputViewDelegate { func didCreatePost(post: Post?) { guard let upost = post else { return } - self.posts?.insert(upost, at: 0) + posts?.insert(upost, at: 0) } } diff --git a/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift b/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift index 119d7c9..0efc157 100644 --- a/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift +++ b/Secretly/ViewControllers/PostInputViewController+CLLocationManagerDelegate.swift @@ -6,12 +6,12 @@ // Copyright © 2021 3zcurdia. All rights reserved. // -import UIKit import CoreLocation +import UIKit extension PostInputViewController: CLLocationManagerDelegate { - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let lastLocation = locations.last else { return } - self.currentLocation = lastLocation.coordinate + currentLocation = lastLocation.coordinate } } diff --git a/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift b/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift index e6ac6cb..bf7cf1d 100644 --- a/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift +++ b/Secretly/ViewControllers/PostInputViewController+UIImagePickerControllerDelegate.swift @@ -6,15 +6,15 @@ // Copyright © 2021 3zcurdia. All rights reserved. // -import UIKit import AVFoundation +import UIKit 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 + previewPost.image = img ImageProcessor(width: 512, heigth: 512).process(img) { self.previewPost.image = $0 self.previewPost.backgroundColor = .black diff --git a/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift b/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift index 5ed93a9..fba40f3 100644 --- a/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift +++ b/Secretly/ViewControllers/PostInputViewController+UITextFieldDelegate.swift @@ -9,8 +9,8 @@ import UIKit extension PostInputViewController: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - self.previewPost.content = textField.text + func textField(_ textField: UITextField, shouldChangeCharactersIn _: NSRange, replacementString _: String) -> Bool { + previewPost.content = textField.text return true } } diff --git a/Secretly/ViewControllers/PostInputViewController.swift b/Secretly/ViewControllers/PostInputViewController.swift index 676ce35..5230fb7 100644 --- a/Secretly/ViewControllers/PostInputViewController.swift +++ b/Secretly/ViewControllers/PostInputViewController.swift @@ -6,8 +6,8 @@ // Copyright © 2021 3zcurdia. All rights reserved. // -import UIKit import CoreLocation +import UIKit struct EmptyPostError: Error {} @@ -17,10 +17,10 @@ protocol PostInputViewDelegate { 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! + @IBOutlet var contentTxt: UITextField! + @IBOutlet var imgPickerButton: UIButton! + @IBOutlet var colorPickerButton: UIButton! + @IBOutlet var previewPost: PreviewPostView! var source: Post? var delegate: PostInputViewDelegate? @@ -32,6 +32,7 @@ class PostInputViewController: UIViewController, UINavigationControllerDelegate view.mediaTypes = ["public.image"] return view }() + let colorPicker = UIColorPickerViewController() override func viewDidLoad() { @@ -69,22 +70,22 @@ class PostInputViewController: UIViewController, UINavigationControllerDelegate } @IBAction - func didTapPublish(_ sender: Any) { + func didTapPublish(_: Any) { do { - try self.createPost() - self.dismiss(animated: true) + try createPost() + dismiss(animated: true) } catch let err { self.errorAlert(err) } } @IBAction - func didTapCancel(_ sender: Any) { - self.dismiss(animated: true) + func didTapCancel(_: Any) { + dismiss(animated: true) } @IBAction - func didTapImagePicker(_ sender: Any) { + func didTapImagePicker(_: Any) { present(imageSourceSelectAlert, animated: true) } @@ -101,33 +102,33 @@ class PostInputViewController: UIViewController, UINavigationControllerDelegate return alert }() - private func openCamera(_ sender: Any) { + private func openCamera(_: Any) { if UIImagePickerController.isSourceTypeAvailable(.camera) { - self.imgPicker.sourceType = .camera + imgPicker.sourceType = .camera } else { - self.imgPicker.sourceType = .photoLibrary + imgPicker.sourceType = .photoLibrary } - self.present(imgPicker, animated: true) + present(imgPicker, animated: true) } - private func openLibrary(_ sender: Any) { - self.imgPicker.sourceType = .savedPhotosAlbum - self.present(imgPicker, animated: true) + private func openLibrary(_: Any) { + imgPicker.sourceType = .savedPhotosAlbum + present(imgPicker, animated: true) } @IBAction - func didTapColorPicker(_ sender: Any) { + func didTapColorPicker(_: Any) { colorPicker.selectedColor = previewPost.backgroundColor ?? .clear present(colorPicker, animated: true) } private func createPost() throws { - let post = try self.buildPost() + let post = try buildPost() createPostService.create(post) { [unowned self] result in switch result { - case .success(let post): + case let .success(post): delegate?.didCreatePost(post: post) - case .failure(let err): + case let .failure(err): self.errorAlert(err) } } @@ -151,10 +152,10 @@ class PostInputViewController: UIViewController, UINavigationControllerDelegate func errorAlert(_ error: Error) { let err = error as? Titleable - let alert = UIAlertController(title: (err?.title ?? "Server Error"), message: error.localizedDescription, preferredStyle: .alert) + 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) + present(alert, animated: true, completion: nil) } public func clear() { diff --git a/Secretly/ViewControllers/PostInputViewController.xib b/Secretly/ViewControllers/PostInputViewController.xib index 7fd920c..34921f1 100644 --- a/Secretly/ViewControllers/PostInputViewController.xib +++ b/Secretly/ViewControllers/PostInputViewController.xib @@ -1,8 +1,8 @@ - + - + diff --git a/Secretly/ViewControllers/UserProfileViewController.swift b/Secretly/ViewControllers/UserProfileViewController.swift new file mode 100644 index 0000000..fb174e8 --- /dev/null +++ b/Secretly/ViewControllers/UserProfileViewController.swift @@ -0,0 +1,44 @@ +// +// UserProfileViewController.swift +// Secretly +// +// Created by Yocelin Garcia Romero on 13/07/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import UIKit + +class UserProfileViewController: UIViewController { + @IBOutlet var userProfileImage: UIImageView! + @IBOutlet var userProfileName: UILabel! + + let service = UserProfileService() + var user: User? { + didSet { + setUserView() + } + } + + override func viewDidLoad() { + super.viewDidLoad() + let backgroundImage = UIImageView(frame: UIScreen.main.bounds) + backgroundImage.image = UIImage(named: "background-green") + backgroundImage.contentMode = .scaleAspectFill + view.insertSubview(backgroundImage, at: 0) + loadUserProgile() + } + + @objc + func loadUserProgile() { + service.showProfile { [unowned self] user in self.user = user } + } + + @objc func setUserView() { + userProfileImage.image = nil + + guard let user = user else { return } + + userProfileName.text = user.username + ImageLoader.load(user.avatarUrl) { [unowned self] img in self.userProfileImage.image = img } + } +} diff --git a/Secretly/ViewControllers/WelcomeViewController.swift b/Secretly/ViewControllers/WelcomeViewController.swift index e27862b..e3161f4 100644 --- a/Secretly/ViewControllers/WelcomeViewController.swift +++ b/Secretly/ViewControllers/WelcomeViewController.swift @@ -9,7 +9,7 @@ import UIKit class WelcomeViewController: UIViewController { - @IBOutlet weak var helloLbl: UILabel! + @IBOutlet var helloLbl: UILabel! let service = CurrentUserService() override func viewDidLoad() { diff --git a/Secretly/Views/AuthorView.swift b/Secretly/Views/AuthorView.swift index 9d32ae3..b41905a 100644 --- a/Secretly/Views/AuthorView.swift +++ b/Secretly/Views/AuthorView.swift @@ -58,30 +58,30 @@ class AuthorView: UIView { } /* - // Only override draw() if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func draw(_ rect: CGRect) { - // Drawing code - } - */ + // 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 + 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) + stack.topAnchor.constraint(equalTo: topAnchor, constant: 3), + stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 3), + stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 3), + stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 3), ]) NSLayoutConstraint.activate([ - avatarImg.widthAnchor.constraint(equalTo: avatarImg.heightAnchor) + avatarImg.widthAnchor.constraint(equalTo: avatarImg.heightAnchor), ]) - avatarImg.layer.cornerRadius = self.frame.height / 2.0 + avatarImg.layer.cornerRadius = frame.height / 2.0 } func updateContent() { @@ -89,5 +89,4 @@ class AuthorView: UIView { 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 index ee08d53..fa2fccc 100644 --- a/Secretly/Views/PostCollectionViewCell.swift +++ b/Secretly/Views/PostCollectionViewCell.swift @@ -10,16 +10,21 @@ import UIKit class PostCollectionViewCell: UICollectionViewCell { 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 commentCounter: UILabel! + + @IBOutlet var authorView: AuthorView! + @IBOutlet var contentLabel: UILabel! + @IBOutlet var imageView: UIImageView! + @IBOutlet var likeState: UIButton! + @IBOutlet var commentCounter: UILabel! + @IBOutlet var likeMsm: UILabel! + @IBOutlet var likeButton: UIButton! override func awakeFromNib() { super.awakeFromNib() @@ -29,13 +34,41 @@ class PostCollectionViewCell: UICollectionViewCell { imageView.image = nil guard let post = post else { return } if let color = UIColor(hex: post.backgroundColor) { - self.backgroundColor = color + backgroundColor = color } - self.contentLabel.text = post.content - self.commentCounter.text = String(describing: post.commentsCount ?? 0) + contentLabel.text = post.content + 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 + authorView.author = post.user + likeMsm.text = "\(post.likesCount ?? 0) likes" + + if post.liked ?? false { + likeButton.setImage(UIImage(systemName: "heart.fill"), for: .normal) + } else { + likeButton.setImage(UIImage(systemName: "heart"), for: .normal) + } + } + + @IBAction func likeAction(_: Any) { + likeService?.action { [unowned self] result in + switch result { + case .success(nil): + likeButton.setImage(UIImage(systemName: "heart"), for: .normal) + post?.unlike() + self.likeMsm.text = "\(post?.likesCount ?? 0) likes" + case .success: + likeButton.setImage(UIImage(systemName: "heart.fill"), for: .normal) + post?.like() + self.likeMsm.text = "\(post?.likesCount ?? 0) likes" + case .failure: + print("Request fail") + } + } + } + + @IBAction func commentAction(_: Any) { + print("Comment") } } diff --git a/Secretly/Views/PostCollectionViewCell.xib b/Secretly/Views/PostCollectionViewCell.xib index 7b3a4e2..489bded 100644 --- a/Secretly/Views/PostCollectionViewCell.xib +++ b/Secretly/Views/PostCollectionViewCell.xib @@ -1,8 +1,8 @@ - + - + @@ -17,71 +17,74 @@ - + - - - - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -89,7 +92,9 @@ - + + + diff --git a/Secretly/Views/PreviewPostVIew.swift b/Secretly/Views/PreviewPostVIew.swift index 9e75064..06ab949 100644 --- a/Secretly/Views/PreviewPostVIew.swift +++ b/Secretly/Views/PreviewPostVIew.swift @@ -11,14 +11,16 @@ import UIKit class PreviewPostView: UIView { public var content: String? { didSet { - self.contentLbl.text = content + contentLbl.text = content } } + public var image: UIImage? { didSet { - self.imgView.image = image + imgView.image = image } } + let imgView: UIImageView = { let iv = UIImageView() iv.backgroundColor = .clear @@ -47,23 +49,23 @@ class PreviewPostView: UIView { } func clear() { - self.imgView.image = nil - self.image = nil - self.contentLbl.text = nil - self.content = nil + imgView.image = nil + image = nil + contentLbl.text = nil + content = nil } private func setupLayout() { addSubview(imgView) addSubview(contentLbl) NSLayoutConstraint.activate([ - imgView.topAnchor.constraint(equalTo: self.topAnchor), - imgView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - imgView.trailingAnchor.constraint(equalTo: self.trailingAnchor), - imgView.bottomAnchor.constraint(equalTo: self.bottomAnchor), - contentLbl.centerYAnchor.constraint(equalTo: self.centerYAnchor), - contentLbl.leadingAnchor.constraint(equalTo: self.leadingAnchor), - contentLbl.trailingAnchor.constraint(equalTo: self.trailingAnchor) + imgView.topAnchor.constraint(equalTo: topAnchor), + imgView.leadingAnchor.constraint(equalTo: leadingAnchor), + imgView.trailingAnchor.constraint(equalTo: trailingAnchor), + imgView.bottomAnchor.constraint(equalTo: bottomAnchor), + contentLbl.centerYAnchor.constraint(equalTo: centerYAnchor), + contentLbl.leadingAnchor.constraint(equalTo: leadingAnchor), + contentLbl.trailingAnchor.constraint(equalTo: trailingAnchor), ]) } } diff --git a/SecretlyTests/SecretlyTests.swift b/SecretlyTests/SecretlyTests.swift index 0c827a6..cd3accd 100644 --- a/SecretlyTests/SecretlyTests.swift +++ b/SecretlyTests/SecretlyTests.swift @@ -6,8 +6,8 @@ // Copyright © 2020 3zcurdia. All rights reserved. // -import XCTest @testable import Secretly +import XCTest class SecretlyTests: XCTestCase { func testExample() {