fix(ios): improve fleet overview and app identity
- Show stable group health in a fixed 2x2 metric grid - Add link-chain state to fleet cards and parse hardware/control/SIM/cellular - Replace the wrapped icon with a full-bleed app icon - Bump the IPA build number
This commit is contained in:
@@ -3,16 +3,27 @@ import SwiftUI
|
||||
public struct AICard<Content: View>: View {
|
||||
private let content: Content
|
||||
private let padding: CGFloat
|
||||
private let emphasis: Bool
|
||||
|
||||
public init(padding: CGFloat = 16, @ViewBuilder content: () -> Content) {
|
||||
public init(
|
||||
padding: CGFloat = 16,
|
||||
emphasis: Bool = false,
|
||||
@ViewBuilder content: () -> Content
|
||||
) {
|
||||
self.padding = padding
|
||||
self.emphasis = emphasis
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
content
|
||||
.padding(padding)
|
||||
.background(AICard.backgroundGradient)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(padding)
|
||||
.background(
|
||||
AICard.backgroundGradient(
|
||||
raisedTop: emphasis
|
||||
)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AITheme.cardRadius, style: .continuous))
|
||||
.shadow(color: .black.opacity(0.07), radius: 18, x: 0, y: 8)
|
||||
.overlay {
|
||||
@@ -22,10 +33,12 @@ public struct AICard<Content: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
private extension AICard {
|
||||
static var backgroundGradient: LinearGradient {
|
||||
extension AICard {
|
||||
fileprivate static func backgroundGradient(raisedTop: Bool) -> LinearGradient {
|
||||
LinearGradient(
|
||||
colors: [AITheme.surfaceRaised, AITheme.surface],
|
||||
colors: raisedTop
|
||||
? [AITheme.surfaceRaised, AITheme.surfaceRaised.opacity(0.94)]
|
||||
: [AITheme.surfaceRaised, AITheme.surface],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
@@ -150,35 +163,49 @@ public struct AIStatCard: View {
|
||||
private let value: String
|
||||
private let detail: String?
|
||||
private let tint: Color
|
||||
private let systemImage: String?
|
||||
|
||||
public init(title: String, value: String, detail: String? = nil, tint: Color) {
|
||||
public init(
|
||||
title: String,
|
||||
value: String,
|
||||
detail: String? = nil,
|
||||
systemImage: String? = nil,
|
||||
tint: Color
|
||||
) {
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.detail = detail
|
||||
self.systemImage = systemImage
|
||||
self.tint = tint
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
AICard(padding: 14) {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(title)
|
||||
.font(.caption.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.85)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
Text(value)
|
||||
.font(.title3.weight(.bold))
|
||||
.foregroundStyle(value == "0" || value.hasPrefix("0/") ? AITheme.muted : tint)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.75)
|
||||
if let detail {
|
||||
Text(detail)
|
||||
.font(.caption)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
.lineLimit(2)
|
||||
AICard(padding: 12) {
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
if let systemImage {
|
||||
Image(systemName: systemImage)
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundStyle(tint)
|
||||
.frame(width: 32, height: 32)
|
||||
.background(tint.opacity(0.10))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(title)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.85)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
Text(value)
|
||||
.font(.headline.weight(.semibold))
|
||||
.foregroundStyle(value == "0" || value.hasPrefix("0/") ? AITheme.muted : tint)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.75)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +473,7 @@ public struct AIKeyValueRow: View {
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
.lineLimit(2)
|
||||
.layoutPriority(1)
|
||||
Spacer(minLength: 12)
|
||||
Text(value)
|
||||
.font(.subheadline.weight(.medium))
|
||||
@@ -455,6 +483,7 @@ public struct AIKeyValueRow: View {
|
||||
.truncationMode(.middle)
|
||||
.minimumScaleFactor(0.85)
|
||||
.textSelection(.enabled)
|
||||
.layoutPriority(2)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
@@ -600,8 +629,7 @@ extension View {
|
||||
}
|
||||
|
||||
func aiBottomActionBar<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
||||
VStack(spacing: 0) {
|
||||
self
|
||||
safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
content()
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 12)
|
||||
|
||||
Reference in New Issue
Block a user