mirror of
https://github.com/TuringSoftware/CrystalFetch.git
synced 2024-11-10 09:13:03 +08:00
225 lines
8.3 KiB
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()
|
|
}
|
|
}
|