Add raster panel icon family
This commit is contained in:
parent
c81712b781
commit
60cc48a4b7
278 changed files with 1992 additions and 621 deletions
|
|
@ -4,17 +4,26 @@ import SwiftUI
|
|||
#if canImport(AuthenticationServices)
|
||||
import AuthenticationServices
|
||||
#endif
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
public protocol AppIconManaging {
|
||||
var supportsAlternateIcons: Bool { get }
|
||||
var alternateIconName: String? { get }
|
||||
func setAlternateIconName(_ iconName: String?) async throws
|
||||
}
|
||||
|
||||
public struct BurrowView: View {
|
||||
private let appIconManager: (any AppIconManaging)?
|
||||
|
||||
@State private var networkViewModel: NetworkViewModel
|
||||
@State private var accountStore = NetworkAccountStore()
|
||||
@State private var activeSheet: ConfigurationSheet?
|
||||
@State private var didRunAutomation = false
|
||||
#if os(iOS)
|
||||
@State private var selectedAppIconName: String?
|
||||
@State private var appIconError: String?
|
||||
@State private var isSettingAppIcon = false
|
||||
#endif
|
||||
|
||||
public var body: some View {
|
||||
NavigationStack {
|
||||
|
|
@ -56,6 +65,10 @@ public struct BurrowView: View {
|
|||
quickAddSection
|
||||
}
|
||||
|
||||
if showsAppIconSection {
|
||||
appIconSection
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
sectionHeader(
|
||||
title: "Networks",
|
||||
|
|
@ -121,10 +134,12 @@ public struct BurrowView: View {
|
|||
}
|
||||
.onAppear {
|
||||
runAutomationIfNeeded()
|
||||
refreshAppIconSelection()
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
public init(appIconManager: (any AppIconManaging)? = nil) {
|
||||
self.appIconManager = appIconManager
|
||||
_networkViewModel = State(
|
||||
initialValue: NetworkViewModel(
|
||||
socketURLResult: Result { try Constants.socketURL }
|
||||
|
|
@ -143,6 +158,12 @@ public struct BurrowView: View {
|
|||
activeSheet = .tailnet
|
||||
}
|
||||
|
||||
private func refreshAppIconSelection() {
|
||||
#if os(iOS)
|
||||
selectedAppIconName = appIconManager?.alternateIconName
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var quickAddSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
|
|
@ -157,6 +178,81 @@ public struct BurrowView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var appIconSection: some View {
|
||||
#if os(iOS)
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
sectionHeader(title: "App Icon", detail: nil)
|
||||
LazyVGrid(
|
||||
columns: [GridItem(.adaptive(minimum: 86), spacing: 12)],
|
||||
alignment: .leading,
|
||||
spacing: 12
|
||||
) {
|
||||
ForEach(AppIconChoice.allCases) { choice in
|
||||
let isSelected = selectedAppIconName == choice.alternateIconName
|
||||
Button {
|
||||
setAppIcon(choice)
|
||||
} label: {
|
||||
VStack(spacing: 8) {
|
||||
Image(choice.previewAssetName)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(width: 72, height: 72)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||
.stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 3)
|
||||
}
|
||||
.shadow(color: .black.opacity(0.18), radius: 5, y: 2)
|
||||
Text(choice.title)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(isSelected ? Color.accentColor : Color.primary)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.75)
|
||||
}
|
||||
.frame(width: 86, height: 104)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(isSettingAppIcon)
|
||||
.accessibilityLabel(choice.accessibilityLabel)
|
||||
}
|
||||
}
|
||||
if let appIconError {
|
||||
Text(appIconError)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
#else
|
||||
EmptyView()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private func setAppIcon(_ choice: AppIconChoice) {
|
||||
guard let appIconManager, appIconManager.supportsAlternateIcons else {
|
||||
return
|
||||
}
|
||||
guard selectedAppIconName != choice.alternateIconName else {
|
||||
return
|
||||
}
|
||||
|
||||
isSettingAppIcon = true
|
||||
appIconError = nil
|
||||
Task { @MainActor in
|
||||
do {
|
||||
try await appIconManager.setAlternateIconName(choice.alternateIconName)
|
||||
selectedAppIconName = appIconManager.alternateIconName
|
||||
} catch {
|
||||
appIconError = error.localizedDescription
|
||||
selectedAppIconName = appIconManager.alternateIconName
|
||||
}
|
||||
isSettingAppIcon = false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ViewBuilder
|
||||
private func sectionHeader(title: String, detail: String?) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
|
|
@ -193,6 +289,80 @@ public struct BurrowView: View {
|
|||
true
|
||||
#endif
|
||||
}
|
||||
|
||||
private var showsAppIconSection: Bool {
|
||||
#if os(iOS)
|
||||
appIconManager?.supportsAlternateIcons == true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private enum AppIconChoice: CaseIterable, Identifiable {
|
||||
case guardian
|
||||
case field
|
||||
case burrow
|
||||
case sentinel
|
||||
case stride
|
||||
case post
|
||||
|
||||
var id: String { previewAssetName }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .guardian:
|
||||
"Guardian"
|
||||
case .field:
|
||||
"Field"
|
||||
case .burrow:
|
||||
"Burrow"
|
||||
case .sentinel:
|
||||
"Sentinel"
|
||||
case .stride:
|
||||
"Stride"
|
||||
case .post:
|
||||
"Post"
|
||||
}
|
||||
}
|
||||
|
||||
var previewAssetName: String {
|
||||
switch self {
|
||||
case .guardian:
|
||||
"BurrowPanel01Preview"
|
||||
case .field:
|
||||
"BurrowPanel02Preview"
|
||||
case .burrow:
|
||||
"BurrowPanel03Preview"
|
||||
case .sentinel:
|
||||
"BurrowPanel04Preview"
|
||||
case .stride:
|
||||
"BurrowPanel05Preview"
|
||||
case .post:
|
||||
"BurrowPanel06Preview"
|
||||
}
|
||||
}
|
||||
|
||||
var alternateIconName: String? {
|
||||
switch self {
|
||||
case .guardian:
|
||||
nil
|
||||
case .field:
|
||||
"BurrowPanel02"
|
||||
case .burrow:
|
||||
"BurrowPanel03"
|
||||
case .sentinel:
|
||||
"BurrowPanel04"
|
||||
case .stride:
|
||||
"BurrowPanel05"
|
||||
case .post:
|
||||
"BurrowPanel06"
|
||||
}
|
||||
}
|
||||
|
||||
var accessibilityLabel: String {
|
||||
"\(title) app icon"
|
||||
}
|
||||
}
|
||||
|
||||
private enum ConfigurationSheet: String, CaseIterable, Identifiable {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue