changeset 20:e281791e0494

finish implementation
author Dennis C. M. <dennis@denniscm.com>
date Sun, 23 Oct 2022 11:48:39 +0100
parents f140bb277c96
children b145c408f791
files GeoQuiz.xcodeproj/project.pbxproj GeoQuiz/Components/ActivityAlert.swift GeoQuiz/Components/ActivityAlertHelper.swift GeoQuiz/Components/AnswerButton.swift GeoQuiz/Components/AnswerButtonHelper.swift GeoQuiz/Components/CityMap.swift GeoQuiz/Components/CityMapHelper.swift GeoQuiz/Components/FlagImage.swift GeoQuiz/Components/FlagImageHelper.swift GeoQuiz/Components/FormLink.swift GeoQuiz/Components/GameButton.swift GeoQuiz/Components/GameButtonHelper.swift GeoQuiz/Components/GameToolbar.swift GeoQuiz/Components/GameToolbarHelper.swift GeoQuiz/Components/LatestGamesPlaceholder.swift GeoQuiz/Components/LinkHelper.swift GeoQuiz/Components/PlayedGamesList.swift GeoQuiz/Components/RecentGame.swift GeoQuiz/Components/RecentGameHelper.swift GeoQuiz/Components/UserImage.swift GeoQuiz/Components/UserImageHelper.swift GeoQuiz/Components/UserProfile.swift GeoQuiz/Components/UserProfileHelper.swift GeoQuiz/GeoQuiz.entitlements GeoQuiz/GeoQuiz.xcdatamodeld/GeoQuiz.xcdatamodel/contents GeoQuiz/GeoQuizApp.swift GeoQuiz/GuessTheCapitalView.swift GeoQuiz/GuessTheFlagView.swift GeoQuiz/GuessThePopulationView.swift GeoQuiz/Info.plist GeoQuiz/Logic/GameProtocol+Extension.swift GeoQuiz/Logic/PersistenceController.swift GeoQuiz/Logic/PlayedGame+CoreDataProperties.swift GeoQuiz/ProfileModalView.swift GeoQuiz/SettingsModalView.swift
diffstat 35 files changed, 658 insertions(+), 561 deletions(-) [+]
line wrap: on
line diff
--- a/GeoQuiz.xcodeproj/project.pbxproj	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz.xcodeproj/project.pbxproj	Sun Oct 23 11:48:39 2022 +0100
@@ -7,7 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		95030CEA28D1BA4D001AA3A1 /* AnswerButtonHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95030CE928D1BA4D001AA3A1 /* AnswerButtonHelper.swift */; };
+		95030CEA28D1BA4D001AA3A1 /* AnswerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */; };
 		9509A8DE28E5A19A00CFCDBA /* countries.json in Resources */ = {isa = PBXBuildFile; fileRef = 9509A8DD28E5A19A00CFCDBA /* countries.json */; };
 		9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9509A8E128E5A3D700CFCDBA /* GuessThePopulationView.swift */; };
 		950C535328F2FA3300179C78 /* BuyPremiumModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950C535228F2FA3300179C78 /* BuyPremiumModalView.swift */; };
@@ -27,32 +27,34 @@
 		9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9539829228C51EDE00B70973 /* GeoQuizApp.swift */; };
 		9539829728C51EDF00B70973 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9539829628C51EDF00B70973 /* Assets.xcassets */; };
 		9539829A28C51EDF00B70973 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9539829928C51EDF00B70973 /* Preview Assets.xcassets */; };
+		954AF4682905397A00180065 /* PlayedGamesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AF4672905397A00180065 /* PlayedGamesList.swift */; };
+		954AF46A29053D4E00180065 /* LatestGamesPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AF46929053D4E00180065 /* LatestGamesPlaceholder.swift */; };
 		955950BB28F15FF2001BDEE8 /* FormatterExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */; };
-		955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */; };
+		955A658128D703EB00CEEC6D /* GameToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658028D703EB00CEEC6D /* GameToolbar.swift */; };
 		955A658328D733E400CEEC6D /* GameProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */; };
 		955A65A928D7815E00CEEC6D /* HapticsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A65A828D7815E00CEEC6D /* HapticsController.swift */; };
-		956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956273E928CB2E98008DC094 /* FlagImageHelper.swift */; };
+		956273EA28CB2E98008DC094 /* FlagImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956273E928CB2E98008DC094 /* FlagImage.swift */; };
 		9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9590359428E098FF00B24560 /* ProfileModalView.swift */; };
 		95919DB628F076BF00F21F8F /* UserController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB528F076BF00F21F8F /* UserController.swift */; };
-		95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* LinkHelper.swift */; };
+		95919DBC28F08D0600F21F8F /* FormLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* FormLink.swift */; };
 		95A4F42929040E350018DFAC /* CoreDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A4F42829040E350018DFAC /* CoreDataController.swift */; };
-		95A4F42B29043DC00018DFAC /* UserImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A4F42A29043DC00018DFAC /* UserImageHelper.swift */; };
+		95A4F42B29043DC00018DFAC /* UserImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A4F42A29043DC00018DFAC /* UserImage.swift */; };
 		95AE8D5728C8750E0067F219 /* FileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* FileController.swift */; };
 		95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AF322928DF293900023ACC /* GuessTheCountryView.swift */; };
-		95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC392C28EC42570049AB49 /* CityMapHelper.swift */; };
+		95BC392D28EC42570049AB49 /* CityMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC392C28EC42570049AB49 /* CityMap.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 */; };
+		95C4315928C6500000212131 /* GameButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C4315828C6500000212131 /* GameButton.swift */; };
 		95C6456C28FE87E4000CD570 /* UserDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456B28FE87E4000CD570 /* UserDataModel.swift */; };
-		95C6456E28FE8C04000CD570 /* UserProfileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */; };
+		95C6456E28FE8C04000CD570 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456D28FE8C04000CD570 /* UserProfile.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 */; };
+		95C6459D290003E1000CD570 /* RecentGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6459C290003E1000CD570 /* RecentGame.swift */; };
 		95C645C229014442000CD570 /* GameInfoProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C645C129014442000CD570 /* GameInfoProtocol.swift */; };
-		95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */; };
+		95CA295028F6BB4500CE0B7A /* ActivityAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA294F28F6BB4500CE0B7A /* ActivityAlert.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 */; };
@@ -60,7 +62,7 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
-		95030CE928D1BA4D001AA3A1 /* AnswerButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerButtonHelper.swift; sourceTree = "<group>"; };
+		95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerButton.swift; sourceTree = "<group>"; };
 		9509A8DD28E5A19A00CFCDBA /* countries.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = countries.json; sourceTree = "<group>"; };
 		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>"; };
@@ -80,32 +82,35 @@
 		9539829228C51EDE00B70973 /* GeoQuizApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoQuizApp.swift; sourceTree = "<group>"; };
 		9539829628C51EDF00B70973 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		9539829928C51EDF00B70973 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
+		954AF4672905397A00180065 /* PlayedGamesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayedGamesList.swift; sourceTree = "<group>"; };
+		954AF46929053D4E00180065 /* LatestGamesPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestGamesPlaceholder.swift; sourceTree = "<group>"; };
+		954AF46B2905433300180065 /* GeoQuiz.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GeoQuiz.entitlements; sourceTree = "<group>"; };
 		955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatterExtension.swift; sourceTree = "<group>"; };
-		955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameToolbarHelper.swift; sourceTree = "<group>"; };
+		955A658028D703EB00CEEC6D /* GameToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameToolbar.swift; sourceTree = "<group>"; };
 		955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameProtocol+Extension.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>"; };
+		956273E928CB2E98008DC094 /* FlagImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImage.swift; sourceTree = "<group>"; };
 		9590359428E098FF00B24560 /* ProfileModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModalView.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>"; };
+		95919DBB28F08D0600F21F8F /* FormLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLink.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>"; };
+		95A4F42A29043DC00018DFAC /* UserImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImage.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>"; };
-		95BC392C28EC42570049AB49 /* CityMapHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMapHelper.swift; sourceTree = "<group>"; };
+		95BC392C28EC42570049AB49 /* CityMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMap.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>"; };
+		95C4315828C6500000212131 /* GameButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameButton.swift; sourceTree = "<group>"; };
 		95C6456B28FE87E4000CD570 /* UserDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataModel.swift; sourceTree = "<group>"; };
-		95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHelper.swift; sourceTree = "<group>"; };
+		95C6456D28FE8C04000CD570 /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.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>"; };
+		95C6459C290003E1000CD570 /* RecentGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentGame.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>"; };
+		95CA294F28F6BB4500CE0B7A /* ActivityAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityAlert.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>"; };
@@ -189,6 +194,7 @@
 		9539829128C51EDE00B70973 /* GeoQuiz */ = {
 			isa = PBXGroup;
 			children = (
+				954AF46B2905433300180065 /* GeoQuiz.entitlements */,
 				95E6188428DDDB5C003359ED /* Info.plist */,
 				9539829628C51EDF00B70973 /* Assets.xcassets */,
 				95C6457528FFC934000CD570 /* GeoQuiz.xcdatamodeld */,
@@ -221,17 +227,19 @@
 		959D414728C87EA600BAAC14 /* Components */ = {
 			isa = PBXGroup;
 			children = (
-				95C4315828C6500000212131 /* GameButtonHelper.swift */,
-				956273E928CB2E98008DC094 /* FlagImageHelper.swift */,
-				95030CE928D1BA4D001AA3A1 /* AnswerButtonHelper.swift */,
-				955A658028D703EB00CEEC6D /* GameToolbarHelper.swift */,
-				95BC392C28EC42570049AB49 /* CityMapHelper.swift */,
-				95919DBB28F08D0600F21F8F /* LinkHelper.swift */,
-				95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */,
-				95C6456D28FE8C04000CD570 /* UserProfileHelper.swift */,
-				95A4F42A29043DC00018DFAC /* UserImageHelper.swift */,
-				95C6459C290003E1000CD570 /* RecentGameHelper.swift */,
+				95C4315828C6500000212131 /* GameButton.swift */,
+				956273E928CB2E98008DC094 /* FlagImage.swift */,
+				95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */,
+				955A658028D703EB00CEEC6D /* GameToolbar.swift */,
+				95BC392C28EC42570049AB49 /* CityMap.swift */,
+				95919DBB28F08D0600F21F8F /* FormLink.swift */,
+				95CA294F28F6BB4500CE0B7A /* ActivityAlert.swift */,
+				95C6456D28FE8C04000CD570 /* UserProfile.swift */,
+				95A4F42A29043DC00018DFAC /* UserImage.swift */,
+				95C6459C290003E1000CD570 /* RecentGame.swift */,
+				954AF4672905397A00180065 /* PlayedGamesList.swift */,
 				952E41E828DC521200198643 /* GameAlertsModifier.swift */,
+				954AF46929053D4E00180065 /* LatestGamesPlaceholder.swift */,
 				95C430F828D0A8E500480D23 /* GradientExtension.swift */,
 				951D197228D485E000671FAD /* ColorExtension.swift */,
 				955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */,
@@ -320,9 +328,9 @@
 			buildActionMask = 2147483647;
 			files = (
 				955A65A928D7815E00CEEC6D /* HapticsController.swift in Sources */,
-				95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */,
+				95BC392D28EC42570049AB49 /* CityMap.swift in Sources */,
 				95C6459A28FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift in Sources */,
-				95A4F42B29043DC00018DFAC /* UserImageHelper.swift in Sources */,
+				95A4F42B29043DC00018DFAC /* UserImage.swift in Sources */,
 				952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */,
 				95197EFD28F339AE00FE67E9 /* StoreKitController.swift in Sources */,
 				9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */,
@@ -330,24 +338,26 @@
 				95A4F42929040E350018DFAC /* CoreDataController.swift in Sources */,
 				95C6457428FFC8E0000CD570 /* PersistenceController.swift in Sources */,
 				95919DB628F076BF00F21F8F /* UserController.swift in Sources */,
-				95C6459D290003E1000CD570 /* RecentGameHelper.swift in Sources */,
-				95C6456E28FE8C04000CD570 /* UserProfileHelper.swift in Sources */,
+				95C6459D290003E1000CD570 /* RecentGame.swift in Sources */,
+				95C6456E28FE8C04000CD570 /* UserProfile.swift in Sources */,
 				95C4315628C64A8C00212131 /* ContentView.swift in Sources */,
 				95C645C229014442000CD570 /* GameInfoProtocol.swift in Sources */,
-				95C4315928C6500000212131 /* GameButtonHelper.swift in Sources */,
-				956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */,
+				954AF4682905397A00180065 /* PlayedGamesList.swift in Sources */,
+				95C4315928C6500000212131 /* GameButton.swift in Sources */,
+				956273EA28CB2E98008DC094 /* FlagImage.swift in Sources */,
 				951AFAED28E5657500A4A4BD /* CityModel.swift in Sources */,
 				950C535328F2FA3300179C78 /* BuyPremiumModalView.swift in Sources */,
+				954AF46A29053D4E00180065 /* LatestGamesPlaceholder.swift in Sources */,
 				951B630228D1A87C004F9877 /* GuessTheCapitalView.swift in Sources */,
 				9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */,
 				95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */,
 				95DB7C032904A968007D01D8 /* MapController.swift in Sources */,
-				95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */,
+				95919DBC28F08D0600F21F8F /* FormLink.swift in Sources */,
 				951AFAEF28E565FE00A4A4BD /* CountryModel.swift in Sources */,
-				95030CEA28D1BA4D001AA3A1 /* AnswerButtonHelper.swift in Sources */,
+				95030CEA28D1BA4D001AA3A1 /* AnswerButton.swift in Sources */,
 				95FA409C28D9881100129B60 /* CountryGameController.swift in Sources */,
-				95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */,
-				955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */,
+				95CA295028F6BB4500CE0B7A /* ActivityAlert.swift in Sources */,
+				955A658128D703EB00CEEC6D /* GameToolbar.swift in Sources */,
 				95AE8D5728C8750E0067F219 /* FileController.swift in Sources */,
 				95C6457728FFC934000CD570 /* GeoQuiz.xcdatamodeld in Sources */,
 				9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */,
@@ -486,6 +496,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = GeoQuiz/GeoQuiz.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_ASSET_PATHS = "\"GeoQuiz/Preview Content\"";
@@ -520,6 +531,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = GeoQuiz/GeoQuiz.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_ASSET_PATHS = "\"GeoQuiz/Preview Content\"";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/ActivityAlert.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,26 @@
+//
+//  ActivityAlert.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 12/10/22.
+//
+
+import SwiftUI
+
+struct ActivityAlert: View {
+    var body: some View {
+        VStack(spacing: 10) {
+            ProgressView()
+            Text("Loading")
+        }
+        .padding()
+        .background(.regularMaterial)
+        .cornerRadius(10)
+    }
+}
+
+struct ActivityAlert_Previews: PreviewProvider {
+    static var previews: some View {
+        ActivityAlert()
+    }
+}
--- a/GeoQuiz/Components/ActivityAlertHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-//
-//  ActivityAlertHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 12/10/22.
-//
-
-import SwiftUI
-
-struct ActivityAlert: View {
-    var body: some View {
-        VStack(spacing: 10) {
-            ProgressView()
-            Text("Loading")
-        }
-        .padding()
-        .background(.regularMaterial)
-        .cornerRadius(10)
-    }
-}
-
-struct ActivityAlert_Previews: PreviewProvider {
-    static var previews: some View {
-        ActivityAlert()
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/AnswerButton.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,39 @@
+//
+//  AnswerButton.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 14/9/22.
+//
+
+import SwiftUI
+
+struct AnswerButton: View {
+    let name: String
+    let color: Color
+    
+    var body: some View {
+        RoundedRectangle(cornerRadius: 15)
+            .foregroundColor(.white)
+            .overlay(
+                Text(name)
+                    .font(.title2.bold())
+                    .foregroundColor(color)
+            )
+    }
+}
+
+struct AnswerButton_Previews: PreviewProvider {
+    static var previews: some View {
+        ZStack {
+            LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom)
+                .ignoresSafeArea()
+            
+            VStack {
+                Spacer()
+                AnswerButton(name: "Madrid", color: .royalLightBlue)
+                    .frame(height: 70)
+            }
+            .padding()
+        }
+    }
+}
--- a/GeoQuiz/Components/AnswerButtonHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-//
-//  AnswerButtonHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 14/9/22.
-//
-
-import SwiftUI
-
-struct AnswerButton: View {
-    let name: String
-    let color: Color
-    
-    var body: some View {
-        RoundedRectangle(cornerRadius: 15)
-            .foregroundColor(.white)
-            .overlay(
-                Text(name)
-                    .font(.title2.bold())
-                    .foregroundColor(color)
-            )
-    }
-}
-
-struct AnswerButton_Previews: PreviewProvider {
-    static var previews: some View {
-        ZStack {
-            LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom)
-                .ignoresSafeArea()
-            
-            VStack {
-                Spacer()
-                AnswerButton(name: "Madrid", color: .royalLightBlue)
-                    .frame(height: 70)
-            }
-            .padding()
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/CityMap.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,51 @@
+//
+//  CityMap.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 4/10/22.
+//
+
+import SwiftUI
+import MapKit
+
+struct CityMap: View {
+    @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 = mapController.image {
+                Image(uiImage: mapImage)
+                    .resizable()
+                    .scaledToFit()
+                    .clipShape(Circle())
+                    .overlay {
+                        Circle()
+                            .strokeBorder(.white, lineWidth: 4)
+                    }
+                    .shadow(radius: 10)
+            } else {
+                ProgressView()
+            }
+        }
+        .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: CityGameController())
+    }
+}
--- a/GeoQuiz/Components/CityMapHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-//
-//  CityMapHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 4/10/22.
-//
-
-import SwiftUI
-import MapKit
-
-struct CityMap: View {
-    @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 = mapController.image {
-                Image(uiImage: mapImage)
-                    .resizable()
-                    .scaledToFit()
-                    .clipShape(Circle())
-                    .overlay {
-                        Circle()
-                            .strokeBorder(.white, lineWidth: 4)
-                    }
-                    .shadow(radius: 10)
-            } else {
-                ProgressView()
-            }
-        }
-        .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: CityGameController())
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/FlagImage.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,28 @@
+//
+//  FlagImage.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 9/9/22.
+//
+
+import SwiftUI
+
+struct FlagImage: View {
+    var flagSymbol: String
+    
+    @Environment(\.colorScheme) var colorScheme
+    
+    var body: some View {
+        Image(flagSymbol)
+            .renderingMode(.original)
+            .resizable()
+            .scaledToFill()
+    }
+}
+
+struct FlagImage_Previews: PreviewProvider {
+    static var previews: some View {
+        FlagImage(flagSymbol: "es")
+            .frame(height: 130)
+    }
+}
--- a/GeoQuiz/Components/FlagImageHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-//
-//  FlagImageHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 9/9/22.
-//
-
-import SwiftUI
-
-struct FlagImage: View {
-    var flagSymbol: String
-    var cornerRadius: Double
-    
-    @Environment(\.colorScheme) var colorScheme
-    
-    var body: some View {
-        Image(flagSymbol)
-            .renderingMode(.original)
-            .resizable()
-            .scaledToFit()
-    }
-}
-
-struct FlagImage_Previews: PreviewProvider {
-    static var previews: some View {
-        FlagImage(flagSymbol: "es", cornerRadius: 20)
-            .frame(height: 130)
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/FormLink.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,41 @@
+//
+//  FormLink.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 7/10/22.
+//
+
+import SwiftUI
+
+struct FormLink: View {
+    var color: Color
+    var symbol: String
+    var text: String
+    var url: URL
+    
+    @Environment(\.openURL) var openURL
+    
+    var body: some View {
+        Link(destination: url) {
+            HStack(alignment: .center, spacing: 20) {
+                Image(systemName: symbol)
+                    .imageScale(.large)
+                    .foregroundColor(color)
+                
+                Text(text)
+                    .foregroundColor(.primary)
+            }
+        }
+    }
+}
+
+struct FormLink_Previews: PreviewProvider {
+    static var previews: some View {
+        FormLink(
+            color: .mayaBlue,
+            symbol: "info.circle.fill",
+            text: "About",
+            url: URL(string: "https://dennistech.io")!
+        )
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/GameButton.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,64 @@
+//
+//  GameButton.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 5/9/22.
+//
+
+import SwiftUI
+
+struct GameButton: View {
+    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: gameInfo.gradient,
+                    startPoint: .trailing, endPoint: .leading
+                )
+            )
+            .frame(height: 180)
+            .frame(maxWidth: 700)
+            .overlay {
+                ZStack(alignment: .trailing) {
+                    VStack(alignment: .leading) {
+                        HStack {
+                            Image(systemName: isActive ? gameInfo.symbol : "lock.fill")
+                                .font(.headline)
+                                .padding(5)
+                                .background(
+                                    RoundedRectangle(cornerRadius: 5)
+                                        .stroke(lineWidth: 1.5)
+                                )
+                            
+                            Spacer()
+                        }
+                        .padding(.bottom)
+                        
+                        VStack(alignment: .leading, spacing: 5) {
+                            Text(gameInfo.level)
+                                .font(.callout)
+                            
+                            Text(gameInfo.name)
+                                .font(.title.bold())
+                        }
+                    }
+                    .foregroundColor(.white)
+                    .padding()
+                }
+            }
+    }
+}
+
+struct GameButton_Previews: PreviewProvider {
+    static var previews: some View {
+        GameButton(gameType: .guessTheFlag, isActive: false)
+    }
+}
--- a/GeoQuiz/Components/GameButtonHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-//
-//  GameButtonHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 5/9/22.
-//
-
-import SwiftUI
-
-struct GameButton: View {
-    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: gameInfo.gradient,
-                    startPoint: .trailing, endPoint: .leading
-                )
-            )
-            .frame(height: 180)
-            .frame(maxWidth: 700)
-            .overlay {
-                ZStack(alignment: .trailing) {
-                    VStack(alignment: .leading) {
-                        HStack {
-                            Image(systemName: isActive ? gameInfo.symbol : "lock.fill")
-                                .font(.headline)
-                                .padding(5)
-                                .background(
-                                    RoundedRectangle(cornerRadius: 5)
-                                        .stroke(lineWidth: 1.5)
-                                )
-                            
-                            Spacer()
-                        }
-                        .padding(.bottom)
-                        
-                        VStack(alignment: .leading, spacing: 5) {
-                            Text(gameInfo.level)
-                                .font(.callout)
-                            
-                            Text(gameInfo.name)
-                                .font(.title.bold())
-                        }
-                    }
-                    .foregroundColor(.white)
-                    .padding()
-                }
-            }
-    }
-}
-
-struct GameButton_Previews: PreviewProvider {
-    static var previews: some View {
-        GameButton(gameType: .guessTheFlag, isActive: false)
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/GameToolbar.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,90 @@
+//
+//  GameToolbar.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 18/9/22.
+//
+
+import SwiftUI
+
+struct GameToolbar<T: Game>: View {
+    @ObservedObject var game: T
+    
+    var color: Color
+    
+    var body: some View {
+        HStack(spacing: 0) {
+            Group {
+                Button {
+                    game.showingExitGameAlert = true
+                } label: {
+                    Image(systemName: "multiply")
+                        .font(.headline)
+                        .foregroundColor(color)
+                        .padding(10)
+                        .background(
+                            Circle()
+                                .foregroundColor(.white)
+                        )
+                }
+            }
+            .font(.headline)
+            .frame(maxWidth: .infinity, alignment: .leading)
+            
+            Group {
+                Text("\(game.userScore)")
+                    .font(.title.bold())
+                    .foregroundColor(color)
+                    .padding()
+                    .background(
+                        Group {
+                            if game.userScore < 1000 {
+                                Circle()
+                            } else {
+                                Capsule()
+                            }
+                        }
+                            .foregroundColor(.white)
+                    )
+            }
+            .font(.title2)
+            .scaleEffect(game.scoreScaleAmount)
+            .frame(maxWidth: .infinity, alignment: .center)
+            
+            Group {
+                HStack {
+                    Image(systemName: "heart.fill")
+                    Text("\(game.userLives)")
+                }
+                .font(.headline)
+                .foregroundColor(color)
+                .padding(10)
+                .background(
+                    Capsule()
+                        .foregroundColor(.white)
+                )
+                .scaleEffect(game.livesScaleAmount)
+            }
+            .font(.headline)
+            .frame(maxWidth: .infinity, alignment: .trailing)
+        }
+    }
+}
+
+struct GameToolbar_Previews: PreviewProvider {
+    static var previews: some View {
+        ZStack {
+            LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom)
+                .ignoresSafeArea()
+            
+            GeometryReader { geo in
+                VStack {
+                    GameToolbar(game: CountryGameController(), color: .mayaBlue)
+                    
+                    Spacer()
+                }
+                .padding()
+            }
+        }
+    }
+}
--- a/GeoQuiz/Components/GameToolbarHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-//
-//  GameToolbarHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 18/9/22.
-//
-
-import SwiftUI
-
-struct GameToolbar<T: Game>: View {
-    @ObservedObject var game: T
-    
-    var color: Color
-    
-    var body: some View {
-        HStack(spacing: 0) {
-            Group {
-                Button {
-                    game.showingExitGameAlert = true
-                } label: {
-                    Image(systemName: "multiply")
-                        .font(.headline)
-                        .foregroundColor(color)
-                        .padding(10)
-                        .background(
-                            Circle()
-                                .foregroundColor(.white)
-                        )
-                }
-            }
-            .font(.headline)
-            .frame(maxWidth: .infinity, alignment: .leading)
-            
-            Group {
-                Text("\(game.userScore)")
-                    .font(.title.bold())
-                    .foregroundColor(color)
-                    .padding()
-                    .background(
-                        Group {
-                            if game.userScore < 1000 {
-                                Circle()
-                            } else {
-                                Capsule()
-                            }
-                        }
-                            .foregroundColor(.white)
-                    )
-            }
-            .font(.title2)
-            .scaleEffect(game.scoreScaleAmount)
-            .frame(maxWidth: .infinity, alignment: .center)
-            
-            Group {
-                HStack {
-                    Image(systemName: "heart.fill")
-                    Text("\(game.userLives)")
-                }
-                .font(.headline)
-                .foregroundColor(color)
-                .padding(10)
-                .background(
-                    Capsule()
-                        .foregroundColor(.white)
-                )
-                .scaleEffect(game.livesScaleAmount)
-            }
-            .font(.headline)
-            .frame(maxWidth: .infinity, alignment: .trailing)
-        }
-    }
-}
-
-struct GameToolbar_Previews: PreviewProvider {
-    static var previews: some View {
-        ZStack {
-            LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom)
-                .ignoresSafeArea()
-            
-            GeometryReader { geo in
-                VStack {
-                    GameToolbar(game: CountryGameController(), color: .mayaBlue)
-                    
-                    Spacer()
-                }
-                .padding()
-            }
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/LatestGamesPlaceholder.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,24 @@
+//
+//  LatestGamesPlaceholder.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 23/10/22.
+//
+
+import SwiftUI
+
+struct LatestGamesPlaceholder: View {
+    var body: some View {
+        VStack(spacing: 20) {
+            Image(systemName: "gamecontroller.fill")
+            Text("No recently played games")
+        }
+        .foregroundColor(.secondary)
+    }
+}
+
+struct LatestGamesPlaceholder_Previews: PreviewProvider {
+    static var previews: some View {
+        LatestGamesPlaceholder()
+    }
+}
--- a/GeoQuiz/Components/LinkHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-//
-//  LinkHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 7/10/22.
-//
-
-import SwiftUI
-
-struct LinkComponent: View {
-    var color: Color
-    var symbol: String
-    var text: String
-    var url: URL
-    
-    @Environment(\.openURL) var openURL
-    
-    var body: some View {
-        Link(destination: url) {
-            HStack(alignment: .center, spacing: 20) {
-                Image(systemName: symbol)
-                    .imageScale(.large)
-                    .foregroundColor(color)
-                
-                Text(text)
-                    .foregroundColor(.primary)
-            }
-        }
-    }
-}
-
-struct LinkComponent_Previews: PreviewProvider {
-    static var previews: some View {
-        LinkComponent(
-            color: .mayaBlue,
-            symbol: "info.circle.fill",
-            text: "About",
-            url: URL(string: "https://dennistech.io")!
-        )
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/PlayedGamesList.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,30 @@
+//
+//  PlayedGamesList.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 23/10/22.
+//
+
+import SwiftUI
+
+struct PlayedGamesList: View {
+    var playedGames: FetchedResults<PlayedGame>
+    
+    @Environment(\.managedObjectContext) var moc
+    
+    var body: some View {
+        List {
+            ForEach(playedGames, id: \.id) { game in
+                RecentGame(game: game)
+            }
+            .onDelete { indexSet in
+                CoreDataController.deleteGame(at: indexSet, from: playedGames, with: moc)
+            }
+        }
+        .navigationTitle("Played games")
+        .navigationBarTitleDisplayMode(.inline)
+        .toolbar {
+            EditButton()
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/RecentGame.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,52 @@
+//
+//  RecentGame.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 19/10/22.
+//
+
+import SwiftUI
+
+struct RecentGame: View {
+    let game: PlayedGame
+    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: gameInfo.gradient,
+                        startPoint: .top, endPoint: .bottom
+                    )
+                )
+                .frame(width: 35, height: 35)
+                .overlay(
+                    Image(systemName: gameInfo.symbol)
+                        .font(.headline)
+                        .foregroundColor(.white)
+                        .padding(5)
+                )
+            
+            VStack(alignment: .leading) {
+                Text(gameInfo.name)
+                    .font(.headline)
+                
+                Text("\(game.date ?? Date(), format: .dateTime)")
+                    .font(.callout)
+                    .foregroundColor(.secondary)
+            }
+            
+            Spacer()
+            
+            Text("\(game.score, format: .number) ⭐️")
+                .font(.headline)
+            
+        }
+    }
+}
--- a/GeoQuiz/Components/RecentGameHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-//
-//  RecentGameHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 19/10/22.
-//
-
-import SwiftUI
-
-struct RecentGame: View {
-    let game: PlayedGame
-    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: gameInfo.gradient,
-                        startPoint: .top, endPoint: .bottom
-                    )
-                )
-                .frame(width: 35, height: 35)
-                .overlay(
-                    Image(systemName: gameInfo.symbol)
-                        .font(.headline)
-                        .foregroundColor(.white)
-                        .padding(5)
-                )
-            
-            VStack(alignment: .leading) {
-                Text(gameInfo.name)
-                    .font(.headline)
-                
-                Text("\(game.date, format: .dateTime)")
-                    .font(.callout)
-                    .foregroundColor(.secondary)
-            }
-            
-            Spacer()
-            
-            Text("\(game.score, format: .number) ⭐️")
-                .font(.headline)
-            
-        }
-        .padding()
-        .background(Color(.secondarySystemGroupedBackground))
-        .cornerRadius(20)
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/UserImage.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,39 @@
+//
+//  UserImage.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 22/10/22.
+//
+
+import SwiftUI
+
+struct UserImage: View {
+    @ObservedObject var userController: UserController
+    
+    var body: some View {
+        if let uiImage = userController.data.uiImage {
+            Circle()
+                .overlay(
+                    Image(uiImage: uiImage)
+                        .resizable()
+                        .scaledToFill()
+                        .clipShape(Circle())
+                )
+        } else {
+            Circle()
+                .foregroundColor(.secondary.opacity(0.3))
+                .overlay(
+                    Image(systemName: "camera.fill")
+                        .foregroundColor(.white)
+                        .font(.title)
+                        .shadow(radius: 5)
+                )
+        }
+    }
+}
+
+struct UserImage_Previews: PreviewProvider {
+    static var previews: some View {
+        UserImage(userController: UserController())
+    }
+}
--- a/GeoQuiz/Components/UserImageHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-//
-//  UserImageHelper.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 22/10/22.
-//
-
-import SwiftUI
-
-struct UserImage: View {
-    @ObservedObject var userController: UserController
-    
-    var body: some View {
-        if let uiImage = userController.data.uiImage {
-            Circle()
-                .overlay(
-                    Image(uiImage: uiImage)
-                        .resizable()
-                        .scaledToFill()
-                        .clipShape(Circle())
-                )
-        } else {
-            Circle()
-                .foregroundColor(.secondary.opacity(0.3))
-                .overlay(
-                    Image(systemName: "person")
-                        .font(.largeTitle)
-                )
-        }
-    }
-}
-
-struct UserImage_Previews: PreviewProvider {
-    static var previews: some View {
-        UserImage(userController: UserController())
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/UserProfile.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,44 @@
+//
+//  UserProfile.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/UserProfileHelper.swift	Sun Oct 23 00:11:38 2022 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-//
-//  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))
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/GeoQuiz.entitlements	Sun Oct 23 11:48:39 2022 +0100
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>aps-environment</key>
+	<string>development</string>
+	<key>com.apple.developer.icloud-container-identifiers</key>
+	<array>
+		<string>iCloud.io.dennistech.geoquiz</string>
+	</array>
+	<key>com.apple.developer.icloud-services</key>
+	<array>
+		<string>CloudKit</string>
+	</array>
+</dict>
+</plist>
--- a/GeoQuiz/GeoQuiz.xcdatamodeld/GeoQuiz.xcdatamodel/contents	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/GeoQuiz.xcdatamodeld/GeoQuiz.xcdatamodel/contents	Sun Oct 23 11:48:39 2022 +0100
@@ -2,15 +2,9 @@
 <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21279" systemVersion="21G115" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
     <entity name="PlayedGame" representedClassName="PlayedGame" syncable="YES">
         <attribute name="correctAnswers" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String]"/>
-        <attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
-        <attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
+        <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="score" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="wrongAnswers" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String]"/>
-        <uniquenessConstraints>
-            <uniquenessConstraint>
-                <constraint value="id"/>
-            </uniquenessConstraint>
-        </uniquenessConstraints>
     </entity>
 </model>
\ No newline at end of file
--- a/GeoQuiz/GeoQuizApp.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/GeoQuizApp.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -10,7 +10,7 @@
 
 @main
 struct GeoQuizApp: App {
-    @StateObject private var persistenceController = PersistenceController()
+    let persistenceController = PersistenceController.shared
     
     init() {
         Purchases.configure(withAPIKey: "appl_BymTxroeoaWiXAraaFjcPlHlqbf")
--- a/GeoQuiz/GuessTheCapitalView.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/GuessTheCapitalView.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -23,7 +23,7 @@
                     
                     Spacer()
                     
-                    FlagImage(flagSymbol: game.correctAnswer.value.flag, cornerRadius: 20)
+                    FlagImage(flagSymbol: game.correctAnswer.value.flag)
                         .clipShape(RoundedRectangle(cornerRadius: 20))
                         .shadow(radius: 10)
                         .frame(height: geo.size.height * 0.15)
--- a/GeoQuiz/GuessTheFlagView.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/GuessTheFlagView.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -46,14 +46,14 @@
                                     game.selector()
                                 }
                             } label: {
-                                FlagImage(flagSymbol: game.data[countryName]!.flag, cornerRadius: 20)
-                                    .clipShape(Circle())
-                                    .overlay {
-                                        Circle()
-                                            .strokeBorder(.white, lineWidth: 4)
-                                    }
+                                Circle()
+                                    .stroke(.white, lineWidth: 6)
+                                    .frame(height: geo.size.height * 0.15)
                                     .shadow(radius: 10)
-                                    .frame(height: geo.size.height * 0.15)
+                                    .overlay(
+                                        FlagImage(flagSymbol: game.data[countryName]!.flag)
+                                            .clipShape(Circle())
+                                    )
                             }
                         }
                     }
--- a/GeoQuiz/GuessThePopulationView.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/GuessThePopulationView.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -23,7 +23,7 @@
                     
                     Spacer()
                     
-                    FlagImage(flagSymbol: game.correctAnswer.value.flag, cornerRadius: 20)
+                    FlagImage(flagSymbol: game.correctAnswer.value.flag)
                         .clipShape(RoundedRectangle(cornerRadius: 20))
                         .shadow(radius: 10)
                         .frame(height: geo.size.height * 0.15)
--- a/GeoQuiz/Info.plist	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/Info.plist	Sun Oct 23 11:48:39 2022 +0100
@@ -5,6 +5,7 @@
 	<key>UIBackgroundModes</key>
 	<array>
 		<string>audio</string>
+		<string>remote-notification</string>
 	</array>
 </dict>
 </plist>
--- a/GeoQuiz/Logic/GameProtocol+Extension.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/Logic/GameProtocol+Extension.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -110,7 +110,6 @@
     func save(_ gameType: GameType, with moc: NSManagedObjectContext) {
         let playedGame = PlayedGame(context: moc)
 
-        playedGame.id = UUID()
         playedGame.type = gameType
         playedGame.date = Date()
         playedGame.score = Int32(userScore)
--- a/GeoQuiz/Logic/PersistenceController.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/Logic/PersistenceController.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -7,24 +7,8 @@
 
 import CoreData
 
-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
-    }
+class PersistenceController {
+    static let shared = PersistenceController()
     
     // Create mock data for previews
     static var preview: PersistenceController = {
@@ -33,7 +17,6 @@
         
         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()
@@ -55,4 +38,22 @@
         
         return result
     }()
+    
+    let container: NSPersistentCloudKitContainer
+
+    init(inMemory: Bool = false) {
+        container = NSPersistentCloudKitContainer(name: "GeoQuiz")
+        
+        if inMemory {
+            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
+        }
+        
+        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
+            if let error = error as NSError? {
+                fatalError("Unresolved error \(error), \(error.userInfo)")
+            }
+        })
+        
+        container.viewContext.automaticallyMergesChangesFromParent = true
+    }
 }
--- a/GeoQuiz/Logic/PlayedGame+CoreDataProperties.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/Logic/PlayedGame+CoreDataProperties.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -16,10 +16,9 @@
         return NSFetchRequest<PlayedGame>(entityName: "PlayedGame")
     }
 
-    @NSManaged public var id: UUID
     @NSManaged public var type: GameType
     @NSManaged public var score: Int32
-    @NSManaged public var date: Date
+    @NSManaged public var date: Date?
     @NSManaged public var correctAnswers: [String]?
     @NSManaged public var wrongAnswers: [String]?
 
--- a/GeoQuiz/ProfileModalView.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/ProfileModalView.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -11,7 +11,7 @@
     @ObservedObject var userController: UserController
     @ObservedObject var storeKitController: StoreKitController
     
-    @State var showingEditModalView = false
+    @State private var showingEditModalView = false
     
     @FetchRequest(sortDescriptors: [
         SortDescriptor(\.date, order: .reverse),
@@ -31,13 +31,36 @@
                     )
                     
                     VStack(spacing: 20) {
-                        ForEach(playedGames.prefix(8)) { playedGame in
-                            RecentGame(game: playedGame)
+                        HStack {
+                            Text("Latest games")
+                                .foregroundColor(.secondary)
+                            
+                            Spacer()
+                            
+                            NavigationLink {
+                                PlayedGamesList(playedGames: playedGames)
+                            } label: {
+                                Text("Show all")
+                            }
+                            .disabled(playedGames.isEmpty)
+                        }
+                        
+                        if playedGames.isEmpty {
+                            Spacer()
+                            LatestGamesPlaceholder()
+                        } else {
+                            ForEach(playedGames.prefix(8)) { playedGame in
+                                RecentGame(game: playedGame)
+                                    .padding()
+                                    .background(Color(.secondarySystemGroupedBackground))
+                                    .cornerRadius(20)
+                            }
                         }
                     }
                 }
                 .padding()
             }
+            .frame(maxWidth: .infinity)
             .navigationTitle("Profile")
             .navigationBarTitleDisplayMode(.inline)
             .background(Color(.systemGroupedBackground))
--- a/GeoQuiz/SettingsModalView.swift	Sun Oct 23 00:11:38 2022 +0100
+++ b/GeoQuiz/SettingsModalView.swift	Sun Oct 23 11:48:39 2022 +0100
@@ -45,21 +45,21 @@
                 }
                 
                 Section {
-                    LinkComponent(
+                    FormLink(
                         color: .mayaBlue,
                         symbol: "info.circle.fill",
                         text: "About",
                         url: URL(string: "https://dennistech.io")!
                     )
                     
-                    LinkComponent(
+                    FormLink(
                         color: .atomicTangerine,
                         symbol: "ant.circle.fill",
                         text: "Report bugs",
                         url: URL(string: "mailto:dmartin@dennistech.io")!
                     )
                     
-                    LinkComponent(
+                    FormLink(
                         color: .blueBell,
                         symbol: "message.circle.fill",
                         text: "Twitter",