BusinessCard/BusinessCardWatch Watch App/Views/WatchContentView.swift

142 lines
4.9 KiB
Swift

import SwiftUI
struct WatchContentView: View {
@Environment(WatchCardStore.self) private var cardStore
@State private var selectedCardID: UUID?
private var displayCards: [WatchCard] {
cardStore.cards
}
private var defaultSelection: UUID? {
cardStore.defaultCardID ?? displayCards.first?.id
}
var body: some View {
if displayCards.isEmpty {
WatchEmptyStateView()
} else if displayCards.count == 1 {
WatchQRCodeCardView(card: displayCards[0])
} else {
TabView(selection: Binding(
get: { selectedCardID ?? defaultSelection ?? displayCards[0].id },
set: { selectedCardID = $0 }
)) {
ForEach(displayCards) { card in
WatchQRCodeCardView(card: card)
.tag(card.id)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
}
}
private struct WatchEmptyStateView: View {
var body: some View {
VStack(spacing: WatchDesign.Spacing.medium) {
Image(systemName: "rectangle.stack")
.font(.title)
.foregroundStyle(Color.WatchPalette.muted)
Text("No Cards")
.font(.headline)
.foregroundStyle(Color.WatchPalette.text)
Text("Open the iPhone app to create cards")
.font(.caption)
.foregroundStyle(Color.WatchPalette.muted)
.multilineTextAlignment(.center)
}
.padding(WatchDesign.Spacing.large)
}
}
private enum WatchQRMode: String, CaseIterable {
case vCard = "vCard"
case appClip = "App Clip"
}
private struct WatchQRCodeCardView: View {
let card: WatchCard
@State private var selectedQRIndex = 0
private var hasAppClipQR: Bool { card.appClipQRCodeImage != nil }
private var qrPages: [(WatchQRMode, Image?)] {
var pages: [(WatchQRMode, Image?)] = [(.vCard, card.qrCodeImage)]
if hasAppClipQR {
pages.append((.appClip, card.appClipQRCodeImage))
}
return pages
}
var body: some View {
VStack(spacing: WatchDesign.Spacing.small) {
if qrPages.count > 1 {
// Swipeable QR codes (vCard App Clip) - no buttons, just swipe
TabView(selection: $selectedQRIndex) {
ForEach(Array(qrPages.enumerated()), id: \.offset) { index, item in
qrCodeContent(image: item.1)
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(height: WatchDesign.Size.qrSize + WatchDesign.Spacing.medium * 2)
} else {
// Single QR code
qrCodeContent(image: card.qrCodeImage ?? card.appClipQRCodeImage)
}
VStack(spacing: WatchDesign.Spacing.extraSmall) {
Text(card.fullName)
.font(.headline)
.foregroundStyle(Color.WatchPalette.text)
.lineLimit(1)
Text(card.role)
.font(.caption)
.foregroundStyle(Color.WatchPalette.muted)
.lineLimit(1)
}
}
.padding(WatchDesign.Spacing.medium)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.WatchPalette.background)
.accessibilityElement(children: .ignore)
.accessibilityLabel(String(localized: "QR code"))
.accessibilityValue("\(card.fullName), \(card.role)")
.accessibilityHint(hasAppClipQR ? String(localized: "Swipe left or right to switch QR code type") : "")
}
@ViewBuilder
private func qrCodeContent(image: Image?) -> some View {
if let image {
image
.resizable()
.interpolation(.none)
.scaledToFit()
.frame(width: WatchDesign.Size.qrSize, height: WatchDesign.Size.qrSize)
.padding(WatchDesign.Spacing.small)
.background(Color.WatchPalette.card)
.clipShape(.rect(cornerRadius: WatchDesign.CornerRadius.large))
} else {
VStack(spacing: WatchDesign.Spacing.small) {
Image(systemName: "qrcode")
.font(.largeTitle)
.foregroundStyle(Color.WatchPalette.muted)
Text("Sync from iPhone")
.font(.caption2)
.foregroundStyle(Color.WatchPalette.muted)
}
.frame(width: WatchDesign.Size.qrSize, height: WatchDesign.Size.qrSize)
.background(Color.WatchPalette.card)
.clipShape(.rect(cornerRadius: WatchDesign.CornerRadius.large))
}
}
}
#Preview {
WatchContentView()
.environment(WatchCardStore())
}