Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-08 22:52:40 -06:00
parent 874908398a
commit 5e2ff8b990
6 changed files with 132 additions and 7 deletions

View File

@ -500,9 +500,6 @@
}
}
}
},
"Share your card and track recipients, or scan someone else's QR code to save their card." : {
},
"Shared With" : {
@ -512,6 +509,9 @@
},
"Support & Funding" : {
},
"Tap + to add a contact, scan a QR code, or track who you share your card with." : {
},
"Tap a field below to add it" : {

View File

@ -75,6 +75,33 @@ final class ContactsStore: ContactTracking {
fetchContacts()
}
/// Creates a new contact manually
func createContact(
name: String,
role: String = "",
company: String = "",
email: String = "",
phone: String = "",
notes: String = "",
tags: String = "",
metAt: String = ""
) {
let contact = Contact(
name: name,
role: role,
company: company,
cardLabel: "Manual",
notes: notes,
tags: tags,
email: email,
phone: phone,
metAt: metAt
)
modelContext.insert(contact)
saveContext()
fetchContacts()
}
/// Updates a contact's notes
func updateNotes(for contact: Contact, notes: String) {
contact.notes = notes

View File

@ -5,6 +5,7 @@ import SwiftData
struct ContactsView: View {
@Environment(AppState.self) private var appState
@State private var showingScanner = false
@State private var showingAddContact = false
var body: some View {
@Bindable var contactsStore = appState.contactsStore
@ -20,12 +21,19 @@ struct ContactsView: View {
.navigationTitle(String.localized("Contacts"))
.toolbar {
ToolbarItem(placement: .primaryAction) {
HStack(spacing: Design.Spacing.small) {
Button(String.localized("Add Contact"), systemImage: "plus") {
showingAddContact = true
}
.accessibilityHint(String.localized("Manually add a new contact"))
Button(String.localized("Scan Card"), systemImage: "qrcode.viewfinder") {
showingScanner = true
}
.accessibilityHint(String.localized("Scan someone else's QR code to save their card"))
}
}
}
.sheet(isPresented: $showingScanner) {
QRScannerView { scannedData in
if !scannedData.isEmpty {
@ -34,6 +42,9 @@ struct ContactsView: View {
showingScanner = false
}
}
.sheet(isPresented: $showingAddContact) {
AddContactSheet()
}
}
}
}
@ -49,7 +60,7 @@ private struct EmptyContactsView: View {
.font(.headline)
.foregroundStyle(Color.Text.primary)
Text("Share your card and track recipients, or scan someone else's QR code to save their card.")
Text("Tap + to add a contact, scan a QR code, or track who you share your card with.")
.font(.subheadline)
.foregroundStyle(Color.Text.secondary)
.multilineTextAlignment(.center)

View File

@ -0,0 +1,85 @@
import SwiftUI
import SwiftData
import Bedrock
struct AddContactSheet: View {
@Environment(AppState.self) private var appState
@Environment(\.dismiss) private var dismiss
@State private var name = ""
@State private var role = ""
@State private var company = ""
@State private var email = ""
@State private var phone = ""
@State private var metAt = ""
private var canSave: Bool {
!name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
var body: some View {
NavigationStack {
Form {
Section(String.localized("Name")) {
TextField(String.localized("Full name"), text: $name)
.textContentType(.name)
.accessibilityLabel(String.localized("Contact name"))
}
Section(String.localized("Role")) {
TextField(String.localized("Job title"), text: $role)
.textContentType(.jobTitle)
TextField(String.localized("Company"), text: $company)
.textContentType(.organizationName)
}
Section(String.localized("Contact")) {
TextField(String.localized("Email"), text: $email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.textInputAutocapitalization(.never)
TextField(String.localized("Phone"), text: $phone)
.keyboardType(.phonePad)
.textContentType(.telephoneNumber)
}
Section(String.localized("Where You Met")) {
TextField(String.localized("Event, location, or how you connected..."), text: $metAt)
}
}
.navigationTitle(String.localized("Add Contact"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(String.localized("Cancel")) {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button(String.localized("Save")) {
saveContact()
}
.bold()
.disabled(!canSave)
}
}
}
}
private func saveContact() {
appState.contactsStore.createContact(
name: name.trimmingCharacters(in: .whitespacesAndNewlines),
role: role.trimmingCharacters(in: .whitespacesAndNewlines),
company: company.trimmingCharacters(in: .whitespacesAndNewlines),
email: email.trimmingCharacters(in: .whitespacesAndNewlines),
phone: phone.trimmingCharacters(in: .whitespacesAndNewlines),
metAt: metAt.trimmingCharacters(in: .whitespacesAndNewlines)
)
dismiss()
}
}
#Preview {
AddContactSheet()
.environment(AppState(modelContext: try! ModelContainer(for: BusinessCard.self, Contact.self).mainContext))
}

View File

@ -67,6 +67,7 @@ Each field has:
### Contacts
- **Add contacts manually**: Tap + to create contacts with name, role, company, email, phone
- Track who you've shared your card with
- **Scan QR codes** to save someone else's business card
- **Notes & annotations**: Add notes about each contact

View File

@ -129,6 +129,7 @@ Reusable components (in `Views/Components/`):
Sheets (in `Views/Sheets/`):
- `RecordContactSheet.swift` — track share recipient
- `ContactFieldEditorSheet.swift` — add/edit contact field with type-specific UI
- `AddContactSheet.swift` — manually add a new contact
Small utilities:
- `Views/EmptyStateView.swift` — empty state placeholder