CrystalFetch/Source/SimpleContentView.swift

225 lines
8.3 KiB
Swift

//
// Copyright © 2023 Turing Software, LLC. 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
struct SimpleContentView: View {
@EnvironmentObject private var worker: Worker
@AppStorage("ShowAdvancedOptions") private var showAdvancedOptions: Bool = false
@State private var isConfirmCancelShown: Bool = false
@State private var isDownloadCompleted: Bool = false
@State private var isWindows10: Bool = false
@State private var selected: SelectedTuple = .default
@State private var selectedBuild: ESDCatalog.File?
@State private var selectedEula: SelectedEULA?
@State private var languages: [DisplayString] = []
@State private var editions: [DisplayString] = []
var body: some View {
VStack {
Form {
Picker("Version", selection: $isWindows10) {
Text("Windows® 11").tag(false)
Text("Windows® 10").tag(true)
}.pickerStyle(.radioGroup)
Picker("Architecture", selection: $selected.architecture) {
if hasArchitecture("ARM64") {
Text("Apple Silicon").tag("ARM64")
}
if hasArchitecture("x64") {
Text("Intel x64").tag("x64")
}
if hasArchitecture("x86") {
Text("Intel x86").tag("x86")
}
}.pickerStyle(.radioGroup)
Picker("Language", selection: $selected.language) {
ForEach(languages) { language in
Text(language.display).tag(language.id)
}
}
Picker("Edition", selection: $selected.edition) {
ForEach(editions) { edition in
Text(edition.display).tag(edition.id)
}
}
if isWindows10 && selected.architecture == "ARM64" {
Text("Note: This build does not work for virtualization on Apple Silicon.")
}
}.disabled(worker.isBusy)
.onChange(of: isWindows10) { newValue in
worker.refreshEsdCatalog(windows10: newValue)
}
.onChange(of: worker.esdCatalog) { _ in
refreshList()
}
.onChange(of: selected) { _ in
refreshList()
}
Spacer()
HStack {
// SwiftUI BUG: ProgressView cannot go to indeterminate mode and back
if let progress = worker.progress {
ProgressView(value: progress) {
progressLabel
} currentValueLabel: {
Text(worker.progressStatus ?? "")
}
} else {
ProgressView(value: nil as Float?) {
progressLabel
} currentValueLabel: {
Text(worker.progressStatus ?? "")
}
}
}
HStack {
if showAdvancedOptions {
ShowWindowButtonView(id: "UUPDump") {
Text("All builds…")
}.disabled(worker.isBusy)
.help("Build custom installation for any build through UUP Dump.")
}
Spacer()
if worker.isBusy {
Button(role: .cancel) {
isConfirmCancelShown.toggle()
} label: {
Text("Cancel")
}.keyboardShortcut(.cancelAction)
.confirmationDialog("Are you sure you want to stop the process?", isPresented: $isConfirmCancelShown) {
Button("Stop", role: .destructive) {
worker.stop()
}
Button("Cancel", role: .cancel) {
isConfirmCancelShown = false
}
}
} else {
Button {
if let eula = selectedBuild?.eula {
selectedEula = SelectedEULA(url: URL(string: eula)!)
} else if let selectedBuild = selectedBuild {
worker.download(selectedBuild)
}
} label: {
Text("Download…")
}.disabled(selectedBuild == nil)
}
}
}
.sheet(item: $selectedEula) { eula in
EULAView(url: eula.url) {
if let selectedBuild = selectedBuild {
worker.download(selectedBuild)
}
}
}
.alert(item: $worker.lastSeenError) { lastSeenError in
Alert(title: Text(lastSeenError.message))
}
.padding()
.onAppear {
worker.refreshEsdCatalog()
refreshList()
}
.onChange(of: worker.completedDownloadUrl) { newValue in
if newValue != nil {
isDownloadCompleted = true
}
}
.fileMover(isPresented: $isDownloadCompleted, file: worker.completedDownloadUrl) { result in
switch result {
case .success(let success):
worker.finalize(isoUrl: worker.completedDownloadUrl!, destinationUrl: success)
case .failure(let failure):
worker.lastSeenError = Worker.ErrorMessage(message: failure.localizedDescription)
}
}
}
@ViewBuilder
private var progressLabel: some View {
if let selectedBuild = selectedBuild {
Text(selectedBuild.name).font(.caption)
} else if !worker.isBusy {
Text("No build found.").font(.caption)
}
}
private func hasArchitecture(_ name: String) -> Bool {
worker.esdCatalog.contains(where: { $0.architecture == name })
}
private func refreshList() {
let archFiltered = worker.esdCatalog.filter({ $0.architecture == selected.architecture })
let languagesList = archFiltered.map({ DisplayString(id: $0.languageCode, display: $0.languagePretty )})
languages = Set(languagesList).sorted(using: KeyPathComparator(\.display))
let languageFiltered = archFiltered.filter({ $0.languageCode == selected.language })
let editionsList = languageFiltered.map({ DisplayString(id: $0.edition, display: $0.editionPretty )})
editions = Set(editionsList).sorted(using: KeyPathComparator(\.display))
selectedBuild = languageFiltered.first(where: { $0.edition == selected.edition })
}
}
private struct DisplayString: Identifiable, Hashable, Equatable {
var id: String
var display: String
func hash(into hasher: inout Hasher) {
hasher.combine(display)
}
static func ==(lhs: DisplayString, rhs: DisplayString) -> Bool {
lhs.display == rhs.display
}
}
private struct SelectedTuple: Equatable {
var architecture: String = ""
var language: String = ""
var edition: String = ""
static var `default`: SelectedTuple = {
var tuple = SelectedTuple()
#if arch(arm64)
tuple.architecture = "ARM64"
#elseif arch(x86_64)
tuple.architecture = "x64"
#else
tuple.architecture = "x86"
#endif
tuple.language = Worker.defaultLocale ?? "en-us"
tuple.edition = "Professional"
return tuple
}()
}
private struct SelectedEULA: Identifiable {
let url: URL
var id: URL {
url
}
}
struct SimpleContentView_Previews: PreviewProvider {
static var previews: some View {
SimpleContentView()
}
}