From 4a285119408146c058ba3f1cb43c157f19f786f6 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Sat, 3 Dec 2022 17:15:03 +0500 Subject: [PATCH 01/25] add profile screen --- ImageFeed.xcodeproj/project.pbxproj | 12 +++ .../Assets.xcassets/Colors/Contents.json | 6 ++ .../YP Black.colorset/Contents.json | 12 +-- .../Colors/YP Grey.colorset/Contents.json | 38 +++++++++ .../Colors/YP White.colorset/Contents.json | 38 +++++++++ .../AccentColor.colorset/Contents.json | 0 .../{ => Icons}/Active.imageset/Active.png | Bin .../{ => Icons}/Active.imageset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 ImageFeed/Assets.xcassets/Icons/Contents.json | 6 ++ .../Icons/Exit.imageset/Contents.json | 21 +++++ .../Icons/Exit.imageset/Exit.png | Bin 0 -> 506 bytes .../{ => Icons}/Logo.imageset/Contents.json | 0 .../{ => Icons}/Logo.imageset/Vector.png | Bin .../No Active.imageset/Contents.json | 0 .../No Active.imageset/No Active.png | Bin ImageFeed/Base.lproj/Main.storyboard | 80 +++++++++++++++++- ImageFeed/Profile/ProfileViewController.swift | 26 ++++++ 18 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 ImageFeed/Assets.xcassets/Colors/Contents.json rename ImageFeed/Assets.xcassets/{ => Colors}/YP Black.colorset/Contents.json (76%) create mode 100644 ImageFeed/Assets.xcassets/Colors/YP Grey.colorset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Colors/YP White.colorset/Contents.json rename ImageFeed/Assets.xcassets/{ => Icons}/AccentColor.colorset/Contents.json (100%) rename ImageFeed/Assets.xcassets/{ => Icons}/Active.imageset/Active.png (100%) rename ImageFeed/Assets.xcassets/{ => Icons}/Active.imageset/Contents.json (100%) rename ImageFeed/Assets.xcassets/{ => Icons}/AppIcon.appiconset/Contents.json (100%) create mode 100644 ImageFeed/Assets.xcassets/Icons/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/Exit.imageset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/Exit.imageset/Exit.png rename ImageFeed/Assets.xcassets/{ => Icons}/Logo.imageset/Contents.json (100%) rename ImageFeed/Assets.xcassets/{ => Icons}/Logo.imageset/Vector.png (100%) rename ImageFeed/Assets.xcassets/{ => Icons}/No Active.imageset/Contents.json (100%) rename ImageFeed/Assets.xcassets/{ => Icons}/No Active.imageset/No Active.png (100%) create mode 100644 ImageFeed/Profile/ProfileViewController.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index c21b349..6eec318 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 50A8567D2931FDA700E476DF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A8567B2931FDA700E476DF /* Main.storyboard */; }; 50A8567F2931FDA800E476DF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A8567E2931FDA800E476DF /* Assets.xcassets */; }; 50A856822931FDA800E476DF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A856802931FDA800E476DF /* LaunchScreen.storyboard */; }; + 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -26,6 +27,7 @@ 50A8567E2931FDA800E476DF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50A856812931FDA800E476DF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50A856832931FDA800E476DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +69,7 @@ 50A856742931FDA700E476DF /* ImageFeed */ = { isa = PBXGroup; children = ( + 50EEBC00293B6E1100E950B5 /* Profile */, 506BFC6F2933D62F0058728A /* ImagesList */, 50A856752931FDA700E476DF /* AppDelegate.swift */, 50A856772931FDA700E476DF /* SceneDelegate.swift */, @@ -78,6 +81,14 @@ path = ImageFeed; sourceTree = ""; }; + 50EEBC00293B6E1100E950B5 /* Profile */ = { + isa = PBXGroup; + children = ( + 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */, + ); + path = Profile; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -151,6 +162,7 @@ files = ( 50A8567A2931FDA700E476DF /* ImagesListControllerViewController.swift in Sources */, 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, + 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */, 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */, ); 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/YP Black.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json similarity index 76% rename from ImageFeed/Assets.xcassets/YP Black.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json index b15ea4c..1d65869 100644 --- a/ImageFeed/Assets.xcassets/YP Black.colorset/Contents.json +++ b/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x22", - "green" : "0x1B", - "red" : "0x1A" + "blue" : "0.133", + "green" : "0.106", + "red" : "0.102" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x22", - "green" : "0x1B", - "red" : "0x1A" + "blue" : "0.133", + "green" : "0.106", + "red" : "0.102" } }, "idiom" : "universal" diff --git a/ImageFeed/Assets.xcassets/Colors/YP Grey.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Grey.colorset/Contents.json new file mode 100644 index 0000000..a6ef923 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/YP Grey.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.706", + "green" : "0.686", + "red" : "0.682" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.706", + "green" : "0.686", + "red" : "0.682" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Colors/YP White.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP White.colorset/Contents.json new file mode 100644 index 0000000..22c4bb0 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/YP White.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/AccentColor.colorset/Contents.json b/ImageFeed/Assets.xcassets/Icons/AccentColor.colorset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/AccentColor.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/AccentColor.colorset/Contents.json diff --git a/ImageFeed/Assets.xcassets/Active.imageset/Active.png b/ImageFeed/Assets.xcassets/Icons/Active.imageset/Active.png similarity index 100% rename from ImageFeed/Assets.xcassets/Active.imageset/Active.png rename to ImageFeed/Assets.xcassets/Icons/Active.imageset/Active.png diff --git a/ImageFeed/Assets.xcassets/Active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/Active.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/Active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/Active.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/AppIcon.appiconset/Contents.json b/ImageFeed/Assets.xcassets/Icons/AppIcon.appiconset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/AppIcon.appiconset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/AppIcon.appiconset/Contents.json diff --git a/ImageFeed/Assets.xcassets/Icons/Contents.json b/ImageFeed/Assets.xcassets/Icons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Icons/Exit.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/Exit.imageset/Contents.json new file mode 100644 index 0000000..be12e6d --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/Exit.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Exit.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Icons/Exit.imageset/Exit.png b/ImageFeed/Assets.xcassets/Icons/Exit.imageset/Exit.png new file mode 100644 index 0000000000000000000000000000000000000000..b4616742a5af2136a05a4a825bc9b9650ef0c076 GIT binary patch literal 506 zcmVscCQ))Qs0?{z)|iw3D)3NalFWeh!EHN;bn16SvGL4B;Zz&+tf=eF=d&rsSse~oUykK z8vTa>Vt!`gT#H~u(w~4K{&TZKbW`7NLJ9D|+5M)R$%!XNJe6q0qQuBV3W^-^8rPyw wJu(Z{rE>)|^npho8G$AQ2Sm - + @@ -96,12 +96,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift new file mode 100644 index 0000000..922dcc3 --- /dev/null +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -0,0 +1,26 @@ +// +// ProfileViewController.swift +// ImageFeed +// +// Created by Albert on 03.12.2022. +// + +import UIKit + +class ProfileViewController: UIViewController { + + @IBOutlet weak var exitButton: UIButton! + @IBOutlet weak var imageProfile: UIImageView! + @IBOutlet weak var name: UILabel! + @IBOutlet weak var nickName: UILabel! + @IBOutlet weak var status: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() + exitButton.setTitle("", for: .normal) + } + + @IBAction func clickExitButton(_ sender: Any) { + + } +} From 011b3817114625f53621b22f2de718fcb0dcfe43 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Sat, 3 Dec 2022 18:52:43 +0500 Subject: [PATCH 02/25] tab bar --- .../Active.png | Bin .../Contents.json | 0 .../Contents.json | 0 .../No Active.png | Bin .../tab_editorial_active.imageset/Active.png | Bin 0 -> 274 bytes .../Contents.json | 21 ++++++++++ .../tab_profile_active.imageset/Contents.json | 21 ++++++++++ .../tab_profile_active.png | Bin 0 -> 473 bytes ImageFeed/Base.lproj/Main.storyboard | 36 ++++++++++++++---- .../ImagesListControllerViewController.swift | 4 +- 10 files changed, 73 insertions(+), 9 deletions(-) rename ImageFeed/Assets.xcassets/Icons/{Active.imageset => liked.imageset}/Active.png (100%) rename ImageFeed/Assets.xcassets/Icons/{Active.imageset => liked.imageset}/Contents.json (100%) rename ImageFeed/Assets.xcassets/Icons/{No Active.imageset => no liked.imageset}/Contents.json (100%) rename ImageFeed/Assets.xcassets/Icons/{No Active.imageset => no liked.imageset}/No Active.png (100%) create mode 100644 ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Active.png create mode 100644 ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/tab_profile_active.imageset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/tab_profile_active.imageset/tab_profile_active.png diff --git a/ImageFeed/Assets.xcassets/Icons/Active.imageset/Active.png b/ImageFeed/Assets.xcassets/Icons/liked.imageset/Active.png similarity index 100% rename from ImageFeed/Assets.xcassets/Icons/Active.imageset/Active.png rename to ImageFeed/Assets.xcassets/Icons/liked.imageset/Active.png diff --git a/ImageFeed/Assets.xcassets/Icons/Active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/liked.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/Icons/Active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/liked.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/Icons/No Active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/no liked.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/Icons/No Active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/no liked.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/Icons/No Active.imageset/No Active.png b/ImageFeed/Assets.xcassets/Icons/no liked.imageset/No Active.png similarity index 100% rename from ImageFeed/Assets.xcassets/Icons/No Active.imageset/No Active.png rename to ImageFeed/Assets.xcassets/Icons/no liked.imageset/No Active.png diff --git a/ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Active.png b/ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Active.png new file mode 100644 index 0000000000000000000000000000000000000000..d522e6f761495040b4eb7a89c70f5e0cfcbab220 GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^@<1%d!3HF+d^$P@NO2Z;L>4nJa0`PlBg3pY55jgR3=A9lx&I`x0{P25T^vIy7~fty%iCnK6kg=2$Mj SSuq#rWCl-HKbLh*2~7aHPiC(G literal 0 HcmV?d00001 diff --git a/ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Contents.json new file mode 100644 index 0000000..0fe7a56 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "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/Icons/tab_profile_active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/tab_profile_active.imageset/Contents.json new file mode 100644 index 0000000..effb505 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/tab_profile_active.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "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/Icons/tab_profile_active.imageset/tab_profile_active.png b/ImageFeed/Assets.xcassets/Icons/tab_profile_active.imageset/tab_profile_active.png new file mode 100644 index 0000000000000000000000000000000000000000..cc36a9336420f6a7dd1ed8cf4d1f1de2c5fcf10c GIT binary patch literal 473 zcmV;~0Ve*5P)VVzzx?9M68K>;y@hn2ffU1;I~0e8wqFu+cmHhoF%5?s$tUthl;2QYqwy_17caVX*;Qt5cXyvsz ziaq~+AzGTsbWY%?vojok^SyP-)RRw@o`?@I7pYJ1N|J6GIeW>1#WpF}@EE`sahu`%lEK<8i% zWCMPeV`s0#%Cq5Hs${!I#gy&pMg P00000NkvXXu0mjf3nk3> literal 0 HcmV?d00001 diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index b4f3eed..0af91d3 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -18,7 +18,7 @@ - + @@ -88,13 +88,14 @@ + - + @@ -130,8 +131,8 @@ - - + + @@ -154,6 +155,7 @@ + @@ -164,14 +166,34 @@ - + + + + + + + + + + + + + + + + + + + + - + + diff --git a/ImageFeed/ImagesList/ImagesListControllerViewController.swift b/ImageFeed/ImagesList/ImagesListControllerViewController.swift index 85c6ebb..337ffe8 100644 --- a/ImageFeed/ImagesList/ImagesListControllerViewController.swift +++ b/ImageFeed/ImagesList/ImagesListControllerViewController.swift @@ -60,9 +60,9 @@ extension ImagesListViewController: UITableViewDataSource { cell.date.text = dateFormatter.string(from: Date()) if indexPath.row % 2 == 0 { - cell.favoriteButton.setImage(UIImage(named: "Active"), for: .normal) + cell.favoriteButton.setImage(UIImage(named: "liked"), for: .normal) } else{ - cell.favoriteButton.setImage(UIImage(named: "No Active"), for: .normal) + cell.favoriteButton.setImage(UIImage(named: "no liked"), for: .normal) } } } From 8dddcaa5f519a9557ce41d3b10221f8435461850 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Wed, 7 Dec 2022 23:11:12 +0500 Subject: [PATCH 03/25] add navigation --- ImageFeed.xcodeproj/project.pbxproj | 12 ++++ .../Icons/Backward.imageset/Backward.png | Bin 0 -> 241 bytes .../Icons/Backward.imageset/Contents.json | 21 +++++++ ImageFeed/Base.lproj/Main.storyboard | 55 ++++++++++++++++-- .../ImagesListControllerViewController.swift | 15 ++++- .../SingleImageViewController.swift | 32 ++++++++++ 6 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 ImageFeed/Assets.xcassets/Icons/Backward.imageset/Backward.png create mode 100644 ImageFeed/Assets.xcassets/Icons/Backward.imageset/Contents.json create mode 100644 ImageFeed/SingleImage/SingleImageViewController.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 6eec318..14f108d 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */; }; 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506BFC6D2933D6160058728A /* ImagesListCell.swift */; }; 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A856752931FDA700E476DF /* AppDelegate.swift */; }; 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A856772931FDA700E476DF /* SceneDelegate.swift */; }; @@ -18,6 +19,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImageViewController.swift; sourceTree = ""; }; 506BFC6D2933D6160058728A /* ImagesListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListCell.swift; sourceTree = ""; }; 50A856722931FDA700E476DF /* ImageFeed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageFeed.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50A856752931FDA700E476DF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -41,6 +43,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5044C5CB2940FC1F00E65D8F /* SingleImage */ = { + isa = PBXGroup; + children = ( + 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */, + ); + path = SingleImage; + sourceTree = ""; + }; 506BFC6F2933D62F0058728A /* ImagesList */ = { isa = PBXGroup; children = ( @@ -69,6 +79,7 @@ 50A856742931FDA700E476DF /* ImageFeed */ = { isa = PBXGroup; children = ( + 5044C5CB2940FC1F00E65D8F /* SingleImage */, 50EEBC00293B6E1100E950B5 /* Profile */, 506BFC6F2933D62F0058728A /* ImagesList */, 50A856752931FDA700E476DF /* AppDelegate.swift */, @@ -164,6 +175,7 @@ 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */, + 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */, 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Backward.png b/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Backward.png new file mode 100644 index 0000000000000000000000000000000000000000..1e28c7e41e38ea5bfe0605204c36a75cca651b31 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#HcuDFkP61P6CZLmC^GSw(=m&w_?Uufc(I+bsO4Gn%+G8q z6*l*6>7Mbs;rP_>iNCL|*Y$K}T2=FH|AVlv6BIZS**re1o}lS|qJq!v+qPr8Q-Wj? eGP46KY}re~?|v5OFHHcth{4m<&t;ucLK6Tp;89ip literal 0 HcmV?d00001 diff --git a/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Contents.json new file mode 100644 index 0000000..a410a9b --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Backward.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 0af91d3..7524f83 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -88,15 +88,58 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -179,8 +222,8 @@ - - + + @@ -190,15 +233,17 @@ + + - + - + diff --git a/ImageFeed/ImagesList/ImagesListControllerViewController.swift b/ImageFeed/ImagesList/ImagesListControllerViewController.swift index 337ffe8..617af4d 100644 --- a/ImageFeed/ImagesList/ImagesListControllerViewController.swift +++ b/ImageFeed/ImagesList/ImagesListControllerViewController.swift @@ -12,6 +12,7 @@ class ImagesListViewController: UIViewController { @IBOutlet private var tableViewImage: UITableView! private var photosName = [String]() + private let ShowSingleImageSegueIdentifier = "ShowSingleImage" private lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() @@ -25,11 +26,22 @@ class ImagesListViewController: UIViewController { photosName = Array(0..<20).map{"\($0)"} } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == ShowSingleImageSegueIdentifier { + let viewController = segue.destination as! SingleImageViewController + let indexPath = sender as! IndexPath + let image = UIImage(named: photosName[indexPath.row]) + viewController.image = image + } else { + super.prepare(for: segue, sender: sender) + } + } } extension ImagesListViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - + performSegue(withIdentifier: ShowSingleImageSegueIdentifier, sender: indexPath) } } @@ -67,3 +79,4 @@ extension ImagesListViewController: UITableViewDataSource { } } + diff --git a/ImageFeed/SingleImage/SingleImageViewController.swift b/ImageFeed/SingleImage/SingleImageViewController.swift new file mode 100644 index 0000000..98388a2 --- /dev/null +++ b/ImageFeed/SingleImage/SingleImageViewController.swift @@ -0,0 +1,32 @@ +// +// SingleImageViewController.swift +// ImageFeed +// +// Created by Albert on 07.12.2022. +// + +import UIKit + +class SingleImageViewController: UIViewController { + + + @IBOutlet weak private var buttonBack: UIButton! + @IBOutlet weak var imageView: UIImageView! + + var image: UIImage! { + didSet { + guard isViewLoaded else { return } // 1 + imageView.image = image // 2 + } + } + + override func viewDidLoad() { + super.viewDidLoad() + buttonBack.setTitle("", for: .normal) + imageView.image = image + } + + @IBAction func clickBackButton(_ sender: Any) { + self.dismiss(animated: true) + } +} From f13301b18f756dc6d6216b30883a4b5e8ad1f5a6 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Sat, 17 Dec 2022 15:37:22 +0500 Subject: [PATCH 04/25] zoom --- .../Icons/Sharing.imageset/Contents.json | 21 ++++++++ .../Icons/Sharing.imageset/Sharing.pdf | Bin 0 -> 2860 bytes ImageFeed/Base.lproj/Main.storyboard | 48 +++++++++++++++--- .../SingleImageViewController.swift | 39 ++++++++++++++ 4 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Sharing.pdf diff --git a/ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Contents.json new file mode 100644 index 0000000..3bfb24e --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Sharing.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Sharing.pdf b/ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Sharing.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eee12c4aebf4b3c88ce5e57ff12c48a04731a30e GIT binary patch literal 2860 zcmb7GO^@3)5WVwP@UlQsATqy60YQLfw<(IY=+@~i=)u)08yB|K+D?(?*Y}M?4(Z(m z(&%8&kDQq|Z$=|t-QK);p}Q!AmP$^4{3)b-`BJ`mCC0-Y{S~IgS3ix1`|}5B0j}xP z>3keFd$GD1|Je@X`uo@N`fdAfIElZ7?$rE9RTeLJ^>~lBhP_aoR=N-1hJ{ZFWIAo> zk*DDxJfAhbxZXFY)}8K6N>Z#o}^at@?9K^5xS8DAFRn4+NY%ay)1&M9hh|TxHbkVwKjjTRR=aRBY zNo~8-yP%RZok1BED54C22AHCx*7bDGqsTtuT$T`!3EtXEC~uN!k-98)6`LuAVBswX zSQt4%GLL1^o0&$YyBr6`IXw=;2s))Nu^HSE2;SQEO)5QQXPFPF(05UV-up~w<^hn@ zGGq!yJ~(N(rDjTf3=PEPfE;`dx@Bb`NQ;7%GL9nWAdBeEhK3C=t`1IS+CX6w7v4F$ zT5X&wHRHB`IPozGbt)Rl{;XN?aEZp=M-!!H6d8G+g7y-98HmhF)=i^whYBv{QGxm)mS)4{un z`PT=G+`j9TvChda&wb{L`u3REzRkuL4DWI0v@_btfKRQ9c;l!v^FX07?zK*scsR#W zGzameoUN^FJcfdCzz@*Pmb4??NuWzd#w|Af_DTB2vajvJ2u+@+F zpMW%;!i6VF@WcUY;@6w=W;fkmntnQt?}d_4Ec(;044zsBaDFsYp+_DVvfz0E8LLp> z2M|rQOUNr^{Q!#9@O<2CcjHmuKD+wi268+*P5be=e73!NzKF6~Pt#da!n5kDx7*Jn W*w6Rc&G9^^V}ecM>gu~+-uwq1^FJ&A literal 0 HcmV?d00001 diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 7524f83..6cce45a 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -107,9 +107,25 @@ - + - + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + - + @@ -233,10 +265,10 @@ - + diff --git a/ImageFeed/SingleImage/SingleImageViewController.swift b/ImageFeed/SingleImage/SingleImageViewController.swift index 98388a2..07b9181 100644 --- a/ImageFeed/SingleImage/SingleImageViewController.swift +++ b/ImageFeed/SingleImage/SingleImageViewController.swift @@ -12,21 +12,60 @@ class SingleImageViewController: UIViewController { @IBOutlet weak private var buttonBack: UIButton! @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var scrollView: UIScrollView! + + @IBOutlet weak var sharingButton: UIButton! var image: UIImage! { didSet { guard isViewLoaded else { return } // 1 imageView.image = image // 2 + rescaleAndCenterImageInScrollView(image: image) } } override func viewDidLoad() { super.viewDidLoad() buttonBack.setTitle("", for: .normal) + sharingButton.setTitle("", for: .normal) imageView.image = image + rescaleAndCenterImageInScrollView(image: image) + scrollView.minimumZoomScale = 0.1 + scrollView.maximumZoomScale = 1.25 } @IBAction func clickBackButton(_ sender: Any) { self.dismiss(animated: true) } + + 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) + } + + @IBAction func clickSharingButton(_ sender: Any) { + let share = UIActivityViewController( + activityItems: [image], + applicationActivities: nil + ) + present(share, animated: true, completion: nil) + } +} + +extension SingleImageViewController: UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + imageView + } } From a4316d17f6a4ad421b61c79df711fe8cf10086ec Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Wed, 21 Dec 2022 13:08:12 +0500 Subject: [PATCH 05/25] manual layout --- .../Colors/YP Red.colorset/Contents.json | 38 +++++++++ .../Icons/Photo.imageset/Contents.json | 21 +++++ .../Icons/Photo.imageset/Photo.pdf | Bin 0 -> 149135 bytes ImageFeed/Base.lproj/Main.storyboard | 59 -------------- ImageFeed/Profile/ProfileViewController.swift | 74 ++++++++++++++++-- 5 files changed, 126 insertions(+), 66 deletions(-) create mode 100644 ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/Photo.imageset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/Photo.imageset/Photo.pdf diff --git a/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json new file mode 100644 index 0000000..11976ca --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6C", + "green" : "0x6B", + "red" : "0xF5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6C", + "green" : "0x6B", + "red" : "0xF5" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Icons/Photo.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/Photo.imageset/Contents.json new file mode 100644 index 0000000..b8b03a0 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/Photo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Photo.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Icons/Photo.imageset/Photo.pdf b/ImageFeed/Assets.xcassets/Icons/Photo.imageset/Photo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7e7e79e32092d1b8b215bf7e6ecdbaca43daec38 GIT binary patch literal 149135 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
    |X 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 z zicr4mIA;y-CD#yO8UBUa=VpS^PB`1l!uf>$^5Eb_LRP3%D~7*;j=iueUIh5 z^4G8v0nKFSp!>?j$=`$9V7!#^(rp0vsxk*GT_4e_^JlsQv?Pm`9Qou5{|iz4$gQqde?1WAd|B33?Hgqms;5igx8Gab>i z?R3%U#>N3A!+zDwfDP(IA7R)i%E7=EyetSMCDEA}3x*KkzK0zsL7pp3)d`av>c$oT zbGgxk)Z+DQJGS;<$$0ndZ!HUd=qpTSP|&SP`Kb)Myj{JHD@llaseB1dt6{fC0T#Z3 z+RPl{?)@Y#G*J9=fk1o~q^Ac1$`*K1s|&~U1rnO9S_~#ZYTx>d3JDdye&r`HIEe8B zWjaz{W2a~|FjDMp@cAlbq>D8!RpP%nWgwaHu=iF1>`G~{*N08FnyKp$TElX$!cWA(S9j|%8 zGLYU!#6hu!MB^}iNT+Pnvj(;jO`m22ch^5Ryj+`>DS+F$Gi*AwCQ$1B=`QPWK!%A555!rCmMJ1qv~|LoM{^H(fLP10 zF?oxP)Fk2bnMXMtFo|xgM1sst=dDb15#QM8`mzQ|m8qGL^_WHHDkm$VyKqDw-|Icl z6&TfoBn5j=VFL|@(glXduQw5a_$44w2O&?;hy+8g? zhd{umqBGEV0p{H5pwfNMIL4WIN@LiLr}BMS0{q4!}hR*bbB6KBg}?*mG%B5L>h0;%QFv`72fh_6tIquTer?SV5P z(H{JL?8uD_Gp8DqbuS%zhp5@CW|?u8g)f#!Q~ZRZ0DA{UT0xx-TW|oc!1eJzr2=t30l5@AahGgTPyOf1gl>O>()VLn|&e^VxrQZe_fkH zWRU0&+1rEfuMF1zmBA|}DK;$*viZ*sG5@)(nR85fd{m6TniEq@6U zF@F^U`u4BPzb^ht13max+TR67{jYXR#Kd8OFiD_${;vM7yuU8~|I$GJm~A$PoRcwr zpd3HU{}_hL@RPgVr{#BBH%#P%p%&3?h(DJDF|4(sF$w_H3N#KslBl|C*Zv+#_ zAlv_l{xSZSMgM+;<;r@-M}w+D%TM4B{dFIwAVTpa=es z$mYL)Wo~J+#tJmWKYq0=T4VlyMYiPsn{ka535>V@%DBeL0<`sii>z%ybN#o-#>V28 zD2*8rpBTf$EH|&aGiY3c9m(5c3?YN~Xv2Ruv6+8zGHBoB=KEKFnFYjW#w@o5XxtrX X5zMrIwT`*vnl)q#jIOSWkL&*jYXb(m literal 0 HcmV?d00001 diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 6cce45a..9a0a8ef 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -179,65 +179,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -267,16 +212,12 @@ - - - - diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift index 922dcc3..df6ebee 100644 --- a/ImageFeed/Profile/ProfileViewController.swift +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -9,18 +9,78 @@ import UIKit class ProfileViewController: UIViewController { - @IBOutlet weak var exitButton: UIButton! - @IBOutlet weak var imageProfile: UIImageView! - @IBOutlet weak var name: UILabel! - @IBOutlet weak var nickName: UILabel! - @IBOutlet weak var status: UILabel! +// @IBOutlet weak var exitButton: UIButton! +// @IBOutlet weak var imageProfile: UIImageView! +// @IBOutlet weak var name: UILabel! +// @IBOutlet weak var nickName: UILabel! +// @IBOutlet weak var status: UILabel! + + let image = UIImageView() + let labelName = UILabel() + let labelNickname = UILabel() + let labelStatus = UILabel() + let button = UIButton.systemButton(with: UIImage(systemName: "ipad.and.arrow.forward")!, target: ProfileViewController.self, action: nil) override func viewDidLoad() { super.viewDidLoad() - exitButton.setTitle("", for: .normal) + + addSubviews() + setViewConfiguration() + activateConstraints() } + + private func addSubviews() { + view.addSubview(image) + view.addSubview(labelName) + view.addSubview(labelNickname) + view.addSubview(labelStatus) + view.addSubview(button) + + image.translatesAutoresizingMaskIntoConstraints = false + labelName.translatesAutoresizingMaskIntoConstraints = false + labelNickname.translatesAutoresizingMaskIntoConstraints = false + labelStatus.translatesAutoresizingMaskIntoConstraints = false + button.translatesAutoresizingMaskIntoConstraints = false + } + + private func setViewConfiguration() { + image.image = UIImage(named: "Photo") - @IBAction func clickExitButton(_ sender: Any) { + + labelName.text = "Екатерина Новикова" + labelName.textColor = UIColor(named: "YP White") + labelName.font = labelName.font.withSize(23) + + labelNickname.text = "@ekaterina_nov" + labelNickname.textColor = UIColor(named: "YP Grey") + labelNickname.font = labelNickname.font.withSize(13) + + labelStatus.text = "Hello, world" + labelStatus.textColor = UIColor(named: "YP White") + labelStatus.font = labelStatus.font.withSize(13) + + button.tintColor = UIColor(named: "YP Red") } + + private func activateConstraints() { + NSLayoutConstraint.activate([ + image.heightAnchor.constraint(equalToConstant: 70), + image.widthAnchor.constraint(equalToConstant: 70), + image.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + image.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + labelName.topAnchor.constraint(equalTo: image.bottomAnchor, constant: 8), + labelName.leadingAnchor.constraint(equalTo: image.leadingAnchor), + labelNickname.topAnchor.constraint(equalTo: labelName.bottomAnchor, constant: 8), + labelNickname.leadingAnchor.constraint(equalTo: labelName.leadingAnchor), + labelStatus.topAnchor.constraint(equalTo: labelNickname.bottomAnchor, constant: 8), + labelStatus.leadingAnchor.constraint(equalTo: labelName.leadingAnchor), + button.centerYAnchor.constraint(equalTo: image.centerYAnchor), + button.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20) + ]) + } + +// @IBAction func clickExitButton(_ sender: Any) { +// +// } } From ba7f05db008bc533eeb558160677303d3e6da33c Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Thu, 22 Dec 2022 14:38:21 +0500 Subject: [PATCH 06/25] fix after review --- ImageFeed/Base.lproj/Main.storyboard | 4 ++-- .../ImagesListControllerViewController.swift | 21 +++++++++++-------- ImageFeed/Profile/ProfileViewController.swift | 18 +++++----------- .../SingleImageViewController.swift | 8 +++---- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 9a0a8ef..be59c45 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -135,7 +135,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -213,13 +307,18 @@ + + + + + diff --git a/ImageFeed/Constants.swift b/ImageFeed/Constants.swift new file mode 100644 index 0000000..07164b1 --- /dev/null +++ b/ImageFeed/Constants.swift @@ -0,0 +1,14 @@ +// +// Constants.swift +// ImageFeed +// +// Created by macOS on 29.12.2022. +// + +import Foundation + +let AccessKey = "kLuw65wDZT845EyoYEfX2gWsq1ueUuX06gvSOEuPFT0" +let SecretKey = "b99lHBvBrUgVpF70lQFrvKJ3hyyCCQMA0tk28nVoTB4" +let RedirectURI = "urn:ietf:wg:oauth:2.0:oob" +let AccessScope = "public+read_user+write_likes" +let DefaultBaseURL = URL(string: "https://api.unsplash.com")! From 652821c4e62fc1072553b4640b0bc17cd717910a Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Fri, 30 Dec 2022 18:23:48 +0500 Subject: [PATCH 08/25] web view autorize --- ImageFeed.xcodeproj/project.pbxproj | 4 ++ ImageFeed/AppDelegate.swift | 2 - ImageFeed/Auth/AuthViewController.swift | 23 +++++++++- ImageFeed/Auth/WebViewViewController.swift | 46 +++++++++++++++++++ .../Auth/WebViewViewControllerDelegate.swift | 15 ++++++ 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 ImageFeed/Auth/WebViewViewControllerDelegate.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index be4d1c4..227b986 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */; }; 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB27295DD00800FDCAE1 /* Constants.swift */; }; 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */; }; 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */; }; @@ -22,6 +23,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewControllerDelegate.swift; sourceTree = ""; }; 223FDB27295DD00800FDCAE1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; @@ -54,6 +56,7 @@ children = ( 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */, 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */, + 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */, ); path = Auth; sourceTree = ""; @@ -189,6 +192,7 @@ buildActionMask = 2147483647; files = ( 50A8567A2931FDA700E476DF /* ImagesListControllerViewController.swift in Sources */, + 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */, 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */, diff --git a/ImageFeed/AppDelegate.swift b/ImageFeed/AppDelegate.swift index d86e7ff..622e1a3 100644 --- a/ImageFeed/AppDelegate.swift +++ b/ImageFeed/AppDelegate.swift @@ -27,7 +27,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { } - - } diff --git a/ImageFeed/Auth/AuthViewController.swift b/ImageFeed/Auth/AuthViewController.swift index 6916c65..ce7b591 100644 --- a/ImageFeed/Auth/AuthViewController.swift +++ b/ImageFeed/Auth/AuthViewController.swift @@ -9,12 +9,33 @@ import UIKit class AuthViewController: UIViewController { - let segueID = "ShowWebView" + private let segueID = "ShowWebView" override func viewDidLoad() { super.viewDidLoad() } + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == segueID { + guard + let webViewViewController = segue.destination as? WebViewViewController + else { fatalError("Failed to prepare for \(segueID)") } + webViewViewController.delegate = self + } else { + super.prepare(for: segue, sender: sender) + } + } +} +extension AuthViewController: WebViewViewControllerDelegate { + func webViewViewController(_ vc: WebViewViewController, didAuthenticateWithCode code: String) { + + } + + func webViewViewControllerDidCancel(_ vc: WebViewViewController) { + dismiss(animated: true) + } + + } diff --git a/ImageFeed/Auth/WebViewViewController.swift b/ImageFeed/Auth/WebViewViewController.swift index 4af45c2..7aec087 100644 --- a/ImageFeed/Auth/WebViewViewController.swift +++ b/ImageFeed/Auth/WebViewViewController.swift @@ -8,16 +8,62 @@ import UIKit import WebKit +fileprivate let UnsplashAuthorizeURLString = "https://unsplash.com/oauth/authorize" + class WebViewViewController: UIViewController { @IBOutlet private weak var webView: WKWebView! + weak var delegate: WebViewViewControllerDelegate? + override func viewDidLoad() { super.viewDidLoad() + webView.navigationDelegate = self + + var urlComponents = URLComponents(string: UnsplashAuthorizeURLString)! + 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) + ] + let url = urlComponents.url! + + let request = URLRequest(url: url) + webView.load(request) } @IBAction private func didTapBackButton(_ sender: Any) { } } + +extension WebViewViewController: WKNavigationDelegate { + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + if let code = code(from: navigationAction) { + //TODO: process 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/ImageFeed/Auth/WebViewViewControllerDelegate.swift b/ImageFeed/Auth/WebViewViewControllerDelegate.swift new file mode 100644 index 0000000..b8aef8f --- /dev/null +++ b/ImageFeed/Auth/WebViewViewControllerDelegate.swift @@ -0,0 +1,15 @@ +// +// WebViewViewControllerDelegate.swift +// ImageFeed +// +// Created by macOS on 30.12.2022. +// + +import Foundation + +protocol WebViewViewControllerDelegate: AnyObject { + + func webViewViewController(_ vc: WebViewViewController, didAuthenticateWithCode code: String) + + func webViewViewControllerDidCancel(_ vc: WebViewViewController) +} From d5f10a4ee692afb4268bbb32c1e1c7c00d6f1638 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Sun, 1 Jan 2023 19:45:14 +0500 Subject: [PATCH 09/25] splash + autorize --- ImageFeed.xcodeproj/project.pbxproj | 28 +++++ ImageFeed/Auth/AuthViewController.swift | 13 +- .../Auth/Models/OAuth2TokenStorage.swift | 26 ++++ ImageFeed/Auth/OAuth2Service.swift | 118 ++++++++++++++++++ ImageFeed/Auth/WebViewViewController.swift | 54 ++++++-- ImageFeed/Base.lproj/Main.storyboard | 70 ++++++++++- .../SplashViewController.swift | 77 ++++++++++++ 7 files changed, 365 insertions(+), 21 deletions(-) create mode 100644 ImageFeed/Auth/Models/OAuth2TokenStorage.swift create mode 100644 ImageFeed/Auth/OAuth2Service.swift create mode 100644 ImageFeed/SplashViewController/SplashViewController.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 227b986..19adb49 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */; }; 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */; }; 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */; }; + 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50487E6429609E7700F41745 /* OAuth2Service.swift */; }; 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506BFC6D2933D6160058728A /* ImagesListCell.swift */; }; 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A856752931FDA700E476DF /* AppDelegate.swift */; }; 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A856772931FDA700E476DF /* SceneDelegate.swift */; }; @@ -19,6 +20,8 @@ 50A8567D2931FDA700E476DF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A8567B2931FDA700E476DF /* Main.storyboard */; }; 50A8567F2931FDA800E476DF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A8567E2931FDA800E476DF /* Assets.xcassets */; }; 50A856822931FDA800E476DF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A856802931FDA800E476DF /* LaunchScreen.storyboard */; }; + 50B8D42F29619E7800508CF6 /* OAuth2TokenStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */; }; + 50B8D4322961AC7200508CF6 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8D4312961AC7200508CF6 /* SplashViewController.swift */; }; 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */; }; /* End PBXBuildFile section */ @@ -28,6 +31,7 @@ 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImageViewController.swift; sourceTree = ""; }; + 50487E6429609E7700F41745 /* OAuth2Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2Service.swift; sourceTree = ""; }; 506BFC6D2933D6160058728A /* ImagesListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListCell.swift; sourceTree = ""; }; 50A856722931FDA700E476DF /* ImageFeed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageFeed.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50A856752931FDA700E476DF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -37,6 +41,8 @@ 50A8567E2931FDA800E476DF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50A856812931FDA800E476DF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50A856832931FDA800E476DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2TokenStorage.swift; sourceTree = ""; }; + 50B8D4312961AC7200508CF6 /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -54,9 +60,11 @@ 223FDB29295DD36600FDCAE1 /* Auth */ = { isa = PBXGroup; children = ( + 50487E6029609D3200F41745 /* Models */, 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */, 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */, 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */, + 50487E6429609E7700F41745 /* OAuth2Service.swift */, ); path = Auth; sourceTree = ""; @@ -69,6 +77,14 @@ path = SingleImage; sourceTree = ""; }; + 50487E6029609D3200F41745 /* Models */ = { + isa = PBXGroup; + children = ( + 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */, + ); + path = Models; + sourceTree = ""; + }; 506BFC6F2933D62F0058728A /* ImagesList */ = { isa = PBXGroup; children = ( @@ -97,6 +113,7 @@ 50A856742931FDA700E476DF /* ImageFeed */ = { isa = PBXGroup; children = ( + 50B8D4302961AC4F00508CF6 /* SplashViewController */, 223FDB29295DD36600FDCAE1 /* Auth */, 5044C5CB2940FC1F00E65D8F /* SingleImage */, 50EEBC00293B6E1100E950B5 /* Profile */, @@ -112,6 +129,14 @@ path = ImageFeed; sourceTree = ""; }; + 50B8D4302961AC4F00508CF6 /* SplashViewController */ = { + isa = PBXGroup; + children = ( + 50B8D4312961AC7200508CF6 /* SplashViewController.swift */, + ); + path = SplashViewController; + sourceTree = ""; + }; 50EEBC00293B6E1100E950B5 /* Profile */ = { isa = PBXGroup; children = ( @@ -193,13 +218,16 @@ files = ( 50A8567A2931FDA700E476DF /* ImagesListControllerViewController.swift in Sources */, 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */, + 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */, 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */, + 50B8D4322961AC7200508CF6 /* SplashViewController.swift in Sources */, 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */, 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */, 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */, 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */, + 50B8D42F29619E7800508CF6 /* OAuth2TokenStorage.swift in Sources */, 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ImageFeed/Auth/AuthViewController.swift b/ImageFeed/Auth/AuthViewController.swift index ce7b591..fbb293c 100644 --- a/ImageFeed/Auth/AuthViewController.swift +++ b/ImageFeed/Auth/AuthViewController.swift @@ -11,10 +11,7 @@ class AuthViewController: UIViewController { private let segueID = "ShowWebView" - override func viewDidLoad() { - super.viewDidLoad() - - } + weak var delegate: AuthViewControllerDelegate? override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == segueID { @@ -30,12 +27,14 @@ class AuthViewController: UIViewController { extension AuthViewController: WebViewViewControllerDelegate { func webViewViewController(_ vc: WebViewViewController, didAuthenticateWithCode code: String) { - + delegate?.authViewController(self, didAuthenticateWithCode: code) } func webViewViewControllerDidCancel(_ vc: WebViewViewController) { dismiss(animated: true) } - - +} + +protocol AuthViewControllerDelegate: AnyObject { + func authViewController(_ vc: AuthViewController, didAuthenticateWithCode code: String) } diff --git a/ImageFeed/Auth/Models/OAuth2TokenStorage.swift b/ImageFeed/Auth/Models/OAuth2TokenStorage.swift new file mode 100644 index 0000000..0a19dd0 --- /dev/null +++ b/ImageFeed/Auth/Models/OAuth2TokenStorage.swift @@ -0,0 +1,26 @@ +// +// OAuth2TokenStorage.swift +// ImageFeed +// +// Created by macOS on 01.01.2023. +// + +import Foundation + +fileprivate let tokenKey = "BearerToken" + +class OAuth2TokenStorage { + var token: String? { + get { + return UserDefaults.standard.string(forKey: tokenKey) + } + + set(newValue) { + if let token = newValue { + UserDefaults.standard.set(token, forKey: tokenKey) + } else { + UserDefaults.standard.removeObject(forKey: tokenKey) + } + } + } +} diff --git a/ImageFeed/Auth/OAuth2Service.swift b/ImageFeed/Auth/OAuth2Service.swift new file mode 100644 index 0000000..59b76b7 --- /dev/null +++ b/ImageFeed/Auth/OAuth2Service.swift @@ -0,0 +1,118 @@ +// +// OAuth2Service.swift +// ImageFeed +// +// Created by macOS on 31.12.2022. +// + + +import Foundation + +enum NetworkError: Error { + case httpStatusCode(Int) + case urlRequestError(Error) + case urlSessionError +} + +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 path = "/oauth/token" + + "?client_id=\(AccessKey)" + + "&&client_secret=\(SecretKey)" + + "&&redirect_uri=\(RedirectURI)" + + "&&code=\(code)" + + "&&grant_type=authorization_code" + + let request = makeRequest(code: code, path: path, httpMethod: "POST", baseURL: URL(string: "https://unsplash.com")!) + 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 + completion(.success(authToken)) + case .failure(let error): + completion(.failure(error)) + } + } + task.resume() + } + + private func makeRequest( + code: String, + path: String, + httpMethod: String, + baseURL: URL = DefaultBaseURL) -> URLRequest { + + var request = URLRequest(url: URL(string: path, relativeTo: baseURL)!) + request.httpMethod = httpMethod + return request + } + + private struct OAuthTokenResponseBody: Codable { + 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" + } + } +} + +extension OAuth2Service { + private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { + + let task = urlSession.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 { + DispatchQueue.main.async { + completion(.success(data)) + } + } else { + DispatchQueue.main.async { + completion(.failure(NetworkError.httpStatusCode(statusCode))) + } + } + } else if let error = error { + DispatchQueue.main.async { + completion(.failure(NetworkError.urlRequestError(error))) + } + } else { + DispatchQueue.main.async { + completion(.failure(NetworkError.urlSessionError)) + } + } + }) + task.resume() + return task + } + + private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { + let decoder = JSONDecoder() + return data(for: request) { (result: Result) in + let response = result.flatMap { data -> Result in + Result { try decoder.decode(OAuthTokenResponseBody.self, from: data) } + } + completion(response) + } + } +} diff --git a/ImageFeed/Auth/WebViewViewController.swift b/ImageFeed/Auth/WebViewViewController.swift index 7aec087..42d4f70 100644 --- a/ImageFeed/Auth/WebViewViewController.swift +++ b/ImageFeed/Auth/WebViewViewController.swift @@ -10,10 +10,11 @@ import WebKit fileprivate let UnsplashAuthorizeURLString = "https://unsplash.com/oauth/authorize" -class WebViewViewController: UIViewController { +final class WebViewViewController: UIViewController { @IBOutlet private weak var webView: WKWebView! - + @IBOutlet weak var progressView: UIProgressView! + weak var delegate: WebViewViewControllerDelegate? override func viewDidLoad() { @@ -28,14 +29,51 @@ class WebViewViewController: UIViewController { URLQueryItem(name: "response_type", value: "code"), URLQueryItem(name: "scope", value: AccessScope) ] - let url = urlComponents.url! + let url = urlComponents.url! let request = URLRequest(url: url) webView.load(request) + + updateProgress() } - @IBAction private func didTapBackButton(_ sender: Any) { + 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 + } + + @IBAction private func didTapBackButton(_ sender: Any) { + delegate?.webViewViewControllerDidCancel(self) } } @@ -45,10 +83,10 @@ extension WebViewViewController: WKNavigationDelegate { decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void ) { - if let code = code(from: navigationAction) { - //TODO: process code - decisionHandler(.cancel) - } else { + if let code = code(from: navigationAction) { + delegate?.webViewViewController(self, didAuthenticateWithCode: code) + decisionHandler(.cancel) + } else { decisionHandler(.allow) } } diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 42e629a..4f08328 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -189,6 +189,23 @@ + + + + + + + + + + + + + + + + + @@ -198,7 +215,7 @@ - + @@ -212,7 +229,7 @@ - + @@ -233,10 +250,11 @@ + - + @@ -262,6 +280,10 @@ + + + + @@ -270,22 +292,57 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -306,6 +363,7 @@ + diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift new file mode 100644 index 0000000..e2235fb --- /dev/null +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -0,0 +1,77 @@ +// +// SplashViewController.swift +// ImageFeed +// +// Created by macOS on 01.01.2023. +// + +import UIKit + +final class SplashViewController: UIViewController { + private let ShowAuthenticationScreenSegueIdentifier = "ShowAuthenticationScreen" + + private let oauth2Service = OAuth2Service.shared + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let token = oauth2Service.authToken { + switchToTabBarController() + } else { + performSegue(withIdentifier: ShowAuthenticationScreenSegueIdentifier, sender: nil) + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setNeedsStatusBarAppearanceUpdate() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + .lightContent + } + + 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: + self.switchToTabBarController() + case .failure: + print("failure fetchOAuthToken") + break + } + } + } +} + From ab1c452241b515ebb8162278d7e6b7a1cd452bc1 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Mon, 2 Jan 2023 23:06:57 +0500 Subject: [PATCH 10/25] sprint11 --- ImageFeed.xcodeproj/project.pbxproj | 51 +++++++ ImageFeed/Auth/OAuth2Service.swift | 104 ++++++++------ ImageFeed/Auth/UIBlockingProgressHUD.swift | 26 ++++ ImageFeed/Auth/WebViewViewController.swift | 33 ++--- ImageFeed/Extensions/URLSession.swift | 47 +++++++ ImageFeed/Profile/ProfileImageService.swift | 127 ++++++++++++++++++ ImageFeed/Profile/ProfileService.swift | 126 +++++++++++++++++ ImageFeed/Profile/ProfileViewController.swift | 44 ++++-- .../SplashViewController.swift | 33 ++++- 9 files changed, 520 insertions(+), 71 deletions(-) create mode 100644 ImageFeed/Auth/UIBlockingProgressHUD.swift create mode 100644 ImageFeed/Extensions/URLSession.swift create mode 100644 ImageFeed/Profile/ProfileImageService.swift create mode 100644 ImageFeed/Profile/ProfileService.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 19adb49..6e3d48f 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -11,6 +11,10 @@ 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB27295DD00800FDCAE1 /* Constants.swift */; }; 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */; }; 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */; }; + 50397DA42962CB750078D6BE /* UIBlockingProgressHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */; }; + 50397DA62962D2730078D6BE /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA52962D2730078D6BE /* ProfileService.swift */; }; + 50397DA82963119B0078D6BE /* ProfileImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA72963119B0078D6BE /* ProfileImageService.swift */; }; + 50397DAB296348080078D6BE /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DAA296348080078D6BE /* URLSession.swift */; }; 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */; }; 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50487E6429609E7700F41745 /* OAuth2Service.swift */; }; 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506BFC6D2933D6160058728A /* ImagesListCell.swift */; }; @@ -22,6 +26,7 @@ 50A856822931FDA800E476DF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50A856802931FDA800E476DF /* LaunchScreen.storyboard */; }; 50B8D42F29619E7800508CF6 /* OAuth2TokenStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */; }; 50B8D4322961AC7200508CF6 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8D4312961AC7200508CF6 /* SplashViewController.swift */; }; + 50B8D4352961F15500508CF6 /* ProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 50B8D4342961F15500508CF6 /* ProgressHUD */; }; 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */; }; /* End PBXBuildFile section */ @@ -30,6 +35,10 @@ 223FDB27295DD00800FDCAE1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; + 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBlockingProgressHUD.swift; sourceTree = ""; }; + 50397DA52962D2730078D6BE /* ProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; + 50397DA72963119B0078D6BE /* ProfileImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImageService.swift; sourceTree = ""; }; + 50397DAA296348080078D6BE /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = ""; }; 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImageViewController.swift; sourceTree = ""; }; 50487E6429609E7700F41745 /* OAuth2Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2Service.swift; sourceTree = ""; }; 506BFC6D2933D6160058728A /* ImagesListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesListCell.swift; sourceTree = ""; }; @@ -51,6 +60,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50B8D4352961F15500508CF6 /* ProgressHUD in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -65,10 +75,19 @@ 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */, 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */, 50487E6429609E7700F41745 /* OAuth2Service.swift */, + 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */, ); path = Auth; sourceTree = ""; }; + 50397DA9296347EC0078D6BE /* Extensions */ = { + isa = PBXGroup; + children = ( + 50397DAA296348080078D6BE /* URLSession.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 5044C5CB2940FC1F00E65D8F /* SingleImage */ = { isa = PBXGroup; children = ( @@ -113,6 +132,7 @@ 50A856742931FDA700E476DF /* ImageFeed */ = { isa = PBXGroup; children = ( + 50397DA9296347EC0078D6BE /* Extensions */, 50B8D4302961AC4F00508CF6 /* SplashViewController */, 223FDB29295DD36600FDCAE1 /* Auth */, 5044C5CB2940FC1F00E65D8F /* SingleImage */, @@ -141,6 +161,8 @@ isa = PBXGroup; children = ( 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */, + 50397DA52962D2730078D6BE /* ProfileService.swift */, + 50397DA72963119B0078D6BE /* ProfileImageService.swift */, ); path = Profile; sourceTree = ""; @@ -161,6 +183,9 @@ dependencies = ( ); name = ImageFeed; + packageProductDependencies = ( + 50B8D4342961F15500508CF6 /* ProgressHUD */, + ); productName = ImageFeed; productReference = 50A856722931FDA700E476DF /* ImageFeed.app */; productType = "com.apple.product-type.application"; @@ -189,6 +214,9 @@ Base, ); mainGroup = 50A856692931FDA700E476DF; + packageReferences = ( + 50B8D4332961F15500508CF6 /* XCRemoteSwiftPackageReference "ProgressHUD" */, + ); productRefGroup = 50A856732931FDA700E476DF /* Products */; projectDirPath = ""; projectRoot = ""; @@ -219,16 +247,20 @@ 50A8567A2931FDA700E476DF /* ImagesListControllerViewController.swift in Sources */, 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */, 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */, + 50397DAB296348080078D6BE /* URLSession.swift in Sources */, 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */, 50B8D4322961AC7200508CF6 /* SplashViewController.swift in Sources */, 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */, 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */, + 50397DA82963119B0078D6BE /* ProfileImageService.swift in Sources */, 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */, 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */, 50B8D42F29619E7800508CF6 /* OAuth2TokenStorage.swift in Sources */, 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */, + 50397DA42962CB750078D6BE /* UIBlockingProgressHUD.swift in Sources */, + 50397DA62962D2730078D6BE /* ProfileService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -452,6 +484,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 50B8D4332961F15500508CF6 /* XCRemoteSwiftPackageReference "ProgressHUD" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/relatedcode/ProgressHUD"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 13.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 50B8D4342961F15500508CF6 /* ProgressHUD */ = { + isa = XCSwiftPackageProductDependency; + package = 50B8D4332961F15500508CF6 /* XCRemoteSwiftPackageReference "ProgressHUD" */; + productName = ProgressHUD; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 50A8566A2931FDA700E476DF /* Project object */; } diff --git a/ImageFeed/Auth/OAuth2Service.swift b/ImageFeed/Auth/OAuth2Service.swift index 59b76b7..afecc1b 100644 --- a/ImageFeed/Auth/OAuth2Service.swift +++ b/ImageFeed/Auth/OAuth2Service.swift @@ -18,6 +18,8 @@ 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 { @@ -30,6 +32,12 @@ final class OAuth2Service { func fetchOAuthToken(_ code: String, completion: @escaping (Result) -> Void) { + assert(Thread.isMainThread) + + if lastCode == code { return } + task?.cancel() + lastCode = code + let path = "/oauth/token" + "?client_id=\(AccessKey)" + "&&client_secret=\(SecretKey)" @@ -38,7 +46,8 @@ final class OAuth2Service { + "&&grant_type=authorization_code" let request = makeRequest(code: code, path: path, httpMethod: "POST", baseURL: URL(string: "https://unsplash.com")!) - let task = object(for: request) { [weak self] result in + + let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in guard let self = self else { return } switch result { case .success(let body): @@ -49,6 +58,19 @@ final class OAuth2Service { completion(.failure(error)) } } + +// 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 +// completion(.success(authToken)) +// case .failure(let error): +// completion(.failure(error)) +// } +// } + self.task = task task.resume() } @@ -78,41 +100,45 @@ final class OAuth2Service { } } -extension OAuth2Service { - private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { - - let task = urlSession.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 { - DispatchQueue.main.async { - completion(.success(data)) - } - } else { - DispatchQueue.main.async { - completion(.failure(NetworkError.httpStatusCode(statusCode))) - } - } - } else if let error = error { - DispatchQueue.main.async { - completion(.failure(NetworkError.urlRequestError(error))) - } - } else { - DispatchQueue.main.async { - completion(.failure(NetworkError.urlSessionError)) - } - } - }) - task.resume() - return task - } - - private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { - let decoder = JSONDecoder() - return data(for: request) { (result: Result) in - let response = result.flatMap { data -> Result in - Result { try decoder.decode(OAuthTokenResponseBody.self, from: data) } - } - completion(response) - } - } -} +//extension OAuth2Service { +// private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { +// +// let task = urlSession.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 { +// DispatchQueue.main.async { +// completion(.success(data)) +// self.task = nil +// if error != nil { +// self.lastCode = nil +// } +// } +// } else { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.httpStatusCode(statusCode))) +// } +// } +// } else if let error = error { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.urlRequestError(error))) +// } +// } else { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.urlSessionError)) +// } +// } +// }) +// task.resume() +// return task +// } +// +// private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { +// let decoder = JSONDecoder() +// return data(for: request) { (result: Result) in +// let response = result.flatMap { data -> Result in +// Result { try decoder.decode(OAuthTokenResponseBody.self, from: data) } +// } +// completion(response) +// } +// } +//} diff --git a/ImageFeed/Auth/UIBlockingProgressHUD.swift b/ImageFeed/Auth/UIBlockingProgressHUD.swift new file mode 100644 index 0000000..f42b756 --- /dev/null +++ b/ImageFeed/Auth/UIBlockingProgressHUD.swift @@ -0,0 +1,26 @@ +// +// UIBlockingProgressHUD.swift +// ImageFeed +// +// Created by macOS on 02.01.2023. +// + +import Foundation +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() + } +} diff --git a/ImageFeed/Auth/WebViewViewController.swift b/ImageFeed/Auth/WebViewViewController.swift index 42d4f70..18fb22f 100644 --- a/ImageFeed/Auth/WebViewViewController.swift +++ b/ImageFeed/Auth/WebViewViewController.swift @@ -17,6 +17,8 @@ final class WebViewViewController: UIViewController { weak var delegate: WebViewViewControllerDelegate? + private var estimatedProgressObservation: NSKeyValueObservation? + override func viewDidLoad() { super.viewDidLoad() @@ -34,38 +36,23 @@ final class WebViewViewController: UIViewController { let request = URLRequest(url: url) webView.load(request) + estimatedProgressObservation = webView.observe( + \.estimatedProgress, + options: [], + changeHandler: { [weak self] _, _ in + guard let self = self else { return } + self.updateProgress() + }) + 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) diff --git a/ImageFeed/Extensions/URLSession.swift b/ImageFeed/Extensions/URLSession.swift new file mode 100644 index 0000000..190d580 --- /dev/null +++ b/ImageFeed/Extensions/URLSession.swift @@ -0,0 +1,47 @@ +// +// URLSession.swift +// ImageFeed +// +// Created by macOS on 02.01.2023. +// + +import Foundation + +extension URLSession { + + func objectTask( + for request: URLRequest, + completion: @escaping (Result) -> Void + ) -> URLSessionTask { + + let fulfillCompletionOnMainThread: (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 { + do { + let decoder = JSONDecoder() + let result = try decoder.decode(T.self, from: data) + + fulfillCompletionOnMainThread(.success(result)) + } catch { + fulfillCompletionOnMainThread(.failure(NetworkError.urlRequestError(error))) + } + } else { + fulfillCompletionOnMainThread(.failure(NetworkError.httpStatusCode(statusCode))) + } + } else if let error = error { + fulfillCompletionOnMainThread(.failure(NetworkError.urlRequestError(error))) + } else { + fulfillCompletionOnMainThread(.failure(NetworkError.urlSessionError)) + } + }) + task.resume() + return task + } +} diff --git a/ImageFeed/Profile/ProfileImageService.swift b/ImageFeed/Profile/ProfileImageService.swift new file mode 100644 index 0000000..41ead9f --- /dev/null +++ b/ImageFeed/Profile/ProfileImageService.swift @@ -0,0 +1,127 @@ +// +// ProfileImageService.swift +// ImageFeed +// +// Created by macOS on 02.01.2023. +// + +import Foundation + +final class ProfileImageService { + + static let shared = ProfileImageService() + + static let DidChangeNotification = Notification.Name(rawValue: "ProfileImageProviderDidChange") + + private(set) var avatarURL: String? + + private let urlSession = URLSession.shared + private var task: URLSessionTask? + + func fetchProfileImageURL(username: String, _ completion: @escaping (Result) -> Void) { + assert(Thread.isMainThread) + + task?.cancel() + + let request = makeRequest(path: "/users/\(username)", httpMethod: "GET", baseURL: DefaultBaseURL) + + let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in + guard let self = self else { return } + switch result { + case .success(let body): + self.avatarURL = body.profile_image.small + completion(.success(body.profile_image.small)) + NotificationCenter.default + .post( + name: ProfileImageService.DidChangeNotification, + object: self, + userInfo: ["URL": self.avatarURL!]) + case .failure(let error): + print(error) + completion(.failure(error)) + } + } + +// let task = object(for: request) { [weak self] result in +// guard let self = self else { return } +// switch result { +// case .success(let body): +// self.avatarURL = body.profile_image.small +// completion(.success(body.profile_image.small)) +// NotificationCenter.default +// .post( +// name: ProfileImageService.DidChangeNotification, +// object: self, +// userInfo: ["URL": self.avatarURL!]) +// case .failure(let error): +// print(error) +// completion(.failure(error)) +// } +// } + self.task = task + task.resume() + } + + private func makeRequest( + path: String, + httpMethod: String, + baseURL: URL = DefaultBaseURL) -> URLRequest { + var request = URLRequest(url: URL(string: path, relativeTo: baseURL)!) + request.setValue("Bearer \(String(describing: OAuth2TokenStorage().token!))", forHTTPHeaderField: "Authorization") + request.httpMethod = httpMethod + return request + } +} + +//extension ProfileImageService { +// private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { +// +// let task = urlSession.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 { +// DispatchQueue.main.async { +// completion(.success(data)) +// self.task = nil +// if error != nil { +// } +// } +// } else { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.httpStatusCode(statusCode))) +// } +// } +// } else if let error = error { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.urlRequestError(error))) +// } +// } else { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.urlSessionError)) +// } +// } +// }) +// task.resume() +// return task +// } +// +// private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { +// let decoder = JSONDecoder() +// return data(for: request) { (result: Result) in +// let response = result.flatMap { data -> Result in +// Result { try decoder.decode(UserResult.self, from: data) } +// } +// completion(response) +// } +// } +//} + +struct UserResult: Codable { + let profile_image: ImageResult +} + +struct ImageResult: Codable { + let small: String + let medium: String + let large: String +} + diff --git a/ImageFeed/Profile/ProfileService.swift b/ImageFeed/Profile/ProfileService.swift new file mode 100644 index 0000000..6c27157 --- /dev/null +++ b/ImageFeed/Profile/ProfileService.swift @@ -0,0 +1,126 @@ +// +// ProfileService.swift +// ImageFeed +// +// Created by macOS on 02.01.2023. +// + +import Foundation + +final class ProfileService { + static let shared = ProfileService() + + private(set) var profile: Profile? + + private let urlSession = URLSession.shared + private var task: URLSessionTask? + + func fetchProfile(completion: @escaping (Result) -> Void) { + assert(Thread.isMainThread) + + task?.cancel() + + let request = makeRequest(path: "/me", httpMethod: "GET", baseURL: DefaultBaseURL) + + let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in + guard let self = self else { return } + switch result { + case .success(let body): + self.profile = Profile(result: body) + completion(.success(body)) + case .failure(let error): + print(error) + completion(.failure(error)) + } + } + +// let task = object(for: request) { [weak self] result in +// guard let self = self else { return } +// switch result { +// case .success(let body): +// self.profile = Profile(result: body) +// completion(.success(body)) +// case .failure(let error): +// print(error) +// completion(.failure(error)) +// } +// } + self.task = task + task.resume() + } + + private func makeRequest( + path: String, + httpMethod: String, + baseURL: URL = DefaultBaseURL) -> URLRequest { + + var request = URLRequest(url: URL(string: path, relativeTo: baseURL)!) + request.setValue("Bearer \(String(describing: OAuth2TokenStorage().token!))", forHTTPHeaderField: "Authorization") + request.httpMethod = httpMethod + return request + } +} + +//extension ProfileService { +// private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { +// +// let task = urlSession.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 { +// DispatchQueue.main.async { +// completion(.success(data)) +// self.task = nil +// if error != nil { +// // self.lastCode = nil +// } +// } +// } else { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.httpStatusCode(statusCode))) +// } +// } +// } else if let error = error { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.urlRequestError(error))) +// } +// } else { +// DispatchQueue.main.async { +// completion(.failure(NetworkError.urlSessionError)) +// } +// } +// }) +// task.resume() +// return task +// } +// +// private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { +// let decoder = JSONDecoder() +// return data(for: request) { (result: Result) in +// let response = result.flatMap { data -> Result in +// Result { try decoder.decode(ProfileResult.self, from: data) } +// } +// completion(response) +// } +// } +//} + +struct ProfileResult: Codable { + let username: String + let first_name: String + let last_name: String + let bio: String? +} + +struct Profile { + let username: String + let name: String + let loginName: String + let bio: String? + + init(result: ProfileResult) { + self.username = result.username + self.name = "\(result.first_name)" + " \(result.last_name)" + self.loginName = "@\(username)" + self.bio = result.bio + } +} diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift index afa0af7..ae323c9 100644 --- a/ImageFeed/Profile/ProfileViewController.swift +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -9,20 +9,51 @@ import UIKit class ProfileViewController: UIViewController { - private let image = UIImageView() - private let labelName = UILabel() - private let labelNickname = UILabel() - private let labelStatus = UILabel() + private var image = UIImageView() + private var labelName = UILabel() + private var labelNickname = UILabel() + private var labelStatus = UILabel() private let button = UIButton.systemButton(with: UIImage(systemName: "ipad.and.arrow.forward")!, target: ProfileViewController.self, action: nil) - + private let profileService = ProfileService.shared + private var profileImageServiceObserver: NSObjectProtocol? + override func viewDidLoad() { super.viewDidLoad() + if let profile = profileService.profile { + updateProfileDetails(profile: profile) + } + + profileImageServiceObserver = NotificationCenter.default + .addObserver( + forName: ProfileImageService.DidChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + guard let self = self else { return } + self.updateAvatar() + } + updateAvatar() + addSubviews() setViewConfiguration() activateConstraints() } + private func updateAvatar() { + guard + let profileImageURL = ProfileImageService.shared.avatarURL, + let url = URL(string: profileImageURL) + else { return } + // TODO [Sprint 11] Обновить аватар, используя Kingfisher + } + + private func updateProfileDetails(profile: Profile) { + self.labelName.text = profile.name + self.labelNickname.text = profile.loginName + self.labelStatus.text = profile.bio + } + private func addSubviews() { view.addSubview(image) view.addSubview(labelName) @@ -40,15 +71,12 @@ class ProfileViewController: UIViewController { private func setViewConfiguration() { image.image = UIImage(named: "Photo") - labelName.text = "Екатерина Новикова" labelName.textColor = UIColor(named: "YP White") labelName.font = labelName.font.withSize(23) - labelNickname.text = "@ekaterina_nov" labelNickname.textColor = UIColor(named: "YP Grey") labelNickname.font = labelNickname.font.withSize(13) - labelStatus.text = "Hello, world" labelStatus.textColor = UIColor(named: "YP White") labelStatus.font = labelStatus.font.withSize(13) diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift index e2235fb..957e679 100644 --- a/ImageFeed/SplashViewController/SplashViewController.swift +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -6,16 +6,19 @@ // import UIKit +import ProgressHUD final class SplashViewController: UIViewController { private let ShowAuthenticationScreenSegueIdentifier = "ShowAuthenticationScreen" private let oauth2Service = OAuth2Service.shared + private let profileService = ProfileService.shared override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if let token = oauth2Service.authToken { + self.fetchProfile() switchToTabBarController() } else { performSegue(withIdentifier: ShowAuthenticationScreenSegueIdentifier, sender: nil) @@ -55,6 +58,7 @@ extension SplashViewController { extension SplashViewController: AuthViewControllerDelegate { func authViewController(_ vc: AuthViewController, didAuthenticateWithCode code: String) { + UIBlockingProgressHUD.show() dismiss(animated: true) { [weak self] in guard let self = self else { return } self.fetchOAuthToken(code) @@ -66,9 +70,36 @@ extension SplashViewController: AuthViewControllerDelegate { guard let self = self else { return } switch result { case .success: + self.fetchProfile() + case .failure: + + let alert = UIAlertController(title: "Что-то пошло не так(", + message: "Не удалось войти в систему", + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: "OK", + style: .default, + handler: { _ in + })) + self.present(alert, animated: true, completion: nil) + + UIBlockingProgressHUD.dismiss() + break + } + } + } + + private func fetchProfile() { + profileService.fetchProfile { [weak self] result in + guard let self = self else { return } + switch result { + case .success: + ProfileImageService.shared.fetchProfileImageURL(username: self.profileService.profile?.username ?? "") { _ in } + UIBlockingProgressHUD.dismiss() self.switchToTabBarController() case .failure: - print("failure fetchOAuthToken") + UIBlockingProgressHUD.dismiss() + print("failure fetchProfile") break } } From 9ed40a296b5c673fbdc8db62911123e35c1d0337 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Tue, 3 Jan 2023 19:17:25 +0500 Subject: [PATCH 11/25] keychain --- ImageFeed.xcodeproj/project.pbxproj | 34 +++++++++++ .../Auth/Models/OAuth2TokenStorage.swift | 8 ++- ImageFeed/Auth/OAuth2Service.swift | 55 ----------------- ImageFeed/Profile/ProfileImageService.swift | 59 ------------------- ImageFeed/Profile/ProfileService.swift | 55 ----------------- ImageFeed/Profile/ProfileViewController.swift | 7 ++- .../SplashViewController.swift | 3 + 7 files changed, 47 insertions(+), 174 deletions(-) diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 6e3d48f..78cfca7 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 50397DA62962D2730078D6BE /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA52962D2730078D6BE /* ProfileService.swift */; }; 50397DA82963119B0078D6BE /* ProfileImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA72963119B0078D6BE /* ProfileImageService.swift */; }; 50397DAB296348080078D6BE /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DAA296348080078D6BE /* URLSession.swift */; }; + 50397DAE296362FF0078D6BE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50397DAD296362FF0078D6BE /* Kingfisher */; }; + 50397DB129646CE00078D6BE /* SwiftKeychainWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 50397DB029646CE00078D6BE /* SwiftKeychainWrapper */; }; 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */; }; 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50487E6429609E7700F41745 /* OAuth2Service.swift */; }; 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506BFC6D2933D6160058728A /* ImagesListCell.swift */; }; @@ -60,7 +62,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 50397DAE296362FF0078D6BE /* Kingfisher in Frameworks */, 50B8D4352961F15500508CF6 /* ProgressHUD in Frameworks */, + 50397DB129646CE00078D6BE /* SwiftKeychainWrapper in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -185,6 +189,8 @@ name = ImageFeed; packageProductDependencies = ( 50B8D4342961F15500508CF6 /* ProgressHUD */, + 50397DAD296362FF0078D6BE /* Kingfisher */, + 50397DB029646CE00078D6BE /* SwiftKeychainWrapper */, ); productName = ImageFeed; productReference = 50A856722931FDA700E476DF /* ImageFeed.app */; @@ -216,6 +222,8 @@ mainGroup = 50A856692931FDA700E476DF; packageReferences = ( 50B8D4332961F15500508CF6 /* XCRemoteSwiftPackageReference "ProgressHUD" */, + 50397DAC296362FF0078D6BE /* XCRemoteSwiftPackageReference "Kingfisher" */, + 50397DAF29646CE00078D6BE /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */, ); productRefGroup = 50A856732931FDA700E476DF /* Products */; projectDirPath = ""; @@ -486,6 +494,22 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 50397DAC296362FF0078D6BE /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.0.0; + }; + }; + 50397DAF29646CE00078D6BE /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jrendel/SwiftKeychainWrapper"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.0; + }; + }; 50B8D4332961F15500508CF6 /* XCRemoteSwiftPackageReference "ProgressHUD" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/relatedcode/ProgressHUD"; @@ -497,6 +521,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 50397DAD296362FF0078D6BE /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 50397DAC296362FF0078D6BE /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; + 50397DB029646CE00078D6BE /* SwiftKeychainWrapper */ = { + isa = XCSwiftPackageProductDependency; + package = 50397DAF29646CE00078D6BE /* XCRemoteSwiftPackageReference "SwiftKeychainWrapper" */; + productName = SwiftKeychainWrapper; + }; 50B8D4342961F15500508CF6 /* ProgressHUD */ = { isa = XCSwiftPackageProductDependency; package = 50B8D4332961F15500508CF6 /* XCRemoteSwiftPackageReference "ProgressHUD" */; diff --git a/ImageFeed/Auth/Models/OAuth2TokenStorage.swift b/ImageFeed/Auth/Models/OAuth2TokenStorage.swift index 0a19dd0..2e911dc 100644 --- a/ImageFeed/Auth/Models/OAuth2TokenStorage.swift +++ b/ImageFeed/Auth/Models/OAuth2TokenStorage.swift @@ -6,20 +6,22 @@ // import Foundation +import SwiftKeychainWrapper fileprivate let tokenKey = "BearerToken" class OAuth2TokenStorage { var token: String? { get { - return UserDefaults.standard.string(forKey: tokenKey) + return KeychainWrapper.standard.string(forKey: tokenKey) } set(newValue) { if let token = newValue { - UserDefaults.standard.set(token, forKey: tokenKey) + KeychainWrapper.standard.set(token, forKey: tokenKey) + } else { - UserDefaults.standard.removeObject(forKey: tokenKey) + KeychainWrapper.standard.removeObject(forKey: tokenKey) } } } diff --git a/ImageFeed/Auth/OAuth2Service.swift b/ImageFeed/Auth/OAuth2Service.swift index afecc1b..2628e4e 100644 --- a/ImageFeed/Auth/OAuth2Service.swift +++ b/ImageFeed/Auth/OAuth2Service.swift @@ -58,18 +58,6 @@ final class OAuth2Service { completion(.failure(error)) } } - -// 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 -// completion(.success(authToken)) -// case .failure(let error): -// completion(.failure(error)) -// } -// } self.task = task task.resume() } @@ -99,46 +87,3 @@ final class OAuth2Service { } } } - -//extension OAuth2Service { -// private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { -// -// let task = urlSession.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 { -// DispatchQueue.main.async { -// completion(.success(data)) -// self.task = nil -// if error != nil { -// self.lastCode = nil -// } -// } -// } else { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.httpStatusCode(statusCode))) -// } -// } -// } else if let error = error { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.urlRequestError(error))) -// } -// } else { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.urlSessionError)) -// } -// } -// }) -// task.resume() -// return task -// } -// -// private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { -// let decoder = JSONDecoder() -// return data(for: request) { (result: Result) in -// let response = result.flatMap { data -> Result in -// Result { try decoder.decode(OAuthTokenResponseBody.self, from: data) } -// } -// completion(response) -// } -// } -//} diff --git a/ImageFeed/Profile/ProfileImageService.swift b/ImageFeed/Profile/ProfileImageService.swift index 41ead9f..fd502b9 100644 --- a/ImageFeed/Profile/ProfileImageService.swift +++ b/ImageFeed/Profile/ProfileImageService.swift @@ -41,23 +41,6 @@ final class ProfileImageService { completion(.failure(error)) } } - -// let task = object(for: request) { [weak self] result in -// guard let self = self else { return } -// switch result { -// case .success(let body): -// self.avatarURL = body.profile_image.small -// completion(.success(body.profile_image.small)) -// NotificationCenter.default -// .post( -// name: ProfileImageService.DidChangeNotification, -// object: self, -// userInfo: ["URL": self.avatarURL!]) -// case .failure(let error): -// print(error) -// completion(.failure(error)) -// } -// } self.task = task task.resume() } @@ -73,48 +56,6 @@ final class ProfileImageService { } } -//extension ProfileImageService { -// private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { -// -// let task = urlSession.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 { -// DispatchQueue.main.async { -// completion(.success(data)) -// self.task = nil -// if error != nil { -// } -// } -// } else { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.httpStatusCode(statusCode))) -// } -// } -// } else if let error = error { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.urlRequestError(error))) -// } -// } else { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.urlSessionError)) -// } -// } -// }) -// task.resume() -// return task -// } -// -// private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { -// let decoder = JSONDecoder() -// return data(for: request) { (result: Result) in -// let response = result.flatMap { data -> Result in -// Result { try decoder.decode(UserResult.self, from: data) } -// } -// completion(response) -// } -// } -//} - struct UserResult: Codable { let profile_image: ImageResult } diff --git a/ImageFeed/Profile/ProfileService.swift b/ImageFeed/Profile/ProfileService.swift index 6c27157..7c02766 100644 --- a/ImageFeed/Profile/ProfileService.swift +++ b/ImageFeed/Profile/ProfileService.swift @@ -33,18 +33,6 @@ final class ProfileService { completion(.failure(error)) } } - -// let task = object(for: request) { [weak self] result in -// guard let self = self else { return } -// switch result { -// case .success(let body): -// self.profile = Profile(result: body) -// completion(.success(body)) -// case .failure(let error): -// print(error) -// completion(.failure(error)) -// } -// } self.task = task task.resume() } @@ -61,49 +49,6 @@ final class ProfileService { } } -//extension ProfileService { -// private func data(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { -// -// let task = urlSession.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 { -// DispatchQueue.main.async { -// completion(.success(data)) -// self.task = nil -// if error != nil { -// // self.lastCode = nil -// } -// } -// } else { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.httpStatusCode(statusCode))) -// } -// } -// } else if let error = error { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.urlRequestError(error))) -// } -// } else { -// DispatchQueue.main.async { -// completion(.failure(NetworkError.urlSessionError)) -// } -// } -// }) -// task.resume() -// return task -// } -// -// private func object(for request: URLRequest, completion: @escaping (Result) -> Void) -> URLSessionTask { -// let decoder = JSONDecoder() -// return data(for: request) { (result: Result) in -// let response = result.flatMap { data -> Result in -// Result { try decoder.decode(ProfileResult.self, from: data) } -// } -// completion(response) -// } -// } -//} - struct ProfileResult: Codable { let username: String let first_name: String diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift index ae323c9..4a743b4 100644 --- a/ImageFeed/Profile/ProfileViewController.swift +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import Kingfisher class ProfileViewController: UIViewController { @@ -45,7 +46,9 @@ class ProfileViewController: UIViewController { let profileImageURL = ProfileImageService.shared.avatarURL, let url = URL(string: profileImageURL) else { return } - // TODO [Sprint 11] Обновить аватар, используя Kingfisher + + let processor = RoundCornerImageProcessor(cornerRadius: 35) + image.kf.setImage(with: url, options: [.processor(processor)]) } private func updateProfileDetails(profile: Profile) { @@ -69,7 +72,7 @@ class ProfileViewController: UIViewController { } private func setViewConfiguration() { - image.image = UIImage(named: "Photo") + // image.image = UIImage(named: "Photo") labelName.textColor = UIColor(named: "YP White") labelName.font = labelName.font.withSize(23) diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift index 957e679..0f34ffd 100644 --- a/ImageFeed/SplashViewController/SplashViewController.swift +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -17,6 +17,9 @@ final class SplashViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + print("CHO") + print(oauth2Service.authToken) + if let token = oauth2Service.authToken { self.fetchProfile() switchToTabBarController() From 84cbf4c121302e0cdf35b5e75cd6eaeff570e660 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Mon, 9 Jan 2023 10:35:18 +0500 Subject: [PATCH 12/25] fix after review --- .../Auth/Models/OAuth2TokenStorage.swift | 12 +++++--- ImageFeed/Auth/OAuth2Service.swift | 30 ++++++++----------- ImageFeed/Base.lproj/Main.storyboard | 6 ++-- ImageFeed/Profile/ProfileViewController.swift | 6 ---- .../SplashViewController.swift | 14 ++++----- 5 files changed, 29 insertions(+), 39 deletions(-) diff --git a/ImageFeed/Auth/Models/OAuth2TokenStorage.swift b/ImageFeed/Auth/Models/OAuth2TokenStorage.swift index 2e911dc..2453433 100644 --- a/ImageFeed/Auth/Models/OAuth2TokenStorage.swift +++ b/ImageFeed/Auth/Models/OAuth2TokenStorage.swift @@ -8,20 +8,24 @@ import Foundation import SwiftKeychainWrapper -fileprivate let tokenKey = "BearerToken" +enum TokenStorageKeys: String { + case tokenKey = "BearerToken" +} class OAuth2TokenStorage { + private let keychain = KeychainWrapper.standard + var token: String? { get { - return KeychainWrapper.standard.string(forKey: tokenKey) + return keychain.string(forKey: TokenStorageKeys.tokenKey.rawValue) } set(newValue) { if let token = newValue { - KeychainWrapper.standard.set(token, forKey: tokenKey) + keychain.set(token, forKey: TokenStorageKeys.tokenKey.rawValue) } else { - KeychainWrapper.standard.removeObject(forKey: tokenKey) + keychain.removeObject(forKey: TokenStorageKeys.tokenKey.rawValue) } } } diff --git a/ImageFeed/Auth/OAuth2Service.swift b/ImageFeed/Auth/OAuth2Service.swift index 2628e4e..1bb1fce 100644 --- a/ImageFeed/Auth/OAuth2Service.swift +++ b/ImageFeed/Auth/OAuth2Service.swift @@ -38,14 +38,19 @@ final class OAuth2Service { task?.cancel() lastCode = code - let path = "/oauth/token" - + "?client_id=\(AccessKey)" - + "&&client_secret=\(SecretKey)" - + "&&redirect_uri=\(RedirectURI)" - + "&&code=\(code)" - + "&&grant_type=authorization_code" + var urlComponents = URLComponents(string: "https://unsplash.com/oauth/token")! + urlComponents.queryItems = [ + URLQueryItem(name: "client_id", value: AccessKey), + URLQueryItem(name: "client_secret", value: SecretKey), + URLQueryItem(name: "redirect_uri", value: RedirectURI), + URLQueryItem(name: "code", value: code), + URLQueryItem(name: "grant_type", value: "authorization_code") + ] + let url = urlComponents.url! + + var request = URLRequest(url: url) + request.httpMethod = "POST" - let request = makeRequest(code: code, path: path, httpMethod: "POST", baseURL: URL(string: "https://unsplash.com")!) let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in guard let self = self else { return } @@ -62,17 +67,6 @@ final class OAuth2Service { task.resume() } - private func makeRequest( - code: String, - path: String, - httpMethod: String, - baseURL: URL = DefaultBaseURL) -> URLRequest { - - var request = URLRequest(url: URL(string: path, relativeTo: baseURL)!) - request.httpMethod = httpMethod - return request - } - private struct OAuthTokenResponseBody: Codable { let accessToken: String let tokenType: String diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 4f08328..4eb1f92 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -265,7 +265,7 @@ - + @@ -296,7 +296,7 @@ - + @@ -306,7 +306,7 @@ - + diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift index 4a743b4..9dbf248 100644 --- a/ImageFeed/Profile/ProfileViewController.swift +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -72,8 +72,6 @@ class ProfileViewController: UIViewController { } private func setViewConfiguration() { - // image.image = UIImage(named: "Photo") - labelName.textColor = UIColor(named: "YP White") labelName.font = labelName.font.withSize(23) @@ -102,8 +100,4 @@ class ProfileViewController: UIViewController { button.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20) ]) } - -// @IBAction func clickExitButton(_ sender: Any) { -// -// } } diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift index 0f34ffd..a10010a 100644 --- a/ImageFeed/SplashViewController/SplashViewController.swift +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -9,7 +9,8 @@ import UIKit import ProgressHUD final class SplashViewController: UIViewController { - private let ShowAuthenticationScreenSegueIdentifier = "ShowAuthenticationScreen" + private let showAuthenticationScreenSegueIdentifier = "ShowAuthenticationScreen" + private let tabBarViewControllerIdentifier = "TabBarViewController" private let oauth2Service = OAuth2Service.shared private let profileService = ProfileService.shared @@ -17,14 +18,11 @@ final class SplashViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - print("CHO") - print(oauth2Service.authToken) - if let token = oauth2Service.authToken { self.fetchProfile() switchToTabBarController() } else { - performSegue(withIdentifier: ShowAuthenticationScreenSegueIdentifier, sender: nil) + performSegue(withIdentifier: showAuthenticationScreenSegueIdentifier, sender: nil) } } @@ -40,18 +38,18 @@ final class SplashViewController: UIViewController { private func switchToTabBarController() { guard let window = UIApplication.shared.windows.first else { fatalError("Invalid Configuration") } let tabBarController = UIStoryboard(name: "Main", bundle: .main) - .instantiateViewController(withIdentifier: "TabBarViewController") + .instantiateViewController(withIdentifier: tabBarViewControllerIdentifier) window.rootViewController = tabBarController } } extension SplashViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == ShowAuthenticationScreenSegueIdentifier { + 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)") } + else { fatalError("Failed to prepare for \(showAuthenticationScreenSegueIdentifier)") } viewController.delegate = self } else { super.prepare(for: segue, sender: sender) From 700fc7493a46316af9471e1ec233003da8004b92 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Fri, 13 Jan 2023 17:08:48 +0500 Subject: [PATCH 13/25] fix after review --- ImageFeed/Profile/ProfileImageService.swift | 14 +++++--- ImageFeed/Profile/ProfileViewController.swift | 4 +-- .../SplashViewController.swift | 34 +++++++++---------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/ImageFeed/Profile/ProfileImageService.swift b/ImageFeed/Profile/ProfileImageService.swift index fd502b9..e36ee8b 100644 --- a/ImageFeed/Profile/ProfileImageService.swift +++ b/ImageFeed/Profile/ProfileImageService.swift @@ -11,7 +11,7 @@ final class ProfileImageService { static let shared = ProfileImageService() - static let DidChangeNotification = Notification.Name(rawValue: "ProfileImageProviderDidChange") + static let didChangeNotification = Notification.Name(rawValue: "ProfileImageProviderDidChange") private(set) var avatarURL: String? @@ -29,11 +29,11 @@ final class ProfileImageService { guard let self = self else { return } switch result { case .success(let body): - self.avatarURL = body.profile_image.small - completion(.success(body.profile_image.small)) + self.avatarURL = body.profileImage.small + completion(.success(body.profileImage.small)) NotificationCenter.default .post( - name: ProfileImageService.DidChangeNotification, + name: ProfileImageService.didChangeNotification, object: self, userInfo: ["URL": self.avatarURL!]) case .failure(let error): @@ -57,7 +57,11 @@ final class ProfileImageService { } struct UserResult: Codable { - let profile_image: ImageResult + let profileImage: ImageResult + + enum CodingKeys: String, CodingKey { + case profileImage = "profile_image" + } } struct ImageResult: Codable { diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift index 9dbf248..c8fed97 100644 --- a/ImageFeed/Profile/ProfileViewController.swift +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -27,7 +27,7 @@ class ProfileViewController: UIViewController { profileImageServiceObserver = NotificationCenter.default .addObserver( - forName: ProfileImageService.DidChangeNotification, + forName: ProfileImageService.didChangeNotification, object: nil, queue: .main ) { [weak self] _ in @@ -48,7 +48,7 @@ class ProfileViewController: UIViewController { else { return } let processor = RoundCornerImageProcessor(cornerRadius: 35) - image.kf.setImage(with: url, options: [.processor(processor)]) + image.kf.setImage(with: url, options: [.processor(processor), .cacheSerializer(FormatIndicatedCacheSerializer.png)]) } private func updateProfileDetails(profile: Profile) { diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift index a10010a..7d1f00d 100644 --- a/ImageFeed/SplashViewController/SplashViewController.swift +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -6,7 +6,6 @@ // import UIKit -import ProgressHUD final class SplashViewController: UIViewController { private let showAuthenticationScreenSegueIdentifier = "ShowAuthenticationScreen" @@ -18,9 +17,9 @@ final class SplashViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - if let token = oauth2Service.authToken { + if oauth2Service.authToken != nil { self.fetchProfile() - switchToTabBarController() + UIBlockingProgressHUD.show() } else { performSegue(withIdentifier: showAuthenticationScreenSegueIdentifier, sender: nil) } @@ -73,19 +72,8 @@ extension SplashViewController: AuthViewControllerDelegate { case .success: self.fetchProfile() case .failure: - - let alert = UIAlertController(title: "Что-то пошло не так(", - message: "Не удалось войти в систему", - preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: "OK", - style: .default, - handler: { _ in - })) - self.present(alert, animated: true, completion: nil) - + self.showAlert() UIBlockingProgressHUD.dismiss() - break } } } @@ -100,10 +88,20 @@ extension SplashViewController: AuthViewControllerDelegate { self.switchToTabBarController() case .failure: UIBlockingProgressHUD.dismiss() - print("failure fetchProfile") - break + self.showAlert() } } } + + func showAlert() { + let alert = UIAlertController(title: "Что-то пошло не так(", + message: "Не удалось войти в систему", + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: "OK", + style: .default, + handler: { _ in + })) + self.present(alert, animated: true, completion: nil) + } } - From 84bade2e457a4fc28714a8fab67a1f4e69ff9e50 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Fri, 20 Jan 2023 12:41:23 +0500 Subject: [PATCH 14/25] code ui --- ImageFeed.xcodeproj/project.pbxproj | 12 ++++ ImageFeed/AppDelegate.swift | 6 +- ImageFeed/Base.lproj/Main.storyboard | 67 ++----------------- ImageFeed/SceneDelegate.swift | 5 +- .../SplashViewController.swift | 37 +++++----- ImageFeed/TabBar/TabBarController.swift | 27 ++++++++ 6 files changed, 76 insertions(+), 78 deletions(-) create mode 100644 ImageFeed/TabBar/TabBarController.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 78cfca7..33b7035 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */; }; + 222804C8297A688400D6B54A /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222804C7297A688400D6B54A /* TabBarController.swift */; }; 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB27295DD00800FDCAE1 /* Constants.swift */; }; 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */; }; 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */; }; @@ -34,6 +35,7 @@ /* Begin PBXFileReference section */ 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewControllerDelegate.swift; sourceTree = ""; }; + 222804C7297A688400D6B54A /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 223FDB27295DD00800FDCAE1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; @@ -71,6 +73,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 222804C6297A684400D6B54A /* TabBar */ = { + isa = PBXGroup; + children = ( + 222804C7297A688400D6B54A /* TabBarController.swift */, + ); + path = TabBar; + sourceTree = ""; + }; 223FDB29295DD36600FDCAE1 /* Auth */ = { isa = PBXGroup; children = ( @@ -136,6 +146,7 @@ 50A856742931FDA700E476DF /* ImageFeed */ = { isa = PBXGroup; children = ( + 222804C6297A684400D6B54A /* TabBar */, 50397DA9296347EC0078D6BE /* Extensions */, 50B8D4302961AC4F00508CF6 /* SplashViewController */, 223FDB29295DD36600FDCAE1 /* Auth */, @@ -255,6 +266,7 @@ 50A8567A2931FDA700E476DF /* ImagesListControllerViewController.swift in Sources */, 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */, 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */, + 222804C8297A688400D6B54A /* TabBarController.swift in Sources */, 50397DAB296348080078D6BE /* URLSession.swift in Sources */, 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, diff --git a/ImageFeed/AppDelegate.swift b/ImageFeed/AppDelegate.swift index 622e1a3..a0b1679 100644 --- a/ImageFeed/AppDelegate.swift +++ b/ImageFeed/AppDelegate.swift @@ -21,11 +21,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - 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) { } } - diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 4eb1f92..1ca3c5c 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -13,13 +13,13 @@ - + - + @@ -41,7 +41,7 @@ - + - - - - - - - - - - - - - - - - @@ -209,7 +193,7 @@ - + @@ -308,51 +292,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - @@ -361,13 +310,11 @@ - - + - diff --git a/ImageFeed/SceneDelegate.swift b/ImageFeed/SceneDelegate.swift index 8d9eac7..c0bfc43 100644 --- a/ImageFeed/SceneDelegate.swift +++ b/ImageFeed/SceneDelegate.swift @@ -14,7 +14,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let _ = (scene as? UIWindowScene) else { return } + guard let scene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: scene) + window?.rootViewController = SplashViewController() + window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) { diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift index 7d1f00d..3f2e23e 100644 --- a/ImageFeed/SplashViewController/SplashViewController.swift +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -14,6 +14,12 @@ final class SplashViewController: UIViewController { private let oauth2Service = OAuth2Service.shared private let profileService = ProfileService.shared + private let logoImage = UIImageView() + + override func viewDidLoad() { + setUI() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -21,7 +27,10 @@ final class SplashViewController: UIViewController { self.fetchProfile() UIBlockingProgressHUD.show() } else { - performSegue(withIdentifier: showAuthenticationScreenSegueIdentifier, sender: nil) + let authViewController = AuthViewController() + authViewController.delegate = self + authViewController.modalPresentationStyle = .fullScreen + self.present(authViewController, animated: true) } } @@ -33,6 +42,18 @@ final class SplashViewController: UIViewController { override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } + + func setUI() { + view.backgroundColor = UIColor(named: "YP Black") + logoImage.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(logoImage) + logoImage.image = UIImage(named: "Logo") + + NSLayoutConstraint.activate([ + logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor), + logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } private func switchToTabBarController() { guard let window = UIApplication.shared.windows.first else { fatalError("Invalid Configuration") } @@ -42,20 +63,6 @@ final class SplashViewController: UIViewController { } } -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) { UIBlockingProgressHUD.show() diff --git a/ImageFeed/TabBar/TabBarController.swift b/ImageFeed/TabBar/TabBarController.swift new file mode 100644 index 0000000..8111eeb --- /dev/null +++ b/ImageFeed/TabBar/TabBarController.swift @@ -0,0 +1,27 @@ +// +// TabBarController.swift +// ImageFeed +// +// Created by macOS on 20.01.2023. +// + +import UIKit + +final class TabBarController: UITabBarController { + + override func awakeFromNib() { + super.awakeFromNib() + let storyboard = UIStoryboard(name: "Main", bundle: .main) + + let imagesListViewController = storyboard.instantiateViewController(withIdentifier: "ImagesListViewController") + + let profileViewController = ProfileViewController() + profileViewController.tabBarItem = UITabBarItem( + title: NSLocalizedString("Profile", comment: ""), + image: UIImage(named: "tab_profile_active"), + selectedImage: nil) + + self.viewControllers = [imagesListViewController, profileViewController] + } + +} From 87b0a9573ca076c2242fbe1fdca37b778f378de9 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Fri, 20 Jan 2023 19:10:55 +0500 Subject: [PATCH 15/25] pagination start --- ImageFeed.xcodeproj/project.pbxproj | 16 ++++++++++++++++ ImageFeed/ImagesList/ImageListService.swift | 18 ++++++++++++++++++ .../ImagesListControllerViewController.swift | 4 ++++ ImageFeed/ImagesList/Models/Photo.swift | 18 ++++++++++++++++++ 4 files changed, 56 insertions(+) create mode 100644 ImageFeed/ImagesList/ImageListService.swift create mode 100644 ImageFeed/ImagesList/Models/Photo.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 33b7035..c476bc2 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -9,9 +9,11 @@ /* Begin PBXBuildFile section */ 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */; }; 222804C8297A688400D6B54A /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222804C7297A688400D6B54A /* TabBarController.swift */; }; + 222804CA297A88B200D6B54A /* ImageListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 222804C9297A88B200D6B54A /* ImageListService.swift */; }; 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB27295DD00800FDCAE1 /* Constants.swift */; }; 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */; }; 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */; }; + 22437B07297AD6210026DE5D /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22437B06297AD6210026DE5D /* Photo.swift */; }; 50397DA42962CB750078D6BE /* UIBlockingProgressHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */; }; 50397DA62962D2730078D6BE /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA52962D2730078D6BE /* ProfileService.swift */; }; 50397DA82963119B0078D6BE /* ProfileImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA72963119B0078D6BE /* ProfileImageService.swift */; }; @@ -36,9 +38,11 @@ /* Begin PBXFileReference section */ 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewControllerDelegate.swift; sourceTree = ""; }; 222804C7297A688400D6B54A /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; + 222804C9297A88B200D6B54A /* ImageListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageListService.swift; sourceTree = ""; }; 223FDB27295DD00800FDCAE1 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; + 22437B06297AD6210026DE5D /* Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBlockingProgressHUD.swift; sourceTree = ""; }; 50397DA52962D2730078D6BE /* ProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; 50397DA72963119B0078D6BE /* ProfileImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImageService.swift; sourceTree = ""; }; @@ -94,6 +98,14 @@ path = Auth; sourceTree = ""; }; + 22437B05297AD6140026DE5D /* Models */ = { + isa = PBXGroup; + children = ( + 22437B06297AD6210026DE5D /* Photo.swift */, + ); + path = Models; + sourceTree = ""; + }; 50397DA9296347EC0078D6BE /* Extensions */ = { isa = PBXGroup; children = ( @@ -121,8 +133,10 @@ 506BFC6F2933D62F0058728A /* ImagesList */ = { isa = PBXGroup; children = ( + 22437B05297AD6140026DE5D /* Models */, 50A856792931FDA700E476DF /* ImagesListControllerViewController.swift */, 506BFC6D2933D6160058728A /* ImagesListCell.swift */, + 222804C9297A88B200D6B54A /* ImageListService.swift */, ); path = ImagesList; sourceTree = ""; @@ -267,6 +281,7 @@ 22227A9A295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift in Sources */, 50487E6529609E7700F41745 /* OAuth2Service.swift in Sources */, 222804C8297A688400D6B54A /* TabBarController.swift in Sources */, + 22437B07297AD6210026DE5D /* Photo.swift in Sources */, 50397DAB296348080078D6BE /* URLSession.swift in Sources */, 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, @@ -281,6 +296,7 @@ 50A856782931FDA700E476DF /* SceneDelegate.swift in Sources */, 50397DA42962CB750078D6BE /* UIBlockingProgressHUD.swift in Sources */, 50397DA62962D2730078D6BE /* ProfileService.swift in Sources */, + 222804CA297A88B200D6B54A /* ImageListService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ImageFeed/ImagesList/ImageListService.swift b/ImageFeed/ImagesList/ImageListService.swift new file mode 100644 index 0000000..023efa9 --- /dev/null +++ b/ImageFeed/ImagesList/ImageListService.swift @@ -0,0 +1,18 @@ +// +// ImageListService.swift +// ImageFeed +// +// Created by macOS on 20.01.2023. +// + +import Foundation + +class ImageListService { + private (set) var photos: [Photo] = [] + + private var lastLoadedPage: Int? + + func fetchPhotosNextPage() { + let nextPage = lastLoadedPage == nil ? 1 : lastLoadedPage!.number + 1 + } +} diff --git a/ImageFeed/ImagesList/ImagesListControllerViewController.swift b/ImageFeed/ImagesList/ImagesListControllerViewController.swift index 1c513e4..f59cc38 100644 --- a/ImageFeed/ImagesList/ImagesListControllerViewController.swift +++ b/ImageFeed/ImagesList/ImagesListControllerViewController.swift @@ -67,6 +67,10 @@ extension ImagesListViewController: UITableViewDataSource { return imagesListCell } + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + //fetchPhotosNextPage() + } + func configCell(for cell: ImagesListCell, with indexPath: IndexPath) { guard let image = UIImage(named: photosName[indexPath.row]) else { return diff --git a/ImageFeed/ImagesList/Models/Photo.swift b/ImageFeed/ImagesList/Models/Photo.swift new file mode 100644 index 0000000..dbf7fab --- /dev/null +++ b/ImageFeed/ImagesList/Models/Photo.swift @@ -0,0 +1,18 @@ +// +// Photo.swift +// ImageFeed +// +// Created by macOS on 20.01.2023. +// + +import Foundation + +struct Photo { + let id: String + let size: CGSize + let createdAt: Date? + let welcomeDescription: String? + let thumbImageURL: String + let largeImageURL: String + let isLiked: Bool +} From 42049e356a2dc6fea6c24b088d0757c5cd51c587 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Sun, 29 Jan 2023 22:38:56 +0500 Subject: [PATCH 16/25] add models --- ImageFeed.xcodeproj/project.pbxproj | 4 +++ ImageFeed/ImagesList/ImageListService.swift | 5 ++- .../ImagesListControllerViewController.swift | 4 ++- ImageFeed/ImagesList/Models/PhotoResult.swift | 32 +++++++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 ImageFeed/ImagesList/Models/PhotoResult.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index c476bc2..3aa8368 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 223FDB2B295DD3AB00FDCAE1 /* WebViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */; }; 223FDB2D295DD49F00FDCAE1 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */; }; 22437B07297AD6210026DE5D /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22437B06297AD6210026DE5D /* Photo.swift */; }; + 503717812986E1F10012E52C /* PhotoResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503717802986E1F10012E52C /* PhotoResult.swift */; }; 50397DA42962CB750078D6BE /* UIBlockingProgressHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */; }; 50397DA62962D2730078D6BE /* ProfileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA52962D2730078D6BE /* ProfileService.swift */; }; 50397DA82963119B0078D6BE /* ProfileImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50397DA72963119B0078D6BE /* ProfileImageService.swift */; }; @@ -43,6 +44,7 @@ 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewController.swift; sourceTree = ""; }; 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 22437B06297AD6210026DE5D /* Photo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; + 503717802986E1F10012E52C /* PhotoResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoResult.swift; sourceTree = ""; }; 50397DA32962CB750078D6BE /* UIBlockingProgressHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBlockingProgressHUD.swift; sourceTree = ""; }; 50397DA52962D2730078D6BE /* ProfileService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileService.swift; sourceTree = ""; }; 50397DA72963119B0078D6BE /* ProfileImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImageService.swift; sourceTree = ""; }; @@ -102,6 +104,7 @@ isa = PBXGroup; children = ( 22437B06297AD6210026DE5D /* Photo.swift */, + 503717802986E1F10012E52C /* PhotoResult.swift */, ); path = Models; sourceTree = ""; @@ -286,6 +289,7 @@ 506BFC6E2933D6160058728A /* ImagesListCell.swift in Sources */, 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */, 50A856762931FDA700E476DF /* AppDelegate.swift in Sources */, + 503717812986E1F10012E52C /* PhotoResult.swift in Sources */, 50B8D4322961AC7200508CF6 /* SplashViewController.swift in Sources */, 223FDB28295DD00800FDCAE1 /* Constants.swift in Sources */, 5044C5CD2940FC4200E65D8F /* SingleImageViewController.swift in Sources */, diff --git a/ImageFeed/ImagesList/ImageListService.swift b/ImageFeed/ImagesList/ImageListService.swift index 023efa9..ff63f97 100644 --- a/ImageFeed/ImagesList/ImageListService.swift +++ b/ImageFeed/ImagesList/ImageListService.swift @@ -9,10 +9,13 @@ import Foundation class ImageListService { private (set) var photos: [Photo] = [] + static let didChangeNotification = Notification.Name(rawValue: "ImagesListServiceDidChange") private var lastLoadedPage: Int? func fetchPhotosNextPage() { - let nextPage = lastLoadedPage == nil ? 1 : lastLoadedPage!.number + 1 + let nextPage = lastLoadedPage == nil ? 1 : lastLoadedPage! + 1 + + } } diff --git a/ImageFeed/ImagesList/ImagesListControllerViewController.swift b/ImageFeed/ImagesList/ImagesListControllerViewController.swift index f59cc38..dea2795 100644 --- a/ImageFeed/ImagesList/ImagesListControllerViewController.swift +++ b/ImageFeed/ImagesList/ImagesListControllerViewController.swift @@ -68,7 +68,9 @@ extension ImagesListViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - //fetchPhotosNextPage() + // if indexPath.row == photosName.count { + //fetchPhotosNextPage() + //} } func configCell(for cell: ImagesListCell, with indexPath: IndexPath) { diff --git a/ImageFeed/ImagesList/Models/PhotoResult.swift b/ImageFeed/ImagesList/Models/PhotoResult.swift new file mode 100644 index 0000000..cf9acb6 --- /dev/null +++ b/ImageFeed/ImagesList/Models/PhotoResult.swift @@ -0,0 +1,32 @@ +// +// PhotoResult.swift +// ImageFeed +// +// Created by Albert on 29.01.2023. +// + +import Foundation + +struct PhotoResult: Codable { + let id: String + let createdAt: String + let updatedAt: String + let width: Int + let height: Int + let color: String + let blurHash: String + let likes: Int + let likedByUser: Bool + let description: String + let user: Array + let urls: UrlResult + +} + +struct UrlResult: Codable { + let raw: String + let full: String + let regular: String + let small: String + let thumb: String +} From f89f141d2a34efe4617574dfcb2339300c57fbae Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Wed, 1 Feb 2023 22:32:39 +0500 Subject: [PATCH 17/25] ImageListService --- ImageFeed.xcodeproj/project.pbxproj | 119 ++++++++++++++++++ ImageFeed/ImagesList/ImageListService.swift | 41 ++++++ ImageFeed/ImagesList/Models/PhotoResult.swift | 17 ++- ImageFeedTests/ImageFeedTests.swift | 29 +++++ 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 ImageFeedTests/ImageFeedTests.swift diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 3aa8368..1772d2a 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -33,9 +33,20 @@ 50B8D42F29619E7800508CF6 /* OAuth2TokenStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */; }; 50B8D4322961AC7200508CF6 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8D4312961AC7200508CF6 /* SplashViewController.swift */; }; 50B8D4352961F15500508CF6 /* ProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 50B8D4342961F15500508CF6 /* ProgressHUD */; }; + 50D4AD9D298AD104000BD664 /* ImageFeedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D4AD9C298AD104000BD664 /* ImageFeedTests.swift */; }; 50EEBC02293B6E2B00E950B5 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 50D4AD9E298AD104000BD664 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50A8566A2931FDA700E476DF /* Project object */; + proxyType = 1; + remoteGlobalIDString = 50A856712931FDA700E476DF; + remoteInfo = ImageFeed; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewViewControllerDelegate.swift; sourceTree = ""; }; 222804C7297A688400D6B54A /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; @@ -62,6 +73,8 @@ 50A856832931FDA800E476DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2TokenStorage.swift; sourceTree = ""; }; 50B8D4312961AC7200508CF6 /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; + 50D4AD9A298AD104000BD664 /* ImageFeedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageFeedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 50D4AD9C298AD104000BD664 /* ImageFeedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFeedTests.swift; sourceTree = ""; }; 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -76,6 +89,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 50D4AD97298AD104000BD664 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -148,6 +168,7 @@ isa = PBXGroup; children = ( 50A856742931FDA700E476DF /* ImageFeed */, + 50D4AD9B298AD104000BD664 /* ImageFeedTests */, 50A856732931FDA700E476DF /* Products */, ); sourceTree = ""; @@ -156,6 +177,7 @@ isa = PBXGroup; children = ( 50A856722931FDA700E476DF /* ImageFeed.app */, + 50D4AD9A298AD104000BD664 /* ImageFeedTests.xctest */, ); name = Products; sourceTree = ""; @@ -189,6 +211,14 @@ path = SplashViewController; sourceTree = ""; }; + 50D4AD9B298AD104000BD664 /* ImageFeedTests */ = { + isa = PBXGroup; + children = ( + 50D4AD9C298AD104000BD664 /* ImageFeedTests.swift */, + ); + path = ImageFeedTests; + sourceTree = ""; + }; 50EEBC00293B6E1100E950B5 /* Profile */ = { isa = PBXGroup; children = ( @@ -224,6 +254,24 @@ productReference = 50A856722931FDA700E476DF /* ImageFeed.app */; productType = "com.apple.product-type.application"; }; + 50D4AD99298AD104000BD664 /* ImageFeedTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 50D4ADA0298AD104000BD664 /* Build configuration list for PBXNativeTarget "ImageFeedTests" */; + buildPhases = ( + 50D4AD96298AD104000BD664 /* Sources */, + 50D4AD97298AD104000BD664 /* Frameworks */, + 50D4AD98298AD104000BD664 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 50D4AD9F298AD104000BD664 /* PBXTargetDependency */, + ); + name = ImageFeedTests; + productName = ImageFeedTests; + productReference = 50D4AD9A298AD104000BD664 /* ImageFeedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -237,6 +285,10 @@ 50A856712931FDA700E476DF = { CreatedOnToolsVersion = 14.1; }; + 50D4AD99298AD104000BD664 = { + CreatedOnToolsVersion = 14.1; + TestTargetID = 50A856712931FDA700E476DF; + }; }; }; buildConfigurationList = 50A8566D2931FDA700E476DF /* Build configuration list for PBXProject "ImageFeed" */; @@ -258,6 +310,7 @@ projectRoot = ""; targets = ( 50A856712931FDA700E476DF /* ImageFeed */, + 50D4AD99298AD104000BD664 /* ImageFeedTests */, ); }; /* End PBXProject section */ @@ -273,6 +326,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 50D4AD98298AD104000BD664 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -304,8 +364,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 50D4AD96298AD104000BD664 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 50D4AD9D298AD104000BD664 /* ImageFeedTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 50D4AD9F298AD104000BD664 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 50A856712931FDA700E476DF /* ImageFeed */; + targetProxy = 50D4AD9E298AD104000BD664 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 50A8567B2931FDA700E476DF /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -502,6 +578,40 @@ }; name = Release; }; + 50D4ADA1298AD104000BD664 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.maxcode2023.ImageFeedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImageFeed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ImageFeed"; + }; + name = Debug; + }; + 50D4ADA2298AD104000BD664 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.maxcode2023.ImageFeedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImageFeed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ImageFeed"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -523,6 +633,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 50D4ADA0298AD104000BD664 /* Build configuration list for PBXNativeTarget "ImageFeedTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 50D4ADA1298AD104000BD664 /* Debug */, + 50D4ADA2298AD104000BD664 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/ImageFeed/ImagesList/ImageListService.swift b/ImageFeed/ImagesList/ImageListService.swift index ff63f97..ad94fb4 100644 --- a/ImageFeed/ImagesList/ImageListService.swift +++ b/ImageFeed/ImagesList/ImageListService.swift @@ -13,9 +13,50 @@ class ImageListService { private var lastLoadedPage: Int? + private let urlSession = URLSession.shared + private var task: URLSessionTask? + func fetchPhotosNextPage() { let nextPage = lastLoadedPage == nil ? 1 : lastLoadedPage! + 1 + assert(Thread.isMainThread) + task?.cancel() + + let request = makeRequest(path: "/photos?page=\(nextPage)", httpMethod: "GET", baseURL: DefaultBaseURL) + let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in + guard let self = self else { return } + switch result { + case .success(let photoResult): + self.photos.append( + Photo( + id: photoResult.id, + size: CGSize(width: photoResult.width, height: photoResult.height), + createdAt: Date(), + welcomeDescription: photoResult.description, + thumbImageURL: photoResult.urls.thumb, + largeImageURL: photoResult.urls.full, + isLiked: photoResult.likedByUser)) + NotificationCenter.default + .post( + name: ImageListService.didChangeNotification, + object: self) + case .failure(let error): + print(error.localizedDescription) + } + } + self.task = task + task.resume() + } + + private func makeRequest( + path: String, + httpMethod: String, + baseURL: URL = DefaultBaseURL) -> URLRequest { + var request = URLRequest(url: URL(string: path, relativeTo: baseURL)!) + request.setValue("Bearer \(String(describing: OAuth2TokenStorage().token!))", forHTTPHeaderField: "Authorization") + request.httpMethod = httpMethod + + return request } } diff --git a/ImageFeed/ImagesList/Models/PhotoResult.swift b/ImageFeed/ImagesList/Models/PhotoResult.swift index cf9acb6..c21da75 100644 --- a/ImageFeed/ImagesList/Models/PhotoResult.swift +++ b/ImageFeed/ImagesList/Models/PhotoResult.swift @@ -18,9 +18,24 @@ struct PhotoResult: Codable { let likes: Int let likedByUser: Bool let description: String - let user: Array + let user: UserResult let urls: UrlResult + enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case updatedAt = "updated_at" + case blurHash = "blur_hash" + case likedByUser = "liked_by_user" + case description = "description" + case user = "user" + case urls = "urls" + case id = "id" + case width = "width" + case height = "height" + case color = "color" + case likes = "likes" + } + } struct UrlResult: Codable { diff --git a/ImageFeedTests/ImageFeedTests.swift b/ImageFeedTests/ImageFeedTests.swift new file mode 100644 index 0000000..523849a --- /dev/null +++ b/ImageFeedTests/ImageFeedTests.swift @@ -0,0 +1,29 @@ +// +// ImageFeedTests.swift +// ImageFeedTests +// +// Created by macOS on 01.02.2023. +// +@testable import ImageFeed +import XCTest + +final class ImageFeedTests: XCTestCase { + + func testFetchPhotos() { + + let service = ImageListService() + + let expectation = self.expectation(description: "Wait for Notification") + NotificationCenter.default.addObserver( + forName: ImageListService.didChangeNotification, + object: nil, + queue: .main) { _ in + expectation.fulfill() + } + + service.fetchPhotosNextPage() + wait(for: [expectation], timeout: 10) + + XCTAssertEqual(service.photos.count, 10) + } +} From 953db1410f1f60716fba345e1c7ea03be252f2fa Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Fri, 3 Feb 2023 19:18:16 +0500 Subject: [PATCH 18/25] fix --- ImageFeed.xcodeproj/project.pbxproj | 4 ++-- ImageFeed/ImagesList/ImageListService.swift | 23 +++++++++++-------- .../ImagesListControllerViewController.swift | 2 ++ ImageFeed/ImagesList/Models/PhotoResult.swift | 3 ++- .../SplashViewController.swift | 3 ++- ImageFeedTests/ImageFeedTests.swift | 1 + 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index 1772d2a..53c2174 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -526,7 +526,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ImageFeed/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UILaunchStoryboardName = Main.storyboard; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; @@ -557,7 +557,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ImageFeed/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UILaunchStoryboardName = Main.storyboard; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; diff --git a/ImageFeed/ImagesList/ImageListService.swift b/ImageFeed/ImagesList/ImageListService.swift index ad94fb4..9b8ae02 100644 --- a/ImageFeed/ImagesList/ImageListService.swift +++ b/ImageFeed/ImagesList/ImageListService.swift @@ -24,19 +24,22 @@ class ImageListService { let request = makeRequest(path: "/photos?page=\(nextPage)", httpMethod: "GET", baseURL: DefaultBaseURL) - let task = urlSession.objectTask(for: request) { [weak self] (result: Result) in + let task = urlSession.objectTask(for: request) { [weak self] (result: Result, Error>) in guard let self = self else { return } switch result { case .success(let photoResult): - self.photos.append( - Photo( - id: photoResult.id, - size: CGSize(width: photoResult.width, height: photoResult.height), - createdAt: Date(), - welcomeDescription: photoResult.description, - thumbImageURL: photoResult.urls.thumb, - largeImageURL: photoResult.urls.full, - isLiked: photoResult.likedByUser)) + print(photoResult) + for i in photoResult.indices { + self.photos.append( + Photo( + id: photoResult[i].id, + size: CGSize(width: photoResult[i].width, height: photoResult[i].height), + createdAt: Date(), + welcomeDescription: photoResult[i].description, + thumbImageURL: photoResult[i].urls.thumb, + largeImageURL: photoResult[i].urls.full, + isLiked: photoResult[i].likedByUser)) + } NotificationCenter.default .post( name: ImageListService.didChangeNotification, diff --git a/ImageFeed/ImagesList/ImagesListControllerViewController.swift b/ImageFeed/ImagesList/ImagesListControllerViewController.swift index dea2795..e6a4df1 100644 --- a/ImageFeed/ImagesList/ImagesListControllerViewController.swift +++ b/ImageFeed/ImagesList/ImagesListControllerViewController.swift @@ -24,6 +24,8 @@ class ImagesListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + ImageListService().fetchPhotosNextPage() + photosName = Array(0..<20).map{"\($0)"} } diff --git a/ImageFeed/ImagesList/Models/PhotoResult.swift b/ImageFeed/ImagesList/Models/PhotoResult.swift index c21da75..4358361 100644 --- a/ImageFeed/ImagesList/Models/PhotoResult.swift +++ b/ImageFeed/ImagesList/Models/PhotoResult.swift @@ -17,7 +17,7 @@ struct PhotoResult: Codable { let blurHash: String let likes: Int let likedByUser: Bool - let description: String + let description: String? let user: UserResult let urls: UrlResult @@ -45,3 +45,4 @@ struct UrlResult: Codable { let small: String let thumb: String } + diff --git a/ImageFeed/SplashViewController/SplashViewController.swift b/ImageFeed/SplashViewController/SplashViewController.swift index 3f2e23e..8488ed4 100644 --- a/ImageFeed/SplashViewController/SplashViewController.swift +++ b/ImageFeed/SplashViewController/SplashViewController.swift @@ -27,7 +27,8 @@ final class SplashViewController: UIViewController { self.fetchProfile() UIBlockingProgressHUD.show() } else { - let authViewController = AuthViewController() + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let authViewController = storyboard.instantiateViewController(withIdentifier: "AuthViewController") as! AuthViewController authViewController.delegate = self authViewController.modalPresentationStyle = .fullScreen self.present(authViewController, animated: true) diff --git a/ImageFeedTests/ImageFeedTests.swift b/ImageFeedTests/ImageFeedTests.swift index 523849a..fc7c7a4 100644 --- a/ImageFeedTests/ImageFeedTests.swift +++ b/ImageFeedTests/ImageFeedTests.swift @@ -9,6 +9,7 @@ import XCTest final class ImageFeedTests: XCTestCase { + func testFetchPhotos() { func testFetchPhotos() { let service = ImageListService() From b8ea2d8146016990beb3f5ded2eb28e30493fd91 Mon Sep 17 00:00:00 2001 From: MaxCode2023 Date: Sun, 5 Feb 2023 21:31:30 +0500 Subject: [PATCH 19/25] images vc impl --- .../Icons/image plug.imageset/Contents.json | 21 ++++++ .../image plug.imageset/scribble.variable.pdf | Bin 0 -> 2191 bytes ImageFeed/Base.lproj/Main.storyboard | 7 +- ImageFeed/ImagesList/ImageListService.swift | 4 ++ ImageFeed/ImagesList/ImagesListCell.swift | 10 +-- .../ImagesListControllerViewController.swift | 60 ++++++++++++------ .../SingleImageViewController.swift | 29 +++++---- 7 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 ImageFeed/Assets.xcassets/Icons/image plug.imageset/Contents.json create mode 100644 ImageFeed/Assets.xcassets/Icons/image plug.imageset/scribble.variable.pdf diff --git a/ImageFeed/Assets.xcassets/Icons/image plug.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/image plug.imageset/Contents.json new file mode 100644 index 0000000..6a88fbb --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/image plug.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "scribble.variable.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Icons/image plug.imageset/scribble.variable.pdf b/ImageFeed/Assets.xcassets/Icons/image plug.imageset/scribble.variable.pdf new file mode 100644 index 0000000000000000000000000000000000000000..98e8227a3cbbc66c9d31bda7d7fcfd7b5e11bbe0 GIT binary patch literal 2191 zcmZuz+in|241M3P;7fqyAtQ3gAvr)0pm8oJ+M=%0x1bNIym4H_Qfnzi+OO}KwJpa< z*M>ae4ln2M=0~S5Uq6)+(S`~8oJ=-D$`z50B3^Y*Yl%s1a;`LkIQm&W6v=g)6` zr+Irm)Ta7C|HpZIari(A;TbMY`@{9TzM57q=f5vE^Vyphbn^DgZ=3z}*R*;ibg`eL zEG`(%ZVyk7r~i59CAEedZLaQBkN1v;{boDW!WlK4R7wqw=yWw%u`X<#YL?!*)JnZT z5I8_&(nE!$77uBC(e>us8f_*~s0d4t))JxGg2ao3aId7Tm`Zq15PG+2!=ix} zytN9CX|80ws4?OgzPV`QOkN^aOVtsjbOrKpYCT<&YJgr&?gXsN=|BT*M8sm&C<;e2 zyoQ?$1*wB7^Ny5I3($vvjcO~no>`&ntyFHNAWcelt4Kp|an;(D6c$Ag<{+SVgo}Hv z1l;-14)_5w7E&wOfFD$GiA?nz78^@PV4g*qbP!M-cB~>s_(rys8410I*pLzrL%IP` zTQh;S6%`GnAxl%(Ta=@CIzST8WVEIfR!}frgS>bXXQPyY>QDqI1}Ml7N^?p@I4+e* zkT865EtfEVMoHVv1S$?IhhyAEx)ycKT%+JK=WV-))Dn2yV6unv#!8!;a#ybiUhe zcZWT_`wdG7&+6BI{+wxby1tli1wYN_m+Ke1&-CtoHyl?3y6JXZM7ulPSvs5dyHD4f zc~4`_o!y!{*=-N=79Pj$x)YwRuOXv$E5h!&!!Shm8}f(CbA(C*!5Q88IoW-Ju6z^! z9+EFC`2$M>;Qr|F9{9=nu>QEac&PgMdVW7K#dL#pe3D}lHG=r|LCG#B+8#xLILC7o zA+ulKLO~);4{u?pel5bXJc&uVx^)#~g{Y%jCc)z=vpU~sW^Cvf?tj>130}a3n d!K=5I|IUbh`IlgQeOTb2YWwu)(W{?d{|83m$~gc4 literal 0 HcmV?d00001 diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 1ca3c5c..a5e4796 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -29,8 +29,9 @@ - + +