diff --git a/Auth/AuthViewController.swift b/Auth/AuthViewController.swift deleted file mode 100644 index 5bee71e..0000000 --- a/Auth/AuthViewController.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// AuthViewController.swift -// ImageFeed - - -import Foundation -import UIKit - -protocol AuthViewControllerDelegate: AnyObject { - func authViewController(_ vc: AuthViewController, didAuthenticateWithCode code: String) -} - -final class AuthViewController: UIViewController { - private let ShowWebViewSegueIdentifier = String("ShowWebView") - - weak var delegate: AuthViewControllerDelegate? - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == ShowWebViewSegueIdentifier { - guard - let webViewViewController = segue.destination as? WebViewViewController - else {fatalError("Failed to prepare for \(ShowWebViewSegueIdentifier)") } - webViewViewController.delegate = self - } else { - super.prepare(for: segue, sender: sender) - } - } -} - -extension AuthViewController: WebViewViewControllerDelegate { - func webViewViewController(_vc: WebViewViewController, didAuthenticateWithCode code: String) { - delegate?.authViewController(self, didAuthenticateWithCode: code) - } - - func webViewViewControllerDidCancel(_ vc: WebViewViewController) { - dismiss(animated: true) - } -} diff --git a/Auth/OAuth2TokenStorage.swift b/Auth/OAuth2TokenStorage.swift deleted file mode 100644 index 8dfe7af..0000000 --- a/Auth/OAuth2TokenStorage.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// OAuth2TokenStorage.swift -// ImageFeed -// - -import Foundation - -final class OAuth2TokenStorage { - static let shared = OAuth2TokenStorage() - - private let bearerTokenKey = "OAuth2BearerToken" - var token: String?{ - get { - return UserDefaults.standard.string(forKey: bearerTokenKey) - } - set { - UserDefaults.standard.set(newValue, forKey: bearerTokenKey) - } - } -} diff --git a/Auth/OAuth2service.swift b/Auth/OAuth2service.swift deleted file mode 100644 index a49ed1f..0000000 --- a/Auth/OAuth2service.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// OAuth2service.swift -// ImageFeed -// - -import Foundation - -final class OAuth2Service { - static let shared = OAuth2Service() - - private let urlSession = URLSession.shared - - private (set) var authToken: String? { - get { - return OAuth2TokenStorage().token - } - set { - OAuth2TokenStorage().token = newValue - } - } - - func fetchOAuthToken( - _ code: String, - completion: @escaping (Result) -> Void - ){ - let request = authTokenRequest(code: code) - let task = object(for: request) { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let body): - let authToken = body.accessToken - self.authToken = authToken - DispatchQueue.main.async { - completion(.success(authToken)) - } - case .failure(let error): - completion(.failure(error)) - } - } - task.resume() - } -} - -extension OAuth2Service { - - private func object( - for request: URLRequest, - completion: @escaping (Result) -> Void - ) -> URLSessionTask { - let decoder = JSONDecoder() - return urlSession.data(for: request) { (result: Result) in - let response = result.flatMap { data -> Result in - Result { try decoder.decode(OAuthTokenResponseBody.self, from: data) } - } - completion(response) - } - } - private func authTokenRequest(code: String) -> URLRequest { - URLRequest.makeHTTPRequest( - path: "/oauth/token" - + "?client_id=\(AccessKey)" - + "&&client_secret=\(SecretKey)" - + "&&redirect_uri=\(RedirectedURI)" - + "&&code=\(code)" - + "&&grant_type=authorization_code", - httpMethod: "POST", - baseURL: URL(string: "https://unsplash.com")! - ) } - - private struct OAuthTokenResponseBody: Decodable { - let accessToken: String - let tokenType: String - let scope: String - let createdAt: Int - - enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case tokenType = "token_type" - case scope - case createdAt = "created_at" - } - let url = URL(string: "https://unsplash.com/oauth/token")! - } -} -// MARK: - HTTP Request - -fileprivate let defaultBaseURL = URL(string: "https://api.unsplash.com")! - -extension URLRequest { - - static func makeHTTPRequest( - path: String, - httpMethod: String, - baseURL: URL = DefaultBaseURL - ) -> URLRequest { - var request = URLRequest(url: URL(string: path, relativeTo: baseURL)!) - request.httpMethod = httpMethod - return request - } } - -// MARK: - Network Connection - -private enum NetworkError: Error { - case httpStatusCode(Int) - case urlRequestError(Error) - case urlSessionError -} - -extension URLSession { - - func data( - for request: URLRequest, - completion: @escaping (Result) -> Void - ) -> URLSessionTask { - let fulfillCompletion: (Result) -> Void = { result in - DispatchQueue.main.async { - completion(result) - } - } - let task = dataTask(with: request, completionHandler: { data, response, error in - if let data = data, - let response = response, - let statusCode = (response as? HTTPURLResponse)?.statusCode - { - if 200 ..< 300 ~= statusCode { - fulfillCompletion(.success(data)) - } else { - fulfillCompletion(.failure(NetworkError.httpStatusCode(statusCode))) - } - } else if let error = error { - fulfillCompletion(.failure(NetworkError.urlRequestError(error))) - } else { - fulfillCompletion(.failure(NetworkError.urlSessionError)) - } - }) - task.resume() - return task - } } diff --git a/Auth/OAuthTokenResponseBody.swift b/Auth/OAuthTokenResponseBody.swift deleted file mode 100644 index a9cdbac..0000000 --- a/Auth/OAuthTokenResponseBody.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// OAuthTokenResponseBody.swift -// ImageFeed -// -// Created by Ina on 08/06/2023. -// - -import Foundation - -private struct OAuthTokenResponseBody: Decodable { - let accessToken: String - let tokenType: String - let scope: String - let createdAt: String - - enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case tokenType = "token_type" - case scope - case createdAt = "created_at" - } - - let url = URL(string: "https://unsplash.com/oauth/token")! -} diff --git a/Auth/WebViewViewController.swift b/Auth/WebViewViewController.swift deleted file mode 100644 index c0f8762..0000000 --- a/Auth/WebViewViewController.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// WebViewController.swift -// ImageFeed -// - -import Foundation -import WebKit - -fileprivate let UnsplashAuthorizedURLString = "https://unsplash.com/oauth/authorize" - -protocol WebViewViewControllerDelegate: AnyObject { - func webViewViewController(_vc: WebViewViewController, didAuthenticateWithCode code: String) - func webViewViewControllerDidCancel(_ vc: WebViewViewController) -} - -final class WebViewViewController: UIViewController { - @IBOutlet private var webView: WKWebView! - - weak var delegate: WebViewViewControllerDelegate? - - @IBAction private func didTapBackButton(_ sender: Any?) { - delegate?.webViewViewControllerDidCancel(self) - } - @IBOutlet private var progressView: UIProgressView! - - override func viewDidLoad() { - super.viewDidLoad() - - webView.navigationDelegate = self - - var urlComponents = URLComponents(string: "https://unsplash.com/oauth/authorize")! - urlComponents.queryItems = [ - URLQueryItem(name: "client_id", value: AccessKey), - URLQueryItem(name: "redirect_uri", value: RedirectedURI), - URLQueryItem(name: "response_type", value: "code"), - URLQueryItem(name: "scope", value: AccessScope) - ] - let url = urlComponents.url! - - let request = URLRequest(url: url) - webView.load(request) - - updateProgress() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - webView.addObserver( - self, - forKeyPath: #keyPath(WKWebView.estimatedProgress), - options: .new, - context: nil) - updateProgress() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - webView.removeObserver( - self, - forKeyPath: #keyPath(WKWebView.estimatedProgress), - context: nil) - } - - override func observeValue( - forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey: Any]?, - context: UnsafeMutableRawPointer? - ) { - if keyPath == #keyPath(WKWebView.estimatedProgress) { - updateProgress() - } else { - super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) - } - } - - private func updateProgress() { - progressView.progress = Float(webView.estimatedProgress) - progressView.isHidden = fabs(webView.estimatedProgress - 1.0) <= 0.0001 - } -} - -extension WebViewViewController: WKNavigationDelegate { - func webView( - _ webView: WKWebView, - decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void - ) { - if let code = code(from: navigationAction) { - delegate?.webViewViewController(_vc: self, didAuthenticateWithCode: code) - decisionHandler(.cancel) - } else { - decisionHandler(.allow) - } - } - - private func code(from navigationAction: WKNavigationAction) -> String? { - if - let url = navigationAction.request.url, - let urlComponents = URLComponents(string: url.absoluteString), - urlComponents.path == "/oauth/authorize/native", - let items = urlComponents.queryItems, - let codeItem = items.first(where: {$0.name == "code"}) - { - return codeItem.value - } else { - return nil - } - } -} diff --git a/Helpers/UIColors+extensions.swift b/Helpers/UIColors+extensions.swift deleted file mode 100644 index 3a8036a..0000000 --- a/Helpers/UIColors+extensions.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// UIColors_extensions.swift -// ImageFeed -// - -import Foundation -import UIKit - -extension UIColor { - static var yp_backgroung: UIColor { UIColor(named: "yp_background") ?? .darkGray} - static var yp_black: UIColor { UIColor(named: "yp_black") ?? .black} - static var yp_blue: UIColor { UIColor(named: "yp_bleu") ?? .blue} - static var yp_gray: UIColor { UIColor(named: "yp_gray") ?? .gray} - static var yp_red: UIColor { UIColor(named: "yp_red") ?? .red} - static var yp_white_alpha: UIColor { UIColor(named: "yp_white_alpha") ?? .white.withAlphaComponent(0.5)} - static var yp_white: UIColor { UIColor(named: "yp_white") ?? .white} -} diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 9900c3f..91435e1 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -7,189 +7,313 @@ objects = { /* Begin PBXBuildFile section */ - A900CD5E2A190C2F000DEC7E /* UIColors+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A900CD5D2A190C2F000DEC7E /* UIColors+extensions.swift */; }; - A9393DAA2A0ADF46002464CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9393DA92A0ADF46002464CB /* AppDelegate.swift */; }; - A9393DAC2A0ADF46002464CB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9393DAB2A0ADF46002464CB /* SceneDelegate.swift */; }; - A9393DAE2A0ADF46002464CB /* ImagesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9393DAD2A0ADF46002464CB /* ImagesListViewController.swift */; }; - A9393DB12A0ADF46002464CB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9393DAF2A0ADF46002464CB /* Main.storyboard */; }; - A9393DB32A0ADF4A002464CB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9393DB22A0ADF4A002464CB /* Assets.xcassets */; }; - A9393DB62A0ADF4A002464CB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9393DB42A0ADF4A002464CB /* LaunchScreen.storyboard */; }; - A95D86682A33CF8500FC3DFF /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95D86672A33CF8500FC3DFF /* SplashViewController.swift */; }; - A9654A382A102DE800787C05 /* SingleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9654A372A102DE800787C05 /* SingleImageViewController.swift */; }; - A985D4E12A30CD5400384AA8 /* NetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985D4E02A30CD5400384AA8 /* NetworkClient.swift */; }; - A985D4E22A31056E00384AA8 /* OAuth2service.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B80E872A2CCA4B000476FB /* OAuth2service.swift */; }; - A985D4E42A310B5B00384AA8 /* OAuth2TokenStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985D4E32A310B5B00384AA8 /* OAuth2TokenStorage.swift */; }; - A986F19B2A0AF18200441AAF /* ImagesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A986F19A2A0AF18200441AAF /* ImagesListCell.swift */; }; - A99E2BF82A0FB7ED0023F4E0 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99E2BF72A0FB7ED0023F4E0 /* ProfileViewController.swift */; }; - A9D4037F2A23A34D00ADE598 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D4037E2A23A34D00ADE598 /* AuthViewController.swift */; }; - A9D403802A23A39100ADE598 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CE696C2A21572D0069C3A9 /* Constants.swift */; }; - A9D403822A23A4F500ADE598 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D403812A23A4F500ADE598 /* WebViewViewController.swift */; }; + 2A08F8FE2A4CAA08007B9E0D /* OAuthTokenResponseBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A08F8FD2A4CAA08007B9E0D /* OAuthTokenResponseBody.swift */; }; + 2A08F9012A4CAA93007B9E0D /* OAuth2Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A08F9002A4CAA93007B9E0D /* OAuth2Service.swift */; }; + 2A08F9032A4CAB4B007B9E0D /* OAuth2TokenStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A08F9022A4CAB4B007B9E0D /* OAuth2TokenStorage.swift */; }; + 2A0DA56B2A6C8C190099011A /* ImagesListServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0DA56A2A6C8C190099011A /* ImagesListServiceTests.swift */; }; + 2A1DC67F2A210CE5007EBDDA /* ImagesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1DC67E2A210CE5007EBDDA /* ImagesListCell.swift */; }; + 2A2AEA4F2A1FC263003EA15E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2AEA4E2A1FC263003EA15E /* AppDelegate.swift */; }; + 2A2AEA512A1FC263003EA15E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2AEA502A1FC263003EA15E /* SceneDelegate.swift */; }; + 2A2AEA532A1FC263003EA15E /* ImagesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2AEA522A1FC263003EA15E /* ImagesListViewController.swift */; }; + 2A2AEA562A1FC263003EA15E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A2AEA542A1FC263003EA15E /* Main.storyboard */; }; + 2A2AEA582A1FC264003EA15E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A2AEA572A1FC264003EA15E /* Assets.xcassets */; }; + 2A2AEA5B2A1FC264003EA15E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A2AEA592A1FC264003EA15E /* LaunchScreen.storyboard */; }; + 2A4EED112A53607D002D192A /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4EED102A53607D002D192A /* SplashViewController.swift */; }; + 2A50922F2A65CB260045410D /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 2A50922E2A65CB260045410D /* Kingfisher */; }; + 2A5092322A671DEC0045410D /* SwiftKeychainWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 2A5092312A671DEC0045410D /* SwiftKeychainWrapper */; }; + 2A5092352A672ACD0045410D /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5092342A672ACD0045410D /* TabBarController.swift */; }; + 2A6141B12A62E48100852742 /* ProfileImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6141B02A62E48100852742 /* ProfileImageService.swift */; }; + 2A7574772A61AC88002F542B /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7574762A61AC88002F542B /* Profile.swift */; }; + 2A7AC0522A58BAF5009C0BD1 /* URLSession+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AC0512A58BAF5009C0BD1 /* URLSession+Extensions.swift */; }; + 2A7AC0542A58BB01009C0BD1 /* URLRequest+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7AC0532A58BB01009C0BD1 /* URLRequest+Extensions.swift */; }; + 2A8ED6512A34832F00697A35 /* SingleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8ED6502A34832F00697A35 /* SingleImageViewController.swift */; }; + 2AA7F56A2A6AAD3100915CFF /* ImagesListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA7F5692A6AAD3100915CFF /* ImagesListService.swift */; }; + 2AD0773B2A600BB8003F71E6 /* ProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 2AD0773A2A600BB8003F71E6 /* ProgressHUD */; }; + 2AD0773D2A606ABA003F71E6 /* UIBlockingProgressHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD0773C2A606ABA003F71E6 /* UIBlockingProgressHUD.swift */; }; + 2AD077402A608F46003F71E6 /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD0773F2A608F46003F71E6 /* ProfileService.swift */; }; + 2AD385D42A6155B200D3878C /* ProfileResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD385D32A6155B200D3878C /* ProfileResult.swift */; }; + 2AD3DA2C2A470C17005AF6CD /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD3DA2B2A470C17005AF6CD /* Constants.swift */; }; + 2AD3DA2F2A473A54005AF6CD /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD3DA2E2A473A54005AF6CD /* AuthViewController.swift */; }; + 2AD3DA312A47403F005AF6CD /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD3DA302A47403F005AF6CD /* WebViewViewController.swift */; }; + 2AD7B6802A33596B003896B9 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7B67F2A33596B003896B9 /* ProfileViewController.swift */; }; + 2AD7B6832A33667F003896B9 /* UIColorExtentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7B6822A33667F003896B9 /* UIColorExtentions.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 2A0DA56C2A6C8C190099011A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2A2AEA432A1FC263003EA15E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2A2AEA4A2A1FC263003EA15E; + remoteInfo = ImageFeed; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ - A900CD5D2A190C2F000DEC7E /* UIColors+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColors+extensions.swift"; sourceTree = ""; }; - A9393DA92A0ADF46002464CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - A9393DAB2A0ADF46002464CB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - A9393DAD2A0ADF46002464CB /* ImagesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListViewController.swift; sourceTree = ""; }; - A9393DB02A0ADF46002464CB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - A9393DB22A0ADF4A002464CB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - A9393DB52A0ADF4A002464CB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - A9393DB72A0ADF4A002464CB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A95D86672A33CF8500FC3DFF /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; - A9654A362A0FFD2C00787C05 /* ImageFeed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageFeed.app; sourceTree = BUILT_PRODUCTS_DIR; }; - A9654A372A102DE800787C05 /* SingleImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImageViewController.swift; sourceTree = ""; }; - A985D4E02A30CD5400384AA8 /* NetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = ""; }; - A985D4E32A310B5B00384AA8 /* OAuth2TokenStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2TokenStorage.swift; sourceTree = ""; }; - A986F19A2A0AF18200441AAF /* ImagesListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListCell.swift; sourceTree = ""; }; - A99E2BF72A0FB7ED0023F4E0 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; - A9B80E872A2CCA4B000476FB /* OAuth2service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2service.swift; sourceTree = ""; }; - A9CE696C2A21572D0069C3A9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - A9D4037E2A23A34D00ADE598 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; - A9D403812A23A4F500ADE598 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; + 2A08F8FD2A4CAA08007B9E0D /* OAuthTokenResponseBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenResponseBody.swift; sourceTree = ""; }; + 2A08F9002A4CAA93007B9E0D /* OAuth2Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2Service.swift; sourceTree = ""; }; + 2A08F9022A4CAB4B007B9E0D /* OAuth2TokenStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2TokenStorage.swift; sourceTree = ""; }; + 2A0DA5682A6C8C190099011A /* ImageFeedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageFeedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A0DA56A2A6C8C190099011A /* ImagesListServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListServiceTests.swift; sourceTree = ""; }; + 2A1DC67E2A210CE5007EBDDA /* ImagesListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListCell.swift; sourceTree = ""; }; + 2A2AEA4B2A1FC263003EA15E /* ImageFeed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageFeed.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A2AEA4E2A1FC263003EA15E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 2A2AEA502A1FC263003EA15E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 2A2AEA522A1FC263003EA15E /* ImagesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListViewController.swift; sourceTree = ""; }; + 2A2AEA552A1FC263003EA15E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 2A2AEA572A1FC264003EA15E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2A2AEA5A2A1FC264003EA15E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 2A2AEA5C2A1FC264003EA15E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2A4EED102A53607D002D192A /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; + 2A5092342A672ACD0045410D /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; + 2A6141B02A62E48100852742 /* ProfileImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImageService.swift; sourceTree = ""; }; + 2A7574762A61AC88002F542B /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = ""; }; + 2A7AC0512A58BAF5009C0BD1 /* URLSession+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Extensions.swift"; sourceTree = ""; }; + 2A7AC0532A58BB01009C0BD1 /* URLRequest+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Extensions.swift"; sourceTree = ""; }; + 2A8ED6502A34832F00697A35 /* SingleImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImageViewController.swift; sourceTree = ""; }; + 2AA7F5692A6AAD3100915CFF /* ImagesListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListService.swift; sourceTree = ""; }; + 2AD0773C2A606ABA003F71E6 /* UIBlockingProgressHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBlockingProgressHUD.swift; sourceTree = ""; }; + 2AD0773F2A608F46003F71E6 /* ProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; + 2AD385D32A6155B200D3878C /* ProfileResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileResult.swift; sourceTree = ""; }; + 2AD3DA2B2A470C17005AF6CD /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 2AD3DA2E2A473A54005AF6CD /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; + 2AD3DA302A47403F005AF6CD /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; + 2AD7B67F2A33596B003896B9 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; + 2AD7B6822A33667F003896B9 /* UIColorExtentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtentions.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - A9393DA32A0ADF46002464CB /* Frameworks */ = { + 2A0DA5652A6C8C190099011A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A2AEA482A1FC263003EA15E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2A50922F2A65CB260045410D /* Kingfisher in Frameworks */, + 2AD0773B2A600BB8003F71E6 /* ProgressHUD in Frameworks */, + 2A5092322A671DEC0045410D /* SwiftKeychainWrapper in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - A900CD5F2A190C68000DEC7E /* Helpers */ = { + 2A08F8FF2A4CAA5F007B9E0D /* Services */ = { isa = PBXGroup; children = ( - A900CD5D2A190C2F000DEC7E /* UIColors+extensions.swift */, + 2A4EED0D2A51FBCF002D192A /* OAuth2 */, + 2AA7F5682A6AAD1000915CFF /* ImagesListService */, + 2A6141AF2A62E43500852742 /* ProfileService */, ); - path = Helpers; + path = Services; + sourceTree = ""; + }; + 2A0DA5692A6C8C190099011A /* ImageFeedTests */ = { + isa = PBXGroup; + children = ( + 2A0DA56A2A6C8C190099011A /* ImagesListServiceTests.swift */, + ); + path = ImageFeedTests; sourceTree = ""; }; - A9393D9D2A0ADF46002464CB = { + 2A1DC6802A210D15007EBDDA /* ImageList */ = { isa = PBXGroup; children = ( - A95D86662A33CF6700FC3DFF /* SplashViewController */, - A9D4037D2A23A32700ADE598 /* Auth */, - A900CD5F2A190C68000DEC7E /* Helpers */, - A9393DA82A0ADF46002464CB /* ImageFeed */, - A9654A362A0FFD2C00787C05 /* ImageFeed.app */, - A986F19C2A0AF20400441AAF /* ImagesList */, - A99E2BFD2A0FB9B30023F4E0 /* Profile */, - A985D4DF2A30CD3100384AA8 /* Services */, - A9A4DCFD2A111CD200171B2D /* SingleImageViewController */, + 2A2AEA522A1FC263003EA15E /* ImagesListViewController.swift */, + 2A1DC67E2A210CE5007EBDDA /* ImagesListCell.swift */, ); + path = ImageList; sourceTree = ""; }; - A9393DA82A0ADF46002464CB /* ImageFeed */ = { + 2A2AEA422A1FC263003EA15E = { isa = PBXGroup; children = ( - A9393DB72A0ADF4A002464CB /* Info.plist */, - A9393DA92A0ADF46002464CB /* AppDelegate.swift */, - A9CE696C2A21572D0069C3A9 /* Constants.swift */, - A9393DAB2A0ADF46002464CB /* SceneDelegate.swift */, - A9393DB22A0ADF4A002464CB /* Assets.xcassets */, - A9393DB42A0ADF4A002464CB /* LaunchScreen.storyboard */, - A9393DAF2A0ADF46002464CB /* Main.storyboard */, + 2A2AEA4D2A1FC263003EA15E /* ImageFeed */, + 2A0DA5692A6C8C190099011A /* ImageFeedTests */, + 2A2AEA4C2A1FC263003EA15E /* Products */, + ); + sourceTree = ""; + }; + 2A2AEA4C2A1FC263003EA15E /* Products */ = { + isa = PBXGroup; + children = ( + 2A2AEA4B2A1FC263003EA15E /* ImageFeed.app */, + 2A0DA5682A6C8C190099011A /* ImageFeedTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 2A2AEA4D2A1FC263003EA15E /* ImageFeed */ = { + isa = PBXGroup; + children = ( + 2A1DC6802A210D15007EBDDA /* ImageList */, + 2AD7B67E2A335905003896B9 /* Profile */, + 2A8ED64F2A3482F100697A35 /* SIngleImage */, + 2AD3DA2D2A473A2D005AF6CD /* Auth */, + 2A08F8FF2A4CAA5F007B9E0D /* Services */, + 2A5092332A672AA30045410D /* TabBarController */, + 2A4EED0F2A53604D002D192A /* Splash */, + 2A7AC0502A58BAC7009C0BD1 /* Helpers */, + 2A2AEA4E2A1FC263003EA15E /* AppDelegate.swift */, + 2A2AEA502A1FC263003EA15E /* SceneDelegate.swift */, + 2AD3DA2B2A470C17005AF6CD /* Constants.swift */, + 2A2AEA542A1FC263003EA15E /* Main.storyboard */, + 2A2AEA572A1FC264003EA15E /* Assets.xcassets */, + 2A2AEA592A1FC264003EA15E /* LaunchScreen.storyboard */, + 2A2AEA5C2A1FC264003EA15E /* Info.plist */, ); path = ImageFeed; sourceTree = ""; }; - A95D86662A33CF6700FC3DFF /* SplashViewController */ = { + 2A4EED0D2A51FBCF002D192A /* OAuth2 */ = { isa = PBXGroup; children = ( - A95D86672A33CF8500FC3DFF /* SplashViewController.swift */, + 2A08F9002A4CAA93007B9E0D /* OAuth2Service.swift */, + 2AD0773C2A606ABA003F71E6 /* UIBlockingProgressHUD.swift */, + 2A08F9022A4CAB4B007B9E0D /* OAuth2TokenStorage.swift */, ); - path = SplashViewController; + path = OAuth2; sourceTree = ""; }; - A985D4DF2A30CD3100384AA8 /* Services */ = { + 2A4EED0F2A53604D002D192A /* Splash */ = { isa = PBXGroup; children = ( - A985D4E02A30CD5400384AA8 /* NetworkClient.swift */, + 2A4EED102A53607D002D192A /* SplashViewController.swift */, ); - path = Services; + path = Splash; sourceTree = ""; }; - A986F19C2A0AF20400441AAF /* ImagesList */ = { + 2A5092332A672AA30045410D /* TabBarController */ = { isa = PBXGroup; children = ( - A986F19A2A0AF18200441AAF /* ImagesListCell.swift */, - A9393DAD2A0ADF46002464CB /* ImagesListViewController.swift */, + 2A5092342A672ACD0045410D /* TabBarController.swift */, ); - name = ImagesList; - path = ImageFeed/ImagesList; + path = TabBarController; sourceTree = ""; }; - A99E2BFD2A0FB9B30023F4E0 /* Profile */ = { + 2A6141AF2A62E43500852742 /* ProfileService */ = { isa = PBXGroup; children = ( - A99E2BF72A0FB7ED0023F4E0 /* ProfileViewController.swift */, + 2AD0773F2A608F46003F71E6 /* ProfileService.swift */, + 2A6141B02A62E48100852742 /* ProfileImageService.swift */, ); - path = Profile; + path = ProfileService; + sourceTree = ""; + }; + 2A7AC0502A58BAC7009C0BD1 /* Helpers */ = { + isa = PBXGroup; + children = ( + 2A7AC0512A58BAF5009C0BD1 /* URLSession+Extensions.swift */, + 2A7AC0532A58BB01009C0BD1 /* URLRequest+Extensions.swift */, + 2AD7B6822A33667F003896B9 /* UIColorExtentions.swift */, + ); + path = Helpers; sourceTree = ""; }; - A9A4DCFD2A111CD200171B2D /* SingleImageViewController */ = { + 2A8ED64F2A3482F100697A35 /* SIngleImage */ = { isa = PBXGroup; children = ( - A9654A372A102DE800787C05 /* SingleImageViewController.swift */, + 2A8ED6502A34832F00697A35 /* SingleImageViewController.swift */, ); - path = SingleImageViewController; + path = SIngleImage; sourceTree = ""; }; - A9B803392A0C23FA0096C848 /* Products */ = { + 2AA7F5682A6AAD1000915CFF /* ImagesListService */ = { isa = PBXGroup; - name = Products; + children = ( + 2AA7F5692A6AAD3100915CFF /* ImagesListService.swift */, + ); + path = ImagesListService; sourceTree = ""; }; - A9D4037D2A23A32700ADE598 /* Auth */ = { + 2AD3DA2D2A473A2D005AF6CD /* Auth */ = { isa = PBXGroup; children = ( - A9D4037E2A23A34D00ADE598 /* AuthViewController.swift */, - A9B80E872A2CCA4B000476FB /* OAuth2service.swift */, - A985D4E32A310B5B00384AA8 /* OAuth2TokenStorage.swift */, - A9D403812A23A4F500ADE598 /* WebViewViewController.swift */, + 2AD3DA2E2A473A54005AF6CD /* AuthViewController.swift */, + 2AD3DA302A47403F005AF6CD /* WebViewViewController.swift */, + 2A08F8FD2A4CAA08007B9E0D /* OAuthTokenResponseBody.swift */, ); path = Auth; sourceTree = ""; }; + 2AD7B67E2A335905003896B9 /* Profile */ = { + isa = PBXGroup; + children = ( + 2AD7B67F2A33596B003896B9 /* ProfileViewController.swift */, + 2AD385D32A6155B200D3878C /* ProfileResult.swift */, + 2A7574762A61AC88002F542B /* Profile.swift */, + ); + path = Profile; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - A9393DA52A0ADF46002464CB /* ImageFeed */ = { + 2A0DA5672A6C8C190099011A /* ImageFeedTests */ = { isa = PBXNativeTarget; - buildConfigurationList = A9393DBA2A0ADF4A002464CB /* Build configuration list for PBXNativeTarget "ImageFeed" */; + buildConfigurationList = 2A0DA56E2A6C8C190099011A /* Build configuration list for PBXNativeTarget "ImageFeedTests" */; buildPhases = ( - A9393DA22A0ADF46002464CB /* Sources */, - A9393DA32A0ADF46002464CB /* Frameworks */, - A9393DA42A0ADF46002464CB /* Resources */, + 2A0DA5642A6C8C190099011A /* Sources */, + 2A0DA5652A6C8C190099011A /* Frameworks */, + 2A0DA5662A6C8C190099011A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2A0DA56D2A6C8C190099011A /* PBXTargetDependency */, + ); + name = ImageFeedTests; + productName = ImageFeedTests; + productReference = 2A0DA5682A6C8C190099011A /* ImageFeedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 2A2AEA4A2A1FC263003EA15E /* ImageFeed */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A2AEA5F2A1FC264003EA15E /* Build configuration list for PBXNativeTarget "ImageFeed" */; + buildPhases = ( + 2A2AEA472A1FC263003EA15E /* Sources */, + 2A2AEA482A1FC263003EA15E /* Frameworks */, + 2A2AEA492A1FC263003EA15E /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ImageFeed; + packageProductDependencies = ( + 2AD0773A2A600BB8003F71E6 /* ProgressHUD */, + 2A50922E2A65CB260045410D /* Kingfisher */, + 2A5092312A671DEC0045410D /* SwiftKeychainWrapper */, + ); productName = ImageFeed; - productReference = A9654A362A0FFD2C00787C05 /* ImageFeed.app */; + productReference = 2A2AEA4B2A1FC263003EA15E /* ImageFeed.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - A9393D9E2A0ADF46002464CB /* Project object */ = { + 2A2AEA432A1FC263003EA15E /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; TargetAttributes = { - A9393DA52A0ADF46002464CB = { - CreatedOnToolsVersion = 14.2; + 2A0DA5672A6C8C190099011A = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 2A2AEA4A2A1FC263003EA15E; + }; + 2A2AEA4A2A1FC263003EA15E = { + CreatedOnToolsVersion = 14.3; }; }; }; - buildConfigurationList = A9393DA12A0ADF46002464CB /* Build configuration list for PBXProject "ImageFeed" */; + buildConfigurationList = 2A2AEA462A1FC263003EA15E /* Build configuration list for PBXProject "ImageFeed" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -197,72 +321,104 @@ en, Base, ); - mainGroup = A9393D9D2A0ADF46002464CB; - productRefGroup = A9393D9D2A0ADF46002464CB; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = A9B803392A0C23FA0096C848 /* Products */; - ProjectRef = A986F19A2A0AF18200441AAF /* ImagesListCell.swift */; - }, + mainGroup = 2A2AEA422A1FC263003EA15E; + packageReferences = ( + 2AD077392A600BB8003F71E6 /* XCRemoteSwiftPackageReference "ProgressHUD" */, + 2A50922D2A65CB260045410D /* XCRemoteSwiftPackageReference "Kingfisher" */, + 2A5092302A671DEC0045410D /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */, ); + productRefGroup = 2A2AEA4C2A1FC263003EA15E /* Products */; + projectDirPath = ""; projectRoot = ""; targets = ( - A9393DA52A0ADF46002464CB /* ImageFeed */, + 2A2AEA4A2A1FC263003EA15E /* ImageFeed */, + 2A0DA5672A6C8C190099011A /* ImageFeedTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - A9393DA42A0ADF46002464CB /* Resources */ = { + 2A0DA5662A6C8C190099011A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A2AEA492A1FC263003EA15E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - A9393DB62A0ADF4A002464CB /* LaunchScreen.storyboard in Resources */, - A9393DB32A0ADF4A002464CB /* Assets.xcassets in Resources */, - A9393DB12A0ADF46002464CB /* Main.storyboard in Resources */, + 2A2AEA5B2A1FC264003EA15E /* LaunchScreen.storyboard in Resources */, + 2A2AEA582A1FC264003EA15E /* Assets.xcassets in Resources */, + 2A2AEA562A1FC263003EA15E /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - A9393DA22A0ADF46002464CB /* Sources */ = { + 2A0DA5642A6C8C190099011A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A9393DAE2A0ADF46002464CB /* ImagesListViewController.swift in Sources */, - A986F19B2A0AF18200441AAF /* ImagesListCell.swift in Sources */, - A99E2BF82A0FB7ED0023F4E0 /* ProfileViewController.swift in Sources */, - A985D4E12A30CD5400384AA8 /* NetworkClient.swift in Sources */, - A9D403822A23A4F500ADE598 /* WebViewViewController.swift in Sources */, - A95D86682A33CF8500FC3DFF /* SplashViewController.swift in Sources */, - A9393DAA2A0ADF46002464CB /* AppDelegate.swift in Sources */, - A985D4E22A31056E00384AA8 /* OAuth2service.swift in Sources */, - A9D4037F2A23A34D00ADE598 /* AuthViewController.swift in Sources */, - A9654A382A102DE800787C05 /* SingleImageViewController.swift in Sources */, - A900CD5E2A190C2F000DEC7E /* UIColors+extensions.swift in Sources */, - A9393DAC2A0ADF46002464CB /* SceneDelegate.swift in Sources */, - A985D4E42A310B5B00384AA8 /* OAuth2TokenStorage.swift in Sources */, - A9D403802A23A39100ADE598 /* Constants.swift in Sources */, + 2A0DA56B2A6C8C190099011A /* ImagesListServiceTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2A2AEA472A1FC263003EA15E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2AD3DA312A47403F005AF6CD /* WebViewViewController.swift in Sources */, + 2AA7F56A2A6AAD3100915CFF /* ImagesListService.swift in Sources */, + 2A7574772A61AC88002F542B /* Profile.swift in Sources */, + 2AD7B6832A33667F003896B9 /* UIColorExtentions.swift in Sources */, + 2AD385D42A6155B200D3878C /* ProfileResult.swift in Sources */, + 2A08F9012A4CAA93007B9E0D /* OAuth2Service.swift in Sources */, + 2A2AEA532A1FC263003EA15E /* ImagesListViewController.swift in Sources */, + 2A1DC67F2A210CE5007EBDDA /* ImagesListCell.swift in Sources */, + 2A7AC0522A58BAF5009C0BD1 /* URLSession+Extensions.swift in Sources */, + 2A4EED112A53607D002D192A /* SplashViewController.swift in Sources */, + 2A08F8FE2A4CAA08007B9E0D /* OAuthTokenResponseBody.swift in Sources */, + 2A8ED6512A34832F00697A35 /* SingleImageViewController.swift in Sources */, + 2AD7B6802A33596B003896B9 /* ProfileViewController.swift in Sources */, + 2A2AEA4F2A1FC263003EA15E /* AppDelegate.swift in Sources */, + 2A5092352A672ACD0045410D /* TabBarController.swift in Sources */, + 2AD077402A608F46003F71E6 /* ProfileService.swift in Sources */, + 2A2AEA512A1FC263003EA15E /* SceneDelegate.swift in Sources */, + 2A6141B12A62E48100852742 /* ProfileImageService.swift in Sources */, + 2AD0773D2A606ABA003F71E6 /* UIBlockingProgressHUD.swift in Sources */, + 2AD3DA2F2A473A54005AF6CD /* AuthViewController.swift in Sources */, + 2A7AC0542A58BB01009C0BD1 /* URLRequest+Extensions.swift in Sources */, + 2A08F9032A4CAB4B007B9E0D /* OAuth2TokenStorage.swift in Sources */, + 2AD3DA2C2A470C17005AF6CD /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 2A0DA56D2A6C8C190099011A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2A2AEA4A2A1FC263003EA15E /* ImageFeed */; + targetProxy = 2A0DA56C2A6C8C190099011A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ - A9393DAF2A0ADF46002464CB /* Main.storyboard */ = { + 2A2AEA542A1FC263003EA15E /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( - A9393DB02A0ADF46002464CB /* Base */, + 2A2AEA552A1FC263003EA15E /* Base */, ); name = Main.storyboard; sourceTree = ""; }; - A9393DB42A0ADF4A002464CB /* LaunchScreen.storyboard */ = { + 2A2AEA592A1FC264003EA15E /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( - A9393DB52A0ADF4A002464CB /* Base */, + 2A2AEA5A2A1FC264003EA15E /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -270,7 +426,47 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - A9393DB82A0ADF4A002464CB /* Debug */ = { + 2A0DA56F2A6C8C190099011A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HAYA2KHL93; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "Practikum-ImageFeed-Application"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImageFeed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ImageFeed"; + }; + name = Debug; + }; + 2A0DA5702A6C8C190099011A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HAYA2KHL93; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "Practikum-ImageFeed-Application"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImageFeed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ImageFeed"; + }; + name = Release; + }; + 2A2AEA5D2A1FC264003EA15E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -320,6 +516,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=*]" = UIStatusBarStyleDefault; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -330,7 +528,7 @@ }; name = Debug; }; - A9393DB92A0ADF4A002464CB /* Release */ = { + 2A2AEA5E2A1FC264003EA15E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -374,6 +572,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=*]" = UIStatusBarStyleDefault; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -384,7 +584,7 @@ }; name = Release; }; - A9393DBB2A0ADF4A002464CB /* Debug */ = { + 2A2AEA602A1FC264003EA15E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -397,6 +597,8 @@ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -408,14 +610,14 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; - A9393DBC2A0ADF4A002464CB /* Release */ = { + 2A2AEA612A1FC264003EA15E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -428,6 +630,8 @@ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -439,7 +643,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; @@ -449,25 +653,79 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - A9393DA12A0ADF46002464CB /* Build configuration list for PBXProject "ImageFeed" */ = { + 2A0DA56E2A6C8C190099011A /* Build configuration list for PBXNativeTarget "ImageFeedTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - A9393DB82A0ADF4A002464CB /* Debug */, - A9393DB92A0ADF4A002464CB /* Release */, + 2A0DA56F2A6C8C190099011A /* Debug */, + 2A0DA5702A6C8C190099011A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - A9393DBA2A0ADF4A002464CB /* Build configuration list for PBXNativeTarget "ImageFeed" */ = { + 2A2AEA462A1FC263003EA15E /* Build configuration list for PBXProject "ImageFeed" */ = { isa = XCConfigurationList; buildConfigurations = ( - A9393DBB2A0ADF4A002464CB /* Debug */, - A9393DBC2A0ADF4A002464CB /* Release */, + 2A2AEA5D2A1FC264003EA15E /* Debug */, + 2A2AEA5E2A1FC264003EA15E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2A2AEA5F2A1FC264003EA15E /* Build configuration list for PBXNativeTarget "ImageFeed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A2AEA602A1FC264003EA15E /* Debug */, + 2A2AEA612A1FC264003EA15E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 2A50922D2A65CB260045410D /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.0.0; + }; + }; + 2A5092302A671DEC0045410D /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jrendel/SwiftKeychainWrapper"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.0; + }; + }; + 2AD077392A600BB8003F71E6 /* XCRemoteSwiftPackageReference "ProgressHUD" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/relatedcode/ProgressHUD"; + requirement = { + kind = exactVersion; + version = 13.7.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2A50922E2A65CB260045410D /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 2A50922D2A65CB260045410D /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; + 2A5092312A671DEC0045410D /* SwiftKeychainWrapper */ = { + isa = XCSwiftPackageProductDependency; + package = 2A5092302A671DEC0045410D /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */; + productName = SwiftKeychainWrapper; + }; + 2AD0773A2A600BB8003F71E6 /* ProgressHUD */ = { + isa = XCSwiftPackageProductDependency; + package = 2AD077392A600BB8003F71E6 /* XCRemoteSwiftPackageReference "ProgressHUD" */; + productName = ProgressHUD; + }; +/* End XCSwiftPackageProductDependency section */ }; - rootObject = A9393D9E2A0ADF46002464CB /* Project object */; + rootObject = 2A2AEA432A1FC263003EA15E /* Project object */; } diff --git a/ImageFeed.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ImageFeed.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..1caa881 --- /dev/null +++ b/ImageFeed.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "b6f62758f21a8c03cd64f4009c037cfa580a256e", + "version" : "7.9.1" + } + }, + { + "identity" : "progresshud", + "kind" : "remoteSourceControl", + "location" : "https://github.com/relatedcode/ProgressHUD", + "state" : { + "revision" : "01b56b31d61512d900b58cb7ac1479509bd7170f", + "version" : "13.7.2" + } + }, + { + "identity" : "swiftkeychainwrapper", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jrendel/SwiftKeychainWrapper", + "state" : { + "revision" : "185a3165346a03767101c4f62e9a545a0fe0530f", + "version" : "4.0.1" + } + } + ], + "version" : 2 +} diff --git a/ImageFeed.xcodeproj/project.xcworkspace/xcuserdata/mikhailandriyuk.xcuserdatad/UserInterfaceState.xcuserstate b/ImageFeed.xcodeproj/project.xcworkspace/xcuserdata/mikhailandriyuk.xcuserdatad/UserInterfaceState.xcuserstate index 0e9ea33..3bac82a 100644 Binary files a/ImageFeed.xcodeproj/project.xcworkspace/xcuserdata/mikhailandriyuk.xcuserdatad/UserInterfaceState.xcuserstate and b/ImageFeed.xcodeproj/project.xcworkspace/xcuserdata/mikhailandriyuk.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ImageFeed.xcodeproj/xcuserdata/Ina.xcuserdatad/xcschemes/xcschememanagement.plist b/ImageFeed.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 51% rename from ImageFeed.xcodeproj/xcuserdata/Ina.xcuserdatad/xcschemes/xcschememanagement.plist rename to ImageFeed.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist index 5309388..5d781a6 100644 --- a/ImageFeed.xcodeproj/xcuserdata/Ina.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ImageFeed.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,23 @@ 0 + SuppressBuildableAutocreation + + 2A0DA5672A6C8C190099011A + + primary + + + 2A17D1672A6C93C80054B7FB + + primary + + + 2A2AEA4A2A1FC263003EA15E + + primary + + + diff --git a/ImageFeed/AppDelegate.swift b/ImageFeed/AppDelegate.swift index 990aa13..15ad7e4 100644 --- a/ImageFeed/AppDelegate.swift +++ b/ImageFeed/AppDelegate.swift @@ -1,26 +1,23 @@ -// -// AppDelegate.swift -// ImageFeed -// - import UIKit +import ProgressHUD @main class AppDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + ProgressHUD.animationType = .systemActivityIndicator + ProgressHUD.colorHUD = .black + ProgressHUD.colorAnimation = .lightGray return true } // MARK: UISceneSession Lifecycle func application(_ 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) + let sceneConfiguration = UISceneConfiguration(name: "Main", + sessionRole: connectingSceneSession.role) + sceneConfiguration.delegateClass = SceneDelegate.self + return sceneConfiguration } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { @@ -28,7 +25,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // 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/ImageFeed/Assets.xcassets/AppIcon.appiconset/1024x1024@2x.png b/ImageFeed/Assets.xcassets/AppIcon.appiconset/1024x1024@2x.png new file mode 100644 index 0000000..c5b8fc4 Binary files /dev/null and b/ImageFeed/Assets.xcassets/AppIcon.appiconset/1024x1024@2x.png differ diff --git a/ImageFeed/Assets.xcassets/AppIcon.appiconset/Contents.json b/ImageFeed/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..e6c3779 100644 --- a/ImageFeed/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ImageFeed/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "1024x1024@2x.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/ImageFeed/Assets.xcassets/colors_yp/Contents.json b/ImageFeed/Assets.xcassets/AuthViewController/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/colors_yp/Contents.json rename to ImageFeed/Assets.xcassets/AuthViewController/Contents.json diff --git a/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Contents.json b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Contents.json new file mode 100644 index 0000000..f6daecc --- /dev/null +++ b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Logo_of_Unsplash.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Logo_of_Unsplash@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Logo_of_Unsplash@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/auth_screen_logo.imageset/auth_screen_logo.png b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash.png similarity index 100% rename from ImageFeed/Assets.xcassets/auth_screen_logo.imageset/auth_screen_logo.png rename to ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash.png diff --git a/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash@2x.png b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash@2x.png new file mode 100644 index 0000000..435533d Binary files /dev/null and b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash@2x.png differ diff --git a/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash@3x.png b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash@3x.png new file mode 100644 index 0000000..7bc844f Binary files /dev/null and b/ImageFeed/Assets.xcassets/AuthViewController/authLogo.imageset/Logo_of_Unsplash@3x.png differ diff --git a/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward.png b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward.png new file mode 100644 index 0000000..73bd422 Binary files /dev/null and b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward.png differ diff --git a/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward@2x.png b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward@2x.png new file mode 100644 index 0000000..8a9db02 Binary files /dev/null and b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward@2x.png differ diff --git a/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward@3x.png b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward@3x.png new file mode 100644 index 0000000..15f8c80 Binary files /dev/null and b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Backward@3x.png differ diff --git a/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Contents.json b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Contents.json new file mode 100644 index 0000000..427a0b8 --- /dev/null +++ b/ImageFeed/Assets.xcassets/AuthViewController/backBlack.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Backward.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Backward@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Backward@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Colors/Contents.json b/ImageFeed/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Colors/YP Backgroung.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Backgroung.colorset/Contents.json new file mode 100644 index 0000000..1d65869 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/YP Backgroung.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.106", + "red" : "0.102" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.106", + "red" : "0.102" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_background.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json similarity index 91% rename from ImageFeed/Assets.xcassets/colors_yp/yp_background.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json index 0a9634f..b298f71 100644 --- a/ImageFeed/Assets.xcassets/colors_yp/yp_background.colorset/Contents.json +++ b/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json @@ -4,7 +4,7 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.500", + "alpha" : "1.000", "blue" : "0.133", "green" : "0.106", "red" : "0.102" diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_gray.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Gray.colorset/Contents.json similarity index 87% rename from ImageFeed/Assets.xcassets/colors_yp/yp_gray.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Colors/YP Gray.colorset/Contents.json index 293a66c..8bc22ff 100644 --- a/ImageFeed/Assets.xcassets/colors_yp/yp_gray.colorset/Contents.json +++ b/ImageFeed/Assets.xcassets/Colors/YP Gray.colorset/Contents.json @@ -16,8 +16,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "localizable" : true } } diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_red.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json similarity index 87% rename from ImageFeed/Assets.xcassets/colors_yp/yp_red.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json index 330b6ee..0fb6f93 100644 --- a/ImageFeed/Assets.xcassets/colors_yp/yp_red.colorset/Contents.json +++ b/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json @@ -16,8 +16,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "localizable" : true } } diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_white.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP White.colorset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/colors_yp/yp_white.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Colors/YP White.colorset/Contents.json diff --git a/ImageFeed/Assets.xcassets/ImagesList/Contents.json b/ImageFeed/Assets.xcassets/ImagesList/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/ImagesList/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Contents.json b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Contents.json new file mode 100644 index 0000000..8a17e01 --- /dev/null +++ b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Rectangle 168.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Rectangle 168@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Rectangle 168@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168.png b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168.png new file mode 100644 index 0000000..5e11723 Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168@2x.png b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168@2x.png new file mode 100644 index 0000000..938c671 Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168@2x.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168@3x.png b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168@3x.png new file mode 100644 index 0000000..7d75d3b Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/gradient.imageset/Rectangle 168@3x.png differ diff --git a/ImageFeed/Assets.xcassets/heart_not_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/Contents.json similarity index 79% rename from ImageFeed/Assets.xcassets/heart_not_active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/Contents.json index 90d0c58..2191ffc 100644 --- a/ImageFeed/Assets.xcassets/heart_not_active.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "No Active@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "No Active@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active.png b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active.png new file mode 100644 index 0000000..7bfc142 Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active@2x.png b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active@2x.png new file mode 100644 index 0000000..65de2ce Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active@2x.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active@3x.png b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active@3x.png new file mode 100644 index 0000000..ac1f36f Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/likeOff.imageset/No Active@3x.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/Contents.json b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/Contents.json new file mode 100644 index 0000000..2191ffc --- /dev/null +++ b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "No Active.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "No Active@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "No Active@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active.png b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active.png new file mode 100644 index 0000000..60a8bbc Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active@2x.png b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active@2x.png new file mode 100644 index 0000000..87711ad Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active@2x.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active@3x.png b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active@3x.png new file mode 100644 index 0000000..7529c1b Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/likeOn.imageset/No Active@3x.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active.svg b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active.svg new file mode 100644 index 0000000..b276181 --- /dev/null +++ b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active@2x.png b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active@2x.png new file mode 100644 index 0000000..40d7ce9 Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active@2x.png differ diff --git a/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active@3x.png b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active@3x.png new file mode 100644 index 0000000..f9f4562 Binary files /dev/null and b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Active@3x.png differ diff --git a/ImageFeed/Assets.xcassets/Vector.imageset/Contents.json b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Contents.json similarity index 72% rename from ImageFeed/Assets.xcassets/Vector.imageset/Contents.json rename to ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Contents.json index 446d6be..65c5330 100644 --- a/ImageFeed/Assets.xcassets/Vector.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/ImagesList/tab_editorial_active.imageset/Contents.json @@ -1,15 +1,17 @@ { "images" : [ { - "filename" : "Vector.png", + "filename" : "Active.svg", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "Active@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Active@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/LaunchScreen/Contents.json b/ImageFeed/Assets.xcassets/LaunchScreen/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/LaunchScreen/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/backward_button.imageset/Contents.json b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Contents.json similarity index 72% rename from ImageFeed/Assets.xcassets/backward_button.imageset/Contents.json rename to ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Contents.json index c907ab2..e207a5c 100644 --- a/ImageFeed/Assets.xcassets/backward_button.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Contents.json @@ -1,15 +1,17 @@ { "images" : [ { - "filename" : "backward_button.png", + "filename" : "Vector.svg", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "Vector@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Vector@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector.svg b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector.svg new file mode 100644 index 0000000..908d0f3 --- /dev/null +++ b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector.svg @@ -0,0 +1,3 @@ + + + diff --git a/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector@2x.png b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector@2x.png new file mode 100644 index 0000000..a2790e6 Binary files /dev/null and b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector@2x.png differ diff --git a/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector@3x.png b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector@3x.png new file mode 100644 index 0000000..436b2cd Binary files /dev/null and b/ImageFeed/Assets.xcassets/LaunchScreen/launchScreenLogo.imageset/Vector@3x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/Contents.json b/ImageFeed/Assets.xcassets/Profile/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Profile/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/logout_pic.imageset/Contents.json b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/Contents.json similarity index 75% rename from ImageFeed/Assets.xcassets/logout_pic.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/Contents.json index 994970d..e1a2bb1 100644 --- a/ImageFeed/Assets.xcassets/logout_pic.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "ipad.and.arrow.forward@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "ipad.and.arrow.forward@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward.png b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward.png new file mode 100644 index 0000000..bd19f39 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward@2x.png b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward@2x.png new file mode 100644 index 0000000..ec22a89 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward@2x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward@3x.png b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward@3x.png new file mode 100644 index 0000000..028d66f Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/ipad.and.arrow.forward.imageset/ipad.and.arrow.forward@3x.png differ diff --git a/ImageFeed/Assets.xcassets/auth_screen_logo.imageset/Contents.json b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Contents.json similarity index 73% rename from ImageFeed/Assets.xcassets/auth_screen_logo.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Contents.json index 0039092..1c7aeaa 100644 --- a/ImageFeed/Assets.xcassets/auth_screen_logo.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Contents.json @@ -1,15 +1,17 @@ { "images" : [ { - "filename" : "auth_screen_logo.png", + "filename" : "Stub.png", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "Stub@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Stub@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub.png b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub.png new file mode 100644 index 0000000..816a58f Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub@2x.png b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub@2x.png new file mode 100644 index 0000000..a140213 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub@2x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub@3x.png b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub@3x.png new file mode 100644 index 0000000..5497994 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/placeholder.imageset/Stub@3x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active.svg b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active.svg new file mode 100644 index 0000000..94e410f --- /dev/null +++ b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active.svg @@ -0,0 +1,3 @@ + + + diff --git a/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active@2x.png b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active@2x.png new file mode 100644 index 0000000..1a7d9b8 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active@2x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active@3x.png b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active@3x.png new file mode 100644 index 0000000..486ad3e Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Active@3x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Contents.json new file mode 100644 index 0000000..65c5330 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Profile/tab_profile_active.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Active.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Active@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Active@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/user_pic.imageset/Contents.json b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Contents.json similarity index 81% rename from ImageFeed/Assets.xcassets/user_pic.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Contents.json index c9bece4..30c4a02 100644 --- a/ImageFeed/Assets.xcassets/user_pic.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "Photo@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Photo@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo.png b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo.png new file mode 100644 index 0000000..75a6d81 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo@2x.png b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo@2x.png new file mode 100644 index 0000000..7ddf7e7 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo@2x.png differ diff --git a/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo@3x.png b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo@3x.png new file mode 100644 index 0000000..c884562 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Profile/userPhoto.imageset/Photo@3x.png differ diff --git a/ImageFeed/Assets.xcassets/SingleImage/Contents.json b/ImageFeed/Assets.xcassets/SingleImage/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/SingleImage/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward.png b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward.png new file mode 100644 index 0000000..491ff87 Binary files /dev/null and b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward.png differ diff --git a/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward@2x.png b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward@2x.png new file mode 100644 index 0000000..da3a3ea Binary files /dev/null and b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward@2x.png differ diff --git a/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward@3x.png b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward@3x.png new file mode 100644 index 0000000..11e43bf Binary files /dev/null and b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Backward@3x.png differ diff --git a/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Contents.json b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Contents.json new file mode 100644 index 0000000..427a0b8 --- /dev/null +++ b/ImageFeed/Assets.xcassets/SingleImage/back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Backward.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Backward@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Backward@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/share_button.imageset/Contents.json b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Contents.json similarity index 80% rename from ImageFeed/Assets.xcassets/share_button.imageset/Contents.json rename to ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Contents.json index ae0cbc2..606a11a 100644 --- a/ImageFeed/Assets.xcassets/share_button.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "Sharing@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Sharing@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing.png b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing.png new file mode 100644 index 0000000..37deacb Binary files /dev/null and b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing.png differ diff --git a/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing@2x.png b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing@2x.png new file mode 100644 index 0000000..f15e370 Binary files /dev/null and b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing@2x.png differ diff --git a/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing@3x.png b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing@3x.png new file mode 100644 index 0000000..877ce76 Binary files /dev/null and b/ImageFeed/Assets.xcassets/SingleImage/sharingButton.imageset/Sharing@3x.png differ diff --git a/ImageFeed/Assets.xcassets/Vector.imageset/Vector.png b/ImageFeed/Assets.xcassets/Vector.imageset/Vector.png deleted file mode 100644 index 90cc92f..0000000 Binary files a/ImageFeed/Assets.xcassets/Vector.imageset/Vector.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/backward_button.imageset/backward_button.png b/ImageFeed/Assets.xcassets/backward_button.imageset/backward_button.png deleted file mode 100644 index 18a50fa..0000000 Binary files a/ImageFeed/Assets.xcassets/backward_button.imageset/backward_button.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_black.colorset/Contents.json b/ImageFeed/Assets.xcassets/colors_yp/yp_black.colorset/Contents.json deleted file mode 100644 index a595d69..0000000 --- a/ImageFeed/Assets.xcassets/colors_yp/yp_black.colorset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.133", - "green" : "0.106", - "red" : "0.102" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "localizable" : true - } -} diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_blue.colorset/Contents.json b/ImageFeed/Assets.xcassets/colors_yp/yp_blue.colorset/Contents.json deleted file mode 100644 index 7c1a08a..0000000 --- a/ImageFeed/Assets.xcassets/colors_yp/yp_blue.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.906", - "green" : "0.447", - "red" : "0.216" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ImageFeed/Assets.xcassets/colors_yp/yp_white_alpha.colorset/Contents.json b/ImageFeed/Assets.xcassets/colors_yp/yp_white_alpha.colorset/Contents.json deleted file mode 100644 index 5995b6a..0000000 --- a/ImageFeed/Assets.xcassets/colors_yp/yp_white_alpha.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.500", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ImageFeed/Assets.xcassets/heart_active.imageset/Active.png b/ImageFeed/Assets.xcassets/heart_active.imageset/Active.png deleted file mode 100644 index d5e0c27..0000000 Binary files a/ImageFeed/Assets.xcassets/heart_active.imageset/Active.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/heart_not_active.imageset/No Active.png b/ImageFeed/Assets.xcassets/heart_not_active.imageset/No Active.png deleted file mode 100644 index 0917446..0000000 Binary files a/ImageFeed/Assets.xcassets/heart_not_active.imageset/No Active.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/imagePlaceholder/Contents.json b/ImageFeed/Assets.xcassets/imagePlaceholder/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/imagePlaceholder/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/heart_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Contents.json similarity index 73% rename from ImageFeed/Assets.xcassets/heart_active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Contents.json index 0fe7a56..1c7aeaa 100644 --- a/ImageFeed/Assets.xcassets/heart_active.imageset/Contents.json +++ b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Contents.json @@ -1,15 +1,17 @@ { "images" : [ { - "filename" : "Active.png", + "filename" : "Stub.png", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "Stub@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Stub@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub.png b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub.png new file mode 100644 index 0000000..5c23b46 Binary files /dev/null and b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub.png differ diff --git a/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub@2x.png b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub@2x.png new file mode 100644 index 0000000..b7b3300 Binary files /dev/null and b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub@2x.png differ diff --git a/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub@3x.png b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub@3x.png new file mode 100644 index 0000000..331caff Binary files /dev/null and b/ImageFeed/Assets.xcassets/imagePlaceholder/ImagePlaceholder.imageset/Stub@3x.png differ diff --git a/ImageFeed/Assets.xcassets/logout_pic.imageset/ipad.and.arrow.forward.png b/ImageFeed/Assets.xcassets/logout_pic.imageset/ipad.and.arrow.forward.png deleted file mode 100644 index b3aef40..0000000 Binary files a/ImageFeed/Assets.xcassets/logout_pic.imageset/ipad.and.arrow.forward.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/1.imageset/1.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 1.imageset/1.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/1.imageset/1.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 1.imageset/1.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/1.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 1.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/1.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 1.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/14.imageset/14.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 10.imageset/14.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/14.imageset/14.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 10.imageset/14.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/14.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 10.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/14.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 10.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/8.imageset/8.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 11.imageset/8.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/8.imageset/8.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 11.imageset/8.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/8.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 11.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/8.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 11.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/16.imageset/16.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 12.imageset/16.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/16.imageset/16.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 12.imageset/16.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/16.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 12.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/16.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 12.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/18.imageset/18.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 13.imageset/18.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/18.imageset/18.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 13.imageset/18.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/18.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 13.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/18.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 13.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/11.imageset/11.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 14.imageset/11.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/11.imageset/11.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 14.imageset/11.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/11.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 14.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/11.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 14.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/17.imageset/17.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 15.imageset/17.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/17.imageset/17.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 15.imageset/17.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/17.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 15.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/17.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 15.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/20.imageset/20.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 16.imageset/20.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/20.imageset/20.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 16.imageset/20.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/20.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 16.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/20.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 16.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/19.imageset/19.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 17.imageset/19.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/19.imageset/19.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 17.imageset/19.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/19.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 17.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/19.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 17.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/13.imageset/13.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 18.imageset/13.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/13.imageset/13.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 18.imageset/13.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/13.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 18.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/13.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 18.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/6.imageset/6.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 19.imageset/6.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/6.imageset/6.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 19.imageset/6.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/6.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 19.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/6.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 19.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/2.imageset/2.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 2.imageset/2.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/2.imageset/2.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 2.imageset/2.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/2.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 2.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/2.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 2.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/3.imageset/3.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 3.imageset/3.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/3.imageset/3.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 3.imageset/3.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/3.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 3.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/3.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 3.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/5.imageset/5.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 4.imageset/5.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/5.imageset/5.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 4.imageset/5.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/5.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 4.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/5.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 4.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/4.imageset/4.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 5.imageset/4.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/4.imageset/4.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 5.imageset/4.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/4.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 5.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/4.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 5.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/7.imageset/7.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 6.imageset/7.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/7.imageset/7.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 6.imageset/7.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/7.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 6.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/7.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 6.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/10.imageset/10.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 7.imageset/10.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/10.imageset/10.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 7.imageset/10.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/10.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 7.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/10.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 7.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/9.imageset/9.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 8.imageset/9.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/9.imageset/9.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 8.imageset/9.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/9.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 8.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/9.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 8.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/12.imageset/12.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88 9.imageset/12.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/12.imageset/12.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 9.imageset/12.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/12.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88 9.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/12.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88 9.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/0.imageset/0.png b/ImageFeed/Assets.xcassets/mockImagesForTable/88.imageset/0.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/0.imageset/0.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/88.imageset/0.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/0.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/88.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/0.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/88.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/15.imageset/15.png b/ImageFeed/Assets.xcassets/mockImagesForTable/888.imageset/15.png similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/15.imageset/15.png rename to ImageFeed/Assets.xcassets/mockImagesForTable/888.imageset/15.png diff --git a/ImageFeed/Assets.xcassets/mockImagesForTable/15.imageset/Contents.json b/ImageFeed/Assets.xcassets/mockImagesForTable/888.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/mockImagesForTable/15.imageset/Contents.json rename to ImageFeed/Assets.xcassets/mockImagesForTable/888.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/nav_back_button.imageset/Contents.json b/ImageFeed/Assets.xcassets/nav_back_button.imageset/Contents.json deleted file mode 100644 index f5c6485..0000000 --- a/ImageFeed/Assets.xcassets/nav_back_button.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "chevron.backward.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ImageFeed/Assets.xcassets/nav_back_button.imageset/chevron.backward.png b/ImageFeed/Assets.xcassets/nav_back_button.imageset/chevron.backward.png deleted file mode 100644 index cbe17eb..0000000 Binary files a/ImageFeed/Assets.xcassets/nav_back_button.imageset/chevron.backward.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/share_button.imageset/Sharing.png b/ImageFeed/Assets.xcassets/share_button.imageset/Sharing.png deleted file mode 100644 index fed9ea2..0000000 Binary files a/ImageFeed/Assets.xcassets/share_button.imageset/Sharing.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/tab_editorial_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/tab_editorial_active.imageset/Contents.json deleted file mode 100644 index b74bb4d..0000000 --- a/ImageFeed/Assets.xcassets/tab_editorial_active.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "tab_editorial_active.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ImageFeed/Assets.xcassets/tab_editorial_active.imageset/tab_editorial_active.png b/ImageFeed/Assets.xcassets/tab_editorial_active.imageset/tab_editorial_active.png deleted file mode 100644 index 25aeb87..0000000 Binary files a/ImageFeed/Assets.xcassets/tab_editorial_active.imageset/tab_editorial_active.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/tab_profile_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/tab_profile_active.imageset/Contents.json deleted file mode 100644 index effb505..0000000 --- a/ImageFeed/Assets.xcassets/tab_profile_active.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "tab_profile_active.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ImageFeed/Assets.xcassets/tab_profile_active.imageset/tab_profile_active.png b/ImageFeed/Assets.xcassets/tab_profile_active.imageset/tab_profile_active.png deleted file mode 100644 index b4bbbc5..0000000 Binary files a/ImageFeed/Assets.xcassets/tab_profile_active.imageset/tab_profile_active.png and /dev/null differ diff --git a/ImageFeed/Assets.xcassets/user_pic.imageset/Photo.png b/ImageFeed/Assets.xcassets/user_pic.imageset/Photo.png deleted file mode 100644 index fd2b555..0000000 Binary files a/ImageFeed/Assets.xcassets/user_pic.imageset/Photo.png and /dev/null differ diff --git a/ImageFeed/Auth/AuthViewController.swift b/ImageFeed/Auth/AuthViewController.swift new file mode 100644 index 0000000..71bf7e8 --- /dev/null +++ b/ImageFeed/Auth/AuthViewController.swift @@ -0,0 +1,56 @@ +import UIKit + +protocol AuthViewControllerDelegate: AnyObject { + func authViewController(_ vc: AuthViewController, didAuthenticateWithCode code: String) +} + +final class AuthViewController: UIViewController { + + private let segueIdentifier = "ShowWebView" + private let oAuth2Service = OAuth2Service() + private let oAuth2TokenStorage = OAuth2TokenStorage() + + weak var delegate: AuthViewControllerDelegate? + + @IBOutlet private weak var authButton: UIButton! + + override func viewDidLoad() { + super.viewDidLoad() + authButton.layer.cornerRadius = 16 + authButton.layer.masksToBounds = true + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == segueIdentifier, + let viewController = segue.destination as? WebViewViewController { + viewController.delegate = self + } else { + super.prepare(for: segue, sender: sender) + } + } + + @IBAction private func didTapLoginButton(_ sender: UIButton) { + + } + + @IBAction private func didTapAuthButton(_ sender: Any?) { + + } +} + +extension AuthViewController: WebViewViewControllerDelegate { + func webViewViewController(_ vc: WebViewViewController, didAuthenticateWithCode code: String) { + delegate?.authViewController(self, didAuthenticateWithCode: code) + } + + func webViewViewControllerDidCancel(_ vc: WebViewViewController) { + dismiss(animated: true) + } +} + +// MARK: Status Bar Style +extension AuthViewController { + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} diff --git a/ImageFeed/Auth/OAuthTokenResponseBody.swift b/ImageFeed/Auth/OAuthTokenResponseBody.swift new file mode 100644 index 0000000..a3994e4 --- /dev/null +++ b/ImageFeed/Auth/OAuthTokenResponseBody.swift @@ -0,0 +1,8 @@ +import Foundation + +struct OAuthTokenResponseBody: Decodable { + let accessToken: String + let tokenType: String + let scope: String + let createdAt: Int +} diff --git a/ImageFeed/Auth/WebViewViewController.swift b/ImageFeed/Auth/WebViewViewController.swift new file mode 100644 index 0000000..9339105 --- /dev/null +++ b/ImageFeed/Auth/WebViewViewController.swift @@ -0,0 +1,108 @@ +import WebKit +import UIKit + +protocol WebViewViewControllerDelegate: AnyObject { + func webViewViewController(_ vc: WebViewViewController, didAuthenticateWithCode code: String) + func webViewViewControllerDidCancel(_ vc: WebViewViewController) +} + +final class WebViewViewController: UIViewController { + + weak var delegate: WebViewViewControllerDelegate? + private var estimatedProgressObservation: NSKeyValueObservation? + + @IBOutlet private var webView: WKWebView! + @IBOutlet private var progressView: UIProgressView! + + override func viewDidLoad() { + super.viewDidLoad() + + self.modalPresentationCapturesStatusBarAppearance = true + + webView.navigationDelegate = self + + makeRequest() + + estimatedProgressObservation = webView.observe(\.estimatedProgress) { [weak self] _, _ in + guard let self else { return } + self.updateProgress() + } + } + + private func makeRequest() { + guard var urlComponents = URLComponents(string: authorizeURLString) else { + fatalError("Incorrect base URL") + } + + urlComponents.queryItems = [ + URLQueryItem(name: "client_id", value: accessKey), + URLQueryItem(name: "redirect_uri", value: redirectURI), + URLQueryItem(name: "response_type", value: "code"), + URLQueryItem(name: "scope", value: accessScope) + ] + + guard let url = urlComponents.url else { + fatalError("Unable to build URL") + } + let request = URLRequest(url: url) + + webView.load(request) + } + + private func updateProgress() { + //progressView.progress = Float(webView.estimatedProgress) + progressView.setProgress(Float(webView.estimatedProgress), animated: true) + progressView.isHidden = fabs(webView.estimatedProgress - 1.0) <= 0.0001 + } + + @IBAction private func didTapBackButton(_ sender: Any?) { + delegate?.webViewViewControllerDidCancel(self) + } +} + +// MARK: - WKNavigationDelegate extension +extension WebViewViewController: WKNavigationDelegate { + func webView(_ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + + if let code = code(from: navigationAction) { + delegate?.webViewViewController(self, didAuthenticateWithCode: code) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } + + private func code(from navigationAction: WKNavigationAction) -> String? { + if let url = navigationAction.request.url, + let urlComponents = URLComponents(string: url.absoluteString), + urlComponents.path == "/oauth/authorize/native", + let queryItems = urlComponents.queryItems, + let codeItem = queryItems.first(where: { $0.name == "code" }) + { + return codeItem.value + } else { + return nil + } + } +} + +// MARK: Clean +extension WebViewViewController { + static func clean() { + HTTPCookieStorage.shared.removeCookies(since: Date.distantPast) + WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in + records.forEach { record in + WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {}) + } + } + } +} + +// MARK: - Status Bar Style +extension WebViewViewController { + override var preferredStatusBarStyle: UIStatusBarStyle { + return .darkContent + } +} diff --git a/ImageFeed/Base.lproj/LaunchScreen.storyboard b/ImageFeed/Base.lproj/LaunchScreen.storyboard index 481b537..028512e 100644 --- a/ImageFeed/Base.lproj/LaunchScreen.storyboard +++ b/ImageFeed/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,8 @@ - - + + - - + @@ -14,30 +13,30 @@ - + - - + + - + - - + + - + - - - + + + diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index db6b68e..3edfe8d 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -1,9 +1,8 @@ - - + + - - + @@ -11,49 +10,45 @@ - + - - + + - - + + + - + - - - - - + - + - + - + - + - - - + + + - - + + - - + + - - - + + @@ -61,327 +56,263 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - - + + + - - - - - + - - - - + + + + - - + - - + + - - - - - - - - - - - - - - - - - + - + - - - + + + - - + + - - + + - - - - + + + + - - - - - + + - - - - + + - - - - - - - - + + + + + + + + - + - - + + - + - + - + - - - + + + - - - - - - + + - - - + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - + - + - - + + - + - + - - - + + + - - - + + + - - - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - + + - + - + - - - - - - - - - - + + + + + + + + - + - + diff --git a/ImageFeed/Constants.swift b/ImageFeed/Constants.swift index 0c2bb41..47f6873 100644 --- a/ImageFeed/Constants.swift +++ b/ImageFeed/Constants.swift @@ -1,16 +1,9 @@ -// -// Constants.swift -// ImageFeed -// - import Foundation -let AccessKey = "KBZStsQ4aG9QZD6Tqc_5AjNIALDiPClynlQlEjcizx4" - -let SecretKey = "MTPiOi_bGk6qccyAP2lg3Gf3GrWN46v4BWjWRTEGGQw" - -let RedirectedURI = "urn:ietf:wg:oauth:2.0:oob" - -let AccessScope = "public+read_user+write_likes" - -let DefaultBaseURL = URL(string: "hppps://api.unsplash.com")! +let defaultBaseURL = URL(string: "https://api.unsplash.com")! +let authorizeURLString = "https://unsplash.com/oauth/authorize" +let accessKey = "KBZStsQ4aG9QZD6Tqc_5AjNIALDiPClynlQlEjcizx4" +let secretKey = "MTPiOi_bGk6qccyAP2lg3Gf3GrWN46v4BWjWRTEGGQw" +let redirectURI = "urn:ietf:wg:oauth:2.0:oob" +let accessScope = "public+read_user+write_likes" +let defaultBaseURL2 = "https://api.unsplash.com" diff --git a/ImageFeed/Helpers/UIColorExtentions.swift b/ImageFeed/Helpers/UIColorExtentions.swift new file mode 100644 index 0000000..dca9ca3 --- /dev/null +++ b/ImageFeed/Helpers/UIColorExtentions.swift @@ -0,0 +1,8 @@ +import UIKit + +extension UIColor { + static var ypBlack: UIColor { UIColor(named: "YP Black") ?? .black } + static var ypWhite: UIColor { UIColor(named: "YP White") ?? .white } + static var ypGray: UIColor { UIColor(named: "YP Gray") ?? .systemGray } + static var ypRed: UIColor { UIColor(named: "YP Red") ?? .systemRed } +} diff --git a/ImageFeed/Helpers/URLRequest+Extensions.swift b/ImageFeed/Helpers/URLRequest+Extensions.swift new file mode 100644 index 0000000..94f51b4 --- /dev/null +++ b/ImageFeed/Helpers/URLRequest+Extensions.swift @@ -0,0 +1,46 @@ +// +// URLRequest+Extensions.swift +// ImageFeed +// + +import Foundation + +extension URLRequest { + + static func makeHTTPRequest( + path: String, + httpMethod: String, + queryItems: [URLQueryItem]? = nil, + baseURL: String + ) -> URLRequest? { + + guard + let url = URL(string: String(describing: baseURL)), + var baseUrl = URL(string: path, relativeTo: url) + else { return nil } + + baseUrl.appendQueryItems(queryItems: queryItems) + var request = URLRequest(url: baseUrl) + request.httpMethod = httpMethod + + if let token = OAuth2TokenStorage.shared.token { + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } + return request + } +} + +extension URL { + mutating func appendQueryItems(queryItems: [URLQueryItem]?) { + guard let queryItems = queryItems, + var urlComponents = URLComponents(string: absoluteString) else { return } + + var currentQueryItems = urlComponents.queryItems ?? [] + currentQueryItems.append(contentsOf: queryItems) + urlComponents.queryItems = currentQueryItems + + if let newUrl = urlComponents.url { + self = newUrl + } + } +} diff --git a/ImageFeed/Helpers/URLSession+Extensions.swift b/ImageFeed/Helpers/URLSession+Extensions.swift new file mode 100644 index 0000000..8289ac4 --- /dev/null +++ b/ImageFeed/Helpers/URLSession+Extensions.swift @@ -0,0 +1,60 @@ +// +// URLSession+Extensions.swift +// ImageFeed +// 0=0 + + +import Foundation + +// MARK: - Network Connection +enum NetworkError: Error { + case httpStatusCode(Int) + case urlRequestError(Error) + case urlSessionError(Error) +} + +extension URLSession { + + func objectTask( + for request: URLRequest, + completion: @escaping (Result) -> Void + ) -> URLSessionTask { + let task = dataTask(with: request) { data, response, error in + + if let error = error { + DispatchQueue.main.async { + print("objectTask1") + completion(.failure(NetworkError.urlSessionError(error))) + } + } + + if let response = response as? HTTPURLResponse { + if !(200..<300 ~= response.statusCode) { + DispatchQueue.main.async { + print("objectTask2") + completion(.failure(NetworkError.httpStatusCode(response.statusCode))) + } + } + } + + if let data = data { + do { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let result = try decoder.decode(DecodingType.self, from: data) + + DispatchQueue.main.async { + completion(.success(result)) + } + } catch { + DispatchQueue.main.async { + print("objectTask3") + completion(.failure(NetworkError.urlSessionError(error))) + } + } + } + } + return task + } +} + diff --git a/ImageFeed/ImageList/ImagesListCell.swift b/ImageFeed/ImageList/ImagesListCell.swift new file mode 100644 index 0000000..a3bf5c7 --- /dev/null +++ b/ImageFeed/ImageList/ImagesListCell.swift @@ -0,0 +1,67 @@ +import UIKit +import Kingfisher + +protocol ImagesListDelegate: AnyObject { + func imagesListCellDidTapLike(_ cell: ImagesListCell) +} + +final class ImagesListCell: UITableViewCell { + + static let reuseIdentifier = "ImagesListCell" + let cache = ImageCache.default + weak var delegate: ImagesListDelegate? + + @IBOutlet weak var likeButton: UIButton! + @IBOutlet weak var dateLabel: UILabel! + @IBOutlet weak var imageCell: UIImageView! + + private lazy var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .long + formatter.timeStyle = .none + formatter.locale = Locale(identifier: "ru_RU") + return formatter + }() + + override func prepareForReuse() { + imageCell.kf.cancelDownloadTask() + } + + func setupCell(from photo: Photo) { + cache.clearMemoryCache() + cache.clearDiskCache() + + let url = URL(string: photo.smallImageURL) + imageCell.kf.indicatorType = .activity + imageCell.kf.setImage(with: url, placeholder: UIImage(named: "ImagePlaceholder")) { result in + switch result { + case .success(let image): + self.imageCell.contentMode = .scaleAspectFill + self.imageCell.image = image.image + case .failure(let error): + print("Ошибка загрузки картинки: \(error)") + self.imageCell.image = UIImage(named: "ImagePlaceholder") + } + } + dateLabel.text = dateFormatter.string(from: photo.createdAt ?? Date()) + setIsLiked(isLiked: photo.isLiked) + } + + func setIsLiked(isLiked: Bool) { + let likeImage = isLiked ? UIImage(named: "likeOn") : UIImage(named: "likeOff") + likeButton.setImage(likeImage, for: .normal) + } + + static func clean() { + let cache = ImageCache.default + cache.clearMemoryCache() + cache.clearDiskCache() + cache.backgroundCleanExpiredDiskCache() + cache.cleanExpiredMemoryCache() + cache.clearCache() + } + + @IBAction private func likeButtonClicked(_ sender: UIButton) { + delegate?.imagesListCellDidTapLike(self) + } +} diff --git a/ImageFeed/ImageList/ImagesListViewController.swift b/ImageFeed/ImageList/ImagesListViewController.swift new file mode 100644 index 0000000..e776c9e --- /dev/null +++ b/ImageFeed/ImageList/ImagesListViewController.swift @@ -0,0 +1,150 @@ +import UIKit + +final class ImagesListViewController: UIViewController { + + private var photos: [Photo] = [] + private let imagesListService = ImagesListService.shared + + private let showSingleImageSegueIdentifier = "ShowSingleImage" + private var imagesListServiceObserver: NSObjectProtocol? + + @IBOutlet private weak var tableView: UITableView! + + // MARK: LifeCycle + override func viewDidLoad() { + super.viewDidLoad() + setupTableView() + setupNotifications() + imagesListService.fetchPhotosNextPage() + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + guard segue.identifier == showSingleImageSegueIdentifier, + let viewController = segue.destination as? SingleImageViewController, + let indexPath = sender as? IndexPath else { + super.prepare(for: segue, sender: sender) + return + } + let photo = photos[indexPath.row].largeImageURL + let largeImageURL = URL(string: photo) + viewController.image = largeImageURL + } + + // MARK: Table View + private func setupTableView() { + tableView.dataSource = self + tableView.delegate = self + tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0) + } +} + +// MARK: Table View Data Source +extension ImagesListViewController: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return photos.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: ImagesListCell.reuseIdentifier, for: indexPath) + guard let imageListCell = cell as? ImagesListCell else { + return UITableViewCell() + } + imageListCell.delegate = self + + let photo = photos[indexPath.row] + + imageListCell.setupCell(from: photo) + tableView.reloadRows(at: [indexPath], with: .automatic) + return imageListCell + } +} + +// MARK: Table View Delegate +extension ImagesListViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + performSegue(withIdentifier: showSingleImageSegueIdentifier, sender: indexPath) + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.row + 1 == photos.count { + imagesListService.fetchPhotosNextPage() + } + + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let photo = photos[indexPath.row] + + let imageInsets = UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16) + + let imageViewWidth = tableView.bounds.width - imageInsets.left - imageInsets.right + let imageWidth = photo.size.width + let scale = imageViewWidth / imageWidth + let cellHeight = photo.size.height * scale + imageInsets.top + imageInsets.bottom + return cellHeight + } +} + +// MARK: Update Table View Animated +extension ImagesListViewController { + private func updateTableViewAnimated() { + let oldCount = photos.count + let newCount = imagesListService.photos.count + photos = imagesListService.photos + if oldCount != newCount { + tableView.performBatchUpdates { + let indexPaths = (oldCount.. Int { - return photosName.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: ImagesListCell.reuseIdentifier, for: indexPath) - cell.selectionStyle = .none - - guard let imageListCell = cell as? ImagesListCell else { - return UITableViewCell() - } - - configCell(for: imageListCell, with: indexPath) - return imageListCell - } -} - -extension ImagesListViewController { - func configCell(for cell: ImagesListCell, with indexPath: IndexPath) { - guard let image = UIImage(named: photosName[indexPath.row]) else { - return - } - - cell.cellImage.image = image - cell.dateLabel.text = dateFormatter.string(from: Date()) - - let isLiked = indexPath.row % 2 == 0 - let likeImage = isLiked ? UIImage(named: "heart_active") : UIImage(named: "heart_not_active") - cell.likeButton.setImage(likeImage, for: .normal) - } -} - -extension ImagesListViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - performSegue(withIdentifier: ShowSingleImageSegueIdentifier, sender: indexPath) - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - guard let image = UIImage(named: photosName[indexPath.row]) else { - return 0 - } - let imageInsets = UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16) - let imageViewWidth = tableView.bounds.width - imageInsets.left - imageInsets.right - let imageWidth = image.size.width - let scale = imageViewWidth / imageWidth - let cellHeight = image.size.height * scale + imageInsets.top + imageInsets.bottom - return cellHeight - } -} diff --git a/ImageFeed/Info.plist b/ImageFeed/Info.plist index dd3c9af..de7cdcc 100644 --- a/ImageFeed/Info.plist +++ b/ImageFeed/Info.plist @@ -21,5 +21,7 @@ + UIViewControllerBasedStatusBarAppearance + diff --git a/ImageFeed/Profile/Profile.swift b/ImageFeed/Profile/Profile.swift new file mode 100644 index 0000000..c8ee56d --- /dev/null +++ b/ImageFeed/Profile/Profile.swift @@ -0,0 +1,15 @@ +import Foundation + +struct Profile { + let username: String? + let name: String? + let loginName: String? + let bio: String? + + init(ProfileResult: ProfileResult) { + self.username = ProfileResult.username + self.name = ProfileResult.firstName + " " + (ProfileResult.lastName ?? "") + self.loginName = "@" + (ProfileResult.username) + self.bio = ProfileResult.bio + } +} diff --git a/ImageFeed/Profile/ProfileResult.swift b/ImageFeed/Profile/ProfileResult.swift new file mode 100644 index 0000000..6fefa62 --- /dev/null +++ b/ImageFeed/Profile/ProfileResult.swift @@ -0,0 +1,8 @@ +import Foundation + +struct ProfileResult: Decodable { + let username: String + let firstName: String + let lastName: String? + let bio: String? +} diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift new file mode 100644 index 0000000..94b878e --- /dev/null +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -0,0 +1,156 @@ +import UIKit +import Kingfisher + +final class ProfileViewController: UIViewController { + + private let profileService = ProfileService.shared + private var profileImageServiceObserver: NSObjectProtocol? + + private let profilePhoto: UIImageView = { + let image = UIImage(named: "userPhoto") + let imageView = UIImageView(image: image) + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private let nameLabel: UILabel = { + let label = UILabel() + label.text = "Екатерина Новикова" + label.textColor = .ypWhite + label.font = UIFont.systemFont(ofSize: 23, weight: .bold) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let nicknameLabel: UILabel = { + let label = UILabel() + label.text = "@ekaerina_nov" + label.textColor = .ypGray + label.font = UIFont.systemFont(ofSize: 13) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let descriptionLabel: UILabel = { + let label = UILabel() + label.text = "Hello, world!" + label.textColor = .ypWhite + label.font = UIFont.systemFont(ofSize: 13) + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + return label + }() + + private lazy var logoutButton: UIButton = { + let button = UIButton() + let image = UIImage(named: "ipad.and.arrow.forward") + button.setImage(image, for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(didTapLogoutButton), for: .touchUpInside) + return button + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .ypBlack + setupViews() + setupAllConstraints() + updateProfileDetails() + observerProfileImageService() + } + + private func updateAvatar() { + guard let profileImageURL = ProfileImageService.shared.avatarURL, + let url = URL(string: profileImageURL) else { return } + + let cache = ImageCache.default + cache.clearDiskCache() + let processor = RoundCornerImageProcessor(cornerRadius: 42) + + profilePhoto.kf.setImage(with: url, + placeholder: UIImage(named: "placeholder"), + options: [.processor(processor), .transition(.fade(1))]) + } + + private func observerProfileImageService() { + profileImageServiceObserver = NotificationCenter.default.addObserver( + forName: ProfileImageService.didChangeNotification, + object: nil, + queue: .main) { [weak self] _ in + guard let self else { return } + updateAvatar() + } + updateAvatar() + } + + private func setupViews() { + view.addSubview(profilePhoto) + view.addSubview(nameLabel) + view.addSubview(nicknameLabel) + view.addSubview(descriptionLabel) + view.addSubview(logoutButton) + } + + private func setupAllConstraints() { + NSLayoutConstraint.activate([ + profilePhoto.heightAnchor.constraint(equalToConstant: 70), + profilePhoto.widthAnchor.constraint(equalToConstant: 70), + profilePhoto.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + profilePhoto.topAnchor.constraint(equalTo: view.topAnchor, constant: 76), + + nameLabel.topAnchor.constraint(equalTo: profilePhoto.bottomAnchor, constant: 8), + nameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + + nicknameLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8), + nicknameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + + descriptionLabel.topAnchor.constraint(equalTo: nicknameLabel.bottomAnchor, constant: 8), + descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + + logoutButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24), + logoutButton.centerYAnchor.constraint(equalTo: profilePhoto.centerYAnchor) + ]) + } + + @objc + private func didTapLogoutButton() { + let alert = UIAlertController(title: "Пока, пока!", message: "Уверены что хотите выйти?", preferredStyle: .alert) + + let yesAction = UIAlertAction(title: "Да", style: .default) { _ in + OAuth2TokenStorage.shared.clean() + WebViewViewController.clean() + ImagesListCell.clean() + + guard let window = UIApplication.shared.windows.first else { + fatalError("invalid configuration") + } + window.rootViewController = SplashViewController() + window.makeKeyAndVisible() + } + + let noAction = UIAlertAction(title: "Нет", style: .default) { _ in + alert.dismiss(animated: true) + } + alert.addAction(yesAction) + alert.addAction(noAction) + present(alert, animated: true) + } +} + +// MARK: - Status Bar Style +extension ProfileViewController { + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} + +// MARK: Update Profile Details +private extension ProfileViewController { + func updateProfileDetails() { + guard let profile = profileService.profile else { return } + nameLabel.text = "\(profile.firstName) \(profile.lastName ?? "")" + nicknameLabel.text = "@\(profile.username)" + descriptionLabel.text = profile.bio + } +} diff --git a/ImageFeed/SIngleImage/SingleImageViewController.swift b/ImageFeed/SIngleImage/SingleImageViewController.swift new file mode 100644 index 0000000..55d58ad --- /dev/null +++ b/ImageFeed/SIngleImage/SingleImageViewController.swift @@ -0,0 +1,105 @@ +import UIKit + +final class SingleImageViewController: UIViewController { + + var image: URL? + + var imageDownload: UIImage? + + @IBOutlet weak private var imageView: UIImageView! + @IBOutlet weak private var scrollView: UIScrollView! + + override func viewDidLoad() { + super.viewDidLoad() + scrollView.delegate = self + scrollView.minimumZoomScale = 0.1 + scrollView.maximumZoomScale = 1.25 + loadAndShowImage(url: image) + } + +// MARK: Load and show image + func loadAndShowImage(url: URL?) { + guard let url else { return } + UIBlockingProgressHUD.show() + imageView.kf.setImage(with: url) { [weak self] result in + UIBlockingProgressHUD.dismiss() + guard let self else { return } + switch result { + case .success(let imageResult): + self.rescaleAndCenterImageInScrollView(image: imageResult.image) + self.imageDownload = imageResult.image + case .failure(let error): + print(error.localizedDescription) + self.showError(url: url) + } + } + } + + @IBAction func didTapBackAction(_ sender: UIButton) { + dismiss(animated: true, completion: nil) + } + @IBAction func didTabShareButton(_ sender: UIButton) { + guard let image = self.imageDownload else { return } + let share = UIActivityViewController( + activityItems: [image], + applicationActivities: nil + ) + share.overrideUserInterfaceStyle = .dark + present(share, animated: true, completion: nil) + } +} + +extension SingleImageViewController: UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + imageView + } + + func scrollViewDidZoom(_ scrollView: UIScrollView) { + let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0) + let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0) + scrollView.contentInset = UIEdgeInsets(top: offsetY, left: offsetX, bottom: 0, right: 0) + } + + private func rescaleAndCenterImageInScrollView(image: UIImage) { + let minZoomScale = scrollView.minimumZoomScale + let maxZoomScale = scrollView.maximumZoomScale + view.layoutIfNeeded() + let visibleRectSize = scrollView.bounds.size + let imageSize = image.size + let hScale = visibleRectSize.width / imageSize.width + let vScale = visibleRectSize.height / imageSize.height + let scale = min(maxZoomScale, max(minZoomScale, max(hScale, vScale))) + scrollView.setZoomScale(scale, animated: false) + scrollView.layoutIfNeeded() + let newContentSize = scrollView.contentSize + let x = (newContentSize.width - visibleRectSize.width) / 2 + let y = (newContentSize.height - visibleRectSize.height) / 2 + scrollView.setContentOffset(CGPoint(x: x, y: y), animated: false) + } +} + +// MARK: Show error +extension SingleImageViewController { + private func showError(url: URL) { + let alert = UIAlertController(title: "Что-то пошло не так.", message: "Попробовать ещё раз?", preferredStyle: .alert) + let repeats = UIAlertAction(title: "Повторить", style: .default) { [weak self] _ in + guard let self else { return } + self.loadAndShowImage(url: url) + } + let cancel = UIAlertAction(title: "Не надо", style: .cancel) { _ in + alert.dismiss(animated: true) + } + + alert.addAction(cancel) + alert.addAction(repeats) + + present(alert, animated: true) + } +} + +// MARK: Status Bar Style +extension SingleImageViewController { + override var preferredStatusBarStyle: UIStatusBarStyle { + .lightContent + } +} diff --git a/ImageFeed/SceneDelegate.swift b/ImageFeed/SceneDelegate.swift index 428794d..011932b 100644 --- a/ImageFeed/SceneDelegate.swift +++ b/ImageFeed/SceneDelegate.swift @@ -1,8 +1,3 @@ -// -// SceneDelegate.swift -// ImageFeed -// - import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -11,10 +6,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: 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 } + + guard let scene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: scene) + + let rootVC = SplashViewController() + + window?.rootViewController = rootVC + + window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) { diff --git a/ImageFeed/Services/ImagesListService/ImagesListService.swift b/ImageFeed/Services/ImagesListService/ImagesListService.swift new file mode 100644 index 0000000..3492e39 --- /dev/null +++ b/ImageFeed/Services/ImagesListService/ImagesListService.swift @@ -0,0 +1,173 @@ +import Foundation +// 0=0 +struct Photo: Codable { + let id: String + let size: CGSize + let createdAt: Date? + let description: String? + let thumbImageURL: String + let largeImageURL: String + let regularImageURL: String + let smallImageURL: String + let isLiked: Bool + + init(_ photoData: PhotoResult, dateFormatter: DateFormatter) { + self.id = photoData.id + self.size = CGSize(width: photoData.width, height: photoData.height) + self.createdAt = dateFormatter.date(from: photoData.createdAt ?? "") + self.description = photoData.description + self.thumbImageURL = photoData.urls.thumb + self.largeImageURL = photoData.urls.full + self.regularImageURL = photoData.urls.regular + self.smallImageURL = photoData.urls.small + self.isLiked = photoData.likedByUser + } +} + +struct PhotoResult: Decodable { + let id: String + let width: Int + let height: Int + let createdAt: String? + let description: String? + let urls: UrlsResult + let likedByUser: Bool +} + +struct UrlsResult: Decodable { + let full: String + let regular: String + let small: String + let thumb: String +} + +struct PhotoLike: Decodable { + let photo: PhotoResult +} + +final class ImagesListService { + + static let shared = ImagesListService() + + static let didChangeNotification = Notification.Name(rawValue: "ImagesListServiceDidChange") + + private let urlSession = URLSession.shared + + private let dateFormatter = DateFormatter() + + private(set) var photos: [Photo] = [] + + private var lastLoadedPage: Int? + private var currentTask: URLSessionTask? + + // MARK: - Public Methods + + func fetchPhotosNextPage() { + assert(Thread.isMainThread) + + guard currentTask == nil else { + return + } + + let nextPage = (lastLoadedPage ?? 0) + 1 + + guard let request = makeRequest(page: nextPage) else { + print("Ошибка при создании запроса") + return + } + + let task = urlSession.objectTask(for: request) { [weak self] (result: Result<[PhotoResult], Error>) in + guard let self = self else { return } + + DispatchQueue.main.async { + switch result { + case .success(let photoResults): + if self.lastLoadedPage == nil { + self.lastLoadedPage = 1 + } else { + self.lastLoadedPage! += 1 + } + + let newPhotos = photoResults.map { Photo($0, dateFormatter: self.dateFormatter) } + self.photos.append(contentsOf: newPhotos) + + NotificationCenter.default.post(name: ImagesListService.didChangeNotification, + object: nil) + + case .failure(let error): + print(error.localizedDescription) + } + } + + self.currentTask = nil + } + + self.currentTask = task + task.resume() + } + + func changeLike(photoId: String, isLike: Bool, completion: @escaping (Result) -> Void) { + if let currentTask = currentTask { + currentTask.cancel() + } + + guard let request = makeLikeRequest(photoId: photoId, isLike: isLike) else { + print("Ошибка при создании запроса") + return + } + + let task = urlSession.objectTask(for: request) { (result: Result) in + DispatchQueue.main.async { + switch result { + case .success: + if let index = self.photos.firstIndex(where: { $0.id == photoId }) { + let photo = self.photos[index] + let newPhotoResult = PhotoResult(id: photo.id, + width: Int(photo.size.width), + height: Int(photo.size.height), + createdAt: photo.createdAt?.description, + description: photo.description, + urls: UrlsResult(full: photo.largeImageURL, + regular: photo.regularImageURL, + small: photo.smallImageURL, + thumb: photo.thumbImageURL), + likedByUser: !photo.isLiked) + let newPhoto = Photo(newPhotoResult, dateFormatter: self.dateFormatter) + self.photos[index] = newPhoto + completion(.success(())) + } + + case .failure(let error): + print("Ошибка при изменении лайка: \(error)") + completion(.failure(error)) + } + } + } + + self.currentTask = task + task.resume() + } + + // MARK: - Private Methods + + private func makeRequest(page: Int) -> URLRequest? { + let queryItems = [ + URLQueryItem(name: "page", value: "\(page)"), + URLQueryItem(name: "per_page", value: "10") + ] + + let baseURLString = defaultBaseURL.absoluteString + return URLRequest.makeHTTPRequest(path: "/photos", + httpMethod: "GET", + queryItems: queryItems, + baseURL: baseURLString) + } + + private func makeLikeRequest(photoId: String, isLike: Bool) -> URLRequest? { + let baseURLString = defaultBaseURL.absoluteString + return URLRequest.makeHTTPRequest(path: "/photos/\(photoId)/like", + httpMethod: isLike ? "POST" : "DELETE", + baseURL: baseURLString) + } +} + diff --git a/ImageFeed/Services/OAuth2/OAuth2Service.swift b/ImageFeed/Services/OAuth2/OAuth2Service.swift new file mode 100644 index 0000000..13573c7 --- /dev/null +++ b/ImageFeed/Services/OAuth2/OAuth2Service.swift @@ -0,0 +1,68 @@ +import Foundation + +final class OAuth2Service { + + static let shared = OAuth2Service() + private let urlSession = URLSession.shared + + private var task: URLSessionTask? + private var lastCode: String? + + private (set) var authToken: String? { + get { + return OAuth2TokenStorage().token + } + set { + OAuth2TokenStorage().token = newValue + } + } + + func fetchAuthToken(code: String, completion: @escaping (Result) -> Void) { + + assert(Thread.isMainThread) + + if lastCode == code { return } + task?.cancel() + lastCode = code + + guard let request = makeRequest(code: code) else { + assertionFailure("Failed to make request") + return + } + + let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in + guard let self = self else { return } + + switch result { + case .success(let tokenResponseBody): + completion(.success(tokenResponseBody.accessToken)) + case .failure(let error): + self.lastCode = nil + completion(.failure(error)) + } + self.task = nil + } + self.task = task + task.resume() + } +} + +// MARK: - Shared helpers +private extension OAuth2Service { + func makeRequest(code: String) -> URLRequest? { + guard let url = URL(string: "https://unsplash.com"), + let request = URLRequest.makeHTTPRequest( + path: "/oauth/token" + + "?client_id=\(accessKey)" + + "&&client_secret=\(secretKey)" + + "&&redirect_uri=\(redirectURI)" + + "&&code=\(code)" + + "&&grant_type=authorization_code", + httpMethod: "POST", + baseURL: String(describing: url)) + else { + return nil + } + return request + } +} diff --git a/ImageFeed/Services/OAuth2/OAuth2TokenStorage.swift b/ImageFeed/Services/OAuth2/OAuth2TokenStorage.swift new file mode 100644 index 0000000..5eeb922 --- /dev/null +++ b/ImageFeed/Services/OAuth2/OAuth2TokenStorage.swift @@ -0,0 +1,31 @@ +import Foundation +import SwiftKeychainWrapper + +protocol OAuth2TokenStorageProtocol { + var token: String? { get set } +} + +private enum Keys: String { + case token +} + +final class OAuth2TokenStorage: OAuth2TokenStorageProtocol { + + static let shared = OAuth2TokenStorage() + + var token: String? { + get { + KeychainWrapper.standard.string(forKey: Keys.token.rawValue) + } + set { + guard let newValue else { + assertionFailure("invalid token") + return + } + KeychainWrapper.standard.set(newValue, forKey: Keys.token.rawValue) + } + } + func clean() { + KeychainWrapper.standard.removeAllKeys() + } +} diff --git a/ImageFeed/Services/OAuth2/UIBlockingProgressHUD.swift b/ImageFeed/Services/OAuth2/UIBlockingProgressHUD.swift new file mode 100644 index 0000000..d3bfba4 --- /dev/null +++ b/ImageFeed/Services/OAuth2/UIBlockingProgressHUD.swift @@ -0,0 +1,23 @@ +import UIKit +import ProgressHUD + +final class UIBlockingProgressHUD { + private static var window: UIWindow? { + return UIApplication.shared.windows.first + } + static func show() { + window?.isUserInteractionEnabled = false + ProgressHUD.show() + } + static func dismiss() { + window?.isUserInteractionEnabled = true + ProgressHUD.dismiss() + } + + static func showWA() { + window?.isUserInteractionEnabled = false + } + static func dismissWA() { + window?.isUserInteractionEnabled = true + } +} diff --git a/ImageFeed/Services/ProfileService/ProfileImageService.swift b/ImageFeed/Services/ProfileService/ProfileImageService.swift new file mode 100644 index 0000000..15c246e --- /dev/null +++ b/ImageFeed/Services/ProfileService/ProfileImageService.swift @@ -0,0 +1,52 @@ +import Foundation + +final class ProfileImageService { + + private struct UserResult: Codable { + let profileImage: ProfileImage + } + + private struct ProfileImage: Codable { + let small: String + let medium: String + let large: String + } + + private var task: URLSessionTask? + private(set) var avatarURL: String? + static let shared = ProfileImageService() + static let didChangeNotification = Notification.Name(rawValue: "ProfileImageProviderDidChange") + private let urlSession = URLSession.shared + private let oAuthTokenStorage = OAuth2TokenStorage() + + private init() {} + + func fetchProfileImageURL(username: String, _ completion: @escaping (Result) -> Void ) { + assert(Thread.isMainThread) + task?.cancel() + + guard let request = URLRequest.makeHTTPRequest(path: "/users/\(username)", + httpMethod: "GET", + baseURL: String(describing: defaultBaseURL)) else { + assertionFailure("Failed to make HTTP request") + return + } + let task = urlSession.objectTask(for: request) { [weak self] (result:Result) in + guard let self else { return } + + switch result { + case .success(let user): + completion(.success(user.profileImage.large)) + NotificationCenter.default.post(name: ProfileImageService.didChangeNotification, + object: self, + userInfo: ["URL": user.profileImage.large]) + self.avatarURL = user.profileImage.large + case .failure(let error): + completion(.failure(error)) + } + self.task = nil + } + self.task = task + task.resume() + } +} diff --git a/ImageFeed/Services/ProfileService/ProfileService.swift b/ImageFeed/Services/ProfileService/ProfileService.swift new file mode 100644 index 0000000..f047055 --- /dev/null +++ b/ImageFeed/Services/ProfileService/ProfileService.swift @@ -0,0 +1,40 @@ +import Foundation + +final class ProfileService { + + static let shared = ProfileService() + + private let urlSession = URLSession.shared + private(set) var profile: ProfileResult? + private var task: URLSessionTask? + private var lastToken: String? + + private init() {} + + func fetchProfile(_ token: String, completion: @escaping (Result) -> Void) { + assert(Thread.isMainThread) + if lastToken == token { return } + + task?.cancel() + lastToken = token + guard let request = URLRequest.makeHTTPRequest(path: "/me", httpMethod: "GET", baseURL: String(describing: defaultBaseURL)) else { + assertionFailure("Failed to make HTTP request") + return + } + let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in + guard let self else { return } + switch result { + case .success(let profileResult): + self.profile = profileResult + completion(.success(profileResult)) + self.task = nil + case .failure(let error): + completion(.failure(error)) + self.lastToken = nil + } + } + self.task = task + + task.resume() + } +} diff --git a/ImageFeed/Splash/SplashViewController.swift b/ImageFeed/Splash/SplashViewController.swift new file mode 100644 index 0000000..777a556 --- /dev/null +++ b/ImageFeed/Splash/SplashViewController.swift @@ -0,0 +1,123 @@ +import UIKit +import ProgressHUD + +final class SplashViewController: UIViewController { + + private let oauth2Service = OAuth2Service() + private let oauth2TokenStorage = OAuth2TokenStorage() + private let profileService = ProfileService.shared + + private var splashImage: UIImageView = { + let image = UIImage(named: "launchScreenLogo") + let imageView = UIImageView(image: image) + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .ypBlack + setupViews() + setupAllConstraints() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let token = oauth2TokenStorage.token { + fetchProfile(token: token) + } else { + switchToAuthViewController() + } + } + + private func setupViews() { + view.addSubview(splashImage) + } + + private func setupAllConstraints() { + NSLayoutConstraint.activate([ + splashImage.centerXAnchor.constraint(equalTo: view.centerXAnchor), + splashImage.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } + + private func switchToTabBarController() { + guard let window = UIApplication.shared.windows.first else { fatalError("Invalid Configuration") } + let tabBarController = UIStoryboard(name: "Main", bundle: .main) + .instantiateViewController(withIdentifier: "TabBarViewController") + window.rootViewController = tabBarController + } + + private func switchToAuthViewController() { + let storyboard = UIStoryboard(name: "Main", bundle: .main) + guard let authViewController = storyboard.instantiateViewController(withIdentifier: "AuthViewController") as? AuthViewController else { return } + authViewController.delegate = self + authViewController.modalPresentationStyle = .fullScreen + + present(authViewController, animated: true) + } + + private func showAlert() { + let alert = UIAlertController( + title: "Что-то пошло не так", + message: "Не удалось войти в систему", + preferredStyle: .alert + ) + let action = UIAlertAction(title: "Ок", style: .cancel) { [weak self] _ in + guard let self else { return } + switchToAuthViewController() + } + alert.addAction(action) + present(alert, animated: true) + } +} + +// MARK: - AuthViewControllerDelegate +extension SplashViewController: AuthViewControllerDelegate { + func authViewController(_ vc: AuthViewController, didAuthenticateWithCode code: String) { + UIBlockingProgressHUD.show() + self.fetchAuthToken(code) + } + + private func fetchAuthToken(_ code: String) { + UIBlockingProgressHUD.show() + oauth2Service.fetchAuthToken(code: code) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let token): + oauth2TokenStorage.token = token + fetchProfile(token: token) + case .failure: + UIBlockingProgressHUD.dismiss() + showAlert() + // TODO: Показать ошибку + // TODO: Sprint_11 + } + } + } + + private func fetchProfile(token: String) { + profileService.fetchProfile(token) { [weak self] result in + guard let self else { return } + switch result { + case .success(let profile): + //guard let userName = self.profileService.profile?.username else { return } + ProfileImageService.shared.fetchProfileImageURL(username: profile.username) { _ in } + self.switchToTabBarController() + UIBlockingProgressHUD.dismiss() + + case .failure: + UIBlockingProgressHUD.dismiss() + showAlert() + } + } + } +} + +// MARK: Status Bar Style +extension SplashViewController { + override var preferredStatusBarStyle: UIStatusBarStyle { + .lightContent + } +} diff --git a/ImageFeed/TabBarController/TabBarController.swift b/ImageFeed/TabBarController/TabBarController.swift new file mode 100644 index 0000000..52b80a0 --- /dev/null +++ b/ImageFeed/TabBarController/TabBarController.swift @@ -0,0 +1,24 @@ +import Foundation +import UIKit + +final class TabBarController: UITabBarController { + + override func awakeFromNib() { + super.awakeFromNib() + let storyboard = UIStoryboard(name: "Main", bundle: .main) + let imagesListViewController = storyboard.instantiateViewController(withIdentifier: "ImagesListViewController") + imagesListViewController.tabBarItem = UITabBarItem( + title: nil, + image: UIImage(named: "tab_editorial_active"), + selectedImage: nil + ) + + let profileViewController = ProfileViewController() + profileViewController.tabBarItem = UITabBarItem( + title: nil, + image: UIImage(named: "tab_profile_active"), + selectedImage: nil + ) + self.viewControllers = [imagesListViewController, profileViewController] + } +} diff --git a/ImageFeedTests/ImagesListServiceTests.swift b/ImageFeedTests/ImagesListServiceTests.swift new file mode 100644 index 0000000..c18d082 --- /dev/null +++ b/ImageFeedTests/ImagesListServiceTests.swift @@ -0,0 +1,32 @@ +// +// ImageFeedTests.swift +// ImageFeedTests +// 08.10.23 + +@testable import ImageFeed +import XCTest + +final class ImagesListServiceTests: XCTestCase { + + func testFetchPhotos() { +// let service = ImagesListService() +// +// let expectation = self.expectation(description: "Wait for Notification") +// NotificationCenter.default.addObserver(forName: ImagesListService.didChangeNotification, +// object: nil, +// queue: .main) { _ in +// print("i knew") +// expectation.fulfill() +// } +// +// service.fetchPhotosNextPage() +// wait(for: [expectation], timeout: 10) +// +// XCTAssertEqual(service.photos.count, 10) +// service.fetchPhotosNextPage() +// service.fetchPhotosNextPage() +// service.fetchPhotosNextPage() + } + + +} diff --git a/Profile/ProfileViewController.swift b/Profile/ProfileViewController.swift deleted file mode 100644 index 2d1f7d3..0000000 --- a/Profile/ProfileViewController.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// ProfileViewController.swift -// ImageFeed -// - -import Foundation -import UIKit - -final class ProfileViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - } - - private func setupProfilePictureView() -> UIImageView { - view.backgroundColor = .black - - let profilePicture = UIImage(named: "user_pic") - let profilePictureView = UIImageView(image: profilePicture) - profilePictureView.layer.masksToBounds = true - profilePictureView.layer.cornerRadius = 35 - profilePictureView.tintColor = .gray - - profilePictureView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(profilePictureView) - - return profilePictureView - } - - private func setupNameLabel() -> UILabel { - let nameLabel = UILabel() - nameLabel.text = "Екатерина Новикова" - nameLabel.textColor = .white - nameLabel.font = UIFont.systemFont(ofSize: 23.0, weight: UIFont.Weight.semibold) - - nameLabel.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(nameLabel) - return nameLabel - } - - private func setupLoginLabel() -> UILabel { - let loginLabel = UILabel() - loginLabel.text = "@ekaterina_nov" - loginLabel.textColor = .gray - loginLabel.font = UIFont.systemFont(ofSize: 13.0) - - loginLabel.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(loginLabel) - return loginLabel - } - - private func setupMessageLabel() -> UILabel { - let messageLabel = UILabel() - messageLabel.text = "Hello, world!" - messageLabel.font = UIFont.systemFont(ofSize: 13.0) - messageLabel.textColor = .white - messageLabel.numberOfLines = 0 - - messageLabel.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(messageLabel) - return messageLabel - } - - private func setupLogoutButton() -> UIButton { - let logoutButton = UIButton.systemButton( - with: UIImage(named: "logout_pic") ?? UIImage(), - target: self, - action: #selector(Self.didTapLogoutButton) - ) - logoutButton.tintColor = .red - - logoutButton.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(logoutButton) - return logoutButton - } - - @objc - private func didTapLogoutButton() { - } - - private func setupViews() { - let profileImage = setupProfilePictureView() - let nameLabel = setupNameLabel() - let loginNameLabel = setupLoginLabel() - let descriptionLabel = setupMessageLabel() - let logoutButton = setupLogoutButton() - - NSLayoutConstraint.activate([ - profileImage.widthAnchor.constraint(equalToConstant: 70), - profileImage.heightAnchor.constraint(equalToConstant: 70), - profileImage.topAnchor.constraint(equalTo: view.topAnchor, constant: 76), - profileImage.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), - - nameLabel.topAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 8), - nameLabel.leadingAnchor.constraint(equalTo: profileImage.leadingAnchor), - nameLabel.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor, constant: -16), - - loginNameLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8), - loginNameLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor), - loginNameLabel.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor, constant: -16), - - descriptionLabel.topAnchor.constraint(equalTo: loginNameLabel.bottomAnchor, constant: 8), - descriptionLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor), - descriptionLabel.trailingAnchor.constraint(greaterThanOrEqualTo: view.trailingAnchor, constant:-16), - - logoutButton.widthAnchor.constraint(equalToConstant: 44), - logoutButton.heightAnchor.constraint(equalToConstant: 44), - logoutButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), - logoutButton.centerYAnchor.constraint(equalTo: profileImage.centerYAnchor) - ]) - } -} diff --git a/Services/NetworkClient.swift b/Services/NetworkClient.swift deleted file mode 100644 index aa4e836..0000000 --- a/Services/NetworkClient.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// NetworkClient.swift -// ImageFeed -// - -import Foundation - -protocol NetworkRouting { - func fetch(url: URL, handler: @escaping (Result) -> Void) -} - -struct NetworkClient: NetworkRouting { - - private enum NetworkError: Error { - case codeError - } - - func fetch(url: URL, handler: @escaping (Result) -> Void) { - let request = URLRequest(url: url) - - let task = URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - handler(.failure(error)) - return - } - - if let response = response as? HTTPURLResponse, - response.statusCode < 200 || response.statusCode >= 300 { - handler(.failure(NetworkError.codeError)) - return - } - - guard let data = data else { return } - handler(.success(data)) - } - - task.resume() - } -} diff --git a/SingleImageViewController/SingleImageViewController.swift b/SingleImageViewController/SingleImageViewController.swift deleted file mode 100644 index 0897a2e..0000000 --- a/SingleImageViewController/SingleImageViewController.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// SingleImageViewController.swift -// ImageFeed -// - -import Foundation -import UIKit - -final class SingleImageViewController: UIViewController { - var image: UIImage! { - didSet { - guard isViewLoaded else { return } - imageView.image = image - rescaleAndCenterImageInScrollView(image: image) - } - } - - @IBOutlet weak var scrollView: UIScrollView! - @IBOutlet private var imageView: UIImageView! - @IBAction private func backwardButton() { - dismiss(animated: true, completion: nil) - } - - @IBAction func didTapShareButton(_ sender: UIButton) { - if let image = image { - let share = UIActivityViewController( - activityItems: [image], - applicationActivities: nil - ) - - share.overrideUserInterfaceStyle = .dark - self.present(share, animated: true, completion: nil) - } - } - - override func viewDidLoad() { - super.viewDidLoad() - imageView.image = image - scrollView.minimumZoomScale = 0.1 - scrollView.maximumZoomScale = 1.25 - rescaleAndCenterImageInScrollView(image: image) - } - - private func rescaleAndCenterImageInScrollView(image: UIImage) { - let minZoomScale = scrollView.minimumZoomScale - let maxZoomScale = scrollView.maximumZoomScale - view.layoutIfNeeded() - let visibleRectSize = scrollView.bounds.size - let imageSize = image.size - let hScale = visibleRectSize.width / imageSize.width - let vScale = visibleRectSize.height / imageSize.height - let scale = min(maxZoomScale, max(minZoomScale, max(hScale, vScale))) - scrollView.setZoomScale(scale, animated: false) - scrollView.layoutIfNeeded() - let newContentSize = scrollView.contentSize - let x = (newContentSize.width - visibleRectSize.width) / 2 - let y = (newContentSize.height - visibleRectSize.height) / 2 - scrollView.setContentOffset(CGPoint(x:x, y:y), animated: false) - } - -} - -extension SingleImageViewController: UIScrollViewDelegate { - func viewForZooming(in scrollView: UIScrollView) -> UIView? { - imageView - } -} - - diff --git a/SplashViewController/SplashViewController.swift b/SplashViewController/SplashViewController.swift deleted file mode 100644 index dfc55da..0000000 --- a/SplashViewController/SplashViewController.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// SplashViewController.swift -// ImageFeed -// - -import Foundation -import UIKit - -final class SplashViewController: UIViewController { - override var preferredStatusBarStyle: UIStatusBarStyle { - .lightContent - } - - private let ShowAuthenticationScreenSegueIdentifier = "ShowAuthenticationScreen" - private let oauth2Service = OAuth2Service() - private let oauth2TokenStorage = OAuth2TokenStorage() - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if let token = oauth2TokenStorage.token { - switchToTabBarController() - } else { // Show Auth Screen - performSegue(withIdentifier: ShowAuthenticationScreenSegueIdentifier, sender: nil) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setNeedsStatusBarAppearanceUpdate() - } - - private func switchToTabBarController() { - guard let window = UIApplication.shared.windows.first else { fatalError("Invalid Configuration") } - let tabBarController = UIStoryboard(name: "Main", bundle: .main) - .instantiateViewController(withIdentifier: "TabBarViewController") - window.rootViewController = tabBarController - } -} - -extension SplashViewController { - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == ShowAuthenticationScreenSegueIdentifier { - guard - let navigationController = segue.destination as? UINavigationController, - let viewController = navigationController.viewControllers[0] as? AuthViewController - else { fatalError("Failed to prepare for \(ShowAuthenticationScreenSegueIdentifier)") } - viewController.delegate = self - } else { - super.prepare(for: segue, sender: sender) - } - } -} - -extension SplashViewController: AuthViewControllerDelegate { - func authViewController(_ vc: AuthViewController, didAuthenticateWithCode code: String) { - dismiss(animated: true) { [weak self] in - guard let self = self else { return } - self.fetchOAuthToken(code) - } - } - - private func fetchOAuthToken(_ code: String) { - oauth2Service.fetchOAuthToken(code) { [weak self] result in - guard let self = self else { return } - switch result { - case .success(let token): - self.oauth2TokenStorage.token = token - self.switchToTabBarController() - - case .failure: - // TODO - break - } - } - } -} - - diff --git a/figma stuff/Vector.png b/figma stuff/Vector.png deleted file mode 100644 index 90cc92f..0000000 Binary files a/figma stuff/Vector.png and /dev/null differ diff --git a/figma stuff/heart_active.zip b/figma stuff/heart_active.zip deleted file mode 100644 index 0df650a..0000000 Binary files a/figma stuff/heart_active.zip and /dev/null differ diff --git a/figma stuff/heart_not_active.zip b/figma stuff/heart_not_active.zip deleted file mode 100644 index 00bc63b..0000000 Binary files a/figma stuff/heart_not_active.zip and /dev/null differ diff --git a/figma stuff/ic 2/42x42/Favorites/Active.png b/figma stuff/ic 2/42x42/Favorites/Active.png deleted file mode 100644 index d5e0c27..0000000 Binary files a/figma stuff/ic 2/42x42/Favorites/Active.png and /dev/null differ diff --git a/figma stuff/ic/42x42/Favorites/No Active.png b/figma stuff/ic/42x42/Favorites/No Active.png deleted file mode 100644 index 0917446..0000000 Binary files a/figma stuff/ic/42x42/Favorites/No Active.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable.zip b/figma stuff/mockImagesForTable.zip deleted file mode 100644 index 9b135d6..0000000 Binary files a/figma stuff/mockImagesForTable.zip and /dev/null differ diff --git a/figma stuff/mockImagesForTable/0.png b/figma stuff/mockImagesForTable/0.png deleted file mode 100644 index d9412a4..0000000 Binary files a/figma stuff/mockImagesForTable/0.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/1.png b/figma stuff/mockImagesForTable/1.png deleted file mode 100644 index 90af559..0000000 Binary files a/figma stuff/mockImagesForTable/1.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/10.png b/figma stuff/mockImagesForTable/10.png deleted file mode 100644 index a9c6e0e..0000000 Binary files a/figma stuff/mockImagesForTable/10.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/11.png b/figma stuff/mockImagesForTable/11.png deleted file mode 100644 index d50beee..0000000 Binary files a/figma stuff/mockImagesForTable/11.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/12.png b/figma stuff/mockImagesForTable/12.png deleted file mode 100644 index d814f30..0000000 Binary files a/figma stuff/mockImagesForTable/12.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/13.png b/figma stuff/mockImagesForTable/13.png deleted file mode 100644 index 56f9e54..0000000 Binary files a/figma stuff/mockImagesForTable/13.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/14.png b/figma stuff/mockImagesForTable/14.png deleted file mode 100644 index 993bd7b..0000000 Binary files a/figma stuff/mockImagesForTable/14.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/15.png b/figma stuff/mockImagesForTable/15.png deleted file mode 100644 index ffeb65a..0000000 Binary files a/figma stuff/mockImagesForTable/15.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/16.png b/figma stuff/mockImagesForTable/16.png deleted file mode 100644 index b2fc316..0000000 Binary files a/figma stuff/mockImagesForTable/16.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/17.png b/figma stuff/mockImagesForTable/17.png deleted file mode 100644 index f2adaa3..0000000 Binary files a/figma stuff/mockImagesForTable/17.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/18.png b/figma stuff/mockImagesForTable/18.png deleted file mode 100644 index 8fceff9..0000000 Binary files a/figma stuff/mockImagesForTable/18.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/19.png b/figma stuff/mockImagesForTable/19.png deleted file mode 100644 index 9fc36d9..0000000 Binary files a/figma stuff/mockImagesForTable/19.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/2.png b/figma stuff/mockImagesForTable/2.png deleted file mode 100644 index ba1a509..0000000 Binary files a/figma stuff/mockImagesForTable/2.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/20.png b/figma stuff/mockImagesForTable/20.png deleted file mode 100644 index 625e0c4..0000000 Binary files a/figma stuff/mockImagesForTable/20.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/3.png b/figma stuff/mockImagesForTable/3.png deleted file mode 100644 index a1833c7..0000000 Binary files a/figma stuff/mockImagesForTable/3.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/4.png b/figma stuff/mockImagesForTable/4.png deleted file mode 100644 index 779fb88..0000000 Binary files a/figma stuff/mockImagesForTable/4.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/5.png b/figma stuff/mockImagesForTable/5.png deleted file mode 100644 index d434ff4..0000000 Binary files a/figma stuff/mockImagesForTable/5.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/6.png b/figma stuff/mockImagesForTable/6.png deleted file mode 100644 index 12cbcc3..0000000 Binary files a/figma stuff/mockImagesForTable/6.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/7.png b/figma stuff/mockImagesForTable/7.png deleted file mode 100644 index 9275052..0000000 Binary files a/figma stuff/mockImagesForTable/7.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/8.png b/figma stuff/mockImagesForTable/8.png deleted file mode 100644 index 4c25a49..0000000 Binary files a/figma stuff/mockImagesForTable/8.png and /dev/null differ diff --git a/figma stuff/mockImagesForTable/9.png b/figma stuff/mockImagesForTable/9.png deleted file mode 100644 index 798fa31..0000000 Binary files a/figma stuff/mockImagesForTable/9.png and /dev/null differ