Mercurial > public > geoquiz
changeset 10:a793f33f05fb
refactor code and fix layout
line wrap: on
line diff
--- a/GeoQuiz.xcodeproj/project.pbxproj Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz.xcodeproj/project.pbxproj Sat Oct 08 21:36:40 2022 +0200 @@ -7,16 +7,15 @@ objects = { /* Begin PBXBuildFile section */ - 95030CEA28D1BA4D001AA3A1 /* AnswerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */; }; + 95030CEA28D1BA4D001AA3A1 /* AnswerButtonHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95030CE928D1BA4D001AA3A1 /* AnswerButtonHelper.swift */; }; 9509A8DE28E5A19A00CFCDBA /* countries.json in Resources */ = {isa = PBXBuildFile; fileRef = 9509A8DD28E5A19A00CFCDBA /* countries.json */; }; - 9509A8E028E5A3C500CFCDBA /* GuessTheCurrencyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9509A8DF28E5A3C500CFCDBA /* GuessTheCurrencyView.swift */; }; 9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9509A8E128E5A3D700CFCDBA /* GuessThePopulationView.swift */; }; 951AFAEA28E5655C00A4A4BD /* cities.json in Resources */ = {isa = PBXBuildFile; fileRef = 951AFAE828E5655C00A4A4BD /* cities.json */; }; 951AFAED28E5657500A4A4BD /* CityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951AFAEC28E5657500A4A4BD /* CityModel.swift */; }; 951AFAEF28E565FE00A4A4BD /* CountryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */; }; - 951AFAF128E5735400A4A4BD /* CityGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951AFAF028E5735400A4A4BD /* CityGame.swift */; }; + 951AFAF128E5735400A4A4BD /* CityGameClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951AFAF028E5735400A4A4BD /* CityGameClass.swift */; }; 951B630228D1A87C004F9877 /* GuessTheCapitalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951B630128D1A87C004F9877 /* GuessTheCapitalView.swift */; }; - 951D197328D485E000671FAD /* CustomColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951D197228D485E000671FAD /* CustomColors.swift */; }; + 951D197328D485E000671FAD /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951D197228D485E000671FAD /* ColorExtension.swift */; }; 952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952E41E828DC521200198643 /* GameAlertsModifier.swift */; }; 952E41ED28DC658900198643 /* SettingsModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952E41EC28DC658900198643 /* SettingsModalView.swift */; }; 952E41F228DC6F6E00198643 /* correctAnswer.wav in Resources */ = {isa = PBXBuildFile; fileRef = 952E41F028DC6F6D00198643 /* correctAnswer.wav */; }; @@ -24,35 +23,35 @@ 9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9539829228C51EDE00B70973 /* GeoQuizApp.swift */; }; 9539829728C51EDF00B70973 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9539829628C51EDF00B70973 /* Assets.xcassets */; }; 9539829A28C51EDF00B70973 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9539829928C51EDF00B70973 /* Preview Assets.xcassets */; }; - 955A658128D703EB00CEEC6D /* GameToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658028D703EB00CEEC6D /* GameToolbar.swift */; }; - 955A658328D733E400CEEC6D /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658228D733E400CEEC6D /* Game.swift */; }; - 955A65A928D7815E00CEEC6D /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A65A828D7815E00CEEC6D /* Haptics.swift */; }; - 956273EA28CB2E98008DC094 /* FlagImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956273E928CB2E98008DC094 /* FlagImage.swift */; }; + 955950BB28F15FF2001BDEE8 /* FormatterExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */; }; + 955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */; }; + 955A658328D733E400CEEC6D /* GameProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */; }; + 955A65A928D7815E00CEEC6D /* HapticsClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A65A828D7815E00CEEC6D /* HapticsClass.swift */; }; + 956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956273E928CB2E98008DC094 /* FlagImageHelper.swift */; }; 9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9590359428E098FF00B24560 /* ProfileModalView.swift */; }; - 95919DB628F076BF00F21F8F /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB528F076BF00F21F8F /* User.swift */; }; + 95919DB628F076BF00F21F8F /* UserClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB528F076BF00F21F8F /* UserClass.swift */; }; 95919DB828F079D100F21F8F /* UserSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB728F079D100F21F8F /* UserSettingsModel.swift */; }; - 95919DBC28F08D0600F21F8F /* LinkComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* LinkComponent.swift */; }; - 95AE8D5728C8750E0067F219 /* Load.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* Load.swift */; }; + 95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* LinkHelper.swift */; }; + 95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* LoadFunc.swift */; }; 95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AF322928DF293900023ACC /* GuessTheCountryView.swift */; }; - 95BC392D28EC42570049AB49 /* CityMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC392C28EC42570049AB49 /* CityMap.swift */; }; - 95C430F928D0A8E500480D23 /* CustomGradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C430F828D0A8E500480D23 /* CustomGradients.swift */; }; + 95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC392C28EC42570049AB49 /* CityMapHelper.swift */; }; + 95C430F928D0A8E500480D23 /* GradientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C430F828D0A8E500480D23 /* GradientExtension.swift */; }; 95C4315628C64A8C00212131 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C4315528C64A8C00212131 /* ContentView.swift */; }; - 95C4315928C6500000212131 /* GameButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C4315828C6500000212131 /* GameButton.swift */; }; + 95C4315928C6500000212131 /* GameButtonHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C4315828C6500000212131 /* GameButtonHelper.swift */; }; 95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */; }; - 95FA409C28D9881100129B60 /* CountryGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409B28D9881100129B60 /* CountryGame.swift */; }; + 95FA409C28D9881100129B60 /* CountryGameClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409B28D9881100129B60 /* CountryGameClass.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerButton.swift; sourceTree = "<group>"; }; + 95030CE928D1BA4D001AA3A1 /* AnswerButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerButtonHelper.swift; sourceTree = "<group>"; }; 9509A8DD28E5A19A00CFCDBA /* countries.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = countries.json; sourceTree = "<group>"; }; - 9509A8DF28E5A3C500CFCDBA /* GuessTheCurrencyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCurrencyView.swift; sourceTree = "<group>"; }; 9509A8E128E5A3D700CFCDBA /* GuessThePopulationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessThePopulationView.swift; sourceTree = "<group>"; }; 951AFAE828E5655C00A4A4BD /* cities.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = cities.json; sourceTree = "<group>"; }; 951AFAEC28E5657500A4A4BD /* CityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityModel.swift; sourceTree = "<group>"; }; 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryModel.swift; sourceTree = "<group>"; }; - 951AFAF028E5735400A4A4BD /* CityGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityGame.swift; sourceTree = "<group>"; }; + 951AFAF028E5735400A4A4BD /* CityGameClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityGameClass.swift; sourceTree = "<group>"; }; 951B630128D1A87C004F9877 /* GuessTheCapitalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCapitalView.swift; sourceTree = "<group>"; }; - 951D197228D485E000671FAD /* CustomColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomColors.swift; sourceTree = "<group>"; }; + 951D197228D485E000671FAD /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; }; 952E41E828DC521200198643 /* GameAlertsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameAlertsModifier.swift; sourceTree = "<group>"; }; 952E41EC28DC658900198643 /* SettingsModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModalView.swift; sourceTree = "<group>"; }; 952E41F028DC6F6D00198643 /* correctAnswer.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = correctAnswer.wav; sourceTree = "<group>"; }; @@ -61,23 +60,24 @@ 9539829228C51EDE00B70973 /* GeoQuizApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoQuizApp.swift; sourceTree = "<group>"; }; 9539829628C51EDF00B70973 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 9539829928C51EDF00B70973 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; - 955A658028D703EB00CEEC6D /* GameToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameToolbar.swift; sourceTree = "<group>"; }; - 955A658228D733E400CEEC6D /* Game.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = "<group>"; }; - 955A65A828D7815E00CEEC6D /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; }; - 956273E928CB2E98008DC094 /* FlagImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImage.swift; sourceTree = "<group>"; }; + 955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatterExtension.swift; sourceTree = "<group>"; }; + 955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameToolbarHelper.swift; sourceTree = "<group>"; }; + 955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameProtocol+Extension.swift"; sourceTree = "<group>"; }; + 955A65A828D7815E00CEEC6D /* HapticsClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsClass.swift; sourceTree = "<group>"; }; + 956273E928CB2E98008DC094 /* FlagImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImageHelper.swift; sourceTree = "<group>"; }; 9590359428E098FF00B24560 /* ProfileModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModalView.swift; sourceTree = "<group>"; }; - 95919DB528F076BF00F21F8F /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; }; + 95919DB528F076BF00F21F8F /* UserClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClass.swift; sourceTree = "<group>"; }; 95919DB728F079D100F21F8F /* UserSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsModel.swift; sourceTree = "<group>"; }; - 95919DBB28F08D0600F21F8F /* LinkComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkComponent.swift; sourceTree = "<group>"; }; - 95AE8D5628C8750E0067F219 /* Load.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Load.swift; sourceTree = "<group>"; }; + 95919DBB28F08D0600F21F8F /* LinkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkHelper.swift; sourceTree = "<group>"; }; + 95AE8D5628C8750E0067F219 /* LoadFunc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadFunc.swift; sourceTree = "<group>"; }; 95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = "<group>"; }; - 95BC392C28EC42570049AB49 /* CityMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMap.swift; sourceTree = "<group>"; }; - 95C430F828D0A8E500480D23 /* CustomGradients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomGradients.swift; sourceTree = "<group>"; }; + 95BC392C28EC42570049AB49 /* CityMapHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMapHelper.swift; sourceTree = "<group>"; }; + 95C430F828D0A8E500480D23 /* GradientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientExtension.swift; sourceTree = "<group>"; }; 95C4315528C64A8C00212131 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; - 95C4315828C6500000212131 /* GameButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameButton.swift; sourceTree = "<group>"; }; + 95C4315828C6500000212131 /* GameButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameButtonHelper.swift; sourceTree = "<group>"; }; 95E6188428DDDB5C003359ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheFlagView.swift; sourceTree = "<group>"; }; - 95FA409B28D9881100129B60 /* CountryGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryGame.swift; sourceTree = "<group>"; }; + 95FA409B28D9881100129B60 /* CountryGameClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryGameClass.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,12 +94,15 @@ 95030CE728D1B60F001AA3A1 /* Logic */ = { isa = PBXGroup; children = ( - 95AE8D5628C8750E0067F219 /* Load.swift */, - 955A65A828D7815E00CEEC6D /* Haptics.swift */, - 955A658228D733E400CEEC6D /* Game.swift */, - 95FA409B28D9881100129B60 /* CountryGame.swift */, - 951AFAF028E5735400A4A4BD /* CityGame.swift */, - 95919DB528F076BF00F21F8F /* User.swift */, + 951AFAEC28E5657500A4A4BD /* CityModel.swift */, + 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */, + 95919DB728F079D100F21F8F /* UserSettingsModel.swift */, + 955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */, + 955A65A828D7815E00CEEC6D /* HapticsClass.swift */, + 95FA409B28D9881100129B60 /* CountryGameClass.swift */, + 951AFAF028E5735400A4A4BD /* CityGameClass.swift */, + 95919DB528F076BF00F21F8F /* UserClass.swift */, + 95AE8D5628C8750E0067F219 /* LoadFunc.swift */, ); path = Logic; sourceTree = "<group>"; @@ -140,13 +143,11 @@ 95C4315528C64A8C00212131 /* ContentView.swift */, 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */, 951B630128D1A87C004F9877 /* GuessTheCapitalView.swift */, - 9509A8DF28E5A3C500CFCDBA /* GuessTheCurrencyView.swift */, + 95AF322928DF293900023ACC /* GuessTheCountryView.swift */, 9509A8E128E5A3D700CFCDBA /* GuessThePopulationView.swift */, - 95AF322928DF293900023ACC /* GuessTheCountryView.swift */, 9590359428E098FF00B24560 /* ProfileModalView.swift */, 952E41EC28DC658900198643 /* SettingsModalView.swift */, - 959D414728C87EA600BAAC14 /* Helpers */, - 959D414528C87E8D00BAAC14 /* Models */, + 959D414728C87EA600BAAC14 /* Components */, 95030CE728D1B60F001AA3A1 /* Logic */, 9520ABBA28C86D0300A3D4D7 /* Resources */, 9539829828C51EDF00B70973 /* Preview Content */, @@ -162,30 +163,21 @@ path = "Preview Content"; sourceTree = "<group>"; }; - 959D414528C87E8D00BAAC14 /* Models */ = { + 959D414728C87EA600BAAC14 /* Components */ = { isa = PBXGroup; children = ( - 951AFAEC28E5657500A4A4BD /* CityModel.swift */, - 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */, - 95919DB728F079D100F21F8F /* UserSettingsModel.swift */, + 95C4315828C6500000212131 /* GameButtonHelper.swift */, + 956273E928CB2E98008DC094 /* FlagImageHelper.swift */, + 95030CE928D1BA4D001AA3A1 /* AnswerButtonHelper.swift */, + 955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */, + 95BC392C28EC42570049AB49 /* CityMapHelper.swift */, + 95919DBB28F08D0600F21F8F /* LinkHelper.swift */, + 952E41E828DC521200198643 /* GameAlertsModifier.swift */, + 95C430F828D0A8E500480D23 /* GradientExtension.swift */, + 951D197228D485E000671FAD /* ColorExtension.swift */, + 955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */, ); - path = Models; - sourceTree = "<group>"; - }; - 959D414728C87EA600BAAC14 /* Helpers */ = { - isa = PBXGroup; - children = ( - 95C4315828C6500000212131 /* GameButton.swift */, - 956273E928CB2E98008DC094 /* FlagImage.swift */, - 95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */, - 955A658028D703EB00CEEC6D /* GameToolbar.swift */, - 952E41E828DC521200198643 /* GameAlertsModifier.swift */, - 95C430F828D0A8E500480D23 /* CustomGradients.swift */, - 951D197228D485E000671FAD /* CustomColors.swift */, - 95BC392C28EC42570049AB49 /* CityMap.swift */, - 95919DBB28F08D0600F21F8F /* LinkComponent.swift */, - ); - path = Helpers; + path = Components; sourceTree = "<group>"; }; /* End PBXGroup section */ @@ -262,33 +254,33 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 955A65A928D7815E00CEEC6D /* Haptics.swift in Sources */, - 95BC392D28EC42570049AB49 /* CityMap.swift in Sources */, + 955A65A928D7815E00CEEC6D /* HapticsClass.swift in Sources */, + 95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */, 952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */, 95919DB828F079D100F21F8F /* UserSettingsModel.swift in Sources */, 9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */, - 955A658328D733E400CEEC6D /* Game.swift in Sources */, - 95919DB628F076BF00F21F8F /* User.swift in Sources */, + 955A658328D733E400CEEC6D /* GameProtocol+Extension.swift in Sources */, + 95919DB628F076BF00F21F8F /* UserClass.swift in Sources */, 95C4315628C64A8C00212131 /* ContentView.swift in Sources */, - 95C4315928C6500000212131 /* GameButton.swift in Sources */, - 956273EA28CB2E98008DC094 /* FlagImage.swift in Sources */, + 95C4315928C6500000212131 /* GameButtonHelper.swift in Sources */, + 956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */, 951AFAED28E5657500A4A4BD /* CityModel.swift in Sources */, 951B630228D1A87C004F9877 /* GuessTheCapitalView.swift in Sources */, - 9509A8E028E5A3C500CFCDBA /* GuessTheCurrencyView.swift in Sources */, 9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */, 95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */, - 95919DBC28F08D0600F21F8F /* LinkComponent.swift in Sources */, + 95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */, 951AFAEF28E565FE00A4A4BD /* CountryModel.swift in Sources */, - 95030CEA28D1BA4D001AA3A1 /* AnswerButton.swift in Sources */, - 95FA409C28D9881100129B60 /* CountryGame.swift in Sources */, - 955A658128D703EB00CEEC6D /* GameToolbar.swift in Sources */, - 95AE8D5728C8750E0067F219 /* Load.swift in Sources */, + 95030CEA28D1BA4D001AA3A1 /* AnswerButtonHelper.swift in Sources */, + 95FA409C28D9881100129B60 /* CountryGameClass.swift in Sources */, + 955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */, + 95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */, 9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */, - 951D197328D485E000671FAD /* CustomColors.swift in Sources */, - 95C430F928D0A8E500480D23 /* CustomGradients.swift in Sources */, + 955950BB28F15FF2001BDEE8 /* FormatterExtension.swift in Sources */, + 951D197328D485E000671FAD /* ColorExtension.swift in Sources */, + 95C430F928D0A8E500480D23 /* GradientExtension.swift in Sources */, 952E41ED28DC658900198643 /* SettingsModalView.swift in Sources */, 95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */, - 951AFAF128E5735400A4A4BD /* CityGame.swift in Sources */, + 951AFAF128E5735400A4A4BD /* CityGameClass.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };
--- a/GeoQuiz/Assets.xcassets/Custom colors/AtomicTangerine.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/AtomicTangerine.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x54", - "green" : "0x8E", - "red" : "0xF7" + "blue" : "84", + "green" : "142", + "red" : "247" } }, "idiom" : "universal" @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "74", - "green" : "132", + "blue" : "54", + "green" : "112", "red" : "247" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/BlueBell.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/BlueBell.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "193", - "green" : "133", + "blue" : "173", + "green" : "113", "red" : "157" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/ChinaPink.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/ChinaPink.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "147", - "green" : "109", + "blue" : "127", + "green" : "89", "red" : "221" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/MaizeCrayola.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/MaizeCrayola.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "74", - "green" : "189", + "blue" : "54", + "green" : "169", "red" : "248" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/MayaBlue.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/MayaBlue.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "238", - "green" : "177", + "blue" : "218", + "green" : "157", "red" : "82" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/MiddleRed.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/MiddleRed.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "103", - "green" : "133", + "blue" : "83", + "green" : "113", "red" : "223" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/PinkLavender.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/PinkLavender.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "221", - "green" : "185", + "blue" : "201", + "green" : "165", "red" : "238" } },
--- a/GeoQuiz/Assets.xcassets/Custom colors/RoyalLightBlue.colorset/Contents.json Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/Assets.xcassets/Custom colors/RoyalLightBlue.colorset/Contents.json Sat Oct 08 21:36:40 2022 +0200 @@ -23,8 +23,8 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "218", - "green" : "105", + "blue" : "198", + "green" : "85", "red" : "90" } },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/AnswerButtonHelper.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,39 @@ +// +// AnswerButtonHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 14/9/22. +// + +import SwiftUI + +struct AnswerButton: View { + let optionName: String + let color: Color + + var body: some View { + RoundedRectangle(cornerRadius: 15) + .foregroundColor(.white) + .overlay( + Text(optionName) + .font(.title2.bold()) + .foregroundColor(color) + ) + } +} + +struct AnswerButton_Previews: PreviewProvider { + static var previews: some View { + ZStack { + LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom) + .ignoresSafeArea() + + VStack { + Spacer() + AnswerButton(optionName: "Madrid", color: .royalLightBlue) + .frame(height: 70) + } + .padding() + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/CityMapHelper.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,69 @@ +// +// CityMapHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 4/10/22. +// + +import SwiftUI +import MapKit + +struct CityMap: View { + @ObservedObject var game: CityGame + @State private var mapImage: UIImage? = nil + + var body: some View { + VStack { + if let mapImage = mapImage { + Image(uiImage: mapImage) + .resizable() + .scaledToFit() + .clipShape(Circle()) + .overlay { + Circle() + .strokeBorder(.white, lineWidth: 4) + } + .shadow(radius: 10) + } else { + ProgressView() + } + } + .onChange(of: game.correctAnswer.value) { _ in getMapImage() } + .onAppear(perform: getMapImage) + } + + private func getMapImage() { + let region = MKCoordinateRegion( + center: CLLocationCoordinate2D( + latitude: game.correctAnswer.value.lat, + longitude: game.correctAnswer.value.lon + ), + span: MKCoordinateSpan( + latitudeDelta: 0.1, + longitudeDelta: 0.1 + ) + ) + + // Map options + let mapOptions = MKMapSnapshotter.Options() + mapOptions.region = region + mapOptions.size = CGSize(width: 500, height: 500) + mapOptions.pointOfInterestFilter = .excludingAll + + // Create the snapshotter and run it + let snapshotter = MKMapSnapshotter(options: mapOptions) + snapshotter.start { (snapshot, error) in + if let snapshot = snapshot { + self.mapImage = snapshot.image + } else if let error = error { + print(error.localizedDescription) + } + } + } +} + +struct CityMap_Previews: PreviewProvider { + static var previews: some View { + CityMap(game: CityGame()) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/ColorExtension.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,43 @@ +// +// ColorExtension.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 16/9/22. +// + +import Foundation +import SwiftUI + +extension ShapeStyle where Self == Color { + static var atomicTangerine: Color { + Color("AtomicTangerine") + } + + static var blueBell: Color { + Color("BlueBell") + } + + static var chinaPink: Color { + Color("ChinaPink") + } + + static var maizeCrayola: Color { + Color("MaizeCrayola") + } + + static var mayaBlue: Color { + Color("MayaBlue") + } + + static var middleRed: Color { + Color("MiddleRed") + } + + static var pinkLavender: Color { + Color("PinkLavender") + } + + static var royalLightBlue: Color { + Color("RoyalLightBlue") + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/FlagImageHelper.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,29 @@ +// +// FlagImageHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 9/9/22. +// + +import SwiftUI + +struct FlagImage: View { + @Environment(\.colorScheme) var colorScheme + + var flagSymbol: String + var cornerRadius: Double + + var body: some View { + Image(flagSymbol) + .renderingMode(.original) + .resizable() + .scaledToFit() + } +} + +struct FlagImage_Previews: PreviewProvider { + static var previews: some View { + FlagImage(flagSymbol: "es", cornerRadius: 20) + .frame(height: 130) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/FormatterExtension.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,22 @@ +// +// Formatters.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 8/10/22. +// + +import Foundation + +extension Formatter { + static let withSeparator: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + return formatter + }() +} + +extension Int { + var formattedWithSeparator: String { + return Formatter.withSeparator.string(for: self) ?? "" + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/GameAlertsModifier.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,55 @@ +// +// GameAlertsModifier.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 22/9/22. +// + +import SwiftUI + +struct GameAlertsModifier<T: Game>: ViewModifier { + @ObservedObject var game: T + @Environment(\.dismiss) var dismiss + + func body(content: Content) -> some View { + content + .alert(game.alertTitle, isPresented: $game.showingWrongAnswerAlert) { + Button("Continue", role: .cancel) { + game.askQuestion { + game.selector() + } + } + } message: { + Text(game.alertMessage) + } + + .alert(game.alertTitle, isPresented: $game.showingGameOverAlert) { + Button("Try again") { + game.reset { + game.selector() + } + } + Button("Exit", role: .cancel) { dismiss()} + } message: { + Text(game.alertMessage) + } + + .alert(game.alertTitle, isPresented: $game.showingEndGameAlert) { + Button("Play again") { + game.reset() { + game.selector() + } + } + Button("Exit", role: .cancel) { dismiss() } + } message: { + Text(game.alertMessage) + } + + .alert("Are you sure?", isPresented: $game.showingExitGameAlert) { + Button("Exit", role: .destructive) { dismiss() } + Button("Cancel", role: .cancel) { } + } message: { + Text("Progress won't be saved.") + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/GameButtonHelper.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,65 @@ +// +// GameButtonHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 5/9/22. +// + +import SwiftUI + +struct GameButton: View { + let gradient: Gradient + let level: String + let symbol: String + let name: String + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 20) + .fill( + LinearGradient( + gradient: gradient, + startPoint: .trailing, endPoint: .leading + ) + ) + .frame(height: 180) + + VStack(alignment: .leading) { + HStack { + Image(systemName: symbol) + .font(.headline) + .padding(5) + .background( + RoundedRectangle(cornerRadius: 5) + .stroke(lineWidth: 1.5) + ) + + Spacer() + } + .padding(.bottom) + + VStack(alignment: .leading, spacing: 5) { + Text(level) + .font(.callout) + + Text(name) + .font(.title.bold()) + } + } + .foregroundColor(.white) + .padding() + } + .frame(maxWidth: 700) + } +} + +struct GameButton_Previews: PreviewProvider { + static var previews: some View { + GameButton( + gradient: .main, + level: "Level 1", + symbol: "checkmark", + name: "Guess the flag" + ) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/GameToolbarHelper.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,90 @@ +// +// GameToolbarHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 18/9/22. +// + +import SwiftUI + +struct GameToolbar<T: Game>: View { + @ObservedObject var game: T + + var color: Color + + var body: some View { + HStack(spacing: 0) { + Group { + Button { + game.showingExitGameAlert = true + } label: { + Image(systemName: "multiply") + .font(.headline) + .foregroundColor(color) + .padding(10) + .background( + Circle() + .foregroundColor(.white) + ) + } + } + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + Group { + Text("\(game.userScore)") + .font(.title.bold()) + .foregroundColor(color) + .padding() + .background( + Group { + if game.userScore < 1000 { + Circle() + } else { + Capsule() + } + } + .foregroundColor(.white) + ) + } + .font(.title2) + .scaleEffect(game.scoreScaleAmount) + .frame(maxWidth: .infinity, alignment: .center) + + Group { + HStack { + Image(systemName: "heart.fill") + Text("\(game.userLives)") + } + .font(.headline) + .foregroundColor(color) + .padding(10) + .background( + Capsule() + .foregroundColor(.white) + ) + .scaleEffect(game.livesScaleAmount) + } + .font(.headline) + .frame(maxWidth: .infinity, alignment: .trailing) + } + } +} + +struct GameToolbar_Previews: PreviewProvider { + static var previews: some View { + ZStack { + LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom) + .ignoresSafeArea() + + GeometryReader { geo in + VStack { + GameToolbar(game: CountryGame(), color: .mayaBlue) + + Spacer() + } + .padding() + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/GradientExtension.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,27 @@ +// +// GradientExtension.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 13/9/22. +// + +import Foundation +import SwiftUI + +extension ShapeStyle where Self == Gradient { + static var main: Gradient { + Gradient(colors: [Color("MayaBlue"), Color("RoyalLightBlue")]) + } + + static var secondary: Gradient { + Gradient(colors: [Color("AtomicTangerine"), Color("ChinaPink")]) + } + + static var tertiary: Gradient { + Gradient(colors: [Color("PinkLavender"), Color("BlueBell")]) + } + + static var quaternary: Gradient { + Gradient(colors: [Color("MaizeCrayola"), Color("MiddleRed")]) + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/LinkHelper.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,41 @@ +// +// LinkHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 7/10/22. +// + +import SwiftUI + +struct LinkComponent: View { + var color: Color + var iconName: String + var text: String + var url: URL + + @Environment(\.openURL) var openURL + + var body: some View { + Link(destination: url) { + HStack(alignment: .center, spacing: 20) { + Image(systemName: iconName) + .imageScale(.large) + .foregroundColor(color) + + Text(text) + .foregroundColor(.primary) + } + } + } +} + +struct LinkComponent_Previews: PreviewProvider { + static var previews: some View { + LinkComponent( + color: .mayaBlue, + iconName: "info.circle.fill", + text: "About", + url: URL(string: "https://dennistech.io")! + ) + } +}
--- a/GeoQuiz/ContentView.swift Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/ContentView.swift Sat Oct 08 21:36:40 2022 +0200 @@ -14,7 +14,11 @@ var body: some View { NavigationView { ScrollView(showsIndicators: false) { - VStack(spacing: 20) { + VStack(alignment: .leading, spacing: 20) { + + Text("Select a game 🎮") + .font(.largeTitle.bold()) + .padding(.bottom) NavigationLink(destination: GuessTheFlagView()) { GameButton( @@ -36,21 +40,18 @@ level: "Level 3", symbol: "globe.americas.fill", name: "Guess the country" ) } - -// NavigationLink( -// destination: Text("Guess the population"), -// tag: GameName.guessThePopulation, -// selection: $gameName -// ) { -// GameButton( -// gradient: .quaternary, -// level: "Level 4", symbol: "person.3.fill", name: "Guess the population" -// ) -// } + + NavigationLink(destination: GuessThePopulationView()) { + GameButton( + gradient: .quaternary, + level: "Level 4", symbol: "person.fill", name: "Guess the population" + ) + } } .padding() } - .navigationTitle("Select a game 🎮") + .navigationTitle("GeoQuiz") + .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button {
--- a/GeoQuiz/GuessTheCapitalView.swift Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/GuessTheCapitalView.swift Sat Oct 08 21:36:40 2022 +0200 @@ -16,18 +16,19 @@ .ignoresSafeArea() GeometryReader { geo in - VStack(spacing: 20) { + VStack { GameToolbar(game: game, color: .atomicTangerine) Spacer() FlagImage(flagSymbol: game.correctAnswer.value.flag, cornerRadius: 20) + .clipShape(RoundedRectangle(cornerRadius: 20)) .shadow(radius: 10) .frame(height: geo.size.height * 0.15) Spacer() - HStack { + VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 10) { Text("Question \(game.questionCounter) of \(game.data.count)") .font(.title3) @@ -39,24 +40,23 @@ .foregroundColor(.white) } - Spacer() - } - - VStack { - ForEach(Array(game.userChoices.keys), id: \.self) { countryName in - Button { - game.answer((key: countryName, value: game.data[countryName]!)) { - game.selector() + VStack(spacing: 15) { + ForEach(Array(game.userChoices.keys), id: \.self) { countryName in + Button { + game.answer((key: countryName, value: game.data[countryName]!)) { + game.selector() + } + } label: { + AnswerButton( + optionName: game.data[countryName]!.capital, + color: .chinaPink + ) + .frame(height: geo.size.height * 0.08) } - } label: { - AnswerButton( - optionName: game.data[countryName]!.capital, - color: .chinaPink - ) - .frame(height: geo.size.height * 0.08) } } } + .frame(maxWidth: 500) } .padding() } @@ -66,10 +66,14 @@ } } -struct GuessCapitalView_Previews: PreviewProvider { +struct GuessTheCapitalView_Previews: PreviewProvider { static var previews: some View { - NavigationView { - GuessTheCapitalView() - } + GuessTheCapitalView() + .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro Max")) + .previewDisplayName("iPhone 14 Pro Max") + + GuessTheCapitalView() + .previewDevice(PreviewDevice(rawValue: "iPad Pro (12.9-inch) (5th generation)")) + .previewDisplayName("iPad Pro (12.9-inch) (5th generation)") } }
--- a/GeoQuiz/GuessTheCountryView.swift Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/GuessTheCountryView.swift Sat Oct 08 21:36:40 2022 +0200 @@ -16,16 +16,17 @@ .ignoresSafeArea() GeometryReader { geo in - VStack(spacing: 20) { + VStack { GameToolbar(game: game, color: .pinkLavender) Spacer() - - CityMap(game: game, geo: geo) + + CityMap(game: game) + .frame(height: geo.size.height * 0.35) Spacer() - HStack { + VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 10) { Text("Question \(game.questionCounter) of \(game.data.count)") .font(.title3) @@ -37,25 +38,23 @@ .foregroundColor(.white) } - Spacer() - } - - VStack { - ForEach(Array(game.userChoices.keys), id: \.self) { cityName in - Button { - game.answer((key: cityName, value: game.data[cityName]!)) { - game.selector() + VStack(spacing: 15) { + ForEach(Array(game.userChoices.keys), id: \.self) { cityName in + Button { + game.answer((key: cityName, value: game.data[cityName]!)) { + game.selector() + } + } label: { + AnswerButton( + optionName: game.data[cityName]!.country, + color: .blueBell + ) + .frame(height: geo.size.height * 0.08) } - } label: { - AnswerButton( - optionName: game.data[cityName]!.country, - color: .blueBell - ) - .frame(height: geo.size.height * 0.08) } } } - + .frame(maxWidth: 500) } .padding() } @@ -68,5 +67,11 @@ struct GuessTheCountryView_Previews: PreviewProvider { static var previews: some View { GuessTheCountryView() + .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro Max")) + .previewDisplayName("iPhone 14 Pro Max") + + GuessTheCountryView() + .previewDevice(PreviewDevice(rawValue: "iPad Pro (12.9-inch) (5th generation)")) + .previewDisplayName("iPad Pro (12.9-inch) (5th generation)") } }
--- a/GeoQuiz/GuessTheCurrencyView.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -// -// GuessTheCurrencyView.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 29/9/22. -// - -import SwiftUI - -struct GuessTheCurrencyView: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} - -struct GuessTheCurrency_Previews: PreviewProvider { - static var previews: some View { - GuessTheCurrencyView() - } -}
--- a/GeoQuiz/GuessTheFlagView.swift Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/GuessTheFlagView.swift Sat Oct 08 21:36:40 2022 +0200 @@ -16,37 +16,44 @@ .ignoresSafeArea() GeometryReader { geo in - VStack(spacing: 20) { + VStack { GameToolbar(game: game, color: .mayaBlue) + .padding(.bottom) - HStack { - VStack(alignment: .leading, spacing: 10) { - Text("Question \(game.questionCounter) of \(game.data.count)") - .font(.title3) - .foregroundColor(.white.opacity(0.7)) - - Text("What is the flag of \(game.correctAnswer.key)?") - .font(.title) - .fontWeight(.semibold) - .foregroundColor(.white) - } + VStack(spacing: 10) { + Text("Question \(game.questionCounter) of \(game.data.count)") + .font(.title3) + .foregroundColor(.white.opacity(0.7)) - Spacer() + Text("What is the flag of") + .font(.title) + .fontWeight(.semibold) + .foregroundColor(.white) + + Text("\(game.correctAnswer.key)?") + .font(.largeTitle.bold()) + .foregroundColor(.white) + } Spacer() - - ForEach(Array(game.userChoices.keys), id: \.self) { countryName in - Button { - game.answer((key: countryName, value: game.data[countryName]!)) { - game.selector() + VStack(spacing: 30) { + ForEach(Array(game.userChoices.keys), id: \.self) { countryName in + Button { + game.answer((key: countryName, value: game.data[countryName]!)) { + game.selector() + } + } label: { + FlagImage(flagSymbol: game.data[countryName]!.flag, cornerRadius: 20) + .clipShape(Circle()) + .overlay { + Circle() + .strokeBorder(.white, lineWidth: 4) + } + .shadow(radius: 10) + .frame(height: geo.size.height * 0.15) } - } label: { - FlagImage(flagSymbol: game.data[countryName]!.flag, cornerRadius: 20) - .shadow(radius: 10) - .frame(height: geo.size.height * 0.15) } - .padding(.top) } Spacer() @@ -62,5 +69,11 @@ struct GuessTheFlagView_Previews: PreviewProvider { static var previews: some View { GuessTheFlagView() + .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro Max")) + .previewDisplayName("iPhone 14 Pro Max") + + GuessTheFlagView() + .previewDevice(PreviewDevice(rawValue: "iPad Pro (12.9-inch) (5th generation)")) + .previewDisplayName("iPad Pro (12.9-inch) (5th generation)") } }
--- a/GeoQuiz/GuessThePopulationView.swift Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/GuessThePopulationView.swift Sat Oct 08 21:36:40 2022 +0200 @@ -8,13 +8,73 @@ import SwiftUI struct GuessThePopulationView: View { + @StateObject var game = CountryGame() + var body: some View { - Text("Hello, World!") + ZStack { + LinearGradient(gradient: .quaternary, startPoint: .top, endPoint: .bottom) + .ignoresSafeArea() + + GeometryReader { geo in + VStack { + GameToolbar(game: game, color: .maizeCrayola) + + Spacer() + + FlagImage(flagSymbol: game.correctAnswer.value.flag, cornerRadius: 20) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .shadow(radius: 10) + .frame(height: geo.size.height * 0.15) + + Spacer() + + VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 10) { + Text("Question \(game.questionCounter) of \(game.data.count)") + .font(.title3) + .foregroundColor(.white.opacity(0.7)) + + Text("What is the population of \(game.correctAnswer.key)?") + .font(.title) + .fontWeight(.semibold) + .foregroundColor(.white) + } + + VStack(spacing: 15) { + ForEach(Array(game.userChoices.keys), id: \.self) { countryName in + Button { + game.answer((key: countryName, value: game.data[countryName]!)) { + game.selector() + } + } label: { + let population = game.data[countryName]!.population + AnswerButton( + optionName: population.formattedWithSeparator, + color: .middleRed + ) + .frame(height: geo.size.height * 0.08) + } + } + } + } + .frame(maxWidth: 500) + } + .padding() + } + } + .navigationBarHidden(true) + .modifier(GameAlertsModifier(game: game)) } } -struct GuessThePopulation_Previews: PreviewProvider { +struct GuessThePopulationView_Previews: PreviewProvider { static var previews: some View { GuessThePopulationView() + .previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro Max")) + .previewDisplayName("iPhone 14 Pro Max") + + GuessThePopulationView() + .previewDevice(PreviewDevice(rawValue: "iPad Pro (12.9-inch) (5th generation)")) + .previewDisplayName("iPad Pro (12.9-inch) (5th generation)") } }
--- a/GeoQuiz/Helpers/AnswerButton.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -// -// AnswerButton.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 14/9/22. -// - -import SwiftUI - -struct AnswerButton: View { - let optionName: String - let color: Color - - var body: some View { - RoundedRectangle(cornerRadius: 15) - .foregroundStyle(.ultraThickMaterial) - .overlay( - Text(optionName) - .font(.title2.bold()) - .foregroundColor(color) - ) - } -} - -struct AnswerButton_Previews: PreviewProvider { - static var previews: some View { - ZStack { - LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom) - .ignoresSafeArea() - - VStack { - Spacer() - AnswerButton(optionName: "Madrid", color: .royalLightBlue) - .frame(height: 70) - } - .padding() - } - } -}
--- a/GeoQuiz/Helpers/CityMap.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -// -// CityMap.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 4/10/22. -// - -import SwiftUI -import MapKit - -struct CityMap: View { - @ObservedObject var game: CityGame - @State private var mapImage: UIImage? = nil - - let geo: GeometryProxy - - var body: some View { - VStack { - if let mapImage = mapImage { - Image(uiImage: mapImage) - .resizable() - .scaledToFit() - .clipShape(RoundedRectangle(cornerRadius: 20)) - .shadow(radius: 10) - } else { - ProgressView() - } - } - .onChange(of: game.correctAnswer.value) { _ in getMapImage() } - .onAppear(perform: getMapImage) - } - - private func getMapImage() { - let region = MKCoordinateRegion( - center: CLLocationCoordinate2D( - latitude: game.correctAnswer.value.lat, - longitude: game.correctAnswer.value.lon - ), - span: MKCoordinateSpan( - latitudeDelta: 0.1, - longitudeDelta: 0.1 - ) - ) - - // Map options - let mapOptions = MKMapSnapshotter.Options() - mapOptions.region = region - mapOptions.size = CGSize(width: geo.size.width * 0.8, height: geo.size.width * 0.8) - mapOptions.pointOfInterestFilter = .excludingAll - - // Create the snapshotter and run it - let snapshotter = MKMapSnapshotter(options: mapOptions) - snapshotter.start { (snapshot, error) in - if let snapshot = snapshot { - self.mapImage = snapshot.image - } else if let error = error { - print(error.localizedDescription) - } - } - } -} - -//struct CityMap_Previews: PreviewProvider { -// static var previews: some View { -// CityMap(game: CityGame()) -// } -//}
--- a/GeoQuiz/Helpers/CustomColors.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -// -// CustomColors.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 16/9/22. -// - -import Foundation -import SwiftUI - -extension ShapeStyle where Self == Color { - static var atomicTangerine: Color { - Color("AtomicTangerine") - } - - static var blueBell: Color { - Color("BlueBell") - } - - static var chinaPink: Color { - Color("ChinaPink") - } - - static var maizeCrayola: Color { - Color("MaizeCrayola") - } - - static var mayaBlue: Color { - Color("MayaBlue") - } - - static var middleRed: Color { - Color("MiddleRed") - } - - static var pinkLavender: Color { - Color("PinkLavender") - } - - static var royalLightBlue: Color { - Color("RoyalLightBlue") - } -}
--- a/GeoQuiz/Helpers/CustomGradients.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -// -// CustomGradients.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 13/9/22. -// - -import Foundation -import SwiftUI - -extension ShapeStyle where Self == Gradient { - static var main: Gradient { - Gradient(colors: [Color("MayaBlue"), Color("RoyalLightBlue")]) - } - - static var secondary: Gradient { - Gradient(colors: [Color("AtomicTangerine"), Color("ChinaPink")]) - } - - static var tertiary: Gradient { - Gradient(colors: [Color("PinkLavender"), Color("BlueBell")]) - } - - static var quaternary: Gradient { - Gradient(colors: [Color("MaizeCrayola"), Color("MiddleRed")]) - } -}
--- a/GeoQuiz/Helpers/FlagImage.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -// -// FlagImage.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 9/9/22. -// - -import SwiftUI - -struct FlagImage: View { - @Environment(\.colorScheme) var colorScheme - - var flagSymbol: String - var cornerRadius: Double - - var body: some View { - Image(flagSymbol) - .renderingMode(.original) - .resizable() - .scaledToFit() - .clipShape(RoundedRectangle(cornerRadius: cornerRadius)) - } -} - -struct FlagImage_Previews: PreviewProvider { - static var previews: some View { - FlagImage(flagSymbol: "np", cornerRadius: 20) - .frame(height: 130) - } -}
--- a/GeoQuiz/Helpers/GameAlertsModifier.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -// -// GameAlertsModifier.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 22/9/22. -// - -import SwiftUI - -struct GameAlertsModifier<T: Game>: ViewModifier { - @ObservedObject var game: T - @Environment(\.dismiss) var dismiss - - func body(content: Content) -> some View { - content - .alert(game.alertTitle, isPresented: $game.showingWrongAnswerAlert) { - Button("Continue", role: .cancel) { - game.askQuestion { - game.selector() - } - } - } message: { - Text(game.alertMessage) - } - - .alert(game.alertTitle, isPresented: $game.showingGameOverAlert) { - Button("Try again") { - game.reset { - game.selector() - } - } - Button("Exit", role: .cancel) { dismiss()} - } message: { - Text(game.alertMessage) - } - - .alert(game.alertTitle, isPresented: $game.showingEndGameAlert) { - Button("Play again") { - game.reset() { - game.selector() - } - } - Button("Exit", role: .cancel) { dismiss() } - } message: { - Text(game.alertMessage) - } - - .alert("Are you sure?", isPresented: $game.showingExitGameAlert) { - Button("Exit", role: .destructive) { dismiss() } - Button("Cancel", role: .cancel) { } - } message: { - Text("Progress won't be saved.") - } - } -}
--- a/GeoQuiz/Helpers/GameButton.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -// -// GameButton.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 5/9/22. -// - -import SwiftUI - -struct GameButton: View { - let gradient: Gradient - let level: String - let symbol: String - let name: String - - var body: some View { - ZStack { - RoundedRectangle(cornerRadius: 20) - .fill( - LinearGradient( - gradient: gradient, - startPoint: .trailing, endPoint: .leading - ) - ) - .frame(height: 180) - - VStack(alignment: .leading) { - HStack { - Image(systemName: symbol) - .font(.headline) - .padding(5) - .background( - RoundedRectangle(cornerRadius: 5) - .stroke(lineWidth: 1.5) - ) - - Spacer() - } - .padding(.bottom) - - VStack(alignment: .leading, spacing: 5) { - Text(level) - .font(.callout) - - Text(name) - .font(.title.bold()) - } - } - .foregroundColor(.white) - .padding() - } - } -} - -struct PlayButton_Previews: PreviewProvider { - static var previews: some View { - GameButton( - gradient: Gradient(colors: [Color("MainDark"), Color("Main")]), - level: "Level 1", - symbol: "checkmark", - name: "Guess the flag" - ) - } -}
--- a/GeoQuiz/Helpers/GameToolbar.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -// -// GameToolbar.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 18/9/22. -// - -import SwiftUI - -struct GameToolbar<T: Game>: View { - @ObservedObject var game: T - - var color: Color - - var body: some View { - HStack(spacing: 0) { - Group { - Button { - game.showingExitGameAlert = true - } label: { - Image(systemName: "multiply") - .font(.headline) - .foregroundColor(color) - .padding(10) - .background( - Circle() - .foregroundStyle(.ultraThickMaterial) - ) - } - } - .font(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - - Group { - Text("\(game.userScore)") - .font(.title.bold()) - .foregroundColor(color) - .padding() - .background( - Group { - if game.userScore < 1000 { - Circle() - } else { - Capsule() - } - } - .foregroundStyle(.ultraThickMaterial) - ) - } - .font(.title2) - .scaleEffect(game.scoreScaleAmount) - .frame(maxWidth: .infinity, alignment: .center) - - Group { - HStack { - Image(systemName: "heart.fill") - Text("\(game.userLives)") - } - .font(.headline) - .foregroundColor(color) - .padding(10) - .background( - Capsule() - .foregroundStyle(.ultraThickMaterial) - ) - .scaleEffect(game.livesScaleAmount) - } - .font(.headline) - .frame(maxWidth: .infinity, alignment: .trailing) - } - } -} - -struct GameToolbar_Previews: PreviewProvider { - static var previews: some View { - ZStack { - LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom) - .ignoresSafeArea() - - GeometryReader { geo in - VStack { - GameToolbar(game: CountryGame(), color: .mayaBlue) - - Spacer() - } - .padding() - } - } - } -}
--- a/GeoQuiz/Helpers/LinkComponent.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -// -// LinkComponent.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 7/10/22. -// - -import SwiftUI - -struct LinkComponent: View { - var color: Color - var iconName: String - var text: String - var url: URL - - @Environment(\.openURL) var openURL - - var body: some View { - Link(destination: url) { - HStack(alignment: .center, spacing: 20) { - Image(systemName: iconName) - .imageScale(.large) - .foregroundColor(color) - - Text(text) - .foregroundColor(.primary) - } - } - } -} - -struct LinkComponent_Previews: PreviewProvider { - static var previews: some View { - LinkComponent( - color: .mayaBlue, - iconName: "info.circle.fill", - text: "About", - url: URL(string: "https://dennistech.io")! - ) - } -}
--- a/GeoQuiz/Logic/CityGame.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -// -// CityGame.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 29/9/22. -// - -import Foundation -import AVFAudio - -class CityGame: Game, ObservableObject { - - // Define type of generics - typealias T = CityModel.CityData - - var data: [String: T] - var dataAsked = [String: T]() - - // Data - @Published var correctAnswer = ( - key: String(), - value: T(country: String(), lat: Double(), lon: Double()) - ) - - // User - @Published var userChoices = [String: T]() - @Published var userScore = 0 - @Published var userLives = 3 - @Published var correctAnswers = [String: T]() - @Published var wrongAnswers = [String: T]() - - // Alerts - @Published var alertTitle = String() - @Published var alertMessage = String() - @Published var showingGameOverAlert = false - @Published var showingEndGameAlert = false - @Published var showingWrongAnswerAlert = false - @Published var showingExitGameAlert = false - - // Animations - @Published var scoreScaleAmount = 1.0 - @Published var livesScaleAmount = 1.0 - - // Sound effects - @Published var player: AVAudioPlayer? - - init() { - let data: CityModel = load("cities.json") - self.data = data.cities - askQuestion { - selector() - } - } -} - -extension CityGame { - func selector() { - - // Get random choices - var userChoices = [String: T]() - - while userChoices.count < 2 { - if let choice = data.randomElement() { - let userChoicesCountry = userChoices.map { $0.value.country } - - if !userChoicesCountry.contains(choice.value.country) { - userChoices[choice.key] = choice.value - } - } else { - fatalError("Couldn't get a random value from data") - } - } - - // Get question asked (correct answer) - let userChoicesCountry = userChoices.map { $0.value.country } - let correctAnswer = data.first(where: { - !userChoices.keys.contains($0.key) && // Avoid duplicated cities - !dataAsked.keys.contains($0.key) && // Avoid cities already asked - !userChoicesCountry.contains($0.value.country) // Avoid duplicated country names in userChoices - }) - - // Unwrap optional - if let correctAnswer = correctAnswer { - userChoices[correctAnswer.key] = correctAnswer.value - dataAsked[correctAnswer.key] = correctAnswer.value - self.correctAnswer = correctAnswer - } else { - fatalError("Couldn't unwrap optional value") - } - - self.userChoices = userChoices - } -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CityGameClass.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,104 @@ +// +// CityGameClass.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 29/9/22. +// + +import Foundation +import AVFAudio + +class CityGame: Game, ObservableObject { + + // Define type of generics + typealias T = CityData.City + + var data: [String: T] + var dataAsked = [String: T]() + + // Data + @Published var correctAnswer = ( + key: String(), + value: T(country: String(), lat: Double(), lon: Double()) + ) + + // User + @Published var userChoices = [String: T]() + @Published var userScore = 0 + @Published var userLives = 3 + @Published var correctAnswers = [String: T]() + @Published var wrongAnswers = [String: T]() + + // Alerts + @Published var alertTitle = String() + @Published var alertMessage = String() + @Published var showingGameOverAlert = false + @Published var showingEndGameAlert = false + @Published var showingWrongAnswerAlert = false + @Published var showingExitGameAlert = false + + // Animations + @Published var scoreScaleAmount = 1.0 + @Published var livesScaleAmount = 1.0 + + // Sound effects + @Published var player: AVAudioPlayer? + + init() { + let data: CityData = load("cities.json") + self.data = data.cities + + let user = User() + userLives = user.settings.numberOfLives + + if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") { + if let decodedUserSettings = try? JSONDecoder().decode(UserSettings.self, from: userSettings) { + userLives = decodedUserSettings.numberOfLives + } + } + + askQuestion { + selector() + } + } +} + +extension CityGame { + func selector() { + + // Get random choices + var userChoices = [String: T]() + + while userChoices.count < 2 { + if let choice = data.randomElement() { + let userChoicesCountry = userChoices.map { $0.value.country } + + if !userChoicesCountry.contains(choice.value.country) { + userChoices[choice.key] = choice.value + } + } else { + fatalError("Couldn't get a random value from data") + } + } + + // Get question asked (correct answer) + let userChoicesCountry = userChoices.map { $0.value.country } + let correctAnswer = data.first(where: { + !userChoices.keys.contains($0.key) && // Avoid duplicated cities + !dataAsked.keys.contains($0.key) && // Avoid cities already asked + !userChoicesCountry.contains($0.value.country) // Avoid duplicated country names in userChoices + }) + + // Unwrap optional + if let correctAnswer = correctAnswer { + userChoices[correctAnswer.key] = correctAnswer.value + dataAsked[correctAnswer.key] = correctAnswer.value + self.correctAnswer = correctAnswer + } else { + fatalError("Couldn't unwrap optional value") + } + + self.userChoices = userChoices + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CityModel.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,22 @@ +// +// CityModel.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 29/9/22. +// + +import Foundation + +struct CityData: Codable { + let cities: [String: City] + + struct City: Codable, Equatable { + let country: String + let lat: Double + let lon: Double + + static func ==(lhs: City, rhs: City) -> Bool { + lhs.country == rhs.country + } + } +}
--- a/GeoQuiz/Logic/CountryGame.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -// -// CountryGame.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 20/9/22. -// - -import Foundation -import AVFAudio - -class CountryGame: Game, ObservableObject { - - // Define type of generics - typealias T = CountryModel.CountryData - - var data: [String: T] - var dataAsked = [String: T]() - - // Data - @Published var correctAnswer = ( - key: String(), - value: T(flag: String(), currency: String(), population: Int(), capital: String()) - ) - - // User - @Published var userChoices = [String: T]() - @Published var userScore = 0 - @Published var userLives = 3 - @Published var correctAnswers = [String: T]() - @Published var wrongAnswers = [String: T]() - - // Alerts - @Published var alertTitle = String() - @Published var alertMessage = String() - @Published var showingGameOverAlert = false - @Published var showingEndGameAlert = false - @Published var showingWrongAnswerAlert = false - @Published var showingExitGameAlert = false - - // Animations - @Published var scoreScaleAmount = 1.0 - @Published var livesScaleAmount = 1.0 - - // Sound effects - @Published var player: AVAudioPlayer? - - init() { - let data: CountryModel = load("countries.json") - self.data = data.countries - - if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") { - if let decodedUserSettings = try? JSONDecoder().decode(UserSettingsModel.self, from: userSettings) { - userLives = decodedUserSettings.numberOfLives - } - } - - askQuestion { - selector() - } - } -} - -extension CountryGame { - func selector() { - - // Get random choices - var userChoices = [String: T]() - - while userChoices.count < 2 { - if let choice = data.randomElement() { - userChoices[choice.key] = choice.value - } else { - fatalError("Couldn't get a random value from data") - } - } - - // Get question asked (correct answer) - let correctAnswer = data.first(where: { - !userChoices.keys.contains($0.key) && // Avoid duplicated countries - !dataAsked.keys.contains($0.key) // Avoid countries already asked - }) - - // Unwrap optional - if let correctAnswer = correctAnswer { - userChoices[correctAnswer.key] = correctAnswer.value - dataAsked[correctAnswer.key] = correctAnswer.value - self.correctAnswer = correctAnswer - } else { - fatalError("Couldn't unwrap optional value") - } - - self.userChoices = userChoices - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CountryGameClass.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,97 @@ +// +// CountryGameClass.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 20/9/22. +// + +import Foundation +import AVFAudio + +class CountryGame: Game, ObservableObject { + + // Define type of generics + typealias T = CountryData.Country + + var data: [String: T] + var dataAsked = [String: T]() + + // Data + @Published var correctAnswer = ( + key: String(), + value: T(flag: String(), currency: String(), population: Int(), capital: String()) + ) + + // User + @Published var userChoices = [String: T]() + @Published var userScore = 0 + @Published var userLives = 3 + @Published var correctAnswers = [String: T]() + @Published var wrongAnswers = [String: T]() + + // Alerts + @Published var alertTitle = String() + @Published var alertMessage = String() + @Published var showingGameOverAlert = false + @Published var showingEndGameAlert = false + @Published var showingWrongAnswerAlert = false + @Published var showingExitGameAlert = false + + // Animations + @Published var scoreScaleAmount = 1.0 + @Published var livesScaleAmount = 1.0 + + // Sound effects + @Published var player: AVAudioPlayer? + + init() { + let data: CountryData = load("countries.json") + self.data = data.countries + + let user = User() + userLives = user.settings.numberOfLives + + if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") { + if let decodedUserSettings = try? JSONDecoder().decode(UserSettings.self, from: userSettings) { + userLives = decodedUserSettings.numberOfLives + } + } + + askQuestion { + selector() + } + } +} + +extension CountryGame { + func selector() { + + // Get random choices + var userChoices = [String: T]() + + while userChoices.count < 2 { + if let choice = data.randomElement() { + userChoices[choice.key] = choice.value + } else { + fatalError("Couldn't get a random value from data") + } + } + + // Get question asked (correct answer) + let correctAnswer = data.first(where: { + !userChoices.keys.contains($0.key) && // Avoid duplicated countries + !dataAsked.keys.contains($0.key) // Avoid countries already asked + }) + + // Unwrap optional + if let correctAnswer = correctAnswer { + userChoices[correctAnswer.key] = correctAnswer.value + dataAsked[correctAnswer.key] = correctAnswer.value + self.correctAnswer = correctAnswer + } else { + fatalError("Couldn't unwrap optional value") + } + + self.userChoices = userChoices + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CountryModel.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,26 @@ +// +// CountryModel.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 29/9/22. +// + +import Foundation + +struct CountryData: Codable { + let countries: [String: Country] + + struct Country: Codable, Equatable, Hashable { + let flag: String + let currency: String + let population: Int + let capital: String + + static func ==(lhs: Country, rhs: Country) -> Bool { + lhs.flag == rhs.flag && + lhs.currency == rhs.currency && + lhs.population == rhs.population && + lhs.capital == rhs.capital + } + } +}
--- a/GeoQuiz/Logic/Game.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -// -// GameProtocol.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 18/9/22. -// - -import Foundation -import SwiftUI -import AVFAudio - -protocol Game: ObservableObject { - - // Define generic type - associatedtype T: Equatable - - // Game - var data: [String: T] { get set} - var dataAsked: [String: T] { get set } - var correctAnswer: (key: String, value: T) { get set } - - // User - var userChoices: [String: T] { get set } - var userScore: Int { get set } - var userLives: Int { get set } - var correctAnswers: [String: T] { get set } - var wrongAnswers: [String: T] { get set } - - // Alerts - var alertTitle: String { get set } - var alertMessage: String { get set } - var showingGameOverAlert: Bool { get set } - var showingEndGameAlert: Bool { get set } - var showingWrongAnswerAlert: Bool { get set } - var showingExitGameAlert: Bool { get set } - - // Animations - var scoreScaleAmount: Double { get set } - var livesScaleAmount: Double { get set } - - // Sound effects - var player: AVAudioPlayer? { get set } - - func selector() -} - -extension Game { - var questionCounter: Int { - dataAsked.count - } - - func askQuestion(selector: () -> Void) { - guard questionCounter < data.count else { - alertTitle = "⭐️ Congratulations ⭐️" - alertMessage = "You completed the game." - showingEndGameAlert = true - - return - } - - selector() - } - - func answer(_ choice: (key: String, value: T), selector: () -> Void) { - var haptics = Haptics() - - if correctAnswer == choice { - haptics.success() - playSound("correctAnswer") - - withAnimation(.easeIn(duration: 0.5)) { - scoreScaleAmount += 1 - userScore += 1 - } - - correctAnswers[correctAnswer.key] = correctAnswer.value - askQuestion { - selector() - } - } else { - haptics.error() - playSound("wrongAnswer") - - withAnimation(.easeIn(duration: 0.5)) { - livesScaleAmount += 1 - userLives -= 1 - } - - wrongAnswers[choice.key] = choice.value - - if userLives == 0 { - alertTitle = "🤕 Game over 🤕" - alertMessage = "Get up and try again." - showingGameOverAlert = true - } else { - alertTitle = "🔴 Wrong 🔴" - alertMessage = "You have \(userLives) lives left." - showingWrongAnswerAlert = true - } - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [self] in - withAnimation(.easeIn(duration: 0.5)) { - scoreScaleAmount = 1 - livesScaleAmount = 1 - } - } - } - - func reset(selector: () -> Void) { - dataAsked = [String: T]() - userScore = 0 - userLives = 3 - correctAnswers = [String: T]() - wrongAnswers = [String: T]() - askQuestion { - selector() - } - } - - private func playSound(_ filename: String) { - let user = User() - - if user.settings.sound { - guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else { - fatalError("Sound file \(filename) couldn't be found") - } - - do { - try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient) - try AVAudioSession.sharedInstance().setActive(true) - } catch { - fatalError("Couldn't activate session") - } - - do { - player = try AVAudioPlayer(contentsOf: soundFileURL) - player?.play() - } catch { - fatalError("Couldn't play sound effect") - } - } - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/GameProtocol+Extension.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,144 @@ +// +// GameProtocol+Extension.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 18/9/22. +// + +import Foundation +import SwiftUI +import AVFAudio + +protocol Game: ObservableObject { + + // Define generic type + associatedtype T: Equatable + + // Game + var data: [String: T] { get set} + var dataAsked: [String: T] { get set } + var correctAnswer: (key: String, value: T) { get set } + + // User + var userChoices: [String: T] { get set } + var userScore: Int { get set } + var userLives: Int { get set } + var correctAnswers: [String: T] { get set } + var wrongAnswers: [String: T] { get set } + + // Alerts + var alertTitle: String { get set } + var alertMessage: String { get set } + var showingGameOverAlert: Bool { get set } + var showingEndGameAlert: Bool { get set } + var showingWrongAnswerAlert: Bool { get set } + var showingExitGameAlert: Bool { get set } + + // Animations + var scoreScaleAmount: Double { get set } + var livesScaleAmount: Double { get set } + + // Sound effects + var player: AVAudioPlayer? { get set } + + func selector() +} + +extension Game { + var questionCounter: Int { + dataAsked.count + } + + func askQuestion(selector: () -> Void) { + guard questionCounter < data.count else { + alertTitle = "⭐️ Congratulations ⭐️" + alertMessage = "You completed the game." + showingEndGameAlert = true + + return + } + + selector() + } + + func answer(_ choice: (key: String, value: T), selector: () -> Void) { + let haptics = Haptics() + + if correctAnswer == choice { + haptics.success() + playSound("correctAnswer") + + withAnimation(.easeIn(duration: 0.5)) { + scoreScaleAmount += 1 + userScore += 1 + } + + correctAnswers[correctAnswer.key] = correctAnswer.value + askQuestion { + selector() + } + } else { + haptics.error() + playSound("wrongAnswer") + + withAnimation(.easeIn(duration: 0.5)) { + livesScaleAmount += 1 + userLives -= 1 + } + + wrongAnswers[choice.key] = choice.value + + if userLives == 0 { + alertTitle = "🤕 Game over 🤕" + alertMessage = "Get up and try again." + showingGameOverAlert = true + } else { + alertTitle = "🔴 Wrong 🔴" + alertMessage = "You have \(userLives) lives left." + showingWrongAnswerAlert = true + } + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [self] in + withAnimation(.easeIn(duration: 0.5)) { + scoreScaleAmount = 1 + livesScaleAmount = 1 + } + } + } + + func reset(selector: () -> Void) { + dataAsked = [String: T]() + userScore = 0 + userLives = 3 + correctAnswers = [String: T]() + wrongAnswers = [String: T]() + askQuestion { + selector() + } + } + + private func playSound(_ filename: String) { + let user = User() + + if user.settings.sound { + guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else { + fatalError("Sound file \(filename) couldn't be found") + } + + do { + try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient) + try AVAudioSession.sharedInstance().setActive(true) + } catch { + fatalError("Couldn't activate session") + } + + do { + player = try AVAudioPlayer(contentsOf: soundFileURL) + player?.play() + } catch { + fatalError("Couldn't play sound effect") + } + } + } +}
--- a/GeoQuiz/Logic/Haptics.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -// -// Haptics.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 18/9/22. -// - -import Foundation -import SwiftUI - -class Haptics { - private var user = User() - - func success() { - if user.settings.haptics { - let generator = UINotificationFeedbackGenerator() - generator.notificationOccurred(.success) - } - } - - func error() { - if user.settings.haptics { - let generator = UINotificationFeedbackGenerator() - generator.notificationOccurred(.error) - } - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/HapticsClass.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,27 @@ +// +// HapticsClass.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 18/9/22. +// + +import Foundation +import SwiftUI + +class Haptics { + private var user = User() + + func success() { + if user.settings.haptics { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.success) + } + } + + func error() { + if user.settings.haptics { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + } +}
--- a/GeoQuiz/Logic/Load.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -// -// Load.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 7/9/22. -// - -import Foundation - -func load<T: Decodable>(_ filename: String) -> T { - let data: Data - - guard let file = Bundle.main.url(forResource: filename, withExtension: nil) - else { - fatalError("Couldn't find \(filename) in main bundle.") - } - - do { - data = try Data(contentsOf: file) - } catch { - fatalError("Couldn't load \(filename) from main bundle:\n\(error)") - } - - do { - let decoder = JSONDecoder() - return try decoder.decode(T.self, from: data) - } catch { - fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/LoadFunc.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,30 @@ +// +// LoadFunc.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 7/9/22. +// + +import Foundation + +func load<T: Decodable>(_ filename: String) -> T { + let data: Data + + guard let file = Bundle.main.url(forResource: filename, withExtension: nil) + else { + fatalError("Couldn't find \(filename) in main bundle.") + } + + do { + data = try Data(contentsOf: file) + } catch { + fatalError("Couldn't load \(filename) from main bundle:\n\(error)") + } + + do { + let decoder = JSONDecoder() + return try decoder.decode(T.self, from: data) + } catch { + fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") + } +}
--- a/GeoQuiz/Logic/User.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -// -// User.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 7/10/22. -// - -import Foundation - -class User: ObservableObject { - @Published var settings = UserSettingsModel() { - didSet { - if let userSettingsEncoded = try? JSONEncoder().encode(settings) { - UserDefaults.standard.set(userSettingsEncoded, forKey: "UserSettings") - } - } - } - - - init() { - if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") { - if let decodedUserSettings = try? JSONDecoder().decode(UserSettingsModel.self, from: userSettings) { - settings = decodedUserSettings - } - } - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/UserClass.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,27 @@ +// +// UserClass.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 7/10/22. +// + +import Foundation + +class User: ObservableObject { + @Published var settings = UserSettings() { + didSet { + if let userSettingsEncoded = try? JSONEncoder().encode(settings) { + UserDefaults.standard.set(userSettingsEncoded, forKey: "UserSettings") + } + } + } + + + init() { + if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") { + if let decodedUserSettings = try? JSONDecoder().decode(UserSettings.self, from: userSettings) { + settings = decodedUserSettings + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/UserSettingsModel.swift Sat Oct 08 21:36:40 2022 +0200 @@ -0,0 +1,14 @@ +// +// UserSettingsModel.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 7/10/22. +// + +import Foundation + +struct UserSettings: Codable { + var haptics: Bool = true + var sound: Bool = true + var numberOfLives: Int = 25 +}
--- a/GeoQuiz/Models/CityModel.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -// -// CityModel.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 29/9/22. -// - -import Foundation - -struct CityModel: Codable { - let cities: [String: CityData] - - struct CityData: Codable, Equatable { - let country: String - let lat: Double - let lon: Double - - static func ==(lhs: CityData, rhs: CityData) -> Bool { - lhs.country == rhs.country - } - } -}
--- a/GeoQuiz/Models/CountryModel.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -// -// CountryModel.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 29/9/22. -// - -import Foundation - -struct CountryModel: Codable { - let countries: [String: CountryData] - - struct CountryData: Codable, Equatable, Hashable { - let flag: String - let currency: String - let population: Int - let capital: String - - static func ==(lhs: CountryData, rhs: CountryData) -> Bool { - lhs.flag == rhs.flag && - lhs.currency == rhs.currency && - lhs.population == rhs.population && - lhs.capital == rhs.capital - } - } -}
--- a/GeoQuiz/Models/UserSettingsModel.swift Fri Oct 07 18:50:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -// -// UserSettingsModel.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 7/10/22. -// - -import Foundation - -struct UserSettingsModel: Codable { - var haptics: Bool = true - var sound: Bool = true - var numberOfLives: Int = 3 -}
--- a/GeoQuiz/SettingsModalView.swift Fri Oct 07 18:50:38 2022 +0200 +++ b/GeoQuiz/SettingsModalView.swift Sat Oct 08 21:36:40 2022 +0200 @@ -10,19 +10,30 @@ struct SettingsModalView: View { @Environment(\.dismiss) var dismiss @StateObject var user = User() + + var lives: [Int] { + var lives = [Int]() + for i in stride(from: 5, to: 55, by: 5) { + lives.append(i) + } + + return lives + } var body: some View { NavigationView { Form { Section { - Picker("Number of lives", selection: $user.settings.numberOfLives) { - ForEach(1..<11) { numberOfLives in + Picker("❤️ Lives", selection: $user.settings.numberOfLives) { + ForEach(lives, id: \.self) { numberOfLives in Text("\(numberOfLives)") .tag(numberOfLives) } } } header: { Text("Game") + } footer: { + Text("Number of lives at the beginning of each game.") } Section {