BusinessCard/BusinessCard/Views/ShareCardView.swift

331 lines
12 KiB
Swift

import SwiftUI
import Bedrock
import SwiftData
struct ShareCardView: View {
@Environment(AppState.self) private var appState
@State private var showingWalletAlert = false
@State private var showingNfcAlert = false
@State private var showingContactSheet = false
@State private var shareOffline = false
@State private var recipientName = ""
@State private var recipientRole = ""
@State private var recipientCompany = ""
var body: some View {
NavigationStack {
ZStack {
// Dark background
Color.ShareSheet.background
.ignoresSafeArea()
ScrollView {
VStack(spacing: Design.Spacing.xLarge) {
if let card = appState.cardStore.selectedCard {
// QR Code section
QRCodeSection(card: card)
// Share options
ShareOptionsSection(
card: card,
shareLinkService: appState.shareLinkService,
shareOffline: $shareOffline,
showWallet: { showingWalletAlert = true },
showNfc: { showingNfcAlert = true }
)
// Track share
TrackShareSection { showingContactSheet = true }
} else {
EmptyShareState()
}
}
.padding(.horizontal, Design.Spacing.large)
.padding(.vertical, Design.Spacing.xLarge)
}
}
.navigationTitle(String.localized("Send Your Card"))
.navigationBarTitleDisplayMode(.inline)
.toolbarColorScheme(.dark, for: .navigationBar)
.toolbarBackground(Color.ShareSheet.background, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.alert(String.localized("Apple Wallet"), isPresented: $showingWalletAlert) {
Button(String.localized("OK")) { }
} message: {
Text("Wallet export is coming soon. We'll let you know as soon as it's ready.")
}
.alert(String.localized("NFC Sharing"), isPresented: $showingNfcAlert) {
Button(String.localized("OK")) { }
} message: {
Text("Hold your phone near another device to share instantly. NFC setup is on the way.")
}
.sheet(isPresented: $showingContactSheet) {
RecordContactSheet(
recipientName: $recipientName,
recipientRole: $recipientRole,
recipientCompany: $recipientCompany
) {
saveContact()
}
}
}
}
private func saveContact() {
guard !recipientName.isEmpty, let card = appState.cardStore.selectedCard else { return }
appState.contactsStore.recordShare(
for: recipientName,
role: recipientRole,
company: recipientCompany,
cardLabel: card.label
)
recipientName = ""
recipientRole = ""
recipientCompany = ""
}
}
// MARK: - QR Code Section
private struct QRCodeSection: View {
let card: BusinessCard
var body: some View {
VStack(spacing: Design.Spacing.large) {
// QR Code
QRCodeView(payload: card.vCardPayload)
.frame(width: Design.CardSize.qrSizeLarge, height: Design.CardSize.qrSizeLarge)
.padding(Design.Spacing.large)
.background(Color.white)
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
// Instruction text
Text("Point your camera at the QR code to receive the card")
.font(.subheadline)
.foregroundStyle(Color.ShareSheet.secondaryText)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
.padding(Design.Spacing.xLarge)
.background(Color.ShareSheet.cardBackground)
.clipShape(.rect(cornerRadius: Design.CornerRadius.xLarge))
}
}
// MARK: - Share Options Section
private struct ShareOptionsSection: View {
let card: BusinessCard
let shareLinkService: ShareLinkProviding
@Binding var shareOffline: Bool
let showWallet: () -> Void
let showNfc: () -> Void
var body: some View {
VStack(spacing: 0) {
// Offline toggle
ShareOfflineToggle(isOn: $shareOffline)
Divider()
.overlay(Color.ShareSheet.rowBackground)
// Copy link
ShareOptionButton.share(
title: String.localized("Copy link"),
systemImage: "doc.on.doc",
item: shareLinkService.shareURL(for: card)
)
Divider()
.overlay(Color.ShareSheet.rowBackground)
// Message options group
VStack(spacing: 0) {
ShareOptionButton.link(
title: String.localized("Text your card"),
systemImage: "message.fill",
url: shareLinkService.smsURL(for: card)
)
Divider()
.overlay(Color.ShareSheet.rowBackground)
ShareOptionButton.link(
title: String.localized("Email your card"),
systemImage: "envelope.fill",
url: shareLinkService.emailURL(for: card)
)
Divider()
.overlay(Color.ShareSheet.rowBackground)
ShareOptionButton.link(
title: String.localized("Send via WhatsApp"),
systemImage: "ellipsis.message.fill",
iconColor: Color.Social.whatsapp,
url: shareLinkService.whatsappURL(for: card)
)
Divider()
.overlay(Color.ShareSheet.rowBackground)
ShareOptionButton.link(
title: String.localized("Send via LinkedIn"),
systemImage: "person.2.fill",
iconColor: Color.Social.linkedIn,
url: shareLinkService.linkedInURL(for: card)
)
Divider()
.overlay(Color.ShareSheet.rowBackground)
ShareOptionButton.action(
title: String.localized("Send another way"),
systemImage: "ellipsis",
action: {}
)
}
}
.background(Color.ShareSheet.cardBackground)
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
}
}
// MARK: - Share Offline Toggle
private struct ShareOfflineToggle: View {
@Binding var isOn: Bool
var body: some View {
Toggle(isOn: $isOn) {
HStack(spacing: Design.Spacing.medium) {
Image(systemName: "wifi.slash")
.foregroundStyle(Color.ShareSheet.secondaryText)
Text("Share card offline")
.foregroundStyle(Color.ShareSheet.text)
}
}
.tint(Color.Accent.red)
.padding(.horizontal, Design.Spacing.large)
.padding(.vertical, Design.Spacing.medium)
}
}
// MARK: - Track Share Section
private struct TrackShareSection: View {
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: Design.Spacing.medium) {
Image(systemName: "person.badge.plus")
.font(.title3)
.foregroundStyle(Color.Accent.red)
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("Track this share")
.font(.headline)
.foregroundStyle(Color.ShareSheet.text)
Text("Record who received your card")
.font(.caption)
.foregroundStyle(Color.ShareSheet.secondaryText)
}
Spacer()
Image(systemName: "chevron.right")
.foregroundStyle(Color.ShareSheet.secondaryText)
}
.padding(Design.Spacing.large)
.background(Color.ShareSheet.cardBackground)
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
}
.buttonStyle(.plain)
.accessibilityHint(String.localized("Opens a form to record who you shared your card with"))
}
}
// MARK: - Empty State
private struct EmptyShareState: View {
var body: some View {
VStack(spacing: Design.Spacing.large) {
Image(systemName: "rectangle.on.rectangle.slash")
.font(.system(size: Design.BaseFontSize.display))
.foregroundStyle(Color.ShareSheet.secondaryText)
Text("No card selected")
.font(.headline)
.foregroundStyle(Color.ShareSheet.text)
Text("Choose a card in the My Cards tab to start sharing.")
.font(.subheadline)
.foregroundStyle(Color.ShareSheet.secondaryText)
.multilineTextAlignment(.center)
}
.padding(Design.Spacing.xLarge)
}
}
// MARK: - Share Option Button
private enum ShareOptionButton {
static func link(
title: String,
systemImage: String,
iconColor: Color = Color.ShareSheet.secondaryText,
url: URL
) -> some View {
Link(destination: url) {
RowContent(title: title, systemImage: systemImage, iconColor: iconColor)
}
.buttonStyle(.plain)
}
static func share(title: String, systemImage: String, item: URL) -> some View {
ShareLink(item: item) {
RowContent(title: title, systemImage: systemImage, iconColor: Color.ShareSheet.secondaryText)
}
.buttonStyle(.plain)
}
static func action(title: String, systemImage: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
RowContent(title: title, systemImage: systemImage, iconColor: Color.ShareSheet.secondaryText)
}
.buttonStyle(.plain)
}
}
private struct RowContent: View {
let title: String
let systemImage: String
let iconColor: Color
var body: some View {
HStack(spacing: Design.Spacing.medium) {
Image(systemName: systemImage)
.font(.body)
.foregroundStyle(iconColor)
.frame(width: Design.Spacing.xLarge)
Text(title)
.foregroundStyle(Color.ShareSheet.text)
Spacer()
}
.padding(.horizontal, Design.Spacing.large)
.padding(.vertical, Design.Spacing.medium)
.contentShape(.rect)
}
}
// MARK: - Preview
#Preview {
ShareCardView()
.environment(AppState(modelContext: try! ModelContainer(for: BusinessCard.self, Contact.self).mainContext))
}