Move the app payload under swift/ while keeping git, package.json, and .smartconfig.json at the repo root. This standardizes the Swift app setup so build, test, run, and watch workflows match the other repos.
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AppSectionTitle: View {
|
||||
let title: String
|
||||
var subtitle: String? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.title3.weight(.semibold))
|
||||
|
||||
if let subtitle, !subtitle.isEmpty {
|
||||
Text(subtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppNotice: View {
|
||||
let message: String
|
||||
var tone: Color = AppTheme.accent
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 10) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.footnote.weight(.bold))
|
||||
.foregroundStyle(tone)
|
||||
Text(message)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.background(tone.opacity(0.08), in: Capsule())
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(AppTheme.border, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppStatusTag: View {
|
||||
let title: String
|
||||
var tone: Color = AppTheme.accent
|
||||
|
||||
var body: some View {
|
||||
Text(title)
|
||||
.font(.caption.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.8)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(tone.opacity(0.12), in: Capsule())
|
||||
.foregroundStyle(tone)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppKeyValue: View {
|
||||
let label: String
|
||||
let value: String
|
||||
var monospaced: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(label.uppercased())
|
||||
.font(.caption2.weight(.bold))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text(value)
|
||||
.font(monospaced ? .subheadline.monospaced() : .subheadline.weight(.semibold))
|
||||
.lineLimit(2)
|
||||
.minimumScaleFactor(0.8)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppMetric: View {
|
||||
let title: String
|
||||
let value: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(title.uppercased())
|
||||
.font(.caption.weight(.bold))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text(value)
|
||||
.font(.title3.weight(.bold))
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppTextSurface: View {
|
||||
let text: String
|
||||
var monospaced: Bool = false
|
||||
|
||||
var body: some View {
|
||||
content
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(16)
|
||||
.background(AppTheme.mutedFill, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.stroke(AppTheme.border, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
#if os(watchOS)
|
||||
Text(text)
|
||||
.font(monospaced ? .body.monospaced() : .body)
|
||||
#else
|
||||
Text(text)
|
||||
.font(monospaced ? .body.monospaced() : .body)
|
||||
.textSelection(.enabled)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct AppTextEditorField: View {
|
||||
@Binding var text: String
|
||||
var minHeight: CGFloat = 120
|
||||
var monospaced: Bool = true
|
||||
|
||||
var body: some View {
|
||||
editor
|
||||
.frame(minHeight: minHeight)
|
||||
.background(AppTheme.mutedFill, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.stroke(AppTheme.border, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var editor: some View {
|
||||
#if os(watchOS)
|
||||
Text(text)
|
||||
.font(monospaced ? .body.monospaced() : .body)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(14)
|
||||
#else
|
||||
TextEditor(text: $text)
|
||||
.font(monospaced ? .body.monospaced() : .body)
|
||||
.scrollContentBackground(.hidden)
|
||||
.autocorrectionDisabled()
|
||||
.padding(14)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct AppActionRow: View {
|
||||
let title: String
|
||||
var subtitle: String? = nil
|
||||
let systemImage: String
|
||||
var tone: Color = AppTheme.accent
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Image(systemName: systemImage)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(tone)
|
||||
.frame(width: 28, height: 28)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
|
||||
if let subtitle, !subtitle.isEmpty {
|
||||
Text(subtitle)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Image(systemName: "arrow.right")
|
||||
.font(.footnote.weight(.bold))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppActionTile: View {
|
||||
let title: String
|
||||
let systemImage: String
|
||||
var tone: Color = AppTheme.accent
|
||||
var isBusy: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
HStack(alignment: .center) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(tone.opacity(0.10))
|
||||
.frame(width: 38, height: 38)
|
||||
|
||||
if isBusy {
|
||||
ProgressView()
|
||||
.tint(tone)
|
||||
} else {
|
||||
Image(systemName: systemImage)
|
||||
.font(.headline.weight(.semibold))
|
||||
.foregroundStyle(tone)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Image(systemName: "arrow.up.right")
|
||||
.font(.caption.weight(.bold))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, minHeight: 92, alignment: .topLeading)
|
||||
.appSurface(radius: 22)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user