gh-XcodesOrg-XcodesApp/Xcodes/Frontend/InfoPane/PlatformsView.swift
2025-09-17 22:37:19 -05:00

142 lines
4.8 KiB
Swift

//
// PlatformsView.swift
// Xcodes
//
// Created by Matt Kiazyk on 2023-12-18.
//
import Foundation
import SwiftUI
import XcodesKit
struct PlatformsView: View {
@EnvironmentObject var appState: AppState
@AppStorage("selectedRuntimeArchitecture") private var selectedVariant: ArchitectureVariant = .universal
let xcode: Xcode
var body: some View {
let builds = xcode.sdks?.allBuilds()
let runtimes = builds?.flatMap { sdkBuild in
appState.downloadableRuntimes.filter {
$0.sdkBuildUpdate?.contains(sdkBuild) ?? false &&
($0.architectures?.isEmpty ?? true ||
($0.architectures?.isUniversal ?? false && selectedVariant == .universal) ||
($0.architectures?.isAppleSilicon ?? false && selectedVariant == .appleSilicon)
)
}
}
let architectures = Set((runtimes ?? []).flatMap { $0.architectures ?? [] })
VStack {
HStack {
Text("Platforms")
.font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
if !architectures.isEmpty {
Spacer()
Picker("Architecture", selection: $selectedVariant) {
ForEach(ArchitectureVariant.allCases, id: \.self) { arch in
Label(arch.displayString, systemImage: arch.iconName)
.tag(arch)
}
.labelStyle(.trailingIcon)
}
.pickerStyle(.menu)
.menuStyle(.button)
.buttonStyle(.borderless)
.fixedSize()
.labelsHidden()
}
}
ForEach(runtimes ?? [], id: \.identifier) { runtime in
runtimeView(runtime: runtime)
.frame(minWidth: 200)
.padding()
.background(.quinary)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
}
}
.xcodesBackground()
}
@ViewBuilder
func runtimeView(runtime: DownloadableRuntime) -> some View {
VStack(spacing: 10) {
HStack {
runtime.icon()
Text("\(runtime.visibleIdentifier)")
.font(.headline)
ForEach(runtime.architectures ?? [], id: \.self) { architecture in
TagView(text: architecture.displayString)
}
pathIfAvailable(xcode: xcode, runtime: runtime)
if runtime.installState == .notInstalled {
// TODO: Update the downloadableRuntimes with the appropriate installState so we don't have to check path awkwardly
if appState.runtimeInstallPath(xcode: xcode, runtime: runtime) != nil {
EmptyView()
} else {
HStack {
Spacer()
DownloadRuntimeButton(runtime: runtime)
}
}
}
Spacer()
Text(runtime.downloadFileSizeString)
.font(.subheadline)
.frame(width: 70, alignment: .trailing)
}
if case let .installing(installationStep) = runtime.installState {
HStack(alignment: .top, spacing: 5){
RuntimeInstallationStepDetailView(installationStep: installationStep)
.fixedSize(horizontal: false, vertical: true)
Spacer()
CancelRuntimeInstallButton(runtime: runtime)
}
}
}
}
@ViewBuilder
func pathIfAvailable(xcode: Xcode, runtime: DownloadableRuntime) -> some View {
if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) {
Button(action: { appState.reveal(path: path.string) }) {
Image(systemName: "arrow.right.circle.fill")
}
.buttonStyle(PlainButtonStyle())
.help("RevealInFinder")
} else {
EmptyView()
}
}
}
#Preview(XcodePreviewName.allCases[0].rawValue) { makePreviewContent(for: 0) }
private func makePreviewContent(for index: Int) -> some View {
let name = XcodePreviewName.allCases[index]
let runtimes = downloadableRuntimes
return PlatformsView(xcode: xcodeDict[name]!)
.environmentObject({ () -> AppState in
let a = AppState()
a.allXcodes = [xcodeDict[name]!]
a.installedRuntimes = installedRuntimes
a.downloadableRuntimes = runtimes
return a
}())
.frame(width: 300)
.padding()
}