diff --git a/M1necraft.xcodeproj/project.pbxproj b/M1necraft.xcodeproj/project.pbxproj index 5fb71cc..115e671 100644 --- a/M1necraft.xcodeproj/project.pbxproj +++ b/M1necraft.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 647D4E1427B172C600EFC1FE /* ModInstallHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D4E1327B172C600EFC1FE /* ModInstallHelpView.swift */; }; 647D4E1627B17CEF00EFC1FE /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D4E1527B17CEF00EFC1FE /* CloseButton.swift */; }; 647D4E1927B402B400EFC1FE /* Files in Frameworks */ = {isa = PBXBuildFile; productRef = 647D4E1827B402B400EFC1FE /* Files */; }; + 64AEBD5927B8EB1500CD1513 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEBD5827B8EB1500CD1513 /* SettingsView.swift */; }; 64CA5E9D2770497400C73E3D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 64CA5E9C2770497400C73E3D /* Alamofire */; }; 64CA5EA42771C96E00C73E3D /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64CA5EA32771C96E00C73E3D /* Utils.swift */; }; 64CA5EA72771DC7800C73E3D /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 64CA5EA62771DC7800C73E3D /* ZIPFoundation */; }; @@ -75,6 +76,7 @@ 647D4E1127B171C500EFC1FE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 647D4E1327B172C600EFC1FE /* ModInstallHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModInstallHelpView.swift; sourceTree = ""; }; 647D4E1527B17CEF00EFC1FE /* CloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseButton.swift; sourceTree = ""; }; + 64AEBD5827B8EB1500CD1513 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 64CA5EA32771C96E00C73E3D /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 64CA5EA82771EE0900C73E3D /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 64D0F30F27AAB11500713A33 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; @@ -138,6 +140,7 @@ children = ( 64243AB7276D9DED009826F8 /* M1necraftApp.swift */, 64243AB9276D9DED009826F8 /* ContentView.swift */, + 64AEBD5827B8EB1500CD1513 /* SettingsView.swift */, 64D0F30F27AAB11500713A33 /* SetupView.swift */, 64EAA53E276F546000A032F1 /* Constants.swift */, 64EAA53A276F448E00A032F1 /* LoginView.swift */, @@ -333,6 +336,7 @@ 647D4E0727B0F9A800EFC1FE /* GameVersionListView.swift in Sources */, 647D4DCD27AFCA2000EFC1FE /* AppDelegate.swift in Sources */, 647D4DCB27AFC6C900EFC1FE /* Main.swift in Sources */, + 64AEBD5927B8EB1500CD1513 /* SettingsView.swift in Sources */, 647D4E0927B0F9B600EFC1FE /* GameVersionListItem.swift in Sources */, 64EAA53F276F546000A032F1 /* Constants.swift in Sources */, 64EAA53B276F448E00A032F1 /* LoginView.swift in Sources */, diff --git a/M1necraft/ContentView.swift b/M1necraft/ContentView.swift index 2824fe4..26fda67 100644 --- a/M1necraft/ContentView.swift +++ b/M1necraft/ContentView.swift @@ -20,7 +20,7 @@ struct ContentView: View { case .settingUp: SetupView(contentViewModel: m) case .completed: - InstallView() + InstallView(contentViewModel: m) case .failed(let error): VStack { Text(error.localizedDescription) @@ -33,7 +33,7 @@ struct ContentView: View { Image(systemName: "info.circle") } Button { - print("settings button pressed") + m.activeSheet = .settings } label: { Image(systemName: "gearshape") } @@ -43,6 +43,8 @@ struct ContentView: View { } else { m.setupStatus = .settingUp } + }.sheet(item: $m.activeSheet) { + $0.modalView(viewModel: m) } } } @@ -50,6 +52,7 @@ struct ContentView: View { extension ContentView { @MainActor class ViewModel: ObservableObject { @Published var setupStatus: SetupStatus = .loading + @Published var activeSheet: Sheet? } } diff --git a/M1necraft/GameVersionListItem.swift b/M1necraft/GameVersionListItem.swift index 06faebb..054da3a 100644 --- a/M1necraft/GameVersionListItem.swift +++ b/M1necraft/GameVersionListItem.swift @@ -8,6 +8,7 @@ import SwiftUI struct GameVersionListItem: View { + @ObservedObject var contentViewModel: ContentView.ViewModel @ObservedObject var m: InstallView.ViewModel @State var version: M1necraftVersion @State var showModInstallHelpModal = false @@ -40,10 +41,10 @@ struct GameVersionListItem: View { case .installed: actionMenuCompat { Button("Install Fabric", action: { - m.activeSheet = .modInstallHelp + contentViewModel.activeSheet = .modInstallHelp }) Button("Install Forge", action: { - m.activeSheet = .modInstallHelp + contentViewModel.activeSheet = .modInstallHelp }) } Button("OPEN", action: { @@ -113,6 +114,6 @@ struct GameVersionListItem: View { struct GameVersionListItem_Previews: PreviewProvider { static var previews: some View { - GameVersionListItem(m: InstallView.ViewModel(), version: M1necraftVersion(name: "1.16.5"), selected: false) + GameVersionListItem(contentViewModel: ContentView.ViewModel(), m: InstallView.ViewModel(), version: M1necraftVersion(name: "1.16.5"), selected: false) } } diff --git a/M1necraft/GameVersionListView.swift b/M1necraft/GameVersionListView.swift index 16a8d94..d238430 100644 --- a/M1necraft/GameVersionListView.swift +++ b/M1necraft/GameVersionListView.swift @@ -8,11 +8,12 @@ import SwiftUI struct GameVersionListView: View { + @ObservedObject var contentViewModel: ContentView.ViewModel @ObservedObject var m: InstallView.ViewModel var body: some View { List(m.versions, selection: $m.selectedMinecraftVersionID) { - GameVersionListItem(m: m, version: $0, selected: m.selectedMinecraftVersionID == $0.id) + GameVersionListItem(contentViewModel: contentViewModel, m: m, version: $0, selected: m.selectedMinecraftVersionID == $0.id) }.onAppear { m.refreshVersions() // set all version states } @@ -21,6 +22,6 @@ struct GameVersionListView: View { struct GameVersionListView_Previews: PreviewProvider { static var previews: some View { - GameVersionListView(m: InstallView.ViewModel()) + GameVersionListView(contentViewModel: ContentView.ViewModel(), m: InstallView.ViewModel()) } } diff --git a/M1necraft/InstallView.swift b/M1necraft/InstallView.swift index dda64c5..797b418 100644 --- a/M1necraft/InstallView.swift +++ b/M1necraft/InstallView.swift @@ -9,14 +9,14 @@ import SwiftUI import Files struct InstallView: View { + @ObservedObject var contentViewModel: ContentView.ViewModel @StateObject var m = ViewModel() let versionRefreshTimer = Timer.publish(every: 5, on: .main, in: .common).autoconnect() var body: some View { VStack { - GameVersionListView(m: m) + GameVersionListView(contentViewModel: contentViewModel, m: m) } - .sheet(item: $m.activeSheet) { $0.modalView(viewModel: m) } .onReceive(versionRefreshTimer, perform: { _ in // TODO: listen to filesystem changes in mclDir instead of refreshing every x secs m.refreshVersions() @@ -28,7 +28,6 @@ extension InstallView { @MainActor class ViewModel: ObservableObject { @Published var versions = supportedVersions @Published var selectedMinecraftVersionID: UUID? - @Published var activeSheet: Sheet? let jsonEncoder = JSONEncoder() let fileManager = FileManager() @@ -105,6 +104,6 @@ extension InstallView { struct InstallView_Previews: PreviewProvider { static var previews: some View { - InstallView() + InstallView(contentViewModel: ContentView.ViewModel()) } } diff --git a/M1necraft/M1necraftApp.swift b/M1necraft/M1necraftApp.swift index 0d28ed7..bb68da2 100644 --- a/M1necraft/M1necraftApp.swift +++ b/M1necraft/M1necraftApp.swift @@ -27,5 +27,8 @@ struct M1necraftApp: App { } #endif } + Settings { + SettingsView(m: m) + } } } diff --git a/M1necraft/ModInstallHelpView.swift b/M1necraft/ModInstallHelpView.swift index 4eea61e..1bf0b5c 100644 --- a/M1necraft/ModInstallHelpView.swift +++ b/M1necraft/ModInstallHelpView.swift @@ -8,7 +8,7 @@ import SwiftUI struct ModInstallHelpView: View { - @StateObject var m: InstallView.ViewModel + @StateObject var m: ContentView.ViewModel var body: some View { VStack(alignment: .leading, spacing: 8) { @@ -33,6 +33,6 @@ struct ModInstallHelpView: View { struct ModInstallHelpView_Previews: PreviewProvider { static var previews: some View { - ModInstallHelpView(m: InstallView.ViewModel()) + ModInstallHelpView(m: ContentView.ViewModel()) } } diff --git a/M1necraft/SettingsView.swift b/M1necraft/SettingsView.swift new file mode 100644 index 0000000..18db26b --- /dev/null +++ b/M1necraft/SettingsView.swift @@ -0,0 +1,68 @@ +// +// SettingsView.swift +// M1necraft +// +// Created by Raphael Tang on 13/2/22. +// + +import SwiftUI + +struct SettingsView: View { + @ObservedObject var m: ContentView.ViewModel + var showCloseButton = false + + var body: some View { + VStack(alignment: .leading, spacing: 5) { + if showCloseButton { + HStack { + Spacer() + CloseButton { + m.activeSheet = nil + } + } + } + + Text("Settings") + .font(.title.bold()) + .padding(.bottom, 8) + Form { + Button("Reset all data") { + try! Paths.global.resetDataDir() + try! Paths.global.mclLwjglnatives.folder?.delete() + try! Paths.global.mclLwjglfatJar.file?.delete() + try! Paths.global.mclJre.forEach { url in + try url.value.folder?.delete() + } + + // delete all installed versions + try! supportedVersions.forEach { version in + try version.isInstalledAt?.folder?.delete() + } + + let jsonEncoder = JSONEncoder() + let jsonDecoder = JSONDecoder() + + var launcherProfiles = try! jsonDecoder.decode(MinecraftLauncherProfiles.self, from: try! String(contentsOf: Paths.global.mclLauncherProfiles).data(using: .utf8)!) + launcherProfiles.profiles.forEach { profileItem in + if profileItem.key.hasPrefix("m1necraft") { + launcherProfiles.profiles.removeValue(forKey: profileItem.key) + } + } + + try! String(data: jsonEncoder.encode(launcherProfiles), encoding: .utf8)?.write(to: Paths.global.mclLauncherProfiles, atomically: false, encoding: .utf8) + + m.setupStatus = .settingUp + m.activeSheet = nil + } + } + } + .padding(.all, 20) + .frame(width: 500) + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView(m: ContentView.ViewModel()) + } +} diff --git a/M1necraft/Utils.swift b/M1necraft/Utils.swift index 7aa4c77..6608ef2 100644 --- a/M1necraft/Utils.swift +++ b/M1necraft/Utils.swift @@ -77,7 +77,11 @@ extension Paths { extension URL { var file: File? { do { - return try File(path: self.path) + if FileManager.default.fileExists(atPath: self.path) { + return try File(path: self.path) + } else { + return nil + } } catch { print("Failed to turn URL(\(self.path)) into a File.") if self.hasDirectoryPath { @@ -88,7 +92,11 @@ extension URL { } var folder: Folder? { do { - return try Folder(path: self.path) + if FileManager.default.dirExists(atPath: self) { + return try Folder(path: self.path) + } else { + return nil + } } catch { print("Failed to turn URL(\(self.path)) into a Folder.") if !self.hasDirectoryPath { @@ -207,28 +215,6 @@ struct VisualEffectView: NSViewRepresentable { } } -extension View { - /** - Applies the given transform if the given condition evaluates to `true`. - - Parameters: - - condition: The condition to evaluate. - - transform: The transform to apply to the source `View`. - - Returns: - Either the original `View` or the modified `View` if the condition is `true`. - */ - @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { - if condition { - transform(self) - } else { - self - } - } - - @ViewBuilder func transform(closure: (Self) -> Content) -> some View { - closure(self) - } -} - enum Sheet: Identifiable { case modInstallHelp case settings @@ -238,10 +224,10 @@ enum Sheet: Identifiable { } @ViewBuilder - func modalView(viewModel: InstallView.ViewModel) -> some View { + func modalView(viewModel: ContentView.ViewModel) -> some View { switch self { case .modInstallHelp: ModInstallHelpView(m: viewModel) - case .settings: Text("SettingsView") + case .settings: SettingsView(m: viewModel, showCloseButton: true) } } }