# HG changeset patch # User Dennis C. M. # Date 1666480298 -3600 # Node ID f140bb277c96b46341da9abf06be9e0bbca7b15d # Parent d20cf93c9812edda4b0f04fddee69d2e535bb541 refactor code diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz.xcodeproj/project.pbxproj --- a/GeoQuiz.xcodeproj/project.pbxproj Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz.xcodeproj/project.pbxproj Sun Oct 23 00:11:38 2022 +0100 @@ -13,11 +13,11 @@ 950C535328F2FA3300179C78 /* BuyPremiumModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950C535228F2FA3300179C78 /* BuyPremiumModalView.swift */; }; 950C535628F3172C00179C78 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = 950C535528F3172C00179C78 /* RevenueCat */; }; 950C535928F3178B00179C78 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 950C535828F3178B00179C78 /* StoreKit.framework */; }; - 95197EFD28F339AE00FE67E9 /* StoreKitRCClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95197EFC28F339AE00FE67E9 /* StoreKitRCClass.swift */; }; + 95197EFD28F339AE00FE67E9 /* StoreKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95197EFC28F339AE00FE67E9 /* StoreKitController.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 /* CityGameClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951AFAF028E5735400A4A4BD /* CityGameClass.swift */; }; + 951AFAF128E5735400A4A4BD /* CityGameController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951AFAF028E5735400A4A4BD /* CityGameController.swift */; }; 951B630228D1A87C004F9877 /* GuessTheCapitalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951B630128D1A87C004F9877 /* GuessTheCapitalView.swift */; }; 951D197328D485E000671FAD /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951D197228D485E000671FAD /* ColorExtension.swift */; }; 952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952E41E828DC521200198643 /* GameAlertsModifier.swift */; }; @@ -30,32 +30,33 @@ 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 */; }; + 955A65A928D7815E00CEEC6D /* HapticsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A65A828D7815E00CEEC6D /* HapticsController.swift */; }; 956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956273E928CB2E98008DC094 /* FlagImageHelper.swift */; }; 9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9590359428E098FF00B24560 /* ProfileModalView.swift */; }; - 95919DB628F076BF00F21F8F /* UserClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB528F076BF00F21F8F /* UserClass.swift */; }; + 95919DB628F076BF00F21F8F /* UserController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB528F076BF00F21F8F /* UserController.swift */; }; 95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* LinkHelper.swift */; }; - 95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* LoadFunc.swift */; }; + 95A4F42929040E350018DFAC /* CoreDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A4F42829040E350018DFAC /* CoreDataController.swift */; }; + 95A4F42B29043DC00018DFAC /* UserImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A4F42A29043DC00018DFAC /* UserImageHelper.swift */; }; + 95AE8D5728C8750E0067F219 /* FileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* FileController.swift */; }; 95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AF322928DF293900023ACC /* GuessTheCountryView.swift */; }; - 95B5C308290190900093A086 /* UserProgressHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B5C307290190900093A086 /* UserProgressHelper.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 /* GameButtonHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C4315828C6500000212131 /* GameButtonHelper.swift */; }; 95C6456C28FE87E4000CD570 /* UserDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456B28FE87E4000CD570 /* UserDataModel.swift */; }; - 95C6456E28FE8C04000CD570 /* UserImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456D28FE8C04000CD570 /* UserImageHelper.swift */; }; + 95C6456E28FE8C04000CD570 /* UserProfileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */; }; 95C6457228FFC4DC000CD570 /* ProfileEditModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */; }; 95C6457428FFC8E0000CD570 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457328FFC8E0000CD570 /* PersistenceController.swift */; }; 95C6457728FFC934000CD570 /* GeoQuiz.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457528FFC934000CD570 /* GeoQuiz.xcdatamodeld */; }; 95C6459A28FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */; }; 95C6459B28FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */; }; 95C6459D290003E1000CD570 /* RecentGameHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6459C290003E1000CD570 /* RecentGameHelper.swift */; }; - 95C645C029011F8E000CD570 /* GameStatsClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C645BF29011F8E000CD570 /* GameStatsClass.swift */; }; - 95C645C229014442000CD570 /* GameInfoProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C645C129014442000CD570 /* GameInfoProtocol+Extension.swift */; }; - 95C645C42901552B000CD570 /* UserProfileComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C645C32901552B000CD570 /* UserProfileComponent.swift */; }; + 95C645C229014442000CD570 /* GameInfoProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C645C129014442000CD570 /* GameInfoProtocol.swift */; }; 95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */; }; + 95DB7C01290492FC007D01D8 /* GameInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95DB7C00290492FC007D01D8 /* GameInfoController.swift */; }; + 95DB7C032904A968007D01D8 /* MapController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95DB7C022904A968007D01D8 /* MapController.swift */; }; 95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */; }; - 95FA409C28D9881100129B60 /* CountryGameClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409B28D9881100129B60 /* CountryGameClass.swift */; }; + 95FA409C28D9881100129B60 /* CountryGameController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409B28D9881100129B60 /* CountryGameController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -64,11 +65,11 @@ 9509A8E128E5A3D700CFCDBA /* GuessThePopulationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessThePopulationView.swift; sourceTree = ""; }; 950C535228F2FA3300179C78 /* BuyPremiumModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyPremiumModalView.swift; sourceTree = ""; }; 950C535828F3178B00179C78 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; - 95197EFC28F339AE00FE67E9 /* StoreKitRCClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitRCClass.swift; sourceTree = ""; }; + 95197EFC28F339AE00FE67E9 /* StoreKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitController.swift; sourceTree = ""; }; 951AFAE828E5655C00A4A4BD /* cities.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = cities.json; sourceTree = ""; }; 951AFAEC28E5657500A4A4BD /* CityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityModel.swift; sourceTree = ""; }; 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryModel.swift; sourceTree = ""; }; - 951AFAF028E5735400A4A4BD /* CityGameClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityGameClass.swift; sourceTree = ""; }; + 951AFAF028E5735400A4A4BD /* CityGameController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityGameController.swift; sourceTree = ""; }; 951B630128D1A87C004F9877 /* GuessTheCapitalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCapitalView.swift; sourceTree = ""; }; 951D197228D485E000671FAD /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; 952E41E828DC521200198643 /* GameAlertsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameAlertsModifier.swift; sourceTree = ""; }; @@ -82,33 +83,34 @@ 955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatterExtension.swift; sourceTree = ""; }; 955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameToolbarHelper.swift; sourceTree = ""; }; 955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameProtocol+Extension.swift"; sourceTree = ""; }; - 955A65A828D7815E00CEEC6D /* HapticsClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsClass.swift; sourceTree = ""; }; + 955A65A828D7815E00CEEC6D /* HapticsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsController.swift; sourceTree = ""; }; 956273E928CB2E98008DC094 /* FlagImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImageHelper.swift; sourceTree = ""; }; 9590359428E098FF00B24560 /* ProfileModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModalView.swift; sourceTree = ""; }; - 95919DB528F076BF00F21F8F /* UserClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClass.swift; sourceTree = ""; }; + 95919DB528F076BF00F21F8F /* UserController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserController.swift; sourceTree = ""; }; 95919DBB28F08D0600F21F8F /* LinkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkHelper.swift; sourceTree = ""; }; - 95AE8D5628C8750E0067F219 /* LoadFunc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadFunc.swift; sourceTree = ""; }; + 95A4F42829040E350018DFAC /* CoreDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataController.swift; sourceTree = ""; }; + 95A4F42A29043DC00018DFAC /* UserImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageHelper.swift; sourceTree = ""; }; + 95AE8D5628C8750E0067F219 /* FileController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileController.swift; sourceTree = ""; }; 95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = ""; }; - 95B5C307290190900093A086 /* UserProgressHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProgressHelper.swift; sourceTree = ""; }; 95BC392C28EC42570049AB49 /* CityMapHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMapHelper.swift; sourceTree = ""; }; 95C430F828D0A8E500480D23 /* GradientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientExtension.swift; sourceTree = ""; }; 95C4315528C64A8C00212131 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 95C4315828C6500000212131 /* GameButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameButtonHelper.swift; sourceTree = ""; }; 95C6456B28FE87E4000CD570 /* UserDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataModel.swift; sourceTree = ""; }; - 95C6456D28FE8C04000CD570 /* UserImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageHelper.swift; sourceTree = ""; }; + 95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHelper.swift; sourceTree = ""; }; 95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditModalView.swift; sourceTree = ""; }; 95C6457328FFC8E0000CD570 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; 95C6457628FFC934000CD570 /* GeoQuiz.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = GeoQuiz.xcdatamodel; sourceTree = ""; }; 95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayedGame+CoreDataClass.swift"; sourceTree = ""; }; 95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayedGame+CoreDataProperties.swift"; sourceTree = ""; }; 95C6459C290003E1000CD570 /* RecentGameHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentGameHelper.swift; sourceTree = ""; }; - 95C645BF29011F8E000CD570 /* GameStatsClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStatsClass.swift; sourceTree = ""; }; - 95C645C129014442000CD570 /* GameInfoProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameInfoProtocol+Extension.swift"; sourceTree = ""; }; - 95C645C32901552B000CD570 /* UserProfileComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileComponent.swift; sourceTree = ""; }; + 95C645C129014442000CD570 /* GameInfoProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameInfoProtocol.swift; sourceTree = ""; }; 95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityAlertHelper.swift; sourceTree = ""; }; + 95DB7C00290492FC007D01D8 /* GameInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameInfoController.swift; sourceTree = ""; }; + 95DB7C022904A968007D01D8 /* MapController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapController.swift; sourceTree = ""; }; 95E6188428DDDB5C003359ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheFlagView.swift; sourceTree = ""; }; - 95FA409B28D9881100129B60 /* CountryGameClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryGameClass.swift; sourceTree = ""; }; + 95FA409B28D9881100129B60 /* CountryGameController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryGameController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -130,16 +132,18 @@ 951AFAEC28E5657500A4A4BD /* CityModel.swift */, 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */, 95C6456B28FE87E4000CD570 /* UserDataModel.swift */, - 95C645C129014442000CD570 /* GameInfoProtocol+Extension.swift */, + 95C645C129014442000CD570 /* GameInfoProtocol.swift */, 955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */, - 955A65A828D7815E00CEEC6D /* HapticsClass.swift */, - 95FA409B28D9881100129B60 /* CountryGameClass.swift */, - 951AFAF028E5735400A4A4BD /* CityGameClass.swift */, - 95919DB528F076BF00F21F8F /* UserClass.swift */, - 95197EFC28F339AE00FE67E9 /* StoreKitRCClass.swift */, - 95C645BF29011F8E000CD570 /* GameStatsClass.swift */, - 95AE8D5628C8750E0067F219 /* LoadFunc.swift */, + 95DB7C00290492FC007D01D8 /* GameInfoController.swift */, + 955A65A828D7815E00CEEC6D /* HapticsController.swift */, + 95FA409B28D9881100129B60 /* CountryGameController.swift */, + 951AFAF028E5735400A4A4BD /* CityGameController.swift */, + 95919DB528F076BF00F21F8F /* UserController.swift */, + 95197EFC28F339AE00FE67E9 /* StoreKitController.swift */, + 95A4F42829040E350018DFAC /* CoreDataController.swift */, 95C6457328FFC8E0000CD570 /* PersistenceController.swift */, + 95AE8D5628C8750E0067F219 /* FileController.swift */, + 95DB7C022904A968007D01D8 /* MapController.swift */, 95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */, 95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */, ); @@ -224,10 +228,9 @@ 95BC392C28EC42570049AB49 /* CityMapHelper.swift */, 95919DBB28F08D0600F21F8F /* LinkHelper.swift */, 95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */, - 95C6456D28FE8C04000CD570 /* UserImageHelper.swift */, + 95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */, + 95A4F42A29043DC00018DFAC /* UserImageHelper.swift */, 95C6459C290003E1000CD570 /* RecentGameHelper.swift */, - 95B5C307290190900093A086 /* UserProgressHelper.swift */, - 95C645C32901552B000CD570 /* UserProfileComponent.swift */, 952E41E828DC521200198643 /* GameAlertsModifier.swift */, 95C430F828D0A8E500480D23 /* GradientExtension.swift */, 951D197228D485E000671FAD /* ColorExtension.swift */, @@ -316,35 +319,36 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 955A65A928D7815E00CEEC6D /* HapticsClass.swift in Sources */, + 955A65A928D7815E00CEEC6D /* HapticsController.swift in Sources */, 95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */, 95C6459A28FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift in Sources */, + 95A4F42B29043DC00018DFAC /* UserImageHelper.swift in Sources */, 952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */, - 95197EFD28F339AE00FE67E9 /* StoreKitRCClass.swift in Sources */, + 95197EFD28F339AE00FE67E9 /* StoreKitController.swift in Sources */, 9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */, 955A658328D733E400CEEC6D /* GameProtocol+Extension.swift in Sources */, + 95A4F42929040E350018DFAC /* CoreDataController.swift in Sources */, 95C6457428FFC8E0000CD570 /* PersistenceController.swift in Sources */, - 95919DB628F076BF00F21F8F /* UserClass.swift in Sources */, + 95919DB628F076BF00F21F8F /* UserController.swift in Sources */, 95C6459D290003E1000CD570 /* RecentGameHelper.swift in Sources */, - 95C6456E28FE8C04000CD570 /* UserImageHelper.swift in Sources */, + 95C6456E28FE8C04000CD570 /* UserProfileHelper.swift in Sources */, 95C4315628C64A8C00212131 /* ContentView.swift in Sources */, - 95C645C229014442000CD570 /* GameInfoProtocol+Extension.swift in Sources */, + 95C645C229014442000CD570 /* GameInfoProtocol.swift in Sources */, 95C4315928C6500000212131 /* GameButtonHelper.swift in Sources */, 956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */, 951AFAED28E5657500A4A4BD /* CityModel.swift in Sources */, 950C535328F2FA3300179C78 /* BuyPremiumModalView.swift in Sources */, 951B630228D1A87C004F9877 /* GuessTheCapitalView.swift in Sources */, 9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */, - 95C645C029011F8E000CD570 /* GameStatsClass.swift in Sources */, 95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */, + 95DB7C032904A968007D01D8 /* MapController.swift in Sources */, 95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */, 951AFAEF28E565FE00A4A4BD /* CountryModel.swift in Sources */, 95030CEA28D1BA4D001AA3A1 /* AnswerButtonHelper.swift in Sources */, - 95FA409C28D9881100129B60 /* CountryGameClass.swift in Sources */, + 95FA409C28D9881100129B60 /* CountryGameController.swift in Sources */, 95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */, 955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */, - 95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */, - 95C645C42901552B000CD570 /* UserProfileComponent.swift in Sources */, + 95AE8D5728C8750E0067F219 /* FileController.swift in Sources */, 95C6457728FFC934000CD570 /* GeoQuiz.xcdatamodeld in Sources */, 9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */, 955950BB28F15FF2001BDEE8 /* FormatterExtension.swift in Sources */, @@ -355,8 +359,8 @@ 95C430F928D0A8E500480D23 /* GradientExtension.swift in Sources */, 952E41ED28DC658900198643 /* SettingsModalView.swift in Sources */, 95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */, - 95B5C308290190900093A086 /* UserProgressHelper.swift in Sources */, - 951AFAF128E5735400A4A4BD /* CityGameClass.swift in Sources */, + 95DB7C01290492FC007D01D8 /* GameInfoController.swift in Sources */, + 951AFAF128E5735400A4A4BD /* CityGameController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Assets.xcassets/Custom colors/Background.colorset/Contents.json --- a/GeoQuiz/Assets.xcassets/Custom colors/Background.colorset/Contents.json Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "246", - "green" : "242", - "red" : "242" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "30", - "green" : "28", - "red" : "28" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/BuyPremiumModalView.swift --- a/GeoQuiz/BuyPremiumModalView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/BuyPremiumModalView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,11 +8,12 @@ import SwiftUI struct BuyPremiumModalView: View { + @ObservedObject var storeKitController: StoreKitController + @Environment(\.dismiss) var dismiss - @ObservedObject var storeKitRC: StoreKitRC var body: some View { - NavigationView { + NavigationStack { ZStack { ScrollView(showsIndicators: false) { VStack(alignment: .center, spacing: 20) { @@ -57,9 +58,9 @@ .foregroundColor(.secondary) VStack { - if let package = storeKitRC.offerings?.current?.lifetime { + if let package = storeKitController.offerings?.current?.lifetime { Button { - storeKitRC.buy(package) + storeKitController.buy(package) } label: { Text("Buy for \(package.storeProduct.localizedPriceString)") .font(.headline) @@ -72,7 +73,7 @@ } } - Button("Restore purchases", action: storeKitRC.restorePurchase) + Button("Restore purchases", action: storeKitController.restorePurchase) } .padding() @@ -86,12 +87,12 @@ } } - if storeKitRC.showingActivityAlert { + if storeKitController.showingActivityAlert { ActivityAlert() } } .navigationBarTitleDisplayMode(.inline) - .onAppear(perform: storeKitRC.fetchOfferings) + .onAppear(perform: storeKitController.fetchOfferings) .toolbar { ToolbarItem(placement: .cancellationAction) { Button { @@ -102,16 +103,16 @@ } } } - .disabled(storeKitRC.showingActivityAlert) - .interactiveDismissDisabled(storeKitRC.showingActivityAlert) + .disabled(storeKitController.showingActivityAlert) + .interactiveDismissDisabled(storeKitController.showingActivityAlert) - .alert(storeKitRC.errorAlertTitle, isPresented: $storeKitRC.showingErrorAlert) { + .alert(storeKitController.errorAlertTitle, isPresented: $storeKitController.showingErrorAlert) { Button("OK", role: .cancel) { } } message: { - Text(storeKitRC.errorAlertMessage) + Text(storeKitController.errorAlertMessage) } - .alert("GeoQuiz Premium is active!", isPresented: $storeKitRC.showingSuccessAlert) { + .alert("GeoQuiz Premium is active!", isPresented: $storeKitController.showingSuccessAlert) { Button("OK", role: .cancel) { dismiss() } } message: { Text("Thanks for supporting indie apps ❤️") @@ -121,6 +122,6 @@ struct BuyPremiumModalView_Previews: PreviewProvider { static var previews: some View { - BuyPremiumModalView(storeKitRC: StoreKitRC()) + BuyPremiumModalView(storeKitController: StoreKitController()) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/AnswerButtonHelper.swift --- a/GeoQuiz/Components/AnswerButtonHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/AnswerButtonHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,14 +8,14 @@ import SwiftUI struct AnswerButton: View { - let optionName: String + let name: String let color: Color var body: some View { RoundedRectangle(cornerRadius: 15) .foregroundColor(.white) .overlay( - Text(optionName) + Text(name) .font(.title2.bold()) .foregroundColor(color) ) @@ -30,7 +30,7 @@ VStack { Spacer() - AnswerButton(optionName: "Madrid", color: .royalLightBlue) + AnswerButton(name: "Madrid", color: .royalLightBlue) .frame(height: 70) } .padding() diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/CityMapHelper.swift --- a/GeoQuiz/Components/CityMapHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/CityMapHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -9,12 +9,18 @@ import MapKit struct CityMap: View { - @ObservedObject var game: CityGame - @State private var mapImage: UIImage? = nil + @ObservedObject var game: CityGameController + + @StateObject var mapController: MapController + + init(game: CityGameController) { + self.game = game + self._mapController = StateObject(wrappedValue: MapController()) + } var body: some View { VStack { - if let mapImage = mapImage { + if let mapImage = mapController.image { Image(uiImage: mapImage) .resizable() .scaledToFit() @@ -28,42 +34,18 @@ 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) - } + .onChange(of: game.correctAnswer.value) { _ in + mapController.getMapImage(lat: game.correctAnswer.value.lat, lon: game.correctAnswer.value.lon) + } + + .onAppear { + mapController.getMapImage(lat: game.correctAnswer.value.lat, lon: game.correctAnswer.value.lon) } } } struct CityMap_Previews: PreviewProvider { static var previews: some View { - CityMap(game: CityGame()) + CityMap(game: CityGameController()) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/ColorExtension.swift --- a/GeoQuiz/Components/ColorExtension.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/ColorExtension.swift Sun Oct 23 00:11:38 2022 +0100 @@ -40,8 +40,4 @@ static var royalLightBlue: Color { Color("RoyalLightBlue") } - - static var customBackground: Color { - Color("Background") - } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/FlagImageHelper.swift --- a/GeoQuiz/Components/FlagImageHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/FlagImageHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,11 +8,11 @@ import SwiftUI struct FlagImage: View { - @Environment(\.colorScheme) var colorScheme - var flagSymbol: String var cornerRadius: Double + @Environment(\.colorScheme) var colorScheme + var body: some View { Image(flagSymbol) .renderingMode(.original) diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/GameButtonHelper.swift --- a/GeoQuiz/Components/GameButtonHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/GameButtonHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,16 +8,19 @@ import SwiftUI struct GameButton: View { - let gradient: Gradient - let level: String - let symbol: String - let name: String + let gameInfo: GameInfo + let isActive: Bool + + init(gameType: GameType, isActive: Bool) { + self.gameInfo = GameInfoController.getInfo(for: gameType) + self.isActive = isActive + } var body: some View { RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( - gradient: gradient, + gradient: gameInfo.gradient, startPoint: .trailing, endPoint: .leading ) ) @@ -27,7 +30,7 @@ ZStack(alignment: .trailing) { VStack(alignment: .leading) { HStack { - Image(systemName: symbol) + Image(systemName: isActive ? gameInfo.symbol : "lock.fill") .font(.headline) .padding(5) .background( @@ -40,10 +43,10 @@ .padding(.bottom) VStack(alignment: .leading, spacing: 5) { - Text(level) + Text(gameInfo.level) .font(.callout) - Text(name) + Text(gameInfo.name) .font(.title.bold()) } } @@ -56,11 +59,6 @@ struct GameButton_Previews: PreviewProvider { static var previews: some View { - GameButton( - gradient: .main, - level: "Level 1", - symbol: "flag.fill", - name: "Guess the flag" - ) + GameButton(gameType: .guessTheFlag, isActive: false) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/GameToolbarHelper.swift --- a/GeoQuiz/Components/GameToolbarHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/GameToolbarHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -79,7 +79,7 @@ GeometryReader { geo in VStack { - GameToolbar(game: CountryGame(), color: .mayaBlue) + GameToolbar(game: CountryGameController(), color: .mayaBlue) Spacer() } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/LinkHelper.swift --- a/GeoQuiz/Components/LinkHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/LinkHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -9,7 +9,7 @@ struct LinkComponent: View { var color: Color - var iconName: String + var symbol: String var text: String var url: URL @@ -18,7 +18,7 @@ var body: some View { Link(destination: url) { HStack(alignment: .center, spacing: 20) { - Image(systemName: iconName) + Image(systemName: symbol) .imageScale(.large) .foregroundColor(color) @@ -33,7 +33,7 @@ static var previews: some View { LinkComponent( color: .mayaBlue, - iconName: "info.circle.fill", + symbol: "info.circle.fill", text: "About", url: URL(string: "https://dennistech.io")! ) diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/RecentGameHelper.swift --- a/GeoQuiz/Components/RecentGameHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/RecentGameHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -9,29 +9,32 @@ struct RecentGame: View { let game: PlayedGame - let name: String - let gradient: Gradient - let symbol: String + let gameInfo: GameInfo + + init(game: PlayedGame) { + self.game = game + self.gameInfo = GameInfoController.getInfo(for: game.type) + } var body: some View { HStack(alignment: .center, spacing: 15) { RoundedRectangle(cornerRadius: 5) .fill( LinearGradient( - gradient: gradient, + gradient: gameInfo.gradient, startPoint: .top, endPoint: .bottom ) ) .frame(width: 35, height: 35) .overlay( - Image(systemName: symbol) + Image(systemName: gameInfo.symbol) .font(.headline) .foregroundColor(.white) .padding(5) ) VStack(alignment: .leading) { - Text(name) + Text(gameInfo.name) .font(.headline) Text("\(game.date, format: .dateTime)") @@ -45,28 +48,8 @@ .font(.headline) } - } - - init(game: PlayedGame) { - self.game = game - - switch game.type { - case .guessTheFlag: - self.name = GuessTheFlagInfo.name - self.gradient = GuessTheFlagInfo.gradient - self.symbol = GuessTheFlagInfo.symbol - case .guessTheCapital: - self.name = GuessTheCapitalInfo.name - self.gradient = GuessTheCapitalInfo.gradient - self.symbol = GuessTheCapitalInfo.symbol - case .guessTheCountry: - self.name = GuessTheCountryInfo.name - self.gradient = GuessTheCountryInfo.gradient - self.symbol = GuessTheCountryInfo.symbol - case .guessThePopulation: - self.name = GuessThePopulationInfo.name - self.gradient = GuessThePopulationInfo.gradient - self.symbol = GuessThePopulationInfo.symbol - } + .padding() + .background(Color(.secondarySystemGroupedBackground)) + .cornerRadius(20) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/UserImageHelper.swift --- a/GeoQuiz/Components/UserImageHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Components/UserImageHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -2,18 +2,17 @@ // UserImageHelper.swift // GeoQuiz // -// Created by Dennis Concepción Martín on 18/10/22. +// Created by Dennis Concepción Martín on 22/10/22. // import SwiftUI struct UserImage: View { - var uiImage: UIImage? + @ObservedObject var userController: UserController var body: some View { - if let uiImage = uiImage { + if let uiImage = userController.data.uiImage { Circle() - .frame(height: 100) .overlay( Image(uiImage: uiImage) .resizable() @@ -22,7 +21,6 @@ ) } else { Circle() - .frame(height: 100) .foregroundColor(.secondary.opacity(0.3)) .overlay( Image(systemName: "person") @@ -34,6 +32,6 @@ struct UserImage_Previews: PreviewProvider { static var previews: some View { - UserImage() + UserImage(userController: UserController()) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/UserProfileComponent.swift --- a/GeoQuiz/Components/UserProfileComponent.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -// -// UserProfileComponent.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 20/10/22. -// - -import SwiftUI - -struct UserProfile: View { - @ObservedObject var user: User - @ObservedObject var storeKitRC: StoreKitRC - - var body: some View { - HStack(spacing: 20) { - UserImage(uiImage: user.data.uiImage) - - VStack(alignment: .leading, spacing: 8) { - Text(user.data.username) - .font(.title) - .fontWeight(.semibold) - - if storeKitRC.isActive { - Text("Premium user ⭐️") - .foregroundColor(.secondary) - } - } - - Spacer() - } - } -} - -struct UserProfile_Previews: PreviewProvider { - static var previews: some View { - UserProfile(user: User(), storeKitRC: StoreKitRC()) - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/UserProfileHelper.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Components/UserProfileHelper.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,44 @@ +// +// UserProfileHelper.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 18/10/22. +// + +import SwiftUI + +struct UserProfile: View { + @ObservedObject var userController: UserController + @ObservedObject var storeKitController: StoreKitController + + @Binding var isShowing: Bool + + var body: some View { + VStack(spacing: 20) { + UserImage(userController: userController) + .frame(height: 150) + .shadow(radius: 10) + + VStack(spacing: 10) { + Text(userController.data.username) + .font(.title.bold()) + + if storeKitController.premiumIsActive { + Text("Premium user ⭐️") + .foregroundColor(.secondary) + } + } + + Button("Edit") { + isShowing = true + } + .buttonStyle(.borderedProminent) + } + } +} + +struct UserProfile_Previews: PreviewProvider { + static var previews: some View { + UserProfile(userController: UserController(), storeKitController: StoreKitController(), isShowing: .constant(true)) + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Components/UserProgressHelper.swift --- a/GeoQuiz/Components/UserProgressHelper.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -// -// UserProgressHelper.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 20/10/22. -// - -import SwiftUI - -struct ViewTest: View { - var body: some View { - GeometryReader { geo in - ZStack { - Capsule() - .frame(height: 6) - } - } - } -} - -struct UserProgress: View { - let name: String - let gradient: Gradient - let score: Int - let maxScore: Int - let pctScore: Double - - var body: some View { - VStack { - Spacer() - HStack { - Text(name) - .font(.headline) - - Spacer() - - Text("\(score) of \(maxScore)") - .font(.callout) - .foregroundColor(.secondary) - } - - GeometryReader { geo in - ZStack(alignment: .leading) { - Capsule() - .foregroundColor(.customBackground) - .frame(height: 6) - - Capsule() - .fill( - LinearGradient( - gradient: gradient, - startPoint: .trailing, endPoint: .leading - ) - ) - .frame(width: geo.size.width * pctScore, height: 6) - } - .frame(height: geo.size.height) - } - } - } - - init(playedGames: FetchedResults, gameType: GameType) { - switch(gameType) { - case . guessTheFlag: - self.name = GuessTheFlagInfo.name - self.gradient = GuessTheFlagInfo.gradient - self.maxScore = GuessTheFlagInfo.numberOfQuestions - case .guessTheCapital: - self.name = GuessTheCapitalInfo.name - self.gradient = GuessTheCapitalInfo.gradient - self.maxScore = GuessTheCapitalInfo.numberOfQuestions - case .guessTheCountry: - self.name = GuessTheCountryInfo.name - self.gradient = GuessTheCountryInfo.gradient - self.maxScore = GuessTheCountryInfo.numberOfQuestions - case .guessThePopulation: - self.name = GuessThePopulationInfo.name - self.gradient = GuessThePopulationInfo.gradient - self.maxScore = GuessThePopulationInfo.numberOfQuestions - } - - let games = playedGames.filter { $0.type == gameType } - self.score = Int(games.max { $0.score < $1.score }?.score ?? 0) - self.pctScore = Double(score) / Double(maxScore) - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/ContentView.swift --- a/GeoQuiz/ContentView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/ContentView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -9,22 +9,21 @@ struct ContentView: View { @State private var path: [GameType] = [] - @State private var showingBuyPremiumModalView = false @State private var showingSettingsModalView = false @State private var showingProfileModalView = false - @StateObject var storeKitRC = StoreKitRC() - @StateObject var user = User() + @StateObject var storeKitController = StoreKitController() + @StateObject var userController = UserController() + + let premiumGames: [GameType] = [.guessTheCapital, .guessTheCountry, .guessThePopulation] var body: some View { NavigationStack(path: $path) { ScrollView(showsIndicators: false) { - - NavigationLink(value: GameType.guessTheFlag) { EmptyView() } - NavigationLink(value: GameType.guessTheCapital) { EmptyView() } - NavigationLink(value: GameType.guessTheCountry) { EmptyView() } - NavigationLink(value: GameType.guessThePopulation) { EmptyView() } + ForEach(GameType.allCases, id: \.rawValue) { gameType in + NavigationLink(value: gameType) { EmptyView() } + } VStack(alignment: .leading, spacing: 30) { Text("Select a game 🎮") @@ -34,122 +33,83 @@ Button { path.append(.guessTheFlag) } label: { - GameButton( - gradient: GuessTheFlagInfo.gradient, - level: GuessTheFlagInfo.level, - symbol: GuessTheFlagInfo.symbol, - name: GuessTheFlagInfo.name - ) - } - - Button { - if storeKitRC.isActive { - path.append(.guessTheCapital) - } else { - showingBuyPremiumModalView = true - } - } label: { - GameButton( - gradient: GuessTheCapitalInfo.gradient, - level: GuessTheCapitalInfo.level, - symbol: storeKitRC.isActive ? GuessTheCapitalInfo.symbol: "lock.fill", - name: GuessTheCapitalInfo.name - ) - } - - Button { - if storeKitRC.isActive { - path.append(.guessTheCountry) - } else { - showingBuyPremiumModalView = true - } - } label: { - GameButton( - gradient: GuessTheCountryInfo.gradient, - level: GuessTheCountryInfo.level, - symbol: storeKitRC.isActive ? GuessTheCountryInfo.symbol: "lock.fill", - name: GuessTheCountryInfo.name - ) + GameButton(gameType: .guessTheFlag, isActive: true) } - Button { - if storeKitRC.isActive { - path.append(.guessThePopulation) - } else { - showingBuyPremiumModalView = true + ForEach(premiumGames, id: \.rawValue) { gameType in + Button { + if storeKitController.premiumIsActive { + path.append(gameType) + } else { + showingBuyPremiumModalView = true + } + } label: { + GameButton(gameType: gameType, isActive: storeKitController.premiumIsActive) + } } - } label: { - GameButton( - gradient: GuessThePopulationInfo.gradient, - level: GuessThePopulationInfo.level, - symbol: storeKitRC.isActive ? GuessThePopulationInfo.symbol: "lock.fill", - name: GuessThePopulationInfo.name - ) } - + .padding() } - .padding() - } - .navigationTitle("GeoQuiz") - .navigationBarTitleDisplayMode(.inline) - - .navigationDestination(for: GameType.self) { gameMode in - switch gameMode { - case .guessTheFlag: - GuessTheFlagView() - case .guessTheCapital: - GuessTheFlagView() - case .guessTheCountry: - GuessTheCountryView() - case .guessThePopulation: - GuessThePopulationView() - } - } - - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button { - showingSettingsModalView = true - } label: { - Label("Settings", systemImage: "gear") + .navigationTitle("GeoQuiz") + .navigationBarTitleDisplayMode(.inline) + + .navigationDestination(for: GameType.self) { gameMode in + switch gameMode { + case .guessTheFlag: + GuessTheFlagView() + case .guessTheCapital: + GuessTheFlagView() + case .guessTheCountry: + GuessTheCountryView() + case .guessThePopulation: + GuessThePopulationView() } } - ToolbarItemGroup { - if !storeKitRC.isActive { + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { Button { - showingBuyPremiumModalView = true + showingSettingsModalView = true } label: { - Label("Buy premium", systemImage: "star") + Label("Settings", systemImage: "gear") } } - Button { - showingProfileModalView = true - } label: { - Label("Profile", systemImage: "person") + ToolbarItemGroup { + if !storeKitController.premiumIsActive { + Button { + showingBuyPremiumModalView = true + } label: { + Label("Buy premium", systemImage: "star") + } + } + + Button { + showingProfileModalView = true + } label: { + Label("Profile", systemImage: "person") + } } } + .sheet(isPresented: $showingBuyPremiumModalView) { + BuyPremiumModalView(storeKitController: storeKitController) + } + + .sheet(isPresented: $showingSettingsModalView) { + SettingsModalView(user: userController) + } + + .sheet(isPresented: $showingProfileModalView) { + ProfileModalView(userController: userController, storeKitController: storeKitController) + } } - .sheet(isPresented: $showingBuyPremiumModalView) { - BuyPremiumModalView(storeKitRC: storeKitRC) - } - - .sheet(isPresented: $showingSettingsModalView) { - SettingsModalView(user: user) - } - - .sheet(isPresented: $showingProfileModalView) { - ProfileModalView(user: user, storeKitRC: storeKitRC) - } + .navigationViewStyle(StackNavigationViewStyle()) } - .navigationViewStyle(StackNavigationViewStyle()) } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + + struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + } } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/GuessTheCapitalView.swift --- a/GeoQuiz/GuessTheCapitalView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/GuessTheCapitalView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,7 +8,7 @@ import SwiftUI struct GuessTheCapitalView: View { - @StateObject var game = CountryGame() + @StateObject var game = CountryGameController() @Environment(\.managedObjectContext) var moc @@ -50,7 +50,7 @@ } } label: { AnswerButton( - optionName: game.data[countryName]!.capital, + name: game.data[countryName]!.capital, color: .chinaPink ) .frame(height: geo.size.height * 0.08) diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/GuessTheCountryView.swift --- a/GeoQuiz/GuessTheCountryView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/GuessTheCountryView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,7 +8,7 @@ import SwiftUI struct GuessTheCountryView: View { - @StateObject var game = CityGame() + @StateObject var game = CityGameController() @Environment(\.managedObjectContext) var moc @@ -48,7 +48,7 @@ } } label: { AnswerButton( - optionName: game.data[cityName]!.country, + name: game.data[cityName]!.country, color: .blueBell ) .frame(height: geo.size.height * 0.08) diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/GuessTheFlagView.swift --- a/GeoQuiz/GuessTheFlagView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/GuessTheFlagView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,7 +8,7 @@ import SwiftUI struct GuessTheFlagView: View { - @StateObject var game = CountryGame() + @StateObject var game = CountryGameController() @Environment(\.managedObjectContext) var moc diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/GuessThePopulationView.swift --- a/GeoQuiz/GuessThePopulationView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/GuessThePopulationView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,7 +8,7 @@ import SwiftUI struct GuessThePopulationView: View { - @StateObject var game = CountryGame() + @StateObject var game = CountryGameController() @Environment(\.managedObjectContext) var moc @@ -51,7 +51,7 @@ } label: { let population = game.data[countryName]!.population AnswerButton( - optionName: population.formattedWithSeparator, + name: population.formattedWithSeparator, color: .middleRed ) .frame(height: geo.size.height * 0.08) diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CityGameClass.swift --- a/GeoQuiz/Logic/CityGameClass.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -// -// 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 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.data.numberOfLives - - if let userData = UserDefaults.standard.data(forKey: "UserData") { - if let decodedUserData = try? JSONDecoder().decode(UserData.self, from: userData) { - userLives = decodedUserData.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 - } -} - diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CityGameController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CityGameController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,103 @@ +// +// CityGameController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 29/9/22. +// + +import Foundation +import AVFAudio + +class CityGameController: Game, ObservableObject { + + // Define type of generics + typealias T = CityModel.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 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 = FileController.load("cities.json") + self.data = data.cities + + let user = UserController() + userLives = user.data.numberOfLives + + if let userData = UserDefaults.standard.data(forKey: "UserData") { + if let decodedUserData = try? JSONDecoder().decode(UserDataModel.self, from: userData) { + userLives = decodedUserData.numberOfLives + } + } + + askQuestion { + selector() + } + } +} + +extension CityGameController { + 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 + } +} + diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CityModel.swift --- a/GeoQuiz/Logic/CityModel.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Logic/CityModel.swift Sun Oct 23 00:11:38 2022 +0100 @@ -7,7 +7,7 @@ import Foundation -struct CityData: Codable { +struct CityModel: Codable { let cities: [String: City] struct City: Codable, Equatable { diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CoreDataController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CoreDataController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,21 @@ +// +// CoreDataController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 22/10/22. +// + +import Foundation +import SwiftUI +import CoreData + +class CoreDataController { + static func deleteGame(at offsets: IndexSet, from games: FetchedResults, with moc: NSManagedObjectContext) { + for offset in offsets { + let game = games[offset] + moc.delete(game) + } + + try? moc.save() + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CountryGameClass.swift --- a/GeoQuiz/Logic/CountryGameClass.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -// -// 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 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.data.numberOfLives - - if let userData = UserDefaults.standard.data(forKey: "UserData") { - if let decodedUserData = try? JSONDecoder().decode(UserData.self, from: userData) { - userLives = decodedUserData.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 - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CountryGameController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/CountryGameController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,96 @@ +// +// CountryGameController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 20/9/22. +// + +import Foundation +import AVFAudio + +class CountryGameController: Game, ObservableObject { + + // Define type of generics + typealias T = CountryModel.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 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 = FileController.load("countries.json") + self.data = data.countries + + let user = UserController() + userLives = user.data.numberOfLives + + if let userData = UserDefaults.standard.data(forKey: "UserData") { + if let decodedUserData = try? JSONDecoder().decode(UserDataModel.self, from: userData) { + userLives = decodedUserData.numberOfLives + } + } + + askQuestion { + selector() + } + } +} + +extension CountryGameController { + 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 + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/CountryModel.swift --- a/GeoQuiz/Logic/CountryModel.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Logic/CountryModel.swift Sun Oct 23 00:11:38 2022 +0100 @@ -7,7 +7,7 @@ import Foundation -struct CountryData: Codable { +struct CountryModel: Codable { let countries: [String: Country] struct Country: Codable, Equatable, Hashable { diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/FileController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/FileController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,32 @@ +// +// FileController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 7/9/22. +// + +import Foundation + +class FileController { + static func load(_ 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)") + } + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/GameInfoController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/GameInfoController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,80 @@ +// +// GameInfoController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 22/10/22. +// + +import Foundation +import SwiftUI + +class GameInfoController { + static func getInfo(for gameType: GameType) -> GameInfo { + switch gameType { + case .guessTheFlag: + return GuessTheFlagInfo() + case .guessTheCapital: + return GuessTheCapitalInfo() + case .guessTheCountry: + return GuessTheCountryInfo() + case .guessThePopulation: + return GuessThePopulationInfo() + } + } + + private struct GuessTheFlagInfo: GameInfo { + let type: GameType = .guessTheFlag + let level = "Level 1" + let name = "Guess the flag" + let isPremium = false + let symbol = "flag.fill" + let gradient: Gradient = .main + + var numberOfQuestions: Int { + let data: CountryModel = FileController.load("countries.json") + return data.countries.count + } + } + + private struct GuessTheCapitalInfo: GameInfo { + let type: GameType = .guessTheFlag + let level = "Level 2" + let name = "Guess the capital" + let isPremium = false + let symbol = "building.2.fill" + let gradient: Gradient = .secondary + + var numberOfQuestions: Int { + let data: CountryModel = FileController.load("countries.json") + return data.countries.count + } + } + + private struct GuessTheCountryInfo: GameInfo { + let type: GameType = .guessTheFlag + let level = "Level 3" + let name = "Guess the country" + let isPremium = false + let symbol = "globe.americas.fill" + let gradient: Gradient = .tertiary + + var numberOfQuestions: Int { + let data: CityModel = FileController.load("cities.json") + return data.cities.count + } + } + + private struct GuessThePopulationInfo: GameInfo { + let type: GameType = .guessTheFlag + let level = "Level 4" + let name = "Guess the population" + let isPremium = false + let symbol = "person.fill" + let gradient: Gradient = .quaternary + + var numberOfQuestions: Int { + let data: CityModel = FileController.load("cities.json") + return data.cities.count + } + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/GameInfoProtocol+Extension.swift --- a/GeoQuiz/Logic/GameInfoProtocol+Extension.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -// -// GameInfoProtocol+Structs.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 20/10/22. -// - -import Foundation -import SwiftUI - -@objc -public enum GameType: Int16 { - case guessTheFlag, guessTheCapital, guessTheCountry, guessThePopulation -} - -protocol GameInfo { - static var type: GameType { get } - static var level: String { get } - static var name: String { get } - static var symbol: String { get } - static var gradient: Gradient { get } - static var numberOfQuestions: Int { get } -} - -class GuessTheFlagInfo: GameInfo { - static let type: GameType = .guessTheFlag - static let level = "Level 1" - static let name = "Guess the flag" - static let symbol = "flag.fill" - static let gradient: Gradient = .main - - static var numberOfQuestions: Int { - let data: CountryData = load("countries.json") - return data.countries.count - } -} - -class GuessTheCapitalInfo: GameInfo { - static let type: GameType = .guessTheFlag - static let level = "Level 2" - static let name = "Guess the capital" - static let symbol = "building.2.fill" - static let gradient: Gradient = .secondary - - static var numberOfQuestions: Int { - let data: CountryData = load("countries.json") - return data.countries.count - } -} - -class GuessTheCountryInfo: GameInfo { - static let type: GameType = .guessTheFlag - static let level = "Level 3" - static let name = "Guess the country" - static let symbol = "globe.americas.fill" - static let gradient: Gradient = .tertiary - - static var numberOfQuestions: Int { - let data: CityData = load("cities.json") - return data.cities.count - } -} - -class GuessThePopulationInfo: GameInfo { - static let type: GameType = .guessTheFlag - static let level = "Level 4" - static let name = "Guess the population" - static let symbol = "person.fill" - static let gradient: Gradient = .quaternary - - static var numberOfQuestions: Int { - let data: CityData = load("cities.json") - return data.cities.count - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/GameInfoProtocol.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/GameInfoProtocol.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,27 @@ +// +// GameInfoProtocol+Structs.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 20/10/22. +// + +import Foundation +import SwiftUI + +@objc +public enum GameType: Int16, CaseIterable { + case guessTheFlag + case guessTheCapital + case guessTheCountry + case guessThePopulation +} + +protocol GameInfo { + var type: GameType { get } + var level: String { get } + var name: String { get } + var isPremium: Bool { get } + var symbol: String { get } + var gradient: Gradient { get } + var numberOfQuestions: Int { get } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/GameProtocol+Extension.swift --- a/GeoQuiz/Logic/GameProtocol+Extension.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Logic/GameProtocol+Extension.swift Sun Oct 23 00:11:38 2022 +0100 @@ -62,7 +62,7 @@ } func answer(_ choice: (key: String, value: T), selector: () -> Void) { - let haptics = Haptics() + let haptics = HapticsController() if correctAnswer == choice { haptics.success() @@ -125,7 +125,7 @@ } private func playSound(_ filename: String) { - let user = User() + let user = UserController() if user.data.sound { guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else { diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/GameStatsClass.swift --- a/GeoQuiz/Logic/GameStatsClass.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -// -// GameStatsClass.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 20/10/22. -// - -import Foundation - -class GameStats { - -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/HapticsClass.swift --- a/GeoQuiz/Logic/HapticsClass.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -// -// 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.data.haptics { - let generator = UINotificationFeedbackGenerator() - generator.notificationOccurred(.success) - } - } - - func error() { - if user.data.haptics { - let generator = UINotificationFeedbackGenerator() - generator.notificationOccurred(.error) - } - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/HapticsController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/HapticsController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,27 @@ +// +// HapticsController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 18/9/22. +// + +import Foundation +import SwiftUI + +class HapticsController { + private var user = UserController() + + func success() { + if user.data.haptics { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.success) + } + } + + func error() { + if user.data.haptics { + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/LoadFunc.swift --- a/GeoQuiz/Logic/LoadFunc.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -// -// LoadFunc.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 7/9/22. -// - -import Foundation - -func load(_ 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)") - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/MapController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/MapController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,36 @@ +// +// MapController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 22/10/22. +// + +import Foundation +import MapKit + +class MapController: ObservableObject { + @Published var image: UIImage? = nil + + func getMapImage(lat: Double, lon: Double) { + let region = MKCoordinateRegion( + center: CLLocationCoordinate2D(latitude: lat, longitude: 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.image = snapshot.image + } else if let error = error { + print(error.localizedDescription) + } + } + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/PersistenceController.swift --- a/GeoQuiz/Logic/PersistenceController.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Logic/PersistenceController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -6,50 +6,53 @@ // import CoreData -import Foundation class PersistenceController: ObservableObject { + let container: NSPersistentContainer + + init(inMemory: Bool = false) { + container = NSPersistentContainer(name: "GeoQuiz") + + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } + + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error { + fatalError("Unresolved error \(error.localizedDescription)") + } + }) + + container.viewContext.automaticallyMergesChangesFromParent = true + } // Create mock data for previews static var preview: PersistenceController = { - let result = PersistenceController() + let result = PersistenceController(inMemory: true) let viewContext = result.container.viewContext -// for _ in 0..<10 { -// let playedGame = PlayedGame(context: viewContext) -// playedGame.id = UUID() -// playedGame.type = GameType(rawValue: Int16.random(in: 0...3))! -// playedGame.score = Int32.random(in: 0...50) -// playedGame.date = Date() -// -// if playedGame.type == .guessTheFlag || playedGame.type == .guessTheCapital { -// playedGame.correctAnswers = ["Bangladesh", "Belgium", "Burkina Faso", "Bermuda", "Jamaica"] -// playedGame.wrongAnswers = ["Belarus", "Russia"] -// } else { -// playedGame.correctAnswers = ["Herat", "Lobito", "Darregueira", "San Juan"] -// playedGame.wrongAnswers = ["San Luis", "Oranjestad"] -// } -// } + for _ in 0..<10 { + let playedGame = PlayedGame(context: viewContext) + playedGame.id = UUID() + playedGame.type = GameType(rawValue: Int16.random(in: 0...3))! + playedGame.score = Int32.random(in: 0...50) + playedGame.date = Date() + + if playedGame.type == .guessTheFlag || playedGame.type == .guessTheCapital { + playedGame.correctAnswers = ["Bangladesh", "Belgium", "Burkina Faso", "Bermuda", "Jamaica"] + playedGame.wrongAnswers = ["Belarus", "Russia"] + } else { + playedGame.correctAnswers = ["Herat", "Lobito", "Darregueira", "San Juan"] + playedGame.wrongAnswers = ["San Luis", "Oranjestad"] + } + } do { try viewContext.save() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } + return result - }() - - // Initialize container - let container = NSPersistentContainer(name: "GeoQuiz") - - init() { - container.loadPersistentStores { description, error in - if let error = error { - print("Core Data failed to load: \(error.localizedDescription)") - } - } - - container.viewContext.automaticallyMergesChangesFromParent = true - } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/StoreKitController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/StoreKitController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,94 @@ +// +// StoreKitController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 9/10/22. +// + +import Foundation +import RevenueCat + +class StoreKitController: ObservableObject { + @Published var errorAlertTitle = "" + @Published var errorAlertMessage = "" + + @Published var showingErrorAlert = false + @Published var showingSuccessAlert = false + @Published var showingActivityAlert = false + + @Published var offerings: Offerings? = nil + @Published var customerInfo: CustomerInfo? { + didSet { + premiumIsActive = customerInfo?.entitlements["Premium"]?.isActive == true + } + } + + @Published var premiumIsActive = false + + init() { + Purchases.shared.getCustomerInfo { (customerInfo, error) in + self.customerInfo = customerInfo + } + } + + func buy(_ package: Package) { + showingActivityAlert = true + + Purchases.shared.purchase(package: package) { (transaction, customerInfo, error, userCancelled) in + if customerInfo?.entitlements["Premium"]?.isActive == true { + self.showingSuccessAlert = true + } + + if let error = error as? RevenueCat.ErrorCode { + switch error { + case .purchaseCancelledError: + self.errorAlertTitle = "Purchase cancelled" + self.errorAlertMessage = "" + self.showingErrorAlert = true + default: + self.errorAlertTitle = "The purchase failed" + self.errorAlertMessage = "If the problem persists, contact me at dmartin@dennistech.io" + self.showingErrorAlert = true + } + } + + self.customerInfo = customerInfo + self.showingActivityAlert = false + } + } + + func restorePurchase() { + showingActivityAlert = true + + Purchases.shared.restorePurchases { customerInfo, error in + if customerInfo?.entitlements["Premium"]?.isActive == true { + self.showingSuccessAlert = true + } else { + self.errorAlertTitle = "Opps!" + self.errorAlertMessage = "You don't have GeoQuiz Premium unlocked." + self.showingErrorAlert = true + } + + if let _ = error { + self.errorAlertTitle = "The purchase couldn't be restored" + self.errorAlertMessage = "If the problem persists, contact me at dmartin@dennistech.io" + self.showingErrorAlert = true + } + + self.customerInfo = customerInfo + self.showingActivityAlert = false + } + } + + func fetchOfferings() { + Purchases.shared.getOfferings { (offerings, error) in + if let _ = error { + self.errorAlertTitle = "The product couldn't be fetched" + self.errorAlertMessage = "If the problem persists, contact me at dmartin@dennistech.io" + self.showingErrorAlert = true + } + + self.offerings = offerings + } + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/StoreKitRCClass.swift --- a/GeoQuiz/Logic/StoreKitRCClass.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -// -// StoreKitRCClass.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 9/10/22. -// - -import Foundation -import RevenueCat - -class StoreKitRC: ObservableObject { - @Published var errorAlertTitle = "" - @Published var errorAlertMessage = "" - - @Published var showingErrorAlert = false - @Published var showingSuccessAlert = false - @Published var showingActivityAlert = false - - @Published var offerings: Offerings? = nil - @Published var customerInfo: CustomerInfo? { - didSet { - isActive = customerInfo?.entitlements["Premium"]?.isActive == true - } - } - - @Published var isActive = false - - init() { - Purchases.shared.getCustomerInfo { (customerInfo, error) in - self.customerInfo = customerInfo - } - } - - func buy(_ package: Package) { - showingActivityAlert = true - - Purchases.shared.purchase(package: package) { (transaction, customerInfo, error, userCancelled) in - if customerInfo?.entitlements["Premium"]?.isActive == true { - self.showingSuccessAlert = true - } - - if let error = error as? RevenueCat.ErrorCode { - switch error { - case .purchaseCancelledError: - self.errorAlertTitle = "Purchase cancelled" - self.errorAlertMessage = "" - self.showingErrorAlert = true - default: - self.errorAlertTitle = "The purchase failed" - self.errorAlertMessage = "If the problem persists, contact me at dmartin@dennistech.io" - self.showingErrorAlert = true - } - } - - self.customerInfo = customerInfo - self.showingActivityAlert = false - } - } - - func restorePurchase() { - showingActivityAlert = true - - Purchases.shared.restorePurchases { customerInfo, error in - if customerInfo?.entitlements["Premium"]?.isActive == true { - self.showingSuccessAlert = true - } else { - self.errorAlertTitle = "Opps!" - self.errorAlertMessage = "You don't have GeoQuiz Premium unlocked." - self.showingErrorAlert = true - } - - if let _ = error { - self.errorAlertTitle = "The purchase couldn't be restored" - self.errorAlertMessage = "If the problem persists, contact me at dmartin@dennistech.io" - self.showingErrorAlert = true - } - - self.customerInfo = customerInfo - self.showingActivityAlert = false - } - } - - func fetchOfferings() { - Purchases.shared.getOfferings { (offerings, error) in - if let _ = error { - self.errorAlertTitle = "The product couldn't be fetched" - self.errorAlertMessage = "If the problem persists, contact me at dmartin@dennistech.io" - self.showingErrorAlert = true - } - - self.offerings = offerings - } - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/UserClass.swift --- a/GeoQuiz/Logic/UserClass.swift Sat Oct 22 08:56:54 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -// -// UserClass.swift -// GeoQuiz -// -// Created by Dennis Concepción Martín on 7/10/22. -// - -import Foundation - -class User: ObservableObject { - @Published var data = UserData() { - didSet { - if let userDataEncoded = try? JSONEncoder().encode(data) { - UserDefaults.standard.set(userDataEncoded, forKey: "UserData") - } - } - } - - init() { - if let userData = UserDefaults.standard.data(forKey: "UserData") { - if let decodedUserData = try? JSONDecoder().decode(UserData.self, from: userData) { - data = decodedUserData - } - } - } -} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/UserController.swift --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GeoQuiz/Logic/UserController.swift Sun Oct 23 00:11:38 2022 +0100 @@ -0,0 +1,26 @@ +// +// UserController.swift +// GeoQuiz +// +// Created by Dennis Concepción Martín on 7/10/22. +// + +import Foundation + +class UserController: ObservableObject { + @Published var data = UserDataModel() { + didSet { + if let userDataEncoded = try? JSONEncoder().encode(data) { + UserDefaults.standard.set(userDataEncoded, forKey: "UserData") + } + } + } + + init() { + if let userData = UserDefaults.standard.data(forKey: "UserData") { + if let decodedUserData = try? JSONDecoder().decode(UserDataModel.self, from: userData) { + data = decodedUserData + } + } + } +} diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/Logic/UserDataModel.swift --- a/GeoQuiz/Logic/UserDataModel.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/Logic/UserDataModel.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,7 +8,7 @@ import Foundation import SwiftUI -struct UserData: Codable { +struct UserDataModel: Codable { // Settings var haptics: Bool = true @@ -16,7 +16,7 @@ var numberOfLives: Int = 25 // Profile - var username: String = "Anonymous" + var username: String = "Unnamed" var imageData: Data? var uiImage: UIImage? { diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/ProfileEditModalView.swift --- a/GeoQuiz/ProfileEditModalView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/ProfileEditModalView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -9,10 +9,17 @@ import PhotosUI struct ProfileEditModalView: View { - @ObservedObject var user: User + @ObservedObject var userController: UserController + + @State var newUsername: String + @State private var selectedImageItem: PhotosPickerItem? = nil + @Environment(\.dismiss) var dismiss - @State private var selectedItem: PhotosPickerItem? = nil + init(user: UserController) { + self.userController = user + self._newUsername = State(initialValue: user.data.username) + } var body: some View { NavigationStack { @@ -20,43 +27,58 @@ Section { HStack { Spacer() - ZStack { - UserImage(uiImage: user.data.uiImage) - .onChange(of: selectedItem) { newItem in - Task { - if let data = try? await newItem?.loadTransferable(type: Data.self) { - user.data.imageData = data - } + PhotosPicker( + selection: $selectedImageItem, + matching: .images, + photoLibrary: .shared()) { + UserImage(userController: userController) + .frame(height: 150) + .overlay( + Image(systemName: "camera.fill") + .foregroundColor(.white) + .font(.title) + .shadow(radius: 5) + ) + } + .onChange(of: selectedImageItem) { newItem in + Task { + if let data = try? await newItem?.loadTransferable(type: Data.self) { + userController.data.imageData = data } } - - PhotosPicker( - selection: $selectedItem, - matching: .images, - photoLibrary: .shared()) { - EmptyView() - } - } + } Spacer() } - } header: { - Text("Profile image") + .listRowBackground(Color.clear) } Section { - TextField("Enter a username", text: $user.data.username) + TextField("Enter a username", text: $newUsername) } header: { Text("Username") } + } .navigationTitle("Edit profile") .navigationBarTitleDisplayMode(.inline) .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + dismiss() + } label: { + Label("Exit", systemImage: "multiply") + } + } + ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { + Button { + userController.data.username = newUsername dismiss() + } label: { + Text("Done") } + .disabled(newUsername.isEmpty) } } } @@ -65,6 +87,6 @@ struct ProfileEditModalView_Previews: PreviewProvider { static var previews: some View { - ProfileEditModalView(user: User()) + ProfileEditModalView(user: UserController()) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/ProfileModalView.swift --- a/GeoQuiz/ProfileModalView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/ProfileModalView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -6,49 +6,41 @@ // import SwiftUI -import PhotosUI struct ProfileModalView: View { - @ObservedObject var user: User - @ObservedObject var storeKitRC: StoreKitRC + @ObservedObject var userController: UserController + @ObservedObject var storeKitController: StoreKitController - @Environment(\.dismiss) var dismiss - @Environment(\.managedObjectContext) var moc + @State var showingEditModalView = false @FetchRequest(sortDescriptors: [ SortDescriptor(\.date, order: .reverse), ]) var playedGames: FetchedResults - @State private var showingEditModalView = false + @Environment(\.dismiss) var dismiss + @Environment(\.managedObjectContext) var moc var body: some View { - NavigationView { - List { - Section { - UserProfile(user: user, storeKitRC: storeKitRC) + NavigationStack { + ScrollView { + VStack(spacing: 30) { + UserProfile( + userController: userController, + storeKitController: storeKitController, + isShowing: $showingEditModalView + ) + + VStack(spacing: 20) { + ForEach(playedGames.prefix(8)) { playedGame in + RecentGame(game: playedGame) + } + } } - - Section { - UserProgress(playedGames: playedGames, gameType: .guessTheFlag) - UserProgress(playedGames: playedGames, gameType: .guessTheCapital) - UserProgress(playedGames: playedGames, gameType: .guessTheCountry) - UserProgress(playedGames: playedGames, gameType: .guessThePopulation) - } header: { - Text("Progress") - } - - Section { - ForEach(playedGames) { playedGame in - RecentGame(game: playedGame) - } - .onDelete(perform: deleteGame) - } header: { - Text("Recent games") - } + .padding() } - .background(.customBackground) .navigationTitle("Profile") .navigationBarTitleDisplayMode(.inline) + .background(Color(.systemGroupedBackground)) .toolbar { ToolbarItem(placement: .cancellationAction) { Button { @@ -57,34 +49,18 @@ Label("Exit", systemImage: "multiply") } } - - ToolbarItem(placement: .navigationBarTrailing) { - Button("Edit") { - showingEditModalView = true - } - } } .sheet(isPresented: $showingEditModalView) { - ProfileEditModalView(user: user) + ProfileEditModalView(user: userController) } } - - } - - private func deleteGame(at offsets: IndexSet) { - for offset in offsets { - let game = playedGames[offset] - moc.delete(game) - } - - try? moc.save() } } struct ProfileView_Previews: PreviewProvider { static var previews: some View { - ProfileModalView(user: User(), storeKitRC: StoreKitRC()) + ProfileModalView(userController: UserController(), storeKitController: StoreKitController()) .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) } } diff -r d20cf93c9812 -r f140bb277c96 GeoQuiz/SettingsModalView.swift --- a/GeoQuiz/SettingsModalView.swift Sat Oct 22 08:56:54 2022 +0100 +++ b/GeoQuiz/SettingsModalView.swift Sun Oct 23 00:11:38 2022 +0100 @@ -8,7 +8,8 @@ import SwiftUI struct SettingsModalView: View { - @ObservedObject var user: User + @ObservedObject var user: UserController + @Environment(\.dismiss) var dismiss var lives: [Int] { @@ -21,7 +22,7 @@ } var body: some View { - NavigationView { + NavigationStack { Form { Section { Picker("❤️ Lives", selection: $user.data.numberOfLives) { @@ -46,21 +47,21 @@ Section { LinkComponent( color: .mayaBlue, - iconName: "info.circle.fill", + symbol: "info.circle.fill", text: "About", url: URL(string: "https://dennistech.io")! ) LinkComponent( color: .atomicTangerine, - iconName: "ant.circle.fill", + symbol: "ant.circle.fill", text: "Report bugs", url: URL(string: "mailto:dmartin@dennistech.io")! ) LinkComponent( color: .blueBell, - iconName: "message.circle.fill", + symbol: "message.circle.fill", text: "Twitter", url: URL(string: "https://twitter.com/dennistech_")! ) @@ -69,6 +70,7 @@ } } .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button { @@ -84,6 +86,6 @@ struct SettingsModalView_Previews: PreviewProvider { static var previews: some View { - SettingsModalView(user: User()) + SettingsModalView(user: UserController()) } }