changeset 9:3540c7efc216

implement UserSettings
author Dennis C. M. <dennis@denniscm.com>
date Fri, 07 Oct 2022 18:50:38 +0200
parents e09959b4e4a8
children a793f33f05fb
files GeoQuiz.xcodeproj/project.pbxproj GeoQuiz/Helpers/LinkComponent.swift GeoQuiz/Logic/CityGame.swift GeoQuiz/Logic/CountryGame.swift GeoQuiz/Logic/Game.swift GeoQuiz/Logic/Haptics.swift GeoQuiz/Logic/PlaySound.swift GeoQuiz/Logic/User.swift GeoQuiz/Models/UserSettingsModel.swift GeoQuiz/SettingsModalView.swift
diffstat 10 files changed, 168 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/GeoQuiz.xcodeproj/project.pbxproj	Thu Oct 06 11:14:34 2022 +0200
+++ b/GeoQuiz.xcodeproj/project.pbxproj	Fri Oct 07 18:50:38 2022 +0200
@@ -19,7 +19,6 @@
 		951D197328D485E000671FAD /* CustomColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951D197228D485E000671FAD /* CustomColors.swift */; };
 		952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952E41E828DC521200198643 /* GameAlertsModifier.swift */; };
 		952E41ED28DC658900198643 /* SettingsModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952E41EC28DC658900198643 /* SettingsModalView.swift */; };
-		952E41EF28DC692200198643 /* PlaySound.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952E41EE28DC692200198643 /* PlaySound.swift */; };
 		952E41F228DC6F6E00198643 /* correctAnswer.wav in Resources */ = {isa = PBXBuildFile; fileRef = 952E41F028DC6F6D00198643 /* correctAnswer.wav */; };
 		952E41F328DC6F6E00198643 /* wrongAnswer.wav in Resources */ = {isa = PBXBuildFile; fileRef = 952E41F128DC6F6E00198643 /* wrongAnswer.wav */; };
 		9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9539829228C51EDE00B70973 /* GeoQuizApp.swift */; };
@@ -30,6 +29,9 @@
 		955A65A928D7815E00CEEC6D /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955A65A828D7815E00CEEC6D /* Haptics.swift */; };
 		956273EA28CB2E98008DC094 /* FlagImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956273E928CB2E98008DC094 /* FlagImage.swift */; };
 		9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9590359428E098FF00B24560 /* ProfileModalView.swift */; };
+		95919DB628F076BF00F21F8F /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB528F076BF00F21F8F /* User.swift */; };
+		95919DB828F079D100F21F8F /* UserSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DB728F079D100F21F8F /* UserSettingsModel.swift */; };
+		95919DBC28F08D0600F21F8F /* LinkComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* LinkComponent.swift */; };
 		95AE8D5728C8750E0067F219 /* Load.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AE8D5628C8750E0067F219 /* Load.swift */; };
 		95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AF322928DF293900023ACC /* GuessTheCountryView.swift */; };
 		95BC392D28EC42570049AB49 /* CityMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC392C28EC42570049AB49 /* CityMap.swift */; };
@@ -53,7 +55,6 @@
 		951D197228D485E000671FAD /* CustomColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomColors.swift; sourceTree = "<group>"; };
 		952E41E828DC521200198643 /* GameAlertsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameAlertsModifier.swift; sourceTree = "<group>"; };
 		952E41EC28DC658900198643 /* SettingsModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModalView.swift; sourceTree = "<group>"; };
-		952E41EE28DC692200198643 /* PlaySound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaySound.swift; sourceTree = "<group>"; };
 		952E41F028DC6F6D00198643 /* correctAnswer.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = correctAnswer.wav; sourceTree = "<group>"; };
 		952E41F128DC6F6E00198643 /* wrongAnswer.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = wrongAnswer.wav; sourceTree = "<group>"; };
 		9539828F28C51EDE00B70973 /* GeoQuiz.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GeoQuiz.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -65,6 +66,9 @@
 		955A65A828D7815E00CEEC6D /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; };
 		956273E928CB2E98008DC094 /* FlagImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImage.swift; sourceTree = "<group>"; };
 		9590359428E098FF00B24560 /* ProfileModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModalView.swift; sourceTree = "<group>"; };
+		95919DB528F076BF00F21F8F /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
+		95919DB728F079D100F21F8F /* UserSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsModel.swift; sourceTree = "<group>"; };
+		95919DBB28F08D0600F21F8F /* LinkComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkComponent.swift; sourceTree = "<group>"; };
 		95AE8D5628C8750E0067F219 /* Load.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Load.swift; sourceTree = "<group>"; };
 		95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = "<group>"; };
 		95BC392C28EC42570049AB49 /* CityMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMap.swift; sourceTree = "<group>"; };
@@ -92,10 +96,10 @@
 			children = (
 				95AE8D5628C8750E0067F219 /* Load.swift */,
 				955A65A828D7815E00CEEC6D /* Haptics.swift */,
-				952E41EE28DC692200198643 /* PlaySound.swift */,
 				955A658228D733E400CEEC6D /* Game.swift */,
 				95FA409B28D9881100129B60 /* CountryGame.swift */,
 				951AFAF028E5735400A4A4BD /* CityGame.swift */,
+				95919DB528F076BF00F21F8F /* User.swift */,
 			);
 			path = Logic;
 			sourceTree = "<group>";
@@ -163,6 +167,7 @@
 			children = (
 				951AFAEC28E5657500A4A4BD /* CityModel.swift */,
 				951AFAEE28E565FE00A4A4BD /* CountryModel.swift */,
+				95919DB728F079D100F21F8F /* UserSettingsModel.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -178,6 +183,7 @@
 				95C430F828D0A8E500480D23 /* CustomGradients.swift */,
 				951D197228D485E000671FAD /* CustomColors.swift */,
 				95BC392C28EC42570049AB49 /* CityMap.swift */,
+				95919DBB28F08D0600F21F8F /* LinkComponent.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -259,17 +265,19 @@
 				955A65A928D7815E00CEEC6D /* Haptics.swift in Sources */,
 				95BC392D28EC42570049AB49 /* CityMap.swift in Sources */,
 				952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */,
+				95919DB828F079D100F21F8F /* UserSettingsModel.swift in Sources */,
 				9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */,
 				955A658328D733E400CEEC6D /* Game.swift in Sources */,
+				95919DB628F076BF00F21F8F /* User.swift in Sources */,
 				95C4315628C64A8C00212131 /* ContentView.swift in Sources */,
 				95C4315928C6500000212131 /* GameButton.swift in Sources */,
 				956273EA28CB2E98008DC094 /* FlagImage.swift in Sources */,
-				952E41EF28DC692200198643 /* PlaySound.swift in Sources */,
 				951AFAED28E5657500A4A4BD /* CityModel.swift in Sources */,
 				951B630228D1A87C004F9877 /* GuessTheCapitalView.swift in Sources */,
 				9509A8E028E5A3C500CFCDBA /* GuessTheCurrencyView.swift in Sources */,
 				9539829328C51EDE00B70973 /* GeoQuizApp.swift in Sources */,
 				95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */,
+				95919DBC28F08D0600F21F8F /* LinkComponent.swift in Sources */,
 				951AFAEF28E565FE00A4A4BD /* CountryModel.swift in Sources */,
 				95030CEA28D1BA4D001AA3A1 /* AnswerButton.swift in Sources */,
 				95FA409C28D9881100129B60 /* CountryGame.swift in Sources */,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Helpers/LinkComponent.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -0,0 +1,41 @@
+//
+//  LinkComponent.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 7/10/22.
+//
+
+import SwiftUI
+
+struct LinkComponent: View {
+    var color: Color
+    var iconName: String
+    var text: String
+    var url: URL
+    
+    @Environment(\.openURL) var openURL
+    
+    var body: some View {
+        Link(destination: url) {
+            HStack(alignment: .center, spacing: 20) {
+                Image(systemName: iconName)
+                    .imageScale(.large)
+                    .foregroundColor(color)
+                
+                Text(text)
+                    .foregroundColor(.primary)
+            }
+        }
+    }
+}
+
+struct LinkComponent_Previews: PreviewProvider {
+    static var previews: some View {
+        LinkComponent(
+            color: .mayaBlue,
+            iconName: "info.circle.fill",
+            text: "About",
+            url: URL(string: "https://dennistech.io")!
+        )
+    }
+}
--- a/GeoQuiz/Logic/CityGame.swift	Thu Oct 06 11:14:34 2022 +0200
+++ b/GeoQuiz/Logic/CityGame.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -7,7 +7,6 @@
 
 import Foundation
 import AVFAudio
-import SwiftUI
 
 class CityGame: Game, ObservableObject {
     
--- a/GeoQuiz/Logic/CountryGame.swift	Thu Oct 06 11:14:34 2022 +0200
+++ b/GeoQuiz/Logic/CountryGame.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -47,6 +47,13 @@
     init() {
         let data: CountryModel = load("countries.json")
         self.data = data.countries
+        
+        if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") {
+            if let decodedUserSettings = try? JSONDecoder().decode(UserSettingsModel.self, from: userSettings) {
+                userLives = decodedUserSettings.numberOfLives
+            }
+        }
+        
         askQuestion {
             selector()
         }
--- a/GeoQuiz/Logic/Game.swift	Thu Oct 06 11:14:34 2022 +0200
+++ b/GeoQuiz/Logic/Game.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -62,8 +62,10 @@
     }
     
     func answer(_ choice: (key: String, value: T), selector: () -> Void) {
+        var haptics = Haptics()
+        
         if correctAnswer == choice {
-            hapticSuccess()
+            haptics.success()
             playSound("correctAnswer")
             
             withAnimation(.easeIn(duration: 0.5)) {
@@ -76,7 +78,7 @@
                 selector()
             }
         } else {
-            hapticError()
+            haptics.error()
             playSound("wrongAnswer")
 
             withAnimation(.easeIn(duration: 0.5)) {
@@ -117,22 +119,26 @@
     }
     
     private func playSound(_ filename: String) {
-        guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else {
-            fatalError("Sound file \(filename) couldn't be found")
-        }
+        let user = User()
         
-        do {
-            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
-            try AVAudioSession.sharedInstance().setActive(true)
-        } catch {
-            fatalError("Couldn't activate session")
-        }
-        
-        do {
-            player = try AVAudioPlayer(contentsOf: soundFileURL)
-            player?.play()
-        } catch {
-            fatalError("Couldn't play sound effect")
+        if user.settings.sound {
+            guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else {
+                fatalError("Sound file \(filename) couldn't be found")
+            }
+            
+            do {
+                try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
+                try AVAudioSession.sharedInstance().setActive(true)
+            } catch {
+                fatalError("Couldn't activate session")
+            }
+            
+            do {
+                player = try AVAudioPlayer(contentsOf: soundFileURL)
+                player?.play()
+            } catch {
+                fatalError("Couldn't play sound effect")
+            }
         }
     }
 }
--- a/GeoQuiz/Logic/Haptics.swift	Thu Oct 06 11:14:34 2022 +0200
+++ b/GeoQuiz/Logic/Haptics.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -8,12 +8,20 @@
 import Foundation
 import SwiftUI
 
-func hapticSuccess() {
-    let generator = UINotificationFeedbackGenerator()
-    generator.notificationOccurred(.success)
-}
+class Haptics {
+    private var user = User()
+    
+    func success() {
+        if user.settings.haptics {
+            let generator = UINotificationFeedbackGenerator()
+            generator.notificationOccurred(.success)
+        }
+    }
 
-func hapticError() {
-    let generator = UINotificationFeedbackGenerator()
-    generator.notificationOccurred(.error)
+    func error() {
+        if user.settings.haptics {
+            let generator = UINotificationFeedbackGenerator()
+            generator.notificationOccurred(.error)
+        }
+    }
 }
--- a/GeoQuiz/Logic/PlaySound.swift	Thu Oct 06 11:14:34 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-//
-//  PlaySound.swift
-//  GeoQuiz
-//
-//  Created by Dennis Concepción Martín on 22/9/22.
-//
-
-import Foundation
-import AVFAudio
-import UIKit
-
-class Sound {
-    private var player: AVAudioPlayer?
-    
-    func play(_ filename: String) {
-        guard let soundFileURL = Bundle.main.url(forResource: filename, withExtension: "wav") else {
-            fatalError("Sound file \(filename) couldn't be found")
-        }
-        
-        do {
-            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
-            try AVAudioSession.sharedInstance().setActive(true)
-        } catch {
-            fatalError("Couldn't activate session")
-        }
-        
-        do {
-            player = try AVAudioPlayer(contentsOf: soundFileURL)
-            player?.play()
-        } catch {
-            fatalError("Couldn't play sound effect")
-        }
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Logic/User.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -0,0 +1,27 @@
+//
+//  User.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 7/10/22.
+//
+
+import Foundation
+
+class User: ObservableObject {
+    @Published var settings = UserSettingsModel() {
+        didSet {
+            if let userSettingsEncoded = try? JSONEncoder().encode(settings) {
+                UserDefaults.standard.set(userSettingsEncoded, forKey: "UserSettings")
+            }
+        }
+    }
+    
+    
+    init() {
+        if let userSettings = UserDefaults.standard.data(forKey: "UserSettings") {
+            if let decodedUserSettings = try? JSONDecoder().decode(UserSettingsModel.self, from: userSettings) {
+                settings = decodedUserSettings
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Models/UserSettingsModel.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -0,0 +1,14 @@
+//
+//  UserSettingsModel.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 7/10/22.
+//
+
+import Foundation
+
+struct UserSettingsModel: Codable {
+    var haptics: Bool = true
+    var sound: Bool = true
+    var numberOfLives: Int = 3
+}
--- a/GeoQuiz/SettingsModalView.swift	Thu Oct 06 11:14:34 2022 +0200
+++ b/GeoQuiz/SettingsModalView.swift	Fri Oct 07 18:50:38 2022 +0200
@@ -9,29 +9,50 @@
 
 struct SettingsModalView: View {
     @Environment(\.dismiss) var dismiss
+    @StateObject var user = User()
     
     var body: some View {
         NavigationView {
             Form {
                 Section {
-                    // Difficulty
+                    Picker("Number of lives", selection: $user.settings.numberOfLives) {
+                        ForEach(1..<11) { numberOfLives in
+                            Text("\(numberOfLives)")
+                                .tag(numberOfLives)
+                        }
+                    }
                 } header: {
                     Text("Game")
-                } footer: {
-                    Text("The harder the difficulty the less lives you get.")
                 }
                 
                 Section {
-                    // Toggle to disable haptics
-                    // Toggle to disable sound effects
+                    Toggle("Haptics", isOn: $user.settings.haptics)
+                    Toggle("Sound effects", isOn: $user.settings.sound)
                 } header: {
                     Text("Effects")
                 }
                 
                 Section {
-                    // About
-                    // Report bugs
-                    // Twitter
+                    LinkComponent(
+                        color: .mayaBlue,
+                        iconName: "info.circle.fill",
+                        text: "About",
+                        url: URL(string: "https://dennistech.io")!
+                    )
+                    
+                    LinkComponent(
+                        color: .atomicTangerine,
+                        iconName: "ant.circle.fill",
+                        text: "Report bugs",
+                        url: URL(string: "mailto:dmartin@dennistech.io")!
+                    )
+                    
+                    LinkComponent(
+                        color: .blueBell,
+                        iconName: "message.circle.fill",
+                        text: "Twitter",
+                        url: URL(string: "https://twitter.com/dennistech_")!
+                    )
                 } header: {
                     Text("Get in touch")
                 }