changeset 14:136928bae534

add user profile
author Dennis C. M. <dennis@denniscm.com>
date Wed, 19 Oct 2022 07:56:33 +0200
parents bdfff35dd43c
children f1967f8cc67b
files GeoQuiz.xcodeproj/project.pbxproj GeoQuiz/Assets.xcassets/flag.imageset/Contents.json GeoQuiz/Assets.xcassets/flag.imageset/flag.png GeoQuiz/Components/ProfileEditModalView.swift GeoQuiz/Components/UserImageHelper.swift GeoQuiz/ContentView.swift GeoQuiz/Logic/CityGameClass.swift GeoQuiz/Logic/CountryGameClass.swift GeoQuiz/Logic/GameModeEnum.swift GeoQuiz/Logic/GameProtocol+Extension.swift GeoQuiz/Logic/GameTypeEnum.swift GeoQuiz/Logic/HapticsClass.swift GeoQuiz/Logic/StoreKitRCClass.swift GeoQuiz/Logic/UserClass.swift GeoQuiz/Logic/UserDataModel.swift GeoQuiz/Logic/UserSettingsModel.swift GeoQuiz/ProfileModalView.swift GeoQuiz/SettingsModalView.swift
diffstat 18 files changed, 358 insertions(+), 167 deletions(-) [+]
line wrap: on
line diff
--- a/GeoQuiz.xcodeproj/project.pbxproj	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz.xcodeproj/project.pbxproj	Wed Oct 19 07:56:33 2022 +0200
@@ -34,7 +34,6 @@
 		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 */; };
-		95919DB828F079D100F21F8F /* UserSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB728F079D100F21F8F /* UserSettingsModel.swift */; };
 		95919DBC28F08D0600F21F8F /* LinkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* LinkHelper.swift */; };
 		95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* LoadFunc.swift */; };
 		95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AF322928DF293900023ACC /* GuessTheCountryView.swift */; };
@@ -42,8 +41,12 @@
 		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 */; };
-		95CA294028F5769700CE0B7A /* GameModeEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA293F28F5769700CE0B7A /* GameModeEnum.swift */; };
+		95C6456C28FE87E4000CD570 /* UserDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456B28FE87E4000CD570 /* UserDataModel.swift */; };
+		95C6456E28FE8C04000CD570 /* UserImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456D28FE8C04000CD570 /* UserImageHelper.swift */; };
+		95C6457228FFC4DC000CD570 /* ProfileEditModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */; };
+		95C6457428FFC8E0000CD570 /* DataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457328FFC8E0000CD570 /* DataController.swift */; };
 		95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */; };
+		95CC404928F98503001F74E1 /* GameTypeEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC404828F98503001F74E1 /* GameTypeEnum.swift */; };
 		95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */; };
 		95FA409C28D9881100129B60 /* CountryGameClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409B28D9881100129B60 /* CountryGameClass.swift */; };
 /* End PBXBuildFile section */
@@ -76,7 +79,6 @@
 		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>"; };
-		95919DB728F079D100F21F8F /* UserSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsModel.swift; sourceTree = "<group>"; };
 		95919DBB28F08D0600F21F8F /* LinkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkHelper.swift; sourceTree = "<group>"; };
 		95AE8D5628C8750E0067F219 /* LoadFunc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadFunc.swift; sourceTree = "<group>"; };
 		95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = "<group>"; };
@@ -84,8 +86,12 @@
 		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>"; };
-		95CA293F28F5769700CE0B7A /* GameModeEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameModeEnum.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>"; };
+		95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditModalView.swift; sourceTree = "<group>"; };
+		95C6457328FFC8E0000CD570 /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataController.swift; sourceTree = "<group>"; };
 		95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityAlertHelper.swift; sourceTree = "<group>"; };
+		95CC404828F98503001F74E1 /* GameTypeEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameTypeEnum.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>"; };
@@ -109,15 +115,16 @@
 			children = (
 				951AFAEC28E5657500A4A4BD /* CityModel.swift */,
 				951AFAEE28E565FE00A4A4BD /* CountryModel.swift */,
-				95919DB728F079D100F21F8F /* UserSettingsModel.swift */,
+				95C6456B28FE87E4000CD570 /* UserDataModel.swift */,
 				955A658228D733E400CEEC6D /* GameProtocol+Extension.swift */,
 				955A65A828D7815E00CEEC6D /* HapticsClass.swift */,
 				95FA409B28D9881100129B60 /* CountryGameClass.swift */,
 				951AFAF028E5735400A4A4BD /* CityGameClass.swift */,
 				95919DB528F076BF00F21F8F /* UserClass.swift */,
 				95197EFC28F339AE00FE67E9 /* StoreKitRCClass.swift */,
+				95CC404828F98503001F74E1 /* GameTypeEnum.swift */,
 				95AE8D5628C8750E0067F219 /* LoadFunc.swift */,
-				95CA293F28F5769700CE0B7A /* GameModeEnum.swift */,
+				95C6457328FFC8E0000CD570 /* DataController.swift */,
 			);
 			path = Logic;
 			sourceTree = "<group>";
@@ -198,10 +205,12 @@
 				95BC392C28EC42570049AB49 /* CityMapHelper.swift */,
 				95919DBB28F08D0600F21F8F /* LinkHelper.swift */,
 				95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */,
+				95C6456D28FE8C04000CD570 /* UserImageHelper.swift */,
 				952E41E828DC521200198643 /* GameAlertsModifier.swift */,
 				95C430F828D0A8E500480D23 /* GradientExtension.swift */,
 				951D197228D485E000671FAD /* ColorExtension.swift */,
 				955950BA28F15FF2001BDEE8 /* FormatterExtension.swift */,
+				95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */,
 			);
 			path = Components;
 			sourceTree = "<group>";
@@ -290,10 +299,12 @@
 				95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */,
 				952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */,
 				95197EFD28F339AE00FE67E9 /* StoreKitRCClass.swift in Sources */,
-				95919DB828F079D100F21F8F /* UserSettingsModel.swift in Sources */,
 				9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */,
 				955A658328D733E400CEEC6D /* GameProtocol+Extension.swift in Sources */,
+				95CC404928F98503001F74E1 /* GameTypeEnum.swift in Sources */,
+				95C6457428FFC8E0000CD570 /* DataController.swift in Sources */,
 				95919DB628F076BF00F21F8F /* UserClass.swift in Sources */,
+				95C6456E28FE8C04000CD570 /* UserImageHelper.swift in Sources */,
 				95C4315628C64A8C00212131 /* ContentView.swift in Sources */,
 				95C4315928C6500000212131 /* GameButtonHelper.swift in Sources */,
 				956273EA28CB2E98008DC094 /* FlagImageHelper.swift in Sources */,
@@ -308,11 +319,12 @@
 				95FA409C28D9881100129B60 /* CountryGameClass.swift in Sources */,
 				95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */,
 				955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */,
-				95CA294028F5769700CE0B7A /* GameModeEnum.swift in Sources */,
 				95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */,
 				9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */,
 				955950BB28F15FF2001BDEE8 /* FormatterExtension.swift in Sources */,
+				95C6456C28FE87E4000CD570 /* UserDataModel.swift in Sources */,
 				951D197328D485E000671FAD /* ColorExtension.swift in Sources */,
+				95C6457228FFC4DC000CD570 /* ProfileEditModalView.swift in Sources */,
 				95C430F928D0A8E500480D23 /* GradientExtension.swift in Sources */,
 				952E41ED28DC658900198643 /* SettingsModalView.swift in Sources */,
 				95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */,
@@ -456,6 +468,7 @@
 				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
 				INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
 				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -489,6 +502,7 @@
 				INFOPLIST_KEY_UILaunchScreen_Generation = YES;
 				INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
 				INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
--- a/GeoQuiz/Assets.xcassets/flag.imageset/Contents.json	Wed Oct 12 11:47:29 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-{
-  "images" : [
-    {
-      "filename" : "flag.png",
-      "idiom" : "universal"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}
Binary file GeoQuiz/Assets.xcassets/flag.imageset/flag.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/ProfileEditModalView.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -0,0 +1,70 @@
+//
+//  ProfileEditModalView.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepci贸n Mart铆n on 19/10/22.
+//
+
+import SwiftUI
+import PhotosUI
+
+struct ProfileEditModalView: View {
+    @ObservedObject var user: User
+    @Environment(\.dismiss) var dismiss
+    
+    @State private var selectedItem: PhotosPickerItem? = nil
+    
+    var body: some View {
+        NavigationStack {
+            Form {
+                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: $selectedItem,
+                                matching: .images,
+                                photoLibrary: .shared()) {
+                                    EmptyView()
+                                }
+                        }
+                        
+                        Spacer()
+                    }
+                } header: {
+                    Text("Profile image")
+                }
+                
+                Section {
+                    TextField("Enter a username", text: $user.data.username)
+                } header: {
+                    Text("Username")
+                }
+            }
+            .navigationTitle("Edit profile")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .navigationBarTrailing) {
+                    Button("Done") {
+                        dismiss()
+                    }
+                }
+            }
+        }
+    }
+}
+
+struct ProfileEditModalView_Previews: PreviewProvider {
+    static var previews: some View {
+        ProfileEditModalView(user: User())
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Components/UserImageHelper.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -0,0 +1,39 @@
+//
+//  UserImageHelper.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepci贸n Mart铆n on 18/10/22.
+//
+
+import SwiftUI
+
+struct UserImage: View {
+    var uiImage: UIImage?
+    
+    var body: some View {
+        if let uiImage = uiImage {
+            Circle()
+                .frame(height: 100)
+                .overlay(
+                    Image(uiImage: uiImage)
+                        .resizable()
+                        .scaledToFill()
+                        .clipShape(Circle())
+                )
+        } else {
+            Circle()
+                .frame(height: 100)
+                .foregroundColor(.secondary.opacity(0.3))
+                .overlay(
+                    Image(systemName: "person")
+                        .font(.largeTitle)
+                )
+        }
+    }
+}
+
+struct UserImage_Previews: PreviewProvider {
+    static var previews: some View {
+        UserImage()
+    }
+}
--- a/GeoQuiz/ContentView.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/ContentView.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -8,116 +8,104 @@
 import SwiftUI
 
 struct ContentView: View {
-    @State private var gameModeSelection: GameMode? = nil
+    @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()
     
     var body: some View {
-        NavigationView {
-            VStack {
-                NavigationLink(
-                    destination: GuessTheFlagView(),
-                    tag: GameMode.guessTheFlag,
-                    selection: $gameModeSelection)
-                {
-                    EmptyView()
-                }
+        NavigationStack(path: $path) {
+            ScrollView(showsIndicators: false) {
                 
-                NavigationLink(
-                    destination: GuessTheCapitalView(),
-                    tag: GameMode.guessTheCapital,
-                    selection: $gameModeSelection)
-                {
-                    EmptyView()
-                }
-                
-                NavigationLink(
-                    destination: GuessTheCountryView(),
-                    tag: GameMode.guessTheCountry,
-                    selection: $gameModeSelection)
-                {
-                    EmptyView()
-                }
-                
-                NavigationLink(
-                    destination: GuessThePopulationView(),
-                    tag: GameMode.guessThePopulation,
-                    selection: $gameModeSelection)
-                {
-                    EmptyView()
-                }
+                NavigationLink(value: GameType.guessTheFlag) { EmptyView() }
+                NavigationLink(value: GameType.guessTheCapital) { EmptyView() }
+                NavigationLink(value: GameType.guessTheCountry) { EmptyView() }
+                NavigationLink(value: GameType.guessThePopulation) { EmptyView() }
                 
-                ScrollView(showsIndicators: false) {
-                    VStack(alignment: .leading, spacing: 30) {
-                        Text("Select a game 馃幃")
-                            .font(.largeTitle.bold())
-                            .padding(.bottom)
-                        
-                        Button {
-                            gameModeSelection = .guessTheFlag
-                        } label: {
-                            GameButton(
-                                gradient: .main,
-                                level: "Level 1",
-                                symbol: "flag.fill",
-                                name: "Guess the flag"
-                            )
-                        }
-                        
-                        Button {
-                            if storeKitRC.isActive {
-                                gameModeSelection = .guessTheCapital
-                            } else {
-                                showingBuyPremiumModalView = true
-                            }
-                        } label: {
-                            GameButton(
-                                gradient: .secondary,
-                                level: "Level 2",
-                                symbol: storeKitRC.isActive ? "building.2.fill": "lock.fill",
-                                name: "Guess the capital"
-                            )
+                VStack(alignment: .leading, spacing: 30) {
+                    Text("Select a game 馃幃")
+                        .font(.largeTitle.bold())
+                        .padding(.bottom)
+                    
+                    Button {
+                        path.append(.guessTheFlag)
+                    } label: {
+                        GameButton(
+                            gradient: .main,
+                            level: "Level 1",
+                            symbol: "flag.fill",
+                            name: "Guess the flag"
+                        )
+                    }
+                    
+                    Button {
+                        if storeKitRC.isActive {
+                            path.append(.guessTheCapital)
+                        } else {
+                            showingBuyPremiumModalView = true
                         }
-                        
-                        Button {
-                            if storeKitRC.isActive {
-                                gameModeSelection = .guessTheCountry
-                            } else {
-                                showingBuyPremiumModalView = true
-                            }
-                        } label: {
-                            GameButton(
-                                gradient: .tertiary,
-                                level: "Level 3",
-                                symbol: storeKitRC.isActive ? "globe.americas.fill": "lock.fill",
-                                name: "Guess the country"
-                            )
+                    } label: {
+                        GameButton(
+                            gradient: .secondary,
+                            level: "Level 2",
+                            symbol: storeKitRC.isActive ? "building.2.fill": "lock.fill",
+                            name: "Guess the capital"
+                        )
+                    }
+                    
+                    Button {
+                        if storeKitRC.isActive {
+                            path.append(.guessTheCountry)
+                        } else {
+                            showingBuyPremiumModalView = true
                         }
-                        
-                        Button {
-                            if storeKitRC.isActive {
-                                gameModeSelection = .guessThePopulation
-                            } else {
-                                showingBuyPremiumModalView = true
-                            }
-                        } label: {
-                            GameButton(
-                                gradient: .quaternary,
-                                level: "Level 4",
-                                symbol: storeKitRC.isActive ? "person.fill": "lock.fill",
-                                name: "Guess the population"
-                            )
+                    } label: {
+                        GameButton(
+                            gradient: .tertiary,
+                            level: "Level 3",
+                            symbol: storeKitRC.isActive ? "globe.americas.fill": "lock.fill",
+                            name: "Guess the country"
+                        )
+                    }
+                    
+                    Button {
+                        if storeKitRC.isActive {
+                            path.append(.guessThePopulation)
+                        } else {
+                            showingBuyPremiumModalView = true
                         }
+                    } label: {
+                        GameButton(
+                            gradient: .quaternary,
+                            level: "Level 4",
+                            symbol: storeKitRC.isActive ? "person.fill": "lock.fill",
+                            name: "Guess the population"
+                        )
                     }
-                    .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 {
@@ -148,11 +136,11 @@
             }
             
             .sheet(isPresented: $showingSettingsModalView) {
-                SettingsModalView()
+                SettingsModalView(user: user)
             }
             
             .sheet(isPresented: $showingProfileModalView) {
-                ProfileModalView()
+                ProfileModalView(user: user, storeKitRC: storeKitRC)
             }
         }
         .navigationViewStyle(StackNavigationViewStyle())
--- a/GeoQuiz/Logic/CityGameClass.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/Logic/CityGameClass.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -49,11 +49,11 @@
         self.data = data.cities
         
         let user = User()
-        userLives = user.settings.numberOfLives
+        userLives = user.data.numberOfLives
         
-        if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") {
-            if let decodedUserSettings = try? JSONDecoder().decode(UserSettings.self, from: userSettings) {
-                userLives = decodedUserSettings.numberOfLives
+        if let userData = UserDefaults.standard.data(forKey: "UserData") {
+            if let decodedUserData = try? JSONDecoder().decode(UserData.self, from: userData) {
+                userLives = decodedUserData.numberOfLives
             }
         }
         
--- a/GeoQuiz/Logic/CountryGameClass.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/Logic/CountryGameClass.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -49,11 +49,11 @@
         self.data = data.countries
         
         let user = User()
-        userLives = user.settings.numberOfLives
+        userLives = user.data.numberOfLives
         
-        if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") {
-            if let decodedUserSettings = try? JSONDecoder().decode(UserSettings.self, from: userSettings) {
-                userLives = decodedUserSettings.numberOfLives
+        if let userData = UserDefaults.standard.data(forKey: "UserData") {
+            if let decodedUserData = try? JSONDecoder().decode(UserData.self, from: userData) {
+                userLives = decodedUserData.numberOfLives
             }
         }
         
--- a/GeoQuiz/Logic/GameModeEnum.swift	Wed Oct 12 11:47:29 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-//
-//  GameModeEnum.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepci贸n Mart铆n on 11/10/22.
-//
-
-import Foundation
-
-enum GameMode {
-    case guessTheFlag, guessTheCapital, guessTheCountry, guessThePopulation
-}
--- a/GeoQuiz/Logic/GameProtocol+Extension.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/Logic/GameProtocol+Extension.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -121,7 +121,7 @@
     private func playSound(_ filename: String) {
         let user = User()
         
-        if user.settings.sound {
+        if user.data.sound {
             guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else {
                 fatalError("Sound file \(filename) couldn't be found")
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Logic/GameTypeEnum.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -0,0 +1,12 @@
+//
+//  GameTypeEnum.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepci贸n Mart铆n on 14/10/22.
+//
+
+import Foundation
+
+enum GameType {
+    case guessTheFlag, guessTheCapital, guessTheCountry, guessThePopulation
+}
--- a/GeoQuiz/Logic/HapticsClass.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/Logic/HapticsClass.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -12,14 +12,14 @@
     private var user = User()
     
     func success() {
-        if user.settings.haptics {
+        if user.data.haptics {
             let generator = UINotificationFeedbackGenerator()
             generator.notificationOccurred(.success)
         }
     }
 
     func error() {
-        if user.settings.haptics {
+        if user.data.haptics {
             let generator = UINotificationFeedbackGenerator()
             generator.notificationOccurred(.error)
         }
--- a/GeoQuiz/Logic/StoreKitRCClass.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/Logic/StoreKitRCClass.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -7,7 +7,6 @@
 
 import Foundation
 import RevenueCat
-import SwiftUI
 
 class StoreKitRC: ObservableObject {
     @Published var errorAlertTitle = ""
--- a/GeoQuiz/Logic/UserClass.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/Logic/UserClass.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -8,19 +8,18 @@
 import Foundation
 
 class User: ObservableObject {
-    @Published var settings = UserSettings() {
+    @Published var data = UserData() {
         didSet {
-            if let userSettingsEncoded = try? JSONEncoder().encode(settings) {
-                UserDefaults.standard.set(userSettingsEncoded, forKey: "UserSettings")
+            if let userDataEncoded = try? JSONEncoder().encode(data) {
+                UserDefaults.standard.set(userDataEncoded, forKey: "UserData")
             }
         }
     }
-    
-    
+
     init() {
-        if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") {
-            if let decodedUserSettings = try? JSONDecoder().decode(UserSettings.self, from: userSettings) {
-                settings = decodedUserSettings
+        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/UserDataModel.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -0,0 +1,26 @@
+//
+//  UserDataModel.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepci贸n Mart铆n on 18/10/22.
+//
+
+import Foundation
+import SwiftUI
+
+struct UserData: Codable {
+    
+    // Settings
+    var haptics: Bool = true
+    var sound: Bool = true
+    var numberOfLives: Int = 25
+    
+    // Profile
+    var username: String = "Anonymous"
+    var imageData: Data?
+    
+    var uiImage: UIImage? {
+        guard let imageData = imageData else { return nil }
+        return UIImage(data: imageData)
+    }
+}
--- a/GeoQuiz/Logic/UserSettingsModel.swift	Wed Oct 12 11:47:29 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-//
-//  UserSettingsModel.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepci贸n Mart铆n on 7/10/22.
-//
-
-import Foundation
-
-struct UserSettings: Codable {
-    var haptics: Bool = true
-    var sound: Bool = true
-    var numberOfLives: Int = 25
-}
--- a/GeoQuiz/ProfileModalView.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/ProfileModalView.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -6,15 +6,97 @@
 //
 
 import SwiftUI
+import PhotosUI
 
 struct ProfileModalView: View {
+    @ObservedObject var user: User
+    @ObservedObject var storeKitRC: StoreKitRC
+    
+    @Environment(\.dismiss) var dismiss
+    
+    @State private var showingEditModalView = false
+    
     var body: some View {
-        Text("Hello, World!")
+        NavigationView {
+            Form {
+                Section {
+                    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)
+                            }
+                        }
+                    }
+                }
+                
+                Section {
+                    VStack(alignment: .leading) {
+                        Text("Game 1")
+                        Capsule()
+                            .frame(height: 6)
+                    }
+                    
+                    VStack(alignment: .leading) {
+                        Text("Game 1")
+                        Capsule()
+                            .frame(height: 6)
+                    }
+                    VStack(alignment: .leading) {
+                        Text("Game 1")
+                        Capsule()
+                            .frame(height: 6)
+                    }
+                    VStack(alignment: .leading) {
+                        Text("Game 1")
+                        Capsule()
+                            .frame(height: 6)
+                    }
+                } header: {
+                    Text("Progress")
+                }
+                
+                Section {
+                    ForEach(1..<10) { _ in
+                        Text("Hello")
+                    }
+                } header: {
+                    Text("Recent games")
+                }
+            }
+            .navigationTitle("Profile")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .cancellationAction) {
+                    Button {
+                        dismiss()
+                    } label: {
+                        Label("Exit", systemImage: "multiply")
+                    }
+                }
+                
+                ToolbarItem(placement: .navigationBarTrailing) {
+                    Button("Edit") {
+                        showingEditModalView = true
+                    }
+                }
+            }
+            
+            .sheet(isPresented: $showingEditModalView) {
+                ProfileEditModalView(user: user)
+            }
+        }
     }
 }
 
 struct ProfileView_Previews: PreviewProvider {
     static var previews: some View {
-        ProfileModalView()
+        ProfileModalView(user: User(), storeKitRC: StoreKitRC())
     }
 }
--- a/GeoQuiz/SettingsModalView.swift	Wed Oct 12 11:47:29 2022 +0200
+++ b/GeoQuiz/SettingsModalView.swift	Wed Oct 19 07:56:33 2022 +0200
@@ -8,8 +8,8 @@
 import SwiftUI
 
 struct SettingsModalView: View {
+    @ObservedObject var user: User
     @Environment(\.dismiss) var dismiss
-    @StateObject var user = User()
 
     var lives: [Int] {
         var lives = [Int]()
@@ -24,7 +24,7 @@
         NavigationView {
             Form {
                 Section {
-                    Picker("鉂わ笍 Lives", selection: $user.settings.numberOfLives) {
+                    Picker("鉂わ笍 Lives", selection: $user.data.numberOfLives) {
                         ForEach(lives, id: \.self) { numberOfLives in
                             Text("\(numberOfLives)")
                                 .tag(numberOfLives)
@@ -37,8 +37,8 @@
                 }
                 
                 Section {
-                    Toggle("Haptics", isOn: $user.settings.haptics)
-                    Toggle("Sound effects", isOn: $user.settings.sound)
+                    Toggle("Haptics", isOn: $user.data.haptics)
+                    Toggle("Sound effects", isOn: $user.data.sound)
                 } header: {
                     Text("Effects")
                 }
@@ -84,6 +84,6 @@
 
 struct SettingsModalView_Previews: PreviewProvider {
     static var previews: some View {
-        SettingsModalView()
+        SettingsModalView(user: User())
     }
 }