342 lines
12 KiB
Swift
342 lines
12 KiB
Swift
import SwiftUI
|
|
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 recipientName = ""
|
|
@State private var recipientRole = ""
|
|
@State private var recipientCompany = ""
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ScrollView {
|
|
VStack(spacing: Design.Spacing.large) {
|
|
Text("Share with anyone")
|
|
.font(.title2)
|
|
.bold()
|
|
.foregroundStyle(Color.Text.primary)
|
|
|
|
if let card = appState.cardStore.selectedCard {
|
|
QRCodeCardView(card: card)
|
|
|
|
ShareOptionsView(
|
|
card: card,
|
|
shareLinkService: appState.shareLinkService,
|
|
showWallet: { showingWalletAlert = true },
|
|
showNfc: { showingNfcAlert = true },
|
|
onShareAction: { showingContactSheet = true }
|
|
)
|
|
|
|
TrackContactButton {
|
|
showingContactSheet = true
|
|
}
|
|
} else {
|
|
EmptyStateView(
|
|
title: String.localized("No card selected"),
|
|
message: String.localized("Choose a card in the My Cards tab to start sharing.")
|
|
)
|
|
}
|
|
}
|
|
.padding(.horizontal, Design.Spacing.large)
|
|
.padding(.vertical, Design.Spacing.xLarge)
|
|
}
|
|
.background(Color.AppBackground.base)
|
|
.navigationTitle(String.localized("Send Work Card"))
|
|
.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
|
|
) {
|
|
if !recipientName.isEmpty, let card = appState.cardStore.selectedCard {
|
|
appState.contactsStore.recordShare(
|
|
for: recipientName,
|
|
role: recipientRole,
|
|
company: recipientCompany,
|
|
cardLabel: card.label
|
|
)
|
|
recipientName = ""
|
|
recipientRole = ""
|
|
recipientCompany = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct TrackContactButton: View {
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack(spacing: Design.Spacing.medium) {
|
|
Image(systemName: "person.badge.plus")
|
|
.foregroundStyle(Color.Accent.red)
|
|
.frame(width: Design.Size.avatarSize, height: Design.Size.avatarSize)
|
|
.background(Color.AppBackground.accent)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
|
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
|
Text("Track this share")
|
|
.font(.headline)
|
|
.foregroundStyle(Color.Text.primary)
|
|
Text("Record who received your card")
|
|
.font(.subheadline)
|
|
.foregroundStyle(Color.Text.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Image(systemName: "chevron.right")
|
|
.foregroundStyle(Color.Text.secondary)
|
|
}
|
|
.padding(Design.Spacing.large)
|
|
.background(Color.AppBackground.elevated)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityHint(String.localized("Opens a form to record who you shared your card with"))
|
|
}
|
|
}
|
|
|
|
private struct RecordContactSheet: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
@Binding var recipientName: String
|
|
@Binding var recipientRole: String
|
|
@Binding var recipientCompany: String
|
|
let onSave: () -> Void
|
|
|
|
private var isValid: Bool {
|
|
!recipientName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section(String.localized("Recipient Details")) {
|
|
TextField(String.localized("Name"), text: $recipientName)
|
|
.textContentType(.name)
|
|
|
|
TextField(String.localized("Role (optional)"), text: $recipientRole)
|
|
.textContentType(.jobTitle)
|
|
|
|
TextField(String.localized("Company (optional)"), text: $recipientCompany)
|
|
.textContentType(.organizationName)
|
|
}
|
|
|
|
Section {
|
|
Text("This person will appear in your Contacts tab so you can track who has your card.")
|
|
.font(.footnote)
|
|
.foregroundStyle(Color.Text.secondary)
|
|
}
|
|
}
|
|
.navigationTitle(String.localized("Track Share"))
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button(String.localized("Cancel")) {
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button(String.localized("Save")) {
|
|
onSave()
|
|
dismiss()
|
|
}
|
|
.disabled(!isValid)
|
|
}
|
|
}
|
|
}
|
|
.presentationDetents([.medium])
|
|
}
|
|
}
|
|
|
|
private struct QRCodeCardView: View {
|
|
let card: BusinessCard
|
|
|
|
var body: some View {
|
|
VStack(spacing: Design.Spacing.medium) {
|
|
QRCodeView(payload: card.vCardPayload)
|
|
.frame(width: Design.Size.qrSize, height: Design.Size.qrSize)
|
|
.padding(Design.Spacing.medium)
|
|
.background(Color.AppBackground.elevated)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
|
|
|
Text("Point your camera at the QR code to receive the card")
|
|
.font(.subheadline)
|
|
.foregroundStyle(Color.Text.secondary)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.padding(Design.Spacing.large)
|
|
.background(card.theme.primaryColor)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.xLarge))
|
|
.shadow(
|
|
color: Color.Text.secondary.opacity(Design.Opacity.hint),
|
|
radius: Design.Shadow.radiusLarge,
|
|
x: Design.Shadow.offsetNone,
|
|
y: Design.Shadow.offsetSmall
|
|
)
|
|
}
|
|
}
|
|
|
|
private struct ShareOptionsView: View {
|
|
let card: BusinessCard
|
|
let shareLinkService: ShareLinkProviding
|
|
let showWallet: () -> Void
|
|
let showNfc: () -> Void
|
|
let onShareAction: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(spacing: Design.Spacing.small) {
|
|
ShareOptionShareRow(
|
|
title: String.localized("Copy link"),
|
|
systemImage: "link",
|
|
item: shareLinkService.shareURL(for: card)
|
|
)
|
|
|
|
ShareOptionLinkRow(
|
|
title: String.localized("Text your card"),
|
|
systemImage: "message",
|
|
url: shareLinkService.smsURL(for: card)
|
|
)
|
|
|
|
ShareOptionLinkRow(
|
|
title: String.localized("Email your card"),
|
|
systemImage: "envelope",
|
|
url: shareLinkService.emailURL(for: card)
|
|
)
|
|
|
|
ShareOptionLinkRow(
|
|
title: String.localized("Send via WhatsApp"),
|
|
systemImage: "message.fill",
|
|
url: shareLinkService.whatsappURL(for: card)
|
|
)
|
|
|
|
ShareOptionLinkRow(
|
|
title: String.localized("Send via LinkedIn"),
|
|
systemImage: "link.circle",
|
|
url: shareLinkService.linkedInURL(for: card)
|
|
)
|
|
|
|
ShareOptionActionRow(
|
|
title: String.localized("Add to Apple Wallet"),
|
|
systemImage: "wallet.pass",
|
|
action: showWallet
|
|
)
|
|
|
|
ShareOptionActionRow(
|
|
title: String.localized("Share via NFC"),
|
|
systemImage: "dot.radiowaves.left.and.right",
|
|
action: showNfc
|
|
)
|
|
}
|
|
.padding(Design.Spacing.large)
|
|
.background(Color.AppBackground.elevated)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
|
}
|
|
}
|
|
|
|
private struct ShareOptionLinkRow: View {
|
|
let title: String
|
|
let systemImage: String
|
|
let url: URL
|
|
|
|
var body: some View {
|
|
Link(destination: url) {
|
|
HStack(spacing: Design.Spacing.medium) {
|
|
ShareRowIcon(systemImage: systemImage)
|
|
Text(title)
|
|
.foregroundStyle(Color.Text.primary)
|
|
Spacer()
|
|
Image(systemName: "chevron.right")
|
|
.foregroundStyle(Color.Text.secondary)
|
|
}
|
|
.padding(.horizontal, Design.Spacing.medium)
|
|
.padding(.vertical, Design.Spacing.small)
|
|
.background(Color.AppBackground.base)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
|
|
private struct ShareOptionShareRow: View {
|
|
let title: String
|
|
let systemImage: String
|
|
let item: URL
|
|
|
|
var body: some View {
|
|
ShareLink(item: item) {
|
|
HStack(spacing: Design.Spacing.medium) {
|
|
ShareRowIcon(systemImage: systemImage)
|
|
Text(title)
|
|
.foregroundStyle(Color.Text.primary)
|
|
Spacer()
|
|
Image(systemName: "chevron.right")
|
|
.foregroundStyle(Color.Text.secondary)
|
|
}
|
|
.padding(.horizontal, Design.Spacing.medium)
|
|
.padding(.vertical, Design.Spacing.small)
|
|
.background(Color.AppBackground.base)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
|
|
private struct ShareOptionActionRow: View {
|
|
let title: String
|
|
let systemImage: String
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack(spacing: Design.Spacing.medium) {
|
|
ShareRowIcon(systemImage: systemImage)
|
|
Text(title)
|
|
.foregroundStyle(Color.Text.primary)
|
|
Spacer()
|
|
Image(systemName: "chevron.right")
|
|
.foregroundStyle(Color.Text.secondary)
|
|
}
|
|
.padding(.horizontal, Design.Spacing.medium)
|
|
.padding(.vertical, Design.Spacing.small)
|
|
.background(Color.AppBackground.base)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
|
|
private struct ShareRowIcon: View {
|
|
let systemImage: String
|
|
|
|
var body: some View {
|
|
Image(systemName: systemImage)
|
|
.foregroundStyle(Color.Accent.red)
|
|
.frame(width: Design.Size.avatarSize, height: Design.Size.avatarSize)
|
|
.background(Color.AppBackground.accent)
|
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
ShareCardView()
|
|
.environment(AppState(modelContext: try! ModelContainer(for: BusinessCard.self, Contact.self).mainContext))
|
|
}
|