changeset 19:f140bb277c96

refactor code
author Dennis C. M. <dennis@denniscm.com>
date Sun, 23 Oct 2022 00:11:38 +0100
parents d20cf93c9812
children e281791e0494
files GeoQuiz.xcodeproj/project.pbxproj GeoQuiz/Assets.xcassets/Custom colors/Background.colorset/Contents.json GeoQuiz/BuyPremiumModalView.swift GeoQuiz/Components/AnswerButtonHelper.swift GeoQuiz/Components/CityMapHelper.swift GeoQuiz/Components/ColorExtension.swift GeoQuiz/Components/FlagImageHelper.swift GeoQuiz/Components/GameButtonHelper.swift GeoQuiz/Components/GameToolbarHelper.swift GeoQuiz/Components/LinkHelper.swift GeoQuiz/Components/RecentGameHelper.swift GeoQuiz/Components/UserImageHelper.swift GeoQuiz/Components/UserProfileComponent.swift GeoQuiz/Components/UserProfileHelper.swift GeoQuiz/Components/UserProgressHelper.swift GeoQuiz/ContentView.swift GeoQuiz/GuessTheCapitalView.swift GeoQuiz/GuessTheCountryView.swift GeoQuiz/GuessTheFlagView.swift GeoQuiz/GuessThePopulationView.swift GeoQuiz/Logic/CityGameClass.swift GeoQuiz/Logic/CityGameController.swift GeoQuiz/Logic/CityModel.swift GeoQuiz/Logic/CoreDataController.swift GeoQuiz/Logic/CountryGameClass.swift GeoQuiz/Logic/CountryGameController.swift GeoQuiz/Logic/CountryModel.swift GeoQuiz/Logic/FileController.swift GeoQuiz/Logic/GameInfoController.swift GeoQuiz/Logic/GameInfoProtocol+Extension.swift GeoQuiz/Logic/GameInfoProtocol.swift GeoQuiz/Logic/GameProtocol+Extension.swift GeoQuiz/Logic/GameStatsClass.swift GeoQuiz/Logic/HapticsClass.swift GeoQuiz/Logic/HapticsController.swift GeoQuiz/Logic/LoadFunc.swift GeoQuiz/Logic/MapController.swift GeoQuiz/Logic/PersistenceController.swift GeoQuiz/Logic/StoreKitController.swift GeoQuiz/Logic/StoreKitRCClass.swift GeoQuiz/Logic/UserClass.swift GeoQuiz/Logic/UserController.swift GeoQuiz/Logic/UserDataModel.swift GeoQuiz/ProfileEditModalView.swift GeoQuiz/ProfileModalView.swift GeoQuiz/SettingsModalView.swift
diffstat 46 files changed, 891 insertions(+), 1005 deletions(-) [+]
line wrap: on
line diff
--- 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 = "<group>"; };
 		950C535228F2FA3300179C78 /* BuyPremiumModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyPremiumModalView.swift; sourceTree = "<group>"; };
 		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 = "<group>"; };
+		95197EFC28F339AE00FE67E9 /* StoreKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKitController.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 /* CityGameClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityGameClass.swift; sourceTree = "<group>"; };
+		951AFAF028E5735400A4A4BD /* CityGameController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityGameController.swift; sourceTree = "<group>"; };
 		951B630128D1A87C004F9877 /* GuessTheCapitalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCapitalView.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>"; };
@@ -82,33 +83,34 @@
 		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>"; };
+		955A65A828D7815E00CEEC6D /* HapticsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsController.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 /* UserClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserClass.swift; sourceTree = "<group>"; };
+		95919DB528F076BF00F21F8F /* UserController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserController.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>"; };
+		95A4F42829040E350018DFAC /* CoreDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataController.swift; sourceTree = "<group>"; };
+		95A4F42A29043DC00018DFAC /* UserImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageHelper.swift; sourceTree = "<group>"; };
+		95AE8D5628C8750E0067F219 /* FileController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileController.swift; sourceTree = "<group>"; };
 		95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = "<group>"; };
-		95B5C307290190900093A086 /* UserProgressHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProgressHelper.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 /* GameButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameButtonHelper.swift; sourceTree = "<group>"; };
 		95C6456B28FE87E4000CD570 /* UserDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataModel.swift; sourceTree = "<group>"; };
-		95C6456D28FE8C04000CD570 /* UserImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageHelper.swift; sourceTree = "<group>"; };
+		95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHelper.swift; sourceTree = "<group>"; };
 		95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditModalView.swift; sourceTree = "<group>"; };
 		95C6457328FFC8E0000CD570 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
 		95C6457628FFC934000CD570 /* GeoQuiz.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = GeoQuiz.xcdatamodel; sourceTree = "<group>"; };
 		95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayedGame+CoreDataClass.swift"; sourceTree = "<group>"; };
 		95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayedGame+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		95C6459C290003E1000CD570 /* RecentGameHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentGameHelper.swift; sourceTree = "<group>"; };
-		95C645BF29011F8E000CD570 /* GameStatsClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStatsClass.swift; sourceTree = "<group>"; };
-		95C645C129014442000CD570 /* GameInfoProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameInfoProtocol+Extension.swift"; sourceTree = "<group>"; };
-		95C645C32901552B000CD570 /* UserProfileComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileComponent.swift; sourceTree = "<group>"; };
+		95C645C129014442000CD570 /* GameInfoProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameInfoProtocol.swift; sourceTree = "<group>"; };
 		95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityAlertHelper.swift; sourceTree = "<group>"; };
+		95DB7C00290492FC007D01D8 /* GameInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameInfoController.swift; sourceTree = "<group>"; };
+		95DB7C022904A968007D01D8 /* MapController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapController.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 /* CountryGameClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryGameClass.swift; sourceTree = "<group>"; };
+		95FA409B28D9881100129B60 /* CountryGameController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountryGameController.swift; sourceTree = "<group>"; };
 /* 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;
 		};
--- 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
-  }
-}
--- 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())
     }
 }
--- 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()
--- 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())
     }
 }
--- 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")
-    }
 }
--- 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)
--- 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)
     }
 }
--- 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()
                 }
--- 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")!
         )
--- 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)
     }
 }
--- 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())
     }
 }
--- 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())
-    }
-}
--- /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))
+    }
+}
--- 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<PlayedGame>, 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)
-    }
-}
--- 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)
+        }
     }
-}
--- 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)
--- 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)
--- 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
     
--- 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)
--- 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
-    }
-}
-
--- /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
+    }
+}
+
--- 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 {
--- /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<PlayedGame>, with moc: NSManagedObjectContext) {
+        for offset in offsets {
+            let game = games[offset]
+            moc.delete(game)
+        }
+        
+        try? moc.save()
+    }
+}
--- 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
-    }
-}
--- /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
+    }
+}
--- 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 {
--- /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<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/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
+        }
+    }
+}
--- 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
-    }
-}
--- /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 }
+}
--- 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 {
--- 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 {
-    
-}
--- 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)
-        }
-    }
-}
--- /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)
+        }
+    }
+}
--- 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<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/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)
+            }
+        }
+    }
+}
--- 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
-    }
 }
--- /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
+        }
+    }
+}
--- 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
-        }
-    }
-}
--- 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
-            }
-        }
-    }
-}
--- /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
+            }
+        }
+    }
+}
--- 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? {
--- 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())
     }
 }
--- 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<PlayedGame>
     
-    @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)
     }
 }
--- 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())
     }
 }