diff --git a/ImageFeed.xcodeproj/project.pbxproj b/ImageFeed.xcodeproj/project.pbxproj index c21b349..19adb49 100644 --- a/ImageFeed.xcodeproj/project.pbxproj +++ b/ImageFeed.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ 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 */; }; + 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 */; }; @@ -14,9 +20,18 @@ 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 */ /* 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 = ""; }; + 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 = ""; }; @@ -26,6 +41,9 @@ 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 */ /* Begin PBXFrameworksBuildPhase section */ @@ -39,6 +57,34 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 223FDB29295DD36600FDCAE1 /* Auth */ = { + isa = PBXGroup; + children = ( + 50487E6029609D3200F41745 /* Models */, + 223FDB2A295DD3AB00FDCAE1 /* WebViewViewController.swift */, + 223FDB2C295DD49F00FDCAE1 /* AuthViewController.swift */, + 22227A99295F1B6B0014DF14 /* WebViewViewControllerDelegate.swift */, + 50487E6429609E7700F41745 /* OAuth2Service.swift */, + ); + path = Auth; + sourceTree = ""; + }; + 5044C5CB2940FC1F00E65D8F /* SingleImage */ = { + isa = PBXGroup; + children = ( + 5044C5CC2940FC4200E65D8F /* SingleImageViewController.swift */, + ); + path = SingleImage; + sourceTree = ""; + }; + 50487E6029609D3200F41745 /* Models */ = { + isa = PBXGroup; + children = ( + 50B8D42E29619E7800508CF6 /* OAuth2TokenStorage.swift */, + ); + path = Models; + sourceTree = ""; + }; 506BFC6F2933D62F0058728A /* ImagesList */ = { isa = PBXGroup; children = ( @@ -67,6 +113,10 @@ 50A856742931FDA700E476DF /* ImageFeed */ = { isa = PBXGroup; children = ( + 50B8D4302961AC4F00508CF6 /* SplashViewController */, + 223FDB29295DD36600FDCAE1 /* Auth */, + 5044C5CB2940FC1F00E65D8F /* SingleImage */, + 50EEBC00293B6E1100E950B5 /* Profile */, 506BFC6F2933D62F0058728A /* ImagesList */, 50A856752931FDA700E476DF /* AppDelegate.swift */, 50A856772931FDA700E476DF /* SceneDelegate.swift */, @@ -74,10 +124,27 @@ 50A8567E2931FDA800E476DF /* Assets.xcassets */, 50A856802931FDA800E476DF /* LaunchScreen.storyboard */, 50A856832931FDA800E476DF /* Info.plist */, + 223FDB27295DD00800FDCAE1 /* Constants.swift */, ); path = ImageFeed; sourceTree = ""; }; + 50B8D4302961AC4F00508CF6 /* SplashViewController */ = { + isa = PBXGroup; + children = ( + 50B8D4312961AC7200508CF6 /* SplashViewController.swift */, + ); + path = SplashViewController; + sourceTree = ""; + }; + 50EEBC00293B6E1100E950B5 /* Profile */ = { + isa = PBXGroup; + children = ( + 50EEBC01293B6E2B00E950B5 /* ProfileViewController.swift */, + ); + path = Profile; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -150,8 +217,17 @@ buildActionMask = 2147483647; 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/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/Assets.xcassets/Colors/Contents.json b/ImageFeed/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json new file mode 100644 index 0000000..1d65869 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Colors/YP Black.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.106", + "red" : "0.102" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.106", + "red" : "0.102" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/Colors/YP 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/YP Black.colorset/Contents.json b/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json similarity index 76% rename from ImageFeed/Assets.xcassets/YP Black.colorset/Contents.json rename to ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json index b15ea4c..11976ca 100644 --- a/ImageFeed/Assets.xcassets/YP Black.colorset/Contents.json +++ b/ImageFeed/Assets.xcassets/Colors/YP Red.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x22", - "green" : "0x1B", - "red" : "0x1A" + "blue" : "0x6C", + "green" : "0x6B", + "red" : "0xF5" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x22", - "green" : "0x1B", - "red" : "0x1A" + "blue" : "0x6C", + "green" : "0x6B", + "red" : "0xF5" } }, "idiom" : "universal" 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/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/Backward.imageset/Backward.png b/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Backward.png new file mode 100644 index 0000000..1e28c7e Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/Backward.imageset/Backward.png differ 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/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 0000000..b461674 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/Exit.imageset/Exit.png differ diff --git a/ImageFeed/Assets.xcassets/Logo.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/Logo.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/Logo.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/Logo.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/Logo.imageset/Vector.png b/ImageFeed/Assets.xcassets/Icons/Logo.imageset/Vector.png similarity index 100% rename from ImageFeed/Assets.xcassets/Logo.imageset/Vector.png rename to ImageFeed/Assets.xcassets/Icons/Logo.imageset/Vector.png 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 0000000..7e7e79e Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/Photo.imageset/Photo.pdf differ 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 0000000..eee12c4 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/Sharing.imageset/Sharing.pdf differ diff --git a/ImageFeed/Assets.xcassets/Active.imageset/Active.png b/ImageFeed/Assets.xcassets/Icons/liked.imageset/Active.png similarity index 100% rename from ImageFeed/Assets.xcassets/Active.imageset/Active.png rename to ImageFeed/Assets.xcassets/Icons/liked.imageset/Active.png diff --git a/ImageFeed/Assets.xcassets/Active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/liked.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/Active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/liked.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/Icons/nav_back_button.imageset/Backward.pdf b/ImageFeed/Assets.xcassets/Icons/nav_back_button.imageset/Backward.pdf new file mode 100644 index 0000000..4da8fe4 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/nav_back_button.imageset/Backward.pdf differ diff --git a/ImageFeed/Assets.xcassets/Icons/nav_back_button.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/nav_back_button.imageset/Contents.json new file mode 100644 index 0000000..1a321b5 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/nav_back_button.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Backward.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFeed/Assets.xcassets/No Active.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/no liked.imageset/Contents.json similarity index 100% rename from ImageFeed/Assets.xcassets/No Active.imageset/Contents.json rename to ImageFeed/Assets.xcassets/Icons/no liked.imageset/Contents.json diff --git a/ImageFeed/Assets.xcassets/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/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 0000000..d522e6f Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/tab_editorial_active.imageset/Active.png differ 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 0000000..cc36a93 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/tab_profile_active.imageset/tab_profile_active.png differ diff --git a/ImageFeed/Assets.xcassets/Icons/unsplash icon.imageset/Contents.json b/ImageFeed/Assets.xcassets/Icons/unsplash icon.imageset/Contents.json new file mode 100644 index 0000000..0d192b3 --- /dev/null +++ b/ImageFeed/Assets.xcassets/Icons/unsplash icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector.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/unsplash icon.imageset/Vector.pdf b/ImageFeed/Assets.xcassets/Icons/unsplash icon.imageset/Vector.pdf new file mode 100644 index 0000000..4acf463 Binary files /dev/null and b/ImageFeed/Assets.xcassets/Icons/unsplash icon.imageset/Vector.pdf differ diff --git a/ImageFeed/Auth/AuthViewController.swift b/ImageFeed/Auth/AuthViewController.swift new file mode 100644 index 0000000..fbb293c --- /dev/null +++ b/ImageFeed/Auth/AuthViewController.swift @@ -0,0 +1,40 @@ +// +// AuthViewController.swift +// ImageFeed +// +// Created by macOS on 29.12.2022. +// + +import UIKit + +class AuthViewController: UIViewController { + + private let segueID = "ShowWebView" + + weak var delegate: AuthViewControllerDelegate? + + 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) { + 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 new file mode 100644 index 0000000..42d4f70 --- /dev/null +++ b/ImageFeed/Auth/WebViewViewController.swift @@ -0,0 +1,107 @@ +// +// WebViewViewController.swift +// ImageFeed +// +// Created by macOS on 29.12.2022. +// + +import UIKit +import WebKit + +fileprivate let UnsplashAuthorizeURLString = "https://unsplash.com/oauth/authorize" + +final class WebViewViewController: UIViewController { + + @IBOutlet private weak var webView: WKWebView! + @IBOutlet weak var progressView: UIProgressView! + + 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) + + updateProgress() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + webView.addObserver( + self, + forKeyPath: #keyPath(WKWebView.estimatedProgress), + options: .new, + context: nil) + + updateProgress() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), context: nil) + } + + override func observeValue( + forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer? + ) { + if keyPath == #keyPath(WKWebView.estimatedProgress) { + updateProgress() + } else { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + } + } + + private func updateProgress() { + progressView.progress = Float(webView.estimatedProgress) + progressView.isHidden = fabs(webView.estimatedProgress - 1.0) <= 0.0001 + } + + @IBAction private func didTapBackButton(_ sender: Any) { + delegate?.webViewViewControllerDidCancel(self) + } +} + +extension WebViewViewController: WKNavigationDelegate { + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + if let code = code(from: navigationAction) { + delegate?.webViewViewController(self, didAuthenticateWithCode: code) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } + + private func code(from navigationAction: WKNavigationAction) -> String? { + if + let url = navigationAction.request.url, + let urlComponents = URLComponents(string: url.absoluteString), + urlComponents.path == "/oauth/authorize/native", + let 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) +} diff --git a/ImageFeed/Base.lproj/Main.storyboard b/ImageFeed/Base.lproj/Main.storyboard index 849958b..4f08328 100644 --- a/ImageFeed/Base.lproj/Main.storyboard +++ b/ImageFeed/Base.lproj/Main.storyboard @@ -1,11 +1,12 @@ - + + @@ -18,7 +19,7 @@ - + @@ -88,20 +89,294 @@ + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + 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")! diff --git a/ImageFeed/ImagesList/ImagesListControllerViewController.swift b/ImageFeed/ImagesList/ImagesListControllerViewController.swift index 85c6ebb..1c513e4 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,25 @@ class ImagesListViewController: UIViewController { photosName = Array(0..<20).map{"\($0)"} } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == showSingleImageSegueIdentifier { + if let viewController = segue.destination as? SingleImageViewController { + if 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) } } @@ -60,10 +75,11 @@ 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) } } } + diff --git a/ImageFeed/Profile/ProfileViewController.swift b/ImageFeed/Profile/ProfileViewController.swift new file mode 100644 index 0000000..afa0af7 --- /dev/null +++ b/ImageFeed/Profile/ProfileViewController.swift @@ -0,0 +1,78 @@ +// +// ProfileViewController.swift +// ImageFeed +// +// Created by Albert on 03.12.2022. +// + +import UIKit + +class ProfileViewController: UIViewController { + + private let image = UIImageView() + private let labelName = UILabel() + private let labelNickname = UILabel() + private let labelStatus = UILabel() + private let button = UIButton.systemButton(with: UIImage(systemName: "ipad.and.arrow.forward")!, target: ProfileViewController.self, action: nil) + + override func viewDidLoad() { + super.viewDidLoad() + + 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") + + 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) { +// +// } +} diff --git a/ImageFeed/SingleImage/SingleImageViewController.swift b/ImageFeed/SingleImage/SingleImageViewController.swift new file mode 100644 index 0000000..c4561b9 --- /dev/null +++ b/ImageFeed/SingleImage/SingleImageViewController.swift @@ -0,0 +1,71 @@ +// +// SingleImageViewController.swift +// ImageFeed +// +// Created by Albert on 07.12.2022. +// + +import UIKit + +class SingleImageViewController: UIViewController { + + + @IBOutlet weak private var buttonBack: UIButton! + @IBOutlet weak private var imageView: UIImageView! + @IBOutlet weak private 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 private 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 private 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 + } +} 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 + } + } + } +} +