123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- //
- // Copyright © 2020 osy. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- import SwiftUI
- // MARK: - Drives list
- struct VMDrivesSettingsView: View {
- @ObservedObject var config: UTMQemuConfiguration
- @Binding var isCreateDriveShown: Bool
- @Binding var isImportDriveShown: Bool
- @State private var attemptDelete: IndexSet?
- @EnvironmentObject private var data: UTMData
-
- var body: some View {
- ForEach($config.drives) { $drive in
- NavigationLink(
- destination: VMConfigDriveDetailsView(config: $drive, requestDriveDelete: .constant(nil)), label: {
- Label(title: { labelTitle(for: drive) }, icon: { Image(systemName: "externaldrive") })
- })
- }.onDelete { offsets in
- attemptDelete = offsets
- }
- .onMove(perform: moveDrives)
- Button {
- isImportDriveShown.toggle()
- } label: {
- Text("Import Drive…")
- }
- Button {
- isCreateDriveShown.toggle()
- } label: {
- Text("New Drive…")
- }
- .nonbrokenSheet(isPresented: $isCreateDriveShown) {
- CreateDrive(newDrive: UTMQemuConfigurationDrive(forArchitecture: config.system.architecture, target: config.system.target), onDismiss: newDrive)
- }
- .globalFileImporter(isPresented: $isImportDriveShown, allowedContentTypes: [.item], onCompletion: importDrive)
- .actionSheet(item: $attemptDelete) { offsets in
- ActionSheet(title: Text("Confirm Delete"), message: Text("Are you sure you want to permanently delete this disk image?"), buttons: [.cancel(), .destructive(Text("Delete")) {
- deleteDrives(offsets: offsets)
- }])
- }
- }
-
- private func labelTitle(for drive: UTMQemuConfigurationDrive) -> Text {
- if drive.interface == .none && drive.imageName == QEMUPackageFileName.efiVariables.rawValue {
- return Text("EFI Variables", comment: "VMDrivesSettingsView")
- } else {
- return Text("\(drive.interface.prettyValue) Drive", comment: "VMDrivesSettingsView")
- }
- }
-
- private func newDrive(drive: UTMQemuConfigurationDrive) {
- config.drives.append(drive)
- }
-
- private func deleteDrives(offsets: IndexSet) {
- config.drives.remove(atOffsets: offsets)
- }
-
- private func moveDrives(source: IndexSet, destination: Int) {
- config.drives.move(fromOffsets: source, toOffset: destination)
- }
-
- private func importDrive(result: Result<URL, Error>) {
- data.busyWorkAsync {
- switch result {
- case .success(let url):
- await MainActor.run {
- var drive = UTMQemuConfigurationDrive(forArchitecture: config.system.architecture, target: config.system.target, isExternal: false)
- drive.imageURL = url
- config.drives.append(drive)
- }
- break
- case .failure(let err):
- throw err
- }
- }
- }
- }
- // MARK: - Create Drive
- private extension View {
- /// A sheet that isn't broken on older versions.
- ///
- /// On iOS 14 and older, .sheet() breaks the table layout for some reason.
- /// This workarounds it by putting the sheet inside an overlay which does
- /// not affect displaying the sheet at all.
- /// - Parameters:
- /// - isPresented: same as .sheet()
- /// - onDismiss: same as .sheet()
- /// - content: same as .sheet()
- /// - Returns: same as .sheet()
- @ViewBuilder func nonbrokenSheet<Content>(isPresented: Binding<Bool>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping () -> Content) -> some View where Content : View {
- if #available(iOS 15, macOS 12, *) {
- self.sheet(isPresented: isPresented, onDismiss: onDismiss, content: content)
- } else {
- self.overlay(EmptyView().sheet(isPresented: isPresented, onDismiss: onDismiss, content: content))
- }
- }
- }
- private struct CreateDrive: View {
- @State var newDrive: UTMQemuConfigurationDrive
- let onDismiss: (UTMQemuConfigurationDrive) -> Void
- @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
-
- var body: some View {
- NavigationView {
- VMConfigDriveCreateView(config: $newDrive)
- .toolbar {
- ToolbarItem(placement: .cancellationAction) {
- Button("Cancel", action: cancel)
- }
- ToolbarItem(placement: .confirmationAction) {
- Button("Done", action: done)
- }
- }
- }.navigationViewStyle(.stack)
- }
-
- private func cancel() {
- presentationMode.wrappedValue.dismiss()
- }
-
- private func done() {
- presentationMode.wrappedValue.dismiss()
- onDismiss(newDrive)
- }
- }
- // MARK: - Preview
- struct VMConfigDrivesView_Previews: PreviewProvider {
- @StateObject static private var config = UTMQemuConfiguration()
-
- static var previews: some View {
- Group {
- VMDrivesSettingsView(config: config, isCreateDriveShown: .constant(false), isImportDriveShown: .constant(false))
- CreateDrive(newDrive: UTMQemuConfigurationDrive()) { _ in
-
- }
- }.onAppear {
- if config.drives.count == 0 {
- var drive = UTMQemuConfigurationDrive(forArchitecture: .x86_64, target: QEMUTarget_x86_64.pc)
- drive.imageName = "test.img"
- drive.imageType = .disk
- drive.interface = .ide
- config.drives.append(drive)
- drive = UTMQemuConfigurationDrive(forArchitecture: .x86_64, target: QEMUTarget_x86_64.pc)
- drive.imageName = "bios.bin"
- drive.imageType = .bios
- drive.interface = .none
- config.drives.append(drive)
- }
- }
- }
- }
|