VMWizardView.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. //
  2. // Copyright © 2021 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import SwiftUI
  17. struct VMWizardView: View {
  18. @StateObject var wizardState = VMWizardState()
  19. @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
  20. var body: some View {
  21. if #available(iOS 16, visionOS 1.0, *) {
  22. WizardNavigationView(wizardState: wizardState) {
  23. presentationMode.wrappedValue.dismiss()
  24. }
  25. } else {
  26. NavigationView {
  27. WizardWrapper(page: .start, wizardState: wizardState) {
  28. presentationMode.wrappedValue.dismiss()
  29. }
  30. }
  31. .navigationViewStyle(.stack)
  32. .alert(item: $wizardState.alertMessage) { msg in
  33. Alert(title: Text(msg.message))
  34. }
  35. }
  36. }
  37. }
  38. fileprivate struct WizardToolbar: ViewModifier {
  39. @ObservedObject var wizardState: VMWizardState
  40. let onDismiss: () -> Void
  41. @EnvironmentObject private var data: UTMData
  42. func body(content: Content) -> some View {
  43. content.toolbar {
  44. ToolbarItem(placement: .navigationBarLeading) {
  45. if wizardState.currentPage == .start {
  46. Button("Cancel") {
  47. onDismiss()
  48. }
  49. }
  50. }
  51. ToolbarItem(placement: .navigationBarTrailing) {
  52. if wizardState.hasNextButton {
  53. Button("Continue") {
  54. wizardState.next()
  55. }
  56. } else if wizardState.currentPage == .summary {
  57. Button("Save") {
  58. onDismiss()
  59. data.busyWorkAsync {
  60. let config = try await wizardState.generateConfig()
  61. if let qemuConfig = config as? UTMQemuConfiguration {
  62. let vm = try await data.create(config: qemuConfig)
  63. if #available(iOS 15, *) {
  64. // This is broken on iOS 14
  65. await MainActor.run {
  66. if wizardState.isGuestToolsInstallRequested {
  67. NotificationCenter.default.post(name: NSNotification.InstallGuestTools, object: vm.wrapped!)
  68. }
  69. }
  70. }
  71. } else {
  72. fatalError("Invalid configuration type.")
  73. }
  74. if await wizardState.isOpenSettingsAfterCreation {
  75. await data.showSettingsForCurrentVM()
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }
  82. }
  83. }
  84. @available(iOS, deprecated: 17, message: "Use WizardViewWrapper")
  85. @available(visionOS, deprecated: 1, message: "Use WizardViewWrapper")
  86. fileprivate struct WizardWrapper: View {
  87. let page: VMWizardPage
  88. @ObservedObject var wizardState: VMWizardState
  89. @State private var nextPage: VMWizardPage?
  90. let onDismiss: () -> Void
  91. @EnvironmentObject private var data: UTMData
  92. var body: some View {
  93. VStack {
  94. WizardViewWrapper(page: page, wizardState: wizardState)
  95. NavigationLink(destination: WizardWrapper(page: .start, wizardState: wizardState, onDismiss: onDismiss), tag: .start, selection: $nextPage) {}
  96. NavigationLink(destination: WizardWrapper(page: .operatingSystem, wizardState: wizardState, onDismiss: onDismiss), tag: .operatingSystem, selection: $nextPage) {}
  97. NavigationLink(destination: WizardWrapper(page: .linuxBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .linuxBoot, selection: $nextPage) {}
  98. NavigationLink(destination: WizardWrapper(page: .windowsBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .windowsBoot, selection: $nextPage) {}
  99. NavigationLink(destination: WizardWrapper(page: .classicMacOSBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .classicMacOSBoot, selection: $nextPage) {}
  100. NavigationLink(destination: WizardWrapper(page: .otherBoot, wizardState: wizardState, onDismiss: onDismiss), tag: .otherBoot, selection: $nextPage) {}
  101. NavigationLink(destination: WizardWrapper(page: .hardware, wizardState: wizardState, onDismiss: onDismiss), tag: .hardware, selection: $nextPage) {}
  102. NavigationLink(destination: WizardWrapper(page: .drives, wizardState: wizardState, onDismiss: onDismiss), tag: .drives, selection: $nextPage) {}
  103. NavigationLink(destination: WizardWrapper(page: .sharing, wizardState: wizardState, onDismiss: onDismiss), tag: .sharing, selection: $nextPage) {}
  104. NavigationLink(destination: WizardWrapper(page: .summary, wizardState: wizardState, onDismiss: onDismiss), tag: .summary, selection: $nextPage) {}
  105. }
  106. .listStyle(.insetGrouped) // needed for iOS 14
  107. .textFieldStyle(.roundedBorder)
  108. .modifier(WizardToolbar(wizardState: wizardState, onDismiss: onDismiss))
  109. .onChange(of: nextPage) { newPage in
  110. if newPage == nil {
  111. wizardState.currentPage = page
  112. wizardState.nextPageBinding = $nextPage
  113. }
  114. }
  115. .onAppear {
  116. wizardState.currentPage = page
  117. wizardState.nextPageBinding = $nextPage
  118. }
  119. .disabled(wizardState.isBusy)
  120. }
  121. }
  122. @available(iOS 16, visionOS 1.0, *)
  123. fileprivate struct WizardNavigationView: View {
  124. @StateObject var wizardState = VMWizardState()
  125. let onDismiss: () -> Void
  126. @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
  127. @State private var navigationPath: NavigationPath = .init()
  128. @State private var previousPage: VMWizardPage?
  129. @State private var isAlertShown: Bool = false
  130. var body: some View {
  131. NavigationStack(path: $wizardState.pageHistory) {
  132. WizardViewWrapper(page: .start, wizardState: wizardState)
  133. .modifier(WizardToolbar(wizardState: wizardState, onDismiss: onDismiss))
  134. .navigationDestination(for: VMWizardPage.self) { page in
  135. WizardViewWrapper(page: page, wizardState: wizardState)
  136. .modifier(WizardToolbar(wizardState: wizardState, onDismiss: onDismiss))
  137. }
  138. .textFieldStyle(.roundedBorder)
  139. .disabled(wizardState.isBusy)
  140. }
  141. .alert("Error", isPresented: $isAlertShown) {
  142. Button("OK", role: .cancel) {
  143. wizardState.alertMessage = nil
  144. }
  145. } message: {
  146. Text(wizardState.alertMessage?.message ?? "")
  147. }
  148. .onChange(of: wizardState.alertMessage?.message) { newValue in
  149. isAlertShown = newValue != nil
  150. }
  151. }
  152. }
  153. fileprivate struct WizardViewWrapper: View {
  154. let page: VMWizardPage
  155. @ObservedObject var wizardState: VMWizardState
  156. var body: some View {
  157. switch page {
  158. case .start:
  159. #if WITH_QEMU_TCI
  160. VMWizardStartViewTCI(wizardState: wizardState)
  161. #else
  162. VMWizardStartView(wizardState: wizardState)
  163. #endif
  164. case .operatingSystem:
  165. VMWizardOSView(wizardState: wizardState)
  166. case .macOSBoot:
  167. EmptyView()
  168. case .linuxBoot:
  169. VMWizardOSLinuxView(wizardState: wizardState)
  170. case .windowsBoot:
  171. VMWizardOSWindowsView(wizardState: wizardState)
  172. case .otherBoot:
  173. VMWizardOSOtherView(wizardState: wizardState)
  174. case .classicMacOSBoot:
  175. VMWizardOSClassicMacView(wizardState: wizardState)
  176. case .hardware:
  177. VMWizardHardwareView(wizardState: wizardState)
  178. case .drives:
  179. VMWizardDrivesView(wizardState: wizardState)
  180. case .sharing:
  181. VMWizardSharingView(wizardState: wizardState)
  182. case .summary:
  183. VMWizardSummaryView(wizardState: wizardState)
  184. }
  185. }
  186. }
  187. struct VMWizardView_Previews: PreviewProvider {
  188. static var previews: some View {
  189. VMWizardView()
  190. }
  191. }