# HG changeset patch # User Dennis C. M. # Date 1665161438 -7200 # Node ID 3540c7efc216ea658648fee3f499a9ab072dda51 # Parent e09959b4e4a8cba523981289335648a7addaa160 implement UserSettings diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz.xcodeproj/project.pbxproj --- 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 = ""; }; 952E41E828DC521200198643 /* GameAlertsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameAlertsModifier.swift; sourceTree = ""; }; 952E41EC28DC658900198643 /* SettingsModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModalView.swift; sourceTree = ""; }; - 952E41EE28DC692200198643 /* PlaySound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaySound.swift; sourceTree = ""; }; 952E41F028DC6F6D00198643 /* correctAnswer.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = correctAnswer.wav; sourceTree = ""; }; 952E41F128DC6F6E00198643 /* wrongAnswer.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = wrongAnswer.wav; sourceTree = ""; }; 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 = ""; }; 956273E928CB2E98008DC094 /* FlagImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagImage.swift; sourceTree = ""; }; 9590359428E098FF00B24560 /* ProfileModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModalView.swift; sourceTree = ""; }; + 95919DB528F076BF00F21F8F /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 95919DB728F079D100F21F8F /* UserSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsModel.swift; sourceTree = ""; }; + 95919DBB28F08D0600F21F8F /* LinkComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkComponent.swift; sourceTree = ""; }; 95AE8D5628C8750E0067F219 /* Load.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Load.swift; sourceTree = ""; }; 95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = ""; }; 95BC392C28EC42570049AB49 /* CityMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMap.swift; sourceTree = ""; }; @@ -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 = ""; @@ -163,6 +167,7 @@ children = ( 951AFAEC28E5657500A4A4BD /* CityModel.swift */, 951AFAEE28E565FE00A4A4BD /* CountryModel.swift */, + 95919DB728F079D100F21F8F /* UserSettingsModel.swift */, ); path = Models; sourceTree = ""; @@ -178,6 +183,7 @@ 95C430F828D0A8E500480D23 /* CustomGradients.swift */, 951D197228D485E000671FAD /* CustomColors.swift */, 95BC392C28EC42570049AB49 /* CityMap.swift */, + 95919DBB28F08D0600F21F8F /* LinkComponent.swift */, ); path = Helpers; sourceTree = ""; @@ -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 */, diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Helpers/LinkComponent.swift --- /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")! + ) + } +} diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Logic/CityGame.swift --- 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 { diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Logic/CountryGame.swift --- 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() } diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Logic/Game.swift --- 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") + } } } } diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Logic/Haptics.swift --- 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) + } + } } diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Logic/PlaySound.swift --- 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") - } - } -} - diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Logic/User.swift --- /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 + } + } + } +} diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/Models/UserSettingsModel.swift --- /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 +} diff -r e09959b4e4a8 -r 3540c7efc216 GeoQuiz/SettingsModalView.swift --- 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") }