diff --git a/MovieQuiz.xcodeproj/project.pbxproj b/MovieQuiz.xcodeproj/project.pbxproj index 45ad649fa4..be116bfa5d 100644 --- a/MovieQuiz.xcodeproj/project.pbxproj +++ b/MovieQuiz.xcodeproj/project.pbxproj @@ -7,6 +7,29 @@ objects = { /* Begin PBXBuildFile section */ + 4F17C5492BA626C70004B3F6 /* MovieQuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17C5482BA626C70004B3F6 /* MovieQuizPresenter.swift */; }; + 4F498C472BA8DB1F00D3C5AC /* MovieQuizViewControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F498C462BA8DB1F00D3C5AC /* MovieQuizViewControllerProtocol.swift */; }; + 4F498C4F2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F498C4E2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.swift */; }; + 4F6A84CC2BA756A900062FCA /* QuizResultsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6A84CB2BA756A900062FCA /* QuizResultsViewModel.swift */; }; + 4F6BE2382B94DAF80070EFFA /* NetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BE2372B94DAF80070EFFA /* NetworkClient.swift */; }; + 4F6BE23A2B94DB4A0070EFFA /* MostPopularMovies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BE2392B94DB4A0070EFFA /* MostPopularMovies.swift */; }; + 4F6BE23C2B94ED590070EFFA /* MoviesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BE23B2B94ED590070EFFA /* MoviesLoader.swift */; }; + 4F705CFC2B8501840090FFA7 /* StatisticService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F705CFB2B8501840090FFA7 /* StatisticService.swift */; }; + 4F90E2B42B83E11900D7BBFA /* QuestionFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90E2B32B83E11900D7BBFA /* QuestionFactoryProtocol.swift */; }; + 4F90E2B62B83F2D300D7BBFA /* QuestionFactoryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90E2B52B83F2D300D7BBFA /* QuestionFactoryDelegate.swift */; }; + 4F90E2B82B84124A00D7BBFA /* AlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90E2B72B84124A00D7BBFA /* AlertModel.swift */; }; + 4F90E2BA2B8412A200D7BBFA /* AlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90E2B92B8412A200D7BBFA /* AlertPresenter.swift */; }; + 4FA410912B9DE0BF0064C7C6 /* YS Display-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4FBE26E22B67F17900BD2A97 /* YS Display-Medium.ttf */; }; + 4FA410922B9DE0C20064C7C6 /* YS Display-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4FBE26E12B67F17900BD2A97 /* YS Display-Bold.ttf */; }; + 4FA410932B9DE2C40064C7C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AD1ABABA2831907F00B3E448 /* Assets.xcassets */; }; + 4FA410972B9DEB6F0064C7C6 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA410962B9DEB6F0064C7C6 /* ArrayTests.swift */; }; + 4FA4109B2B9DFC6A0064C7C6 /* MoviesLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA4109A2B9DFC6A0064C7C6 /* MoviesLoaderTests.swift */; }; + 4FA410A52B9F778A0064C7C6 /* MovieQuizUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA410A42B9F778A0064C7C6 /* MovieQuizUITests.swift */; }; + 4FBE26E32B67F17900BD2A97 /* YS Display-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4FBE26E12B67F17900BD2A97 /* YS Display-Bold.ttf */; }; + 4FBE26E42B67F17900BD2A97 /* YS Display-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4FBE26E22B67F17900BD2A97 /* YS Display-Medium.ttf */; }; + 4FFBB97C2B8292C000379E94 /* QuestionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFBB97B2B8292C000379E94 /* QuestionFactory.swift */; }; + 4FFBB97F2B83A9E100379E94 /* QuizStepViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFBB97E2B83A9E100379E94 /* QuizStepViewModel.swift */; }; + 4FFBB9832B83AA6000379E94 /* QuizQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFBB9822B83AA6000379E94 /* QuizQuestion.swift */; }; AD1ABAB22831907B00B3E448 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1ABAB12831907B00B3E448 /* AppDelegate.swift */; }; AD1ABAB42831907B00B3E448 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1ABAB32831907B00B3E448 /* SceneDelegate.swift */; }; AD1ABAB62831907B00B3E448 /* MovieQuizViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1ABAB52831907B00B3E448 /* MovieQuizViewController.swift */; }; @@ -18,7 +41,54 @@ AD7AFA552836189F00399704 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD7AFA542836189F00399704 /* Array+Extensions.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 4F498C502BA8DC7B00D3C5AC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AD1ABAA62831907B00B3E448 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AD1ABAAD2831907B00B3E448; + remoteInfo = MovieQuiz; + }; + 4FA4108A2B9DD5180064C7C6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AD1ABAA62831907B00B3E448 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AD1ABAAD2831907B00B3E448; + remoteInfo = MovieQuiz; + }; + 4FA410A82B9F778A0064C7C6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AD1ABAA62831907B00B3E448 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AD1ABAAD2831907B00B3E448; + remoteInfo = MovieQuiz; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 4F17C5482BA626C70004B3F6 /* MovieQuizPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieQuizPresenter.swift; sourceTree = ""; }; + 4F498C462BA8DB1F00D3C5AC /* MovieQuizViewControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieQuizViewControllerProtocol.swift; sourceTree = ""; }; + 4F498C4C2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MovieQuizPresenterTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4F498C4E2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieQuizPresenterTest.swift; sourceTree = ""; }; + 4F6A84CB2BA756A900062FCA /* QuizResultsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizResultsViewModel.swift; sourceTree = ""; }; + 4F6BE2372B94DAF80070EFFA /* NetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = ""; }; + 4F6BE2392B94DB4A0070EFFA /* MostPopularMovies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MostPopularMovies.swift; sourceTree = ""; }; + 4F6BE23B2B94ED590070EFFA /* MoviesLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesLoader.swift; sourceTree = ""; }; + 4F705CFB2B8501840090FFA7 /* StatisticService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticService.swift; sourceTree = ""; }; + 4F90E2B32B83E11900D7BBFA /* QuestionFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionFactoryProtocol.swift; sourceTree = ""; }; + 4F90E2B52B83F2D300D7BBFA /* QuestionFactoryDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionFactoryDelegate.swift; sourceTree = ""; }; + 4F90E2B72B84124A00D7BBFA /* AlertModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertModel.swift; sourceTree = ""; }; + 4F90E2B92B8412A200D7BBFA /* AlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = ""; }; + 4FA410862B9DD5180064C7C6 /* MovieQuizTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MovieQuizTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4FA410962B9DEB6F0064C7C6 /* ArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = ""; }; + 4FA4109A2B9DFC6A0064C7C6 /* MoviesLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesLoaderTests.swift; sourceTree = ""; }; + 4FA410A22B9F778A0064C7C6 /* MovieQuizUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MovieQuizUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4FA410A42B9F778A0064C7C6 /* MovieQuizUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieQuizUITests.swift; sourceTree = ""; }; + 4FBE26E12B67F17900BD2A97 /* YS Display-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "YS Display-Bold.ttf"; sourceTree = ""; }; + 4FBE26E22B67F17900BD2A97 /* YS Display-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "YS Display-Medium.ttf"; sourceTree = ""; }; + 4FFBB97B2B8292C000379E94 /* QuestionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionFactory.swift; sourceTree = ""; }; + 4FFBB97E2B83A9E100379E94 /* QuizStepViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizStepViewModel.swift; sourceTree = ""; }; + 4FFBB9822B83AA6000379E94 /* QuizQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizQuestion.swift; sourceTree = ""; }; AD1ABAAE2831907B00B3E448 /* MovieQuiz.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MovieQuiz.app; sourceTree = BUILT_PRODUCTS_DIR; }; AD1ABAB12831907B00B3E448 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; AD1ABAB32831907B00B3E448 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -33,6 +103,27 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 4F498C492BA8DC7B00D3C5AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4FA410832B9DD5180064C7C6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4FA4109F2B9F778A0064C7C6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; AD1ABAAB2831907B00B3E448 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -43,11 +134,81 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 4F498C4D2BA8DC7B00D3C5AC /* MovieQuizPresenterTest */ = { + isa = PBXGroup; + children = ( + 4F498C4E2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.swift */, + ); + path = MovieQuizPresenterTest; + sourceTree = ""; + }; + 4FA410872B9DD5180064C7C6 /* MovieQuizTests */ = { + isa = PBXGroup; + children = ( + 4FA410962B9DEB6F0064C7C6 /* ArrayTests.swift */, + 4FA4109A2B9DFC6A0064C7C6 /* MoviesLoaderTests.swift */, + ); + path = MovieQuizTests; + sourceTree = ""; + }; + 4FA410A32B9F778A0064C7C6 /* MovieQuizUITests */ = { + isa = PBXGroup; + children = ( + 4FA410A42B9F778A0064C7C6 /* MovieQuizUITests.swift */, + ); + path = MovieQuizUITests; + sourceTree = ""; + }; + 4FBE26E52B67F18B00BD2A97 /* Fonts */ = { + isa = PBXGroup; + children = ( + 4FBE26E62B67F1DB00BD2A97 /* YS Display */, + ); + path = Fonts; + sourceTree = ""; + }; + 4FBE26E62B67F1DB00BD2A97 /* YS Display */ = { + isa = PBXGroup; + children = ( + 4FBE26E22B67F17900BD2A97 /* YS Display-Medium.ttf */, + 4FBE26E12B67F17900BD2A97 /* YS Display-Bold.ttf */, + ); + path = "YS Display"; + sourceTree = ""; + }; + 4FFBB97A2B82926B00379E94 /* Services */ = { + isa = PBXGroup; + children = ( + 4FFBB97B2B8292C000379E94 /* QuestionFactory.swift */, + 4F90E2B32B83E11900D7BBFA /* QuestionFactoryProtocol.swift */, + 4F90E2B52B83F2D300D7BBFA /* QuestionFactoryDelegate.swift */, + 4F705CFB2B8501840090FFA7 /* StatisticService.swift */, + 4F6BE2372B94DAF80070EFFA /* NetworkClient.swift */, + 4F6BE23B2B94ED590070EFFA /* MoviesLoader.swift */, + 4F498C462BA8DB1F00D3C5AC /* MovieQuizViewControllerProtocol.swift */, + ); + path = Services; + sourceTree = ""; + }; + 4FFBB97D2B83A9BD00379E94 /* Models */ = { + isa = PBXGroup; + children = ( + 4FFBB97E2B83A9E100379E94 /* QuizStepViewModel.swift */, + 4F6A84CB2BA756A900062FCA /* QuizResultsViewModel.swift */, + 4FFBB9822B83AA6000379E94 /* QuizQuestion.swift */, + 4F90E2B72B84124A00D7BBFA /* AlertModel.swift */, + 4F6BE2392B94DB4A0070EFFA /* MostPopularMovies.swift */, + ); + path = Models; + sourceTree = ""; + }; 8F4738322848DE2A005DF65E /* Presentation */ = { isa = PBXGroup; children = ( AD1ABAB52831907B00B3E448 /* MovieQuizViewController.swift */, + 4F17C5482BA626C70004B3F6 /* MovieQuizPresenter.swift */, AD1ABAB72831907B00B3E448 /* Main.storyboard */, + 4F90E2B92B8412A200D7BBFA /* AlertPresenter.swift */, ); path = Presentation; sourceTree = ""; @@ -55,6 +216,7 @@ 8F4738332848DE46005DF65E /* Resources */ = { isa = PBXGroup; children = ( + 4FBE26E52B67F18B00BD2A97 /* Fonts */, AD1ABABC2831907F00B3E448 /* LaunchScreen.storyboard */, AD1ABABA2831907F00B3E448 /* Assets.xcassets */, AD1ABABF2831907F00B3E448 /* Info.plist */, @@ -66,6 +228,9 @@ isa = PBXGroup; children = ( AD1ABAB02831907B00B3E448 /* MovieQuiz */, + 4FA410872B9DD5180064C7C6 /* MovieQuizTests */, + 4FA410A32B9F778A0064C7C6 /* MovieQuizUITests */, + 4F498C4D2BA8DC7B00D3C5AC /* MovieQuizPresenterTest */, AD1ABAAF2831907B00B3E448 /* Products */, ); sourceTree = ""; @@ -74,6 +239,9 @@ isa = PBXGroup; children = ( AD1ABAAE2831907B00B3E448 /* MovieQuiz.app */, + 4FA410862B9DD5180064C7C6 /* MovieQuizTests.xctest */, + 4FA410A22B9F778A0064C7C6 /* MovieQuizUITests.xctest */, + 4F498C4C2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.xctest */, ); name = Products; sourceTree = ""; @@ -81,6 +249,8 @@ AD1ABAB02831907B00B3E448 /* MovieQuiz */ = { isa = PBXGroup; children = ( + 4FFBB97D2B83A9BD00379E94 /* Models */, + 4FFBB97A2B82926B00379E94 /* Services */, 8F4738322848DE2A005DF65E /* Presentation */, ADF0CF75283FDAA10075F54D /* Helpers */, 8F4738332848DE46005DF65E /* Resources */, @@ -103,6 +273,60 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 4F498C4B2BA8DC7B00D3C5AC /* MovieQuizPresenterTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4F498C522BA8DC7B00D3C5AC /* Build configuration list for PBXNativeTarget "MovieQuizPresenterTest" */; + buildPhases = ( + 4F498C482BA8DC7B00D3C5AC /* Sources */, + 4F498C492BA8DC7B00D3C5AC /* Frameworks */, + 4F498C4A2BA8DC7B00D3C5AC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4F498C512BA8DC7B00D3C5AC /* PBXTargetDependency */, + ); + name = MovieQuizPresenterTest; + productName = MovieQuizPresenterTest; + productReference = 4F498C4C2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 4FA410852B9DD5180064C7C6 /* MovieQuizTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4FA4108C2B9DD5180064C7C6 /* Build configuration list for PBXNativeTarget "MovieQuizTests" */; + buildPhases = ( + 4FA410822B9DD5180064C7C6 /* Sources */, + 4FA410832B9DD5180064C7C6 /* Frameworks */, + 4FA410842B9DD5180064C7C6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4FA4108B2B9DD5180064C7C6 /* PBXTargetDependency */, + ); + name = MovieQuizTests; + productName = MovieQuizTests; + productReference = 4FA410862B9DD5180064C7C6 /* MovieQuizTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 4FA410A12B9F778A0064C7C6 /* MovieQuizUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4FA410AA2B9F778A0064C7C6 /* Build configuration list for PBXNativeTarget "MovieQuizUITests" */; + buildPhases = ( + 4FA4109E2B9F778A0064C7C6 /* Sources */, + 4FA4109F2B9F778A0064C7C6 /* Frameworks */, + 4FA410A02B9F778A0064C7C6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4FA410A92B9F778A0064C7C6 /* PBXTargetDependency */, + ); + name = MovieQuizUITests; + productName = MovieQuizUITests; + productReference = 4FA410A22B9F778A0064C7C6 /* MovieQuizUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; AD1ABAAD2831907B00B3E448 /* MovieQuiz */ = { isa = PBXNativeTarget; buildConfigurationList = AD1ABAC22831907F00B3E448 /* Build configuration list for PBXNativeTarget "MovieQuiz" */; @@ -127,10 +351,23 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1330; - LastUpgradeCheck = 1330; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; ORGANIZATIONNAME = ""; TargetAttributes = { + 4F498C4B2BA8DC7B00D3C5AC = { + CreatedOnToolsVersion = 15.3; + TestTargetID = AD1ABAAD2831907B00B3E448; + }; + 4FA410852B9DD5180064C7C6 = { + CreatedOnToolsVersion = 15.3; + LastSwiftMigration = 1530; + TestTargetID = AD1ABAAD2831907B00B3E448; + }; + 4FA410A12B9F778A0064C7C6 = { + CreatedOnToolsVersion = 15.3; + TestTargetID = AD1ABAAD2831907B00B3E448; + }; AD1ABAAD2831907B00B3E448 = { CreatedOnToolsVersion = 13.3.1; }; @@ -150,11 +387,38 @@ projectRoot = ""; targets = ( AD1ABAAD2831907B00B3E448 /* MovieQuiz */, + 4FA410852B9DD5180064C7C6 /* MovieQuizTests */, + 4FA410A12B9F778A0064C7C6 /* MovieQuizUITests */, + 4F498C4B2BA8DC7B00D3C5AC /* MovieQuizPresenterTest */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 4F498C4A2BA8DC7B00D3C5AC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4FA410842B9DD5180064C7C6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4FA410912B9DE0BF0064C7C6 /* YS Display-Medium.ttf in Resources */, + 4FA410932B9DE2C40064C7C6 /* Assets.xcassets in Resources */, + 4FA410922B9DE0C20064C7C6 /* YS Display-Bold.ttf in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4FA410A02B9F778A0064C7C6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; AD1ABAAC2831907B00B3E448 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -162,19 +426,60 @@ AD1ABABE2831907F00B3E448 /* LaunchScreen.storyboard in Resources */, AD1ABABB2831907F00B3E448 /* Assets.xcassets in Resources */, AD1ABAB92831907B00B3E448 /* Main.storyboard in Resources */, + 4FBE26E32B67F17900BD2A97 /* YS Display-Bold.ttf in Resources */, + 4FBE26E42B67F17900BD2A97 /* YS Display-Medium.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 4F498C482BA8DC7B00D3C5AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4F498C4F2BA8DC7B00D3C5AC /* MovieQuizPresenterTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4FA410822B9DD5180064C7C6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4FA410972B9DEB6F0064C7C6 /* ArrayTests.swift in Sources */, + 4FA4109B2B9DFC6A0064C7C6 /* MoviesLoaderTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4FA4109E2B9F778A0064C7C6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4FA410A52B9F778A0064C7C6 /* MovieQuizUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AD1ABAAA2831907B00B3E448 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4F17C5492BA626C70004B3F6 /* MovieQuizPresenter.swift in Sources */, AD5EE5DE284D7887003966EF /* UIColor+Extensions.swift in Sources */, + 4F90E2BA2B8412A200D7BBFA /* AlertPresenter.swift in Sources */, + 4F90E2B82B84124A00D7BBFA /* AlertModel.swift in Sources */, AD7AFA552836189F00399704 /* Array+Extensions.swift in Sources */, + 4F90E2B42B83E11900D7BBFA /* QuestionFactoryProtocol.swift in Sources */, + 4F498C472BA8DB1F00D3C5AC /* MovieQuizViewControllerProtocol.swift in Sources */, + 4F6A84CC2BA756A900062FCA /* QuizResultsViewModel.swift in Sources */, + 4F90E2B62B83F2D300D7BBFA /* QuestionFactoryDelegate.swift in Sources */, AD1ABAB62831907B00B3E448 /* MovieQuizViewController.swift in Sources */, + 4F6BE2382B94DAF80070EFFA /* NetworkClient.swift in Sources */, + 4F705CFC2B8501840090FFA7 /* StatisticService.swift in Sources */, + 4FFBB97F2B83A9E100379E94 /* QuizStepViewModel.swift in Sources */, + 4FFBB9832B83AA6000379E94 /* QuizQuestion.swift in Sources */, + 4F6BE23C2B94ED590070EFFA /* MoviesLoader.swift in Sources */, + 4FFBB97C2B8292C000379E94 /* QuestionFactory.swift in Sources */, + 4F6BE23A2B94DB4A0070EFFA /* MostPopularMovies.swift in Sources */, AD77F5742857F8810062FB14 /* Date+Extensions.swift in Sources */, AD1ABAB22831907B00B3E448 /* AppDelegate.swift in Sources */, AD1ABAB42831907B00B3E448 /* SceneDelegate.swift in Sources */, @@ -183,6 +488,24 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 4F498C512BA8DC7B00D3C5AC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AD1ABAAD2831907B00B3E448 /* MovieQuiz */; + targetProxy = 4F498C502BA8DC7B00D3C5AC /* PBXContainerItemProxy */; + }; + 4FA4108B2B9DD5180064C7C6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AD1ABAAD2831907B00B3E448 /* MovieQuiz */; + targetProxy = 4FA4108A2B9DD5180064C7C6 /* PBXContainerItemProxy */; + }; + 4FA410A92B9F778A0064C7C6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AD1ABAAD2831907B00B3E448 /* MovieQuiz */; + targetProxy = 4FA410A82B9F778A0064C7C6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ AD1ABAB72831907B00B3E448 /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -203,10 +526,149 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 4F498C532BA8DC7B00D3C5AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6D5U6N6YZA; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizPresenterTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MovieQuiz.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MovieQuiz"; + }; + name = Debug; + }; + 4F498C542BA8DC7B00D3C5AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6D5U6N6YZA; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizPresenterTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MovieQuiz.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MovieQuiz"; + }; + name = Release; + }; + 4FA4108D2B9DD5180064C7C6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6D5U6N6YZA; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "MovieQuizTests/MovieQuizTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MovieQuiz.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MovieQuiz"; + }; + name = Debug; + }; + 4FA4108E2B9DD5180064C7C6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6D5U6N6YZA; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "MovieQuizTests/MovieQuizTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MovieQuiz.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MovieQuiz"; + }; + name = Release; + }; + 4FA410AB2B9F778A0064C7C6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6D5U6N6YZA; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MovieQuiz; + }; + name = Debug; + }; + 4FA410AC2B9F778A0064C7C6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6D5U6N6YZA; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.4; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = MovieQuiz; + }; + name = Release; + }; AD1ABAC02831907F00B3E448 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -239,6 +701,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -267,6 +730,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -299,6 +763,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -323,7 +788,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = N29P9VDV4Y; + DEVELOPMENT_TEAM = 6D5U6N6YZA; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MovieQuiz/Resources/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -339,7 +804,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuiz; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizz; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -353,7 +818,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = N29P9VDV4Y; + DEVELOPMENT_TEAM = 6D5U6N6YZA; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MovieQuiz/Resources/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -369,7 +834,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuiz; + PRODUCT_BUNDLE_IDENTIFIER = ru.yandex.practicum.MovieQuizz; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -380,6 +845,33 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 4F498C522BA8DC7B00D3C5AC /* Build configuration list for PBXNativeTarget "MovieQuizPresenterTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4F498C532BA8DC7B00D3C5AC /* Debug */, + 4F498C542BA8DC7B00D3C5AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4FA4108C2B9DD5180064C7C6 /* Build configuration list for PBXNativeTarget "MovieQuizTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4FA4108D2B9DD5180064C7C6 /* Debug */, + 4FA4108E2B9DD5180064C7C6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4FA410AA2B9F778A0064C7C6 /* Build configuration list for PBXNativeTarget "MovieQuizUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4FA410AB2B9F778A0064C7C6 /* Debug */, + 4FA410AC2B9F778A0064C7C6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; AD1ABAA92831907B00B3E448 /* Build configuration list for PBXProject "MovieQuiz" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MovieQuiz/Helpers/UIColor+Extensions.swift b/MovieQuiz/Helpers/UIColor+Extensions.swift index 1ba50d2c81..3c64b5684e 100644 --- a/MovieQuiz/Helpers/UIColor+Extensions.swift +++ b/MovieQuiz/Helpers/UIColor+Extensions.swift @@ -1,3 +1,11 @@ import UIKit -extension UIColor { } + /*extension UIColor { + static var ypGreen: UIColor { UIColor(named: "YP Green") ?? UIColor.green } + static var ypRed: UIColor { UIColor(named: "YP Red") ?? UIColor.red } + static var ypBlack: UIColor { UIColor(named: "YP Black") ?? UIColor.black} + static var ypBackground: UIColor { UIColor(named: "YP Background") ?? UIColor.darkGray } + static var ypGray: UIColor { UIColor(named: "YP Gray") ?? UIColor.gray } + static var ypWhite: UIColor { UIColor(named: "YP White") ?? UIColor.white} + } +*/ diff --git a/MovieQuiz/Models/AlertModel.swift b/MovieQuiz/Models/AlertModel.swift new file mode 100644 index 0000000000..de8a57d79a --- /dev/null +++ b/MovieQuiz/Models/AlertModel.swift @@ -0,0 +1,15 @@ +// +// AlertModel.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 20.02.2024. +// + +import Foundation + +struct AlertModel { + let title: String + let message: String + let buttonText: String + let completion: (() -> Void)? +} diff --git a/MovieQuiz/Models/MostPopularMovies.swift b/MovieQuiz/Models/MostPopularMovies.swift new file mode 100644 index 0000000000..09c8f74d62 --- /dev/null +++ b/MovieQuiz/Models/MostPopularMovies.swift @@ -0,0 +1,25 @@ +// +// MostPopularMovies.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 03.03.2024. +// + +import Foundation + +struct MostPopularMovies: Codable { + let errorMessage: String + let items: [MostPopularMovie] +} + +struct MostPopularMovie: Codable { + let title: String + let rating: String + let imageURL: URL + + private enum CodingKeys: String, CodingKey { + case title = "fullTitle" + case rating = "imDbRating" + case imageURL = "image" + } +} diff --git a/MovieQuiz/Models/QuizQuestion.swift b/MovieQuiz/Models/QuizQuestion.swift new file mode 100644 index 0000000000..ead3bee04e --- /dev/null +++ b/MovieQuiz/Models/QuizQuestion.swift @@ -0,0 +1,14 @@ +// +// QuizQuestion.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 19.02.2024. +// + +import Foundation + +struct QuizQuestion { + let image: Data + let text: String + let correctAnswer: Bool +} diff --git a/MovieQuiz/Models/QuizResultsViewModel.swift b/MovieQuiz/Models/QuizResultsViewModel.swift new file mode 100644 index 0000000000..e4fec40cdd --- /dev/null +++ b/MovieQuiz/Models/QuizResultsViewModel.swift @@ -0,0 +1,14 @@ +// +// QuizResultsViewModel.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 17.03.2024. +// + +import Foundation + +struct QuizResultsViewModel { + let title: String + let text: String + let buttonText: String +} diff --git a/MovieQuiz/Models/QuizStepViewModel.swift b/MovieQuiz/Models/QuizStepViewModel.swift new file mode 100644 index 0000000000..905663485f --- /dev/null +++ b/MovieQuiz/Models/QuizStepViewModel.swift @@ -0,0 +1,15 @@ +// +// QuizStepViewModel.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 19.02.2024. +// + +import Foundation +import UIKit + +struct QuizStepViewModel { + let image: UIImage + let question: String + let questionNumber: String + } diff --git a/MovieQuiz/Presentation/AlertPresenter.swift b/MovieQuiz/Presentation/AlertPresenter.swift new file mode 100644 index 0000000000..a6dab0866b --- /dev/null +++ b/MovieQuiz/Presentation/AlertPresenter.swift @@ -0,0 +1,26 @@ +// +// AlertPresenter.swift +// MovieQuiz +// +// Created by ДЖООООН СИНААААА ТА ТА ДА ТААААМ on 20.02.2024. +// + +import UIKit + +class AlertPresenter { + weak var viewController: UIViewController? + + init(viewController: UIViewController) { + self.viewController = viewController + } + + func showAlert(with model: AlertModel) { + let alert = UIAlertController(title: model.title, message: model.message, preferredStyle: .alert) + let action = UIAlertAction(title: model.buttonText, style: .default) { _ in + model.completion?() + } + alert.addAction(action) + viewController?.present(alert, animated: true, completion: nil) + } +} + diff --git a/MovieQuiz/Presentation/Base.lproj/Main.storyboard b/MovieQuiz/Presentation/Base.lproj/Main.storyboard index cf28943f42..88187e6a7e 100644 --- a/MovieQuiz/Presentation/Base.lproj/Main.storyboard +++ b/MovieQuiz/Presentation/Base.lproj/Main.storyboard @@ -1,12 +1,21 @@ - - + + - + + + + + YSDisplay-Bold + + + YSDisplay-Medium + + @@ -15,13 +24,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + - + + + + + + + + + diff --git a/MovieQuiz/Presentation/MovieQuizPresenter.swift b/MovieQuiz/Presentation/MovieQuizPresenter.swift new file mode 100644 index 0000000000..9cb2b9c9b0 --- /dev/null +++ b/MovieQuiz/Presentation/MovieQuizPresenter.swift @@ -0,0 +1,180 @@ +// +// MovieQuizPresenter.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 16.03.2024. +// + +import Foundation +import UIKit + +final class MovieQuizPresenter: QuestionFactoryDelegate { + + // MARK: - Переменные и константы + + private var correctAnswers: Int = 0 + private let questionsAmount: Int = 10 + private var currentQuestionIndex: Int = 0 + private var currentQuestion: QuizQuestion? + private let statisticService: StatisticService! + var questionFactory: QuestionFactoryProtocol? + private weak var viewController: MovieQuizViewControllerProtocol? + + init(viewController: MovieQuizViewControllerProtocol) { + self.viewController = viewController + + + statisticService = StatisticServiceImplementation() + + questionFactory = QuestionFactory(moviesLoader: MoviesLoader(), delegate: self) + questionFactory?.loadData() + viewController.showLoadingIndicator() + } + + // MARK: - Method "IsLastQuestion" + + func isLastQuestion() -> Bool { + currentQuestionIndex >= questionsAmount - 1 + } + + // MARK: - Method "ResetQuestionIndex" + + func resetQuestionIndex() { + currentQuestionIndex = 0 + } + + // MARK: - Method "IsLastQuestion" + + func switchToNextQuestion() { + currentQuestionIndex += 1 + } + + // MARK: - Method "RestartGame" + + func restartGame() { + currentQuestionIndex = 0 + correctAnswers = 0 + questionFactory?.requestNextQuestion() + } + + // MARK: - Method "DidAnswer" + + func didAnswer(isCorrectAnswer: Bool) { + if isCorrectAnswer { + correctAnswers += 1 + } + } + + // MARK: - Method "Convert" + + func convert(model: QuizQuestion) -> QuizStepViewModel { + return QuizStepViewModel( + image: UIImage(data: model.image) ?? UIImage(), + question: model.text, + questionNumber: "\(currentQuestionIndex + 1)/\(questionsAmount)") + } + + // MARK: - QuestionFactoryDelegate + + func didLoadDataFromServer() { + viewController?.hideLoadingIndicator() + questionFactory?.requestNextQuestion() + } + + func didFailToLoadData(with error: Error) { + let message = error.localizedDescription + viewController?.showNetworkError(message: message) + } + + // MARK: - Method "DidAnswer" + + private func didAnswer(isYes: Bool) { + guard let currentQuestion = currentQuestion else { + return + } + + let givenAnswer = isYes + + proceedWithAnswer(isCorrect: givenAnswer == currentQuestion.correctAnswer) + } + + + // MARK: - Button Yes + + func yesButtonClicked() { + didAnswer(isYes: true) + } + + // MARK: - Button No + + func noButtonClicked() { + didAnswer(isYes: false) + } + + // MARK: - Method "DidReceiveNextQuestion" + + func didReceiveNextQuestion(question: QuizQuestion?) { + + guard let question = question else { + return + } + + currentQuestion = question + let viewModel = convert(model: question) + DispatchQueue.main.async { [weak self] in + self?.viewController?.show(quiz: viewModel) + } + + } + + // MARK: - Method "ShowNextQuestionOrResults" + + private func proceedToNextQuestionOrResults() { + if self.isLastQuestion() { + let text = "Вы ответили на \(correctAnswers) из 10, попробуйте ещё раз!" + + let viewModel = QuizResultsViewModel( + title: "Этот раунд окончен!", + text: text, + buttonText: "Сыграть ещё раз") + viewController?.show(quiz: viewModel) + } else { + self.switchToNextQuestion() + questionFactory?.requestNextQuestion() + } + } + + // MARK: - Method "MakeResultsMessage" + + func makeResultsMessage() -> String { + statisticService.store(correct: correctAnswers, total: questionsAmount) + + let bestGame = statisticService.bestGame + + let totalPlaysCountLine = "Количество сыгранных квизов: \(statisticService.gamesCount)" + let currentGameResultLine = "Ваш результат: \(correctAnswers)\\\(questionsAmount)" + let bestGameInfoLine = "Рекорд: \(bestGame.correct)\\\(bestGame.total)" + + " (\(bestGame.date.dateTimeString))" + let averageAccuracyLine = "Средняя точность: \(String(format: "%.2f", statisticService.totalAccuracy))%" + + let resultMessage = [ + currentGameResultLine, totalPlaysCountLine, bestGameInfoLine, averageAccuracyLine + ].joined(separator: "\n") + + return resultMessage + } + + // MARK: - Method "ShowAnswerResult" + + private func proceedWithAnswer(isCorrect: Bool) { + didAnswer(isCorrectAnswer: isCorrect) + + viewController?.highlightImageBorder(isCorrectAnswer: isCorrect) + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + guard let self = self else { return } + self.proceedToNextQuestionOrResults() + } + } + +} diff --git a/MovieQuiz/Presentation/MovieQuizViewController.swift b/MovieQuiz/Presentation/MovieQuizViewController.swift index 7aa88b8a27..2b9e848f49 100644 --- a/MovieQuiz/Presentation/MovieQuizViewController.swift +++ b/MovieQuiz/Presentation/MovieQuizViewController.swift @@ -1,72 +1,121 @@ import UIKit -final class MovieQuizViewController: UIViewController { +final class MovieQuizViewController: UIViewController, MovieQuizViewControllerProtocol { + + // MARK: - UIOutlets + + @IBOutlet private var imageView: UIImageView! + @IBOutlet private var textLabel: UILabel! + @IBOutlet private var counterLabel: UILabel! + @IBOutlet private var activityIndicator: UIActivityIndicatorView! + + // MARK: - Переменные и константы + + private var alertPresenter: AlertPresenter? + private var statisticService: StatisticService! + private var presenter: MovieQuizPresenter! + // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() + + presenter = MovieQuizPresenter(viewController: self) + + imageView.layer.cornerRadius = 20 + + + alertPresenter = AlertPresenter(viewController: self) + } + + // MARK: - Button Yes + + @IBAction private func yesButtonClicked(_ sender: UIButton) { + presenter.yesButtonClicked() + } + + // MARK: - Button No + + @IBAction private func noButtonClicked(_ sender: UIButton) { + presenter.noButtonClicked() + } + + // MARK: - Method "ShowLoadingIndicator" + + func showLoadingIndicator() { + activityIndicator.isHidden = false + activityIndicator.startAnimating() + } + + // MARK: - Method "HideLoadingIndicator" + + func hideLoadingIndicator() { + activityIndicator.isHidden = true + } + + // MARK: - Method "Show Quiz Result" + + func show(quiz result: QuizResultsViewModel) { + let message = presenter.makeResultsMessage() + + let alert = UIAlertController( + title: result.title, + message: message, + preferredStyle: .alert) + + let action = UIAlertAction(title: result.buttonText, style: .default) { [weak self] _ in + guard let self = self else { return } + + self.presenter.restartGame() + } + + alert.addAction(action) + + self.present(alert, animated: true, completion: nil) + } + + // MARK: - Method "ShowNetworkError" + + func showNetworkError(message: String) { + hideLoadingIndicator() + + let alertModel = AlertModel(title: "Ошибка", + message: message, + buttonText: "Попробовать еще раз") { [weak self] in + guard let self = self else { return } + + self.presenter.resetQuestionIndex() + self.presenter.restartGame() + + } + alertPresenter?.showAlert(with: alertModel) + self.presenter.questionFactory?.loadData() + + } + + // MARK: - Method "resetImageViewBorder" + + private func resetImageViewBorder() { + imageView.layer.borderWidth = 0 + imageView.layer.borderColor = UIColor.clear.cgColor + } + + // MARK: - Method "Show Quiz Step" + + func show(quiz step: QuizStepViewModel) { + counterLabel.text = step.questionNumber + imageView.image = step.image + textLabel.text = step.question + resetImageViewBorder() + } + + // MARK: - Method "HighlightImageBorder" + + func highlightImageBorder(isCorrectAnswer: Bool) { + imageView.layer.masksToBounds = true + imageView.layer.borderWidth = 8 + imageView.layer.borderColor = isCorrectAnswer ? UIColor.ypGreen.cgColor : UIColor.ypRed.cgColor } } -/* - Mock-данные - - - Картинка: The Godfather - Настоящий рейтинг: 9,2 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: ДА - - - Картинка: The Dark Knight - Настоящий рейтинг: 9 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: ДА - - - Картинка: Kill Bill - Настоящий рейтинг: 8,1 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: ДА - - - Картинка: The Avengers - Настоящий рейтинг: 8 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: ДА - - - Картинка: Deadpool - Настоящий рейтинг: 8 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: ДА - - - Картинка: The Green Knight - Настоящий рейтинг: 6,6 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: ДА - - - Картинка: Old - Настоящий рейтинг: 5,8 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: НЕТ - - - Картинка: The Ice Age Adventures of Buck Wild - Настоящий рейтинг: 4,3 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: НЕТ - - - Картинка: Tesla - Настоящий рейтинг: 5,1 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: НЕТ - - - Картинка: Vivarium - Настоящий рейтинг: 5,8 - Вопрос: Рейтинг этого фильма больше чем 6? - Ответ: НЕТ - */ +/// Из комбинации лени и логики получаются программисты. diff --git a/MovieQuiz/Resources/Assets.xcassets/Colors/YP Background.colorset/Contents.json b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Background.colorset/Contents.json new file mode 100644 index 0000000000..55e5b268d6 --- /dev/null +++ b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Background.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "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" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MovieQuiz/Resources/Assets.xcassets/Colors/YP Black.colorset/Contents.json b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Black.colorset/Contents.json new file mode 100644 index 0000000000..76de6f7ce1 --- /dev/null +++ b/MovieQuiz/Resources/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" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MovieQuiz/Resources/Assets.xcassets/Colors/YP Green.colorset/Contents.json b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Green.colorset/Contents.json new file mode 100644 index 0000000000..cbbcbdeb02 --- /dev/null +++ b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Green.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.557", + "green" : "0.761", + "red" : "0.376" + } + }, + "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/MovieQuiz/Resources/Assets.xcassets/Colors/YP Grey.colorset/Contents.json b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Grey.colorset/Contents.json new file mode 100644 index 0000000000..c69532dd1f --- /dev/null +++ b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Grey.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.922", + "green" : "0.910", + "red" : "0.902" + } + }, + "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/MovieQuiz/Resources/Assets.xcassets/Colors/YP Red.colorset/Contents.json b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Red.colorset/Contents.json new file mode 100644 index 0000000000..ea7f11102d --- /dev/null +++ b/MovieQuiz/Resources/Assets.xcassets/Colors/YP Red.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.424", + "green" : "0.420", + "red" : "0.961" + } + }, + "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/MovieQuiz/Resources/Assets.xcassets/Colors/YP White.colorset/Contents.json b/MovieQuiz/Resources/Assets.xcassets/Colors/YP White.colorset/Contents.json new file mode 100644 index 0000000000..22c4bb0a8d --- /dev/null +++ b/MovieQuiz/Resources/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/MovieQuiz/Resources/Base.lproj/LaunchScreen.storyboard b/MovieQuiz/Resources/Base.lproj/LaunchScreen.storyboard index d1e0da06b2..af0b67fa98 100644 --- a/MovieQuiz/Resources/Base.lproj/LaunchScreen.storyboard +++ b/MovieQuiz/Resources/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,10 @@ - + - + + @@ -15,13 +16,31 @@ + + + + + + + + + + + + - + + + + + + + diff --git a/MovieQuiz/Resources/Fonts/YS Display/YS Display-Bold.ttf b/MovieQuiz/Resources/Fonts/YS Display/YS Display-Bold.ttf new file mode 100644 index 0000000000..f9b3f03cce Binary files /dev/null and b/MovieQuiz/Resources/Fonts/YS Display/YS Display-Bold.ttf differ diff --git a/MovieQuiz/Resources/Fonts/YS Display/YS Display-Medium.ttf b/MovieQuiz/Resources/Fonts/YS Display/YS Display-Medium.ttf new file mode 100644 index 0000000000..cc63032e21 Binary files /dev/null and b/MovieQuiz/Resources/Fonts/YS Display/YS Display-Medium.ttf differ diff --git a/MovieQuiz/Resources/Info.plist b/MovieQuiz/Resources/Info.plist index dd3c9afdae..b6bd21c7fc 100644 --- a/MovieQuiz/Resources/Info.plist +++ b/MovieQuiz/Resources/Info.plist @@ -2,6 +2,11 @@ + UIAppFonts + + YS Display-Bold.ttf + YS Display-Medium.ttf + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/MovieQuiz/Services/MovieQuizViewControllerProtocol.swift b/MovieQuiz/Services/MovieQuizViewControllerProtocol.swift new file mode 100644 index 0000000000..0b23ac1a4d --- /dev/null +++ b/MovieQuiz/Services/MovieQuizViewControllerProtocol.swift @@ -0,0 +1,20 @@ +// +// MovieQuizViewControllerProtocol.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 18.03.2024. +// + +import Foundation + +protocol MovieQuizViewControllerProtocol: AnyObject { + func show(quiz step: QuizStepViewModel) + func show(quiz result: QuizResultsViewModel) + + func highlightImageBorder(isCorrectAnswer: Bool) + + func showLoadingIndicator() + func hideLoadingIndicator() + + func showNetworkError(message: String) +} diff --git a/MovieQuiz/Services/MoviesLoader.swift b/MovieQuiz/Services/MoviesLoader.swift new file mode 100644 index 0000000000..37ea75425b --- /dev/null +++ b/MovieQuiz/Services/MoviesLoader.swift @@ -0,0 +1,43 @@ +// +// MoviesLoader.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 03.03.2024. +// + +import Foundation + +protocol MoviesLoading { + func loadMovies(handler: @escaping (Result) -> Void) +} + +struct MoviesLoader: MoviesLoading { + private let networkClient: NetworkRouting + + init(networkClient: NetworkRouting = NetworkClient()) { + self.networkClient = networkClient + } + + private var mostPopularMoviesUrl: URL { + guard let url = URL(string: "https://tv-api.com/en/API/Top250Movies/k_zcuw1ytf") else { + preconditionFailure("Unable to construct mostPopularMoviesUrl") + } + return url + } + + func loadMovies(handler: @escaping (Result) -> Void) { + networkClient.fetch(url: mostPopularMoviesUrl) { result in + switch result { + case .success(let data): + do { + let mostPopularMovies = try JSONDecoder().decode(MostPopularMovies.self, from: data) + handler(.success(mostPopularMovies)) + } catch { + handler(.failure(error)) + } + case .failure(let error): + handler(.failure(error)) + } + } + } +} diff --git a/MovieQuiz/Services/NetworkClient.swift b/MovieQuiz/Services/NetworkClient.swift new file mode 100644 index 0000000000..e8f94b7084 --- /dev/null +++ b/MovieQuiz/Services/NetworkClient.swift @@ -0,0 +1,93 @@ +// +// NetworkClient.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 03.03.2024. +// + +import Foundation + +protocol NetworkRouting { + func fetch(url: URL, handler: @escaping (Result) -> Void) +} + +struct NetworkClient: NetworkRouting { + + private enum NetworkError: Error { + case codeError + } + + func fetch(url: URL, handler: @escaping (Result) -> Void) { + let request = URLRequest(url: url) + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + + if let error = error { + handler(.failure(error)) + return + } + + if let response = response as? HTTPURLResponse, + response.statusCode < 200 || response.statusCode >= 300 { + handler(.failure(NetworkError.codeError)) + return + } + + guard let data = data else { return } + handler(.success(data)) + } + + task.resume() + } +} + +struct StubNetworkClient: NetworkRouting { + + enum TestError: Error { + case test + } + + let emulateError: Bool + + func fetch(url: URL, handler: @escaping (Result) -> Void) { + if emulateError { + handler(.failure(TestError.test)) + } else { + handler(.success(expectedResponse)) + } + } + + private var expectedResponse: Data { + """ + { + "errorMessage" : "", + "items" : [ + { + "crew" : "Dan Trachtenberg (dir.), Amber Midthunder, Dakota Beavers", + "fullTitle" : "Prey (2022)", + "id" : "tt11866324", + "imDbRating" : "7.2", + "imDbRatingCount" : "93332", + "image" : "https://m.media-amazon.com/images/M/MV5BMDBlMDYxMDktOTUxMS00MjcxLWE2YjQtNjNhMjNmN2Y3ZDA1XkEyXkFqcGdeQXVyMTM1MTE1NDMx._V1_Ratio0.6716_AL_.jpg", + "rank" : "1", + "rankUpDown" : "+23", + "title" : "Prey", + "year" : "2022" + }, + { + "crew" : "Anthony Russo (dir.), Ryan Gosling, Chris Evans", + "fullTitle" : "The Gray Man (2022)", + "id" : "tt1649418", + "imDbRating" : "6.5", + "imDbRatingCount" : "132890", + "image" : "https://m.media-amazon.com/images/M/MV5BOWY4MmFiY2QtMzE1YS00NTg1LWIwOTQtYTI4ZGUzNWIxNTVmXkEyXkFqcGdeQXVyODk4OTc3MTY@._V1_Ratio0.6716_AL_.jpg", + "rank" : "2", + "rankUpDown" : "-1", + "title" : "The Gray Man", + "year" : "2022" + } + ] + } + """.data(using: .utf8) ?? Data() + } +} diff --git a/MovieQuiz/Services/QuestionFactory.swift b/MovieQuiz/Services/QuestionFactory.swift new file mode 100644 index 0000000000..62eabcd644 --- /dev/null +++ b/MovieQuiz/Services/QuestionFactory.swift @@ -0,0 +1,66 @@ +// +// QuestionFactory.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 18.02.2024. +// + +import Foundation +final class QuestionFactory: QuestionFactoryProtocol { + + + private var movies: [MostPopularMovie] = [] + private let moviesLoader: MoviesLoading + weak var delegate: QuestionFactoryDelegate? + + init(moviesLoader: MoviesLoading, delegate: QuestionFactoryDelegate?) { + self.moviesLoader = moviesLoader + self.delegate = delegate + } + + func loadData() { + moviesLoader.loadMovies { [weak self] result in + DispatchQueue.main.async { + guard let self = self else { return } + switch result { + case .success(let mostPopularMovies): + self.movies = mostPopularMovies.items + self.delegate?.didLoadDataFromServer() + case .failure(let error): + self.delegate?.didFailToLoadData(with: error) + } + } + } + } + + func requestNextQuestion() { + DispatchQueue.global().async { [weak self] in + guard let self = self else { return } + let index = (0.. 7 + + let question = QuizQuestion(image: imageData, + text: text, + correctAnswer: correctAnswer) + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.didReceiveNextQuestion(question: question) + } + } + } +} diff --git a/MovieQuiz/Services/QuestionFactoryDelegate.swift b/MovieQuiz/Services/QuestionFactoryDelegate.swift new file mode 100644 index 0000000000..3ae4bded1b --- /dev/null +++ b/MovieQuiz/Services/QuestionFactoryDelegate.swift @@ -0,0 +1,13 @@ +// +// QuestionFactoryDelegate.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 19.02.2024. +// +import Foundation + +protocol QuestionFactoryDelegate: AnyObject { + func didReceiveNextQuestion(question: QuizQuestion?) + func didLoadDataFromServer() + func didFailToLoadData(with error: Error) +} diff --git a/MovieQuiz/Services/QuestionFactoryProtocol.swift b/MovieQuiz/Services/QuestionFactoryProtocol.swift new file mode 100644 index 0000000000..c55945d1db --- /dev/null +++ b/MovieQuiz/Services/QuestionFactoryProtocol.swift @@ -0,0 +1,14 @@ +// +// QuestionFactoryProtocol.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 19.02.2024. +// + +import Foundation + +protocol QuestionFactoryProtocol { + var delegate: QuestionFactoryDelegate? { get set } + func requestNextQuestion() + func loadData() +} diff --git a/MovieQuiz/Services/StatisticService.swift b/MovieQuiz/Services/StatisticService.swift new file mode 100644 index 0000000000..e81d5d3d0a --- /dev/null +++ b/MovieQuiz/Services/StatisticService.swift @@ -0,0 +1,74 @@ +// +// StatisticService.swift +// MovieQuiz +// +// Created by Кирилл Марьясов on 20.02.2024. +// + +import Foundation + +final class StatisticServiceImplementation: StatisticService { + + private let userDefaults = UserDefaults.standard + + private enum Keys: String { + case correct, total, bestGame, gamesCount, totalAccuracy + } + + func store(correct count: Int, total amount: Int) { + let currentGame = GameRecord(correct: count, total: amount, date: Date()) + + gamesCount += 1 + + let newTotalAccuracy = ((totalAccuracy * Double(gamesCount - 1)) + (Double(count) / Double(amount) * 100)) / Double(gamesCount) + totalAccuracy = newTotalAccuracy + + if currentGame.isBetterThan(bestGame) { + bestGame = currentGame + } + } + + var bestGame: GameRecord { + get { + guard let data = userDefaults.data(forKey: Keys.bestGame.rawValue), + let record = try? JSONDecoder().decode(GameRecord.self, from: data) else { + return .init(correct: 0, total: 0, date: Date()) + } + return record + } + set { + guard let data = try? JSONEncoder().encode(newValue) else { + print("Unable to save the best game result") + return + } + userDefaults.set(data, forKey: Keys.bestGame.rawValue) + } + } + + var totalAccuracy: Double { + get { userDefaults.double(forKey: Keys.totalAccuracy.rawValue) } + set { userDefaults.set(newValue, forKey: Keys.totalAccuracy.rawValue) } + } + + var gamesCount: Int { + get { userDefaults.integer(forKey: Keys.gamesCount.rawValue) } + set { userDefaults.set(newValue, forKey: Keys.gamesCount.rawValue) } + } +} + +protocol StatisticService { + func store(correct count: Int, total amount: Int) + var totalAccuracy: Double { get set } + var gamesCount: Int { get set } + var bestGame: GameRecord { get set } +} + +struct GameRecord: Codable { + let correct: Int + let total: Int + let date: Date + + func isBetterThan(_ another: GameRecord) -> Bool { + correct > another.correct + } +} diff --git a/MovieQuizPresenterTest/MovieQuizPresenterTest.swift b/MovieQuizPresenterTest/MovieQuizPresenterTest.swift new file mode 100644 index 0000000000..3d54146f67 --- /dev/null +++ b/MovieQuizPresenterTest/MovieQuizPresenterTest.swift @@ -0,0 +1,59 @@ +// +// MovieQuizPresenterTest.swift +// MovieQuizPresenterTest +// +// Created by Кирилл Марьясов on 18.03.2024. +// + + +import XCTest +@testable import MovieQuiz + +final class MovieQuizViewControllerMock: MovieQuizViewControllerProtocol { + func showAlert(model: MovieQuiz.AlertModel) { + + } + + func updateButtonState(isEnabled: Bool) { + + } + + func show(quiz step: MovieQuiz.QuizStepViewModel) { + + } + + func show(quiz result: MovieQuiz.QuizResultsViewModel) { + + } + + func highlightImageBorder(isCorrectAnswer: Bool) { + + } + + func showLoadingIndicator() { + + } + + func hideLoadingIndicator() { + + } + + func showNetworkError(message: String) { + + } +} + +final class MovieQuizPresentertest: XCTestCase { + func testPresenterConvertModel() throws { + let viewControllerMock = MovieQuizViewControllerMock() + let sut = MovieQuizPresenter(viewController: viewControllerMock) + + let emptyData = Data() + let question = QuizQuestion(image: emptyData, text: "Question Text", correctAnswer: true) + let viewModel = sut.convert(model: question) + + XCTAssertNotNil(viewModel.image) + XCTAssertEqual(viewModel.question, "Question Text") + XCTAssertEqual(viewModel.questionNumber, "1/10") + } +} diff --git a/MovieQuizTests/ArrayTests.swift b/MovieQuizTests/ArrayTests.swift new file mode 100644 index 0000000000..66f14e6c14 --- /dev/null +++ b/MovieQuizTests/ArrayTests.swift @@ -0,0 +1,34 @@ +// +// ArrayTests.swift +// MovieQuizTests +// +// Created by Кирилл Марьясов on 10.03.2024. +// + +import XCTest +@testable import MovieQuiz + +class ArrayTests : XCTestCase { + func testGetValueInRange() throws { + //Given + let array = [1,1,2,3,5] + + //When + let value = array [safe: 2] + + //Then + XCTAssertNotNil(value) + XCTAssertEqual(value, 2) + } + + func testGetValueOutOfRange() throws { + //Given + let array = [1,1,2,3,5] + + //When + let value = array [safe: 20] + + //Then + XCTAssertNil(value) + } +} diff --git a/MovieQuizTests/MovieQuizTests-Bridging-Header.h b/MovieQuizTests/MovieQuizTests-Bridging-Header.h new file mode 100644 index 0000000000..1b2cb5d6d0 --- /dev/null +++ b/MovieQuizTests/MovieQuizTests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/MovieQuizTests/MovieQuizTests.swift b/MovieQuizTests/MovieQuizTests.swift new file mode 100644 index 0000000000..4abcbb6e6e --- /dev/null +++ b/MovieQuizTests/MovieQuizTests.swift @@ -0,0 +1,30 @@ +// +// MovieQuizTests.swift +// MovieQuizTests +// +// Created by Кирилл Марьясов on 10.03.2024. +// + +import XCTest + +struct ArithmeticOperations { + func addition(num1: Int, num2: Int) -> Int { + return num1 + num2 + } + + func subtraction(num1: Int, num2: Int) -> Int { + return num1 - num2 + } + + func multiplication(num1: Int, num2: Int) -> Int { + return num1 * num2 + } +} + +class MovieQuizTests: XCTestCase { + func testAddition() throws { + let arithmeticOperations = ArithmeticOperations() + let result = arithmeticOperations.addition(num1: 1, num2: 2) + XCTAssertEqual(result, 3) + } +} diff --git a/MovieQuizTests/MoviesLoaderTests.swift b/MovieQuizTests/MoviesLoaderTests.swift new file mode 100644 index 0000000000..58d2e2581c --- /dev/null +++ b/MovieQuizTests/MoviesLoaderTests.swift @@ -0,0 +1,58 @@ +// +// MoviesLoaderTests.swift +// MovieQuizTests +// +// Created by Кирилл Марьясов on 10.03.2024. +// + +import Foundation + +import XCTest +@testable import MovieQuiz + +class MoviesLoaderTests: XCTestCase { + func testSuccessLoading() throws { + // Given + let stubNetworkClient = StubNetworkClient(emulateError: false) // говорим, что не хотим эмулировать ошибку + let loader = MoviesLoader(networkClient: stubNetworkClient) + + // When + let expectation = expectation(description: "Loading expectation") + + loader.loadMovies { result in + // Then + switch result { + case .success(let movies): + // давайте проверим, что пришло, например, два фильма — ведь в тестовых данных их всего два + XCTAssertEqual(movies.items.count, 2) + expectation.fulfill() + case .failure(_): + XCTFail("Unexpected failure") + } + } + + waitForExpectations(timeout: 1) + } + + func testFailureLoading() throws { + // Given + let stubNetworkClient = StubNetworkClient(emulateError: true) // говорим, что хотим эмулировать ошибку + let loader = MoviesLoader(networkClient: stubNetworkClient) + + // When + let expectation = expectation(description: "Loading expectation") + + loader.loadMovies { result in + // Then + switch result { + case .failure(let error): + XCTAssertNotNil(error) + expectation.fulfill() + case .success(_): + XCTFail("Unexpected failure") + } + } + + waitForExpectations(timeout: 1) + } +} diff --git a/MovieQuizUITests/MovieQuizUITests.swift b/MovieQuizUITests/MovieQuizUITests.swift new file mode 100644 index 0000000000..cce59d7d9c --- /dev/null +++ b/MovieQuizUITests/MovieQuizUITests.swift @@ -0,0 +1,92 @@ +// +// MovieQuizUITests.swift +// MovieQuizUITests +// +// Created by Кирилл Марьясов on 11.03.2024. +// +import XCTest + +final class MovieQuizUITests: XCTestCase { + + var app: XCUIApplication! + + override func setUpWithError() throws { + try super.setUpWithError() + + app = XCUIApplication() + app.launch() + continueAfterFailure = false + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + + app.terminate() + app = nil + } + + func testYesButton() { + sleep(3) + + let firstPoster = app.images["Poster"] + let firstPosterData = firstPoster.screenshot().pngRepresentation + + app.buttons["Yes"].tap() + sleep(3) + + let secondPoster = app.images["Poster"] + let secondPosterdata = secondPoster.screenshot().pngRepresentation + + XCTAssertNotEqual(firstPosterData, secondPosterdata) + } + func testNoButton() { + sleep(3) + let firstPoster = app.images["Poster"] + let firstPosterData = firstPoster.screenshot().pngRepresentation + + app.buttons["No"].tap() + sleep(3) + + let secondPoster = app.images["Poster"] + let secondPosterData = secondPoster.screenshot().pngRepresentation + + let indexLabel = app.staticTexts["Index"] + + XCTAssertEqual(indexLabel.label, "2/10") + XCTAssertNotEqual(firstPosterData, secondPosterData) + } + + func testGameFinish() { + sleep(3) + + for _ in 1...10 { + app.buttons["Yes"].tap() + sleep(3) + } + + let alert = app.alerts["Этот раунд окончен!"] + + XCTAssertTrue(alert.exists) + XCTAssertTrue(alert.label == "Этот раунд окончен!") + XCTAssertTrue(alert.buttons.firstMatch.label == "Сыграть ещё раз") + } + + func testAlertDismiss() { + sleep(3) + for _ in 1...10 { + app.buttons["No"].tap() + sleep(3) + } + + let alert = app.alerts["Этот раунд окончен!"] + alert.buttons.firstMatch.tap() + + sleep(3) + + let indexLabel = app.staticTexts["Index"] + + XCTAssertFalse(alert.exists) + XCTAssertTrue(indexLabel.label == "1/10") + } +} +