Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
80439171bb
commit
2b679b0167
@ -817,10 +817,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Show first-run onboarding again on next app launch" : {
|
||||
"comment" : "A description of the reset onboarding feature.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Social Media" : {
|
||||
|
||||
},
|
||||
|
||||
@ -14,11 +14,20 @@ struct WatchCard: Codable, Identifiable, Hashable {
|
||||
var isDefault: Bool
|
||||
/// Pre-generated QR code PNG data from iOS (CoreImage not available on watchOS)
|
||||
var qrCodeImageData: Data?
|
||||
|
||||
/// Returns a SwiftUI Image from the synced QR code data
|
||||
/// Pre-generated App Clip URL QR code PNG data
|
||||
var appClipQRCodeImageData: Data?
|
||||
|
||||
/// Returns a SwiftUI Image from the synced vCard QR code data
|
||||
var qrCodeImage: Image? {
|
||||
guard let data = qrCodeImageData,
|
||||
let uiImage = UIImage(data: data) else { return nil }
|
||||
return Image(uiImage: uiImage)
|
||||
}
|
||||
|
||||
/// Returns a SwiftUI Image from the synced App Clip QR code data
|
||||
var appClipQRCodeImage: Image? {
|
||||
guard let data = appClipQRCodeImageData,
|
||||
let uiImage = UIImage(data: data) else { return nil }
|
||||
return Image(uiImage: uiImage)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,6 +117,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swipe left or right to switch QR code type" : {
|
||||
"comment" : "A hint text that appears when a user has an App Clip QR code and is viewing a vCard QR code. It instructs the user to swipe to switch between the two types of QR codes.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Sync from iPhone" : {
|
||||
"comment" : "A description of how to sync a watch code from the iPhone app.",
|
||||
"isCommentAutoGenerated" : true
|
||||
|
||||
@ -57,7 +57,8 @@ final class WatchConnectivityService: NSObject {
|
||||
website: syncable.website,
|
||||
location: syncable.location,
|
||||
isDefault: syncable.isDefault,
|
||||
qrCodeImageData: syncable.qrCodeImageData
|
||||
qrCodeImageData: syncable.qrCodeImageData,
|
||||
appClipQRCodeImageData: syncable.appClipQRCodeImageData
|
||||
)
|
||||
}
|
||||
WatchDesign.debugLog("WatchConnectivity: Decoded \(watchCards.count) cards")
|
||||
@ -122,4 +123,5 @@ private struct SyncableCard: Codable, Identifiable {
|
||||
var twitter: String
|
||||
var instagram: String
|
||||
var qrCodeImageData: Data?
|
||||
var appClipQRCodeImageData: Data?
|
||||
}
|
||||
|
||||
@ -2,12 +2,32 @@ 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 let card = cardStore.defaultCard {
|
||||
WatchQRCodeCardView(card: card)
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,43 +52,51 @@ private struct WatchEmptyStateView: View {
|
||||
}
|
||||
}
|
||||
|
||||
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.medium) {
|
||||
if let image = card.qrCodeImage {
|
||||
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 {
|
||||
// Fallback when no QR code synced yet
|
||||
VStack(spacing: WatchDesign.Spacing.small) {
|
||||
Image(systemName: "qrcode")
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Color.WatchPalette.muted)
|
||||
Text("Sync from iPhone")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(Color.WatchPalette.muted)
|
||||
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)
|
||||
}
|
||||
}
|
||||
.frame(width: WatchDesign.Size.qrSize, height: WatchDesign.Size.qrSize)
|
||||
.background(Color.WatchPalette.card)
|
||||
.clipShape(.rect(cornerRadius: WatchDesign.CornerRadius.large))
|
||||
.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)
|
||||
@ -77,6 +105,33 @@ private struct WatchQRCodeCardView: View {
|
||||
.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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user