diff --git a/BusinessCard/Design/DesignConstants.swift b/BusinessCard/Design/DesignConstants.swift index e6952dc..3222a70 100644 --- a/BusinessCard/Design/DesignConstants.swift +++ b/BusinessCard/Design/DesignConstants.swift @@ -29,6 +29,8 @@ extension Design { static let widgetPhoneHeight: CGFloat = 120 static let widgetWatchSize: CGFloat = 100 static let floatingButtonSize: CGFloat = 56 + /// Bottom offset for floating button above tab bar. + static let floatingButtonBottomOffset: CGFloat = 72 } } @@ -49,7 +51,9 @@ extension Color { enum AppBackground { static let base = Color(red: 0.97, green: 0.96, blue: 0.94) + static let secondary = Color(red: 0.95, green: 0.95, blue: 0.95) static let elevated = Color(red: 1.0, green: 1.0, blue: 1.0) + static let card = Color(red: 1.0, green: 1.0, blue: 1.0) static let accent = Color(red: 0.95, green: 0.91, blue: 0.86) } diff --git a/BusinessCard/Resources/Localizable.xcstrings b/BusinessCard/Resources/Localizable.xcstrings index ecd8a2a..cc510f8 100644 --- a/BusinessCard/Resources/Localizable.xcstrings +++ b/BusinessCard/Resources/Localizable.xcstrings @@ -75,6 +75,12 @@ } } } + }, + "Add note" : { + + }, + "Add tag" : { + }, "Address" : { @@ -151,6 +157,9 @@ }, "Company Website" : { + }, + "Connection details" : { + }, "Contact" : { @@ -304,12 +313,18 @@ }, "Messaging" : { + }, + "More..." : { + }, "No card selected" : { }, "No contacts yet" : { + }, + "Notes" : { + }, "Open on Apple Watch" : { "localizations" : { @@ -663,6 +678,9 @@ }, "Work" : { + }, + "Write down a memorable reminder about your contact" : { + } }, "version" : "1.1" diff --git a/BusinessCard/State/ContactsStore.swift b/BusinessCard/State/ContactsStore.swift index 46acc29..d450594 100644 --- a/BusinessCard/State/ContactsStore.swift +++ b/BusinessCard/State/ContactsStore.swift @@ -84,7 +84,8 @@ final class ContactsStore: ContactTracking { phone: String = "", notes: String = "", tags: String = "", - metAt: String = "" + metAt: String = "", + followUpDate: Date? = nil ) { let contact = Contact( name: name, @@ -93,6 +94,7 @@ final class ContactsStore: ContactTracking { cardLabel: "Manual", notes: notes, tags: tags, + followUpDate: followUpDate, email: email, phone: phone, metAt: metAt diff --git a/BusinessCard/Views/ContactDetailView.swift b/BusinessCard/Views/ContactDetailView.swift index 2f7c09d..8a42306 100644 --- a/BusinessCard/Views/ContactDetailView.swift +++ b/BusinessCard/Views/ContactDetailView.swift @@ -8,127 +8,171 @@ struct ContactDetailView: View { @Bindable var contact: Contact - @State private var isEditing = false @State private var showingDeleteConfirmation = false + @State private var showingMoreActions = false + @State private var showingAddTag = false + @State private var showingAddNote = false + @State private var newTag = "" var body: some View { - List { - // Header section - Section { - ContactHeaderView(contact: contact) - } - .listRowBackground(Color.clear) - - // Contact info section - if !contact.email.isEmpty || !contact.phone.isEmpty { - Section(String.localized("Contact")) { - if !contact.email.isEmpty { - ContactInfoRow( - title: String.localized("Email"), - value: contact.email, - systemImage: "envelope", - action: { openURL("mailto:\(contact.email)") } - ) - } - if !contact.phone.isEmpty { - ContactInfoRow( - title: String.localized("Phone"), - value: contact.phone, - systemImage: "phone", - action: { openURL("tel:\(contact.phone)") } - ) - } - } - } - - // Notes section - Section(String.localized("Notes")) { - TextField(String.localized("Add notes about this contact..."), text: $contact.notes, axis: .vertical) - .lineLimit(3...10) - } - - // Tags section - Section(String.localized("Tags")) { - TextField(String.localized("Tags (comma separated)"), text: $contact.tags) - .accessibilityHint(String.localized("e.g. client, VIP, networking")) - - if !contact.tagList.isEmpty { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: Design.Spacing.small) { - ForEach(contact.tagList, id: \.self) { tag in - Text(tag) - .font(.caption) - .padding(.horizontal, Design.Spacing.small) - .padding(.vertical, Design.Spacing.xSmall) - .background(Color.Accent.red.opacity(Design.Opacity.hint)) - .foregroundStyle(Color.Accent.red) - .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) + ZStack(alignment: .bottom) { + ScrollView { + VStack(spacing: 0) { + // Header banner + ContactBannerView(contact: contact) + + // Content + VStack(alignment: .leading, spacing: Design.Spacing.large) { + // Name + Text(contact.name.isEmpty ? String.localized("Contact") : contact.name) + .font(.largeTitle) + .bold() + .foregroundStyle(Color.Text.primary) + + // Connection details + VStack(alignment: .leading, spacing: Design.Spacing.small) { + Text("Connection details") + .font(.subheadline) + .foregroundStyle(Color.Text.tertiary) + + HStack(spacing: Design.Spacing.small) { + Image(systemName: "calendar") + .foregroundStyle(Color.Text.primary) + Text(contact.lastSharedDate, format: .dateTime.day().month().year().hour().minute()) + .font(.subheadline) + .foregroundStyle(Color.Text.primary) } } - } - } - } - - // Follow-up section - Section(String.localized("Follow-up")) { - Toggle(String.localized("Set Reminder"), isOn: Binding( - get: { contact.followUpDate != nil }, - set: { newValue in - if newValue { - contact.followUpDate = .now.addingTimeInterval(86400 * 7) // 1 week default - } else { - contact.followUpDate = nil + + // Tags + HStack(spacing: Design.Spacing.small) { + ForEach(contact.tagList, id: \.self) { tag in + TagPill(text: tag, onDelete: { + removeTag(tag) + }) + } + + Button { + showingAddTag = true + } label: { + Label(String.localized("Add tag"), systemImage: "plus") + .font(.subheadline) + .bold() + .foregroundStyle(Color.AppText.inverted) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(Color.Text.primary) + .clipShape(.capsule) + } } + + // Contact info card + if !contact.email.isEmpty || !contact.phone.isEmpty { + VStack(spacing: 0) { + if !contact.email.isEmpty { + ContactInfoRow( + icon: "envelope.fill", + value: contact.email, + label: "Home", + action: { openURL("mailto:\(contact.email)") } + ) + if !contact.phone.isEmpty { + Divider() + .padding(.leading, Design.Spacing.xLarge + Design.CardSize.avatarSize) + } + } + if !contact.phone.isEmpty { + ContactInfoRow( + icon: "phone.fill", + value: contact.phone, + label: "Cell", + action: { openURL("tel:\(contact.phone)") } + ) + } + } + .background(Color.AppBackground.card) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } + + // Notes section + VStack(alignment: .leading, spacing: Design.Spacing.medium) { + Text("Notes") + .font(.headline) + .bold() + .foregroundStyle(Color.Text.primary) + + if contact.notes.isEmpty { + NotesEmptyState() + } else { + Text(contact.notes) + .font(.body) + .foregroundStyle(Color.Text.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(Design.Spacing.medium) + .background(Color.AppBackground.card) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } + + Button { + showingAddNote = true + } label: { + Label(String.localized("Add note"), systemImage: "plus") + .font(.subheadline) + .foregroundStyle(Color.Text.primary) + .frame(maxWidth: .infinity) + .padding(.vertical, Design.Spacing.medium) + .background(Color.AppBackground.card) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + .overlay( + RoundedRectangle(cornerRadius: Design.CornerRadius.large) + .stroke(Color.Text.tertiary.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin) + ) + } + } + .padding(.top, Design.Spacing.medium) + + // Spacer for bottom bar + Spacer() + .frame(height: Design.Spacing.xLarge * 4) } - )) - - if let followUpDate = contact.followUpDate { - DatePicker( - String.localized("Reminder Date"), - selection: Binding( - get: { followUpDate }, - set: { contact.followUpDate = $0 } - ), - displayedComponents: .date - ) - - if contact.isFollowUpOverdue { - Label(String.localized("Overdue"), systemImage: "exclamationmark.circle.fill") - .foregroundStyle(Color.Accent.red) - } + .padding(.horizontal, Design.Spacing.large) + .padding(.top, Design.Spacing.large) } } + .background(Color.AppBackground.secondary) + .ignoresSafeArea(edges: .top) - // Met at section - Section(String.localized("Where You Met")) { - TextField(String.localized("Event, location, or how you connected..."), text: $contact.metAt) - } - - // Activity section - Section(String.localized("Activity")) { - LabeledContent(String.localized("Last Shared")) { - Text(appState.contactsStore.relativeShareDate(for: contact)) - } - - LabeledContent(String.localized("Card Used")) { - Text(String.localized(contact.cardLabel)) - } - - if contact.isReceivedCard { - Label(String.localized("Received via QR scan"), systemImage: "qrcode") - .foregroundStyle(Color.Text.secondary) - } - } - - // Delete section - Section { - Button(String.localized("Delete Contact"), role: .destructive) { - showingDeleteConfirmation = true + // Bottom action bar + BottomActionBar( + onMore: { showingMoreActions = true }, + onAddTag: { showingAddTag = true }, + onAddNote: { showingAddNote = true } + ) + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + // Edit action - could navigate to edit mode + } label: { + Image(systemName: "square.and.pencil") + .foregroundStyle(Color.AppText.inverted) } } } - .navigationTitle(contact.name) - .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.hidden, for: .navigationBar) + .confirmationDialog(String.localized("More"), isPresented: $showingMoreActions) { + Button(String.localized("Download contact")) { + // Download action + } + Button(String.localized("Share contact")) { + // Share action + } + Button(String.localized("Delete Contact"), role: .destructive) { + showingDeleteConfirmation = true + } + Button(String.localized("Cancel"), role: .cancel) { } + } .alert(String.localized("Delete Contact"), isPresented: $showingDeleteConfirmation) { Button(String.localized("Cancel"), role: .cancel) { } Button(String.localized("Delete"), role: .destructive) { @@ -138,98 +182,270 @@ struct ContactDetailView: View { } message: { Text("Are you sure you want to delete this contact?") } + .alert(String.localized("Add Tag"), isPresented: $showingAddTag) { + TextField(String.localized("Tag name"), text: $newTag) + Button(String.localized("Cancel"), role: .cancel) { + newTag = "" + } + Button(String.localized("Add")) { + addTag() + } + } + .sheet(isPresented: $showingAddNote) { + AddNoteSheet(notes: $contact.notes) + } } private func openURL(_ urlString: String) { guard let url = URL(string: urlString) else { return } UIApplication.shared.open(url) } -} - -private struct ContactHeaderView: View { - let contact: Contact - var body: some View { - VStack(spacing: Design.Spacing.medium) { - if let photoData = contact.photoData, let uiImage = UIImage(data: photoData) { - Image(uiImage: uiImage) - .resizable() - .scaledToFill() - .frame(width: Design.CardSize.qrSize / 2, height: Design.CardSize.qrSize / 2) - .clipShape(.circle) - } else { - Image(systemName: contact.avatarSystemName) - .font(.system(size: Design.BaseFontSize.display)) - .foregroundStyle(Color.Accent.red) - .frame(width: Design.CardSize.qrSize / 2, height: Design.CardSize.qrSize / 2) - .background(Color.AppBackground.accent) - .clipShape(.circle) - } - - VStack(spacing: Design.Spacing.xSmall) { - Text(contact.name) - .font(.title2) - .bold() - .foregroundStyle(Color.Text.primary) - - if !contact.role.isEmpty || !contact.company.isEmpty { - Text("\(contact.role)\(contact.role.isEmpty || contact.company.isEmpty ? "" : " at ")\(contact.company)") - .font(.subheadline) - .foregroundStyle(Color.Text.secondary) - } - } + private func addTag() { + let tag = newTag.trimmingCharacters(in: .whitespacesAndNewlines) + guard !tag.isEmpty else { return } + + if contact.tags.isEmpty { + contact.tags = tag + } else { + contact.tags += ", \(tag)" } - .frame(maxWidth: .infinity) - .padding(.vertical, Design.Spacing.medium) + newTag = "" + } + + private func removeTag(_ tag: String) { + var tags = contact.tagList + tags.removeAll { $0 == tag } + contact.tags = tags.joined(separator: ", ") } } +// MARK: - Banner View + +private struct ContactBannerView: View { + let contact: Contact + + private var initials: String { + let parts = contact.name.split(separator: " ") + if parts.count >= 2 { + return "\(parts[0].prefix(1))\(parts[1].prefix(1))".uppercased() + } else if let first = parts.first { + return String(first.prefix(2)).uppercased() + } + return "?" + } + + var body: some View { + ZStack { + // Gradient background + LinearGradient( + colors: [ + Color.CardPalette.coral, + Color.CardPalette.coral.opacity(Design.Opacity.strong) + ], + startPoint: .top, + endPoint: .bottom + ) + + // Decorative circles + Circle() + .fill(Color.white.opacity(Design.Opacity.subtle)) + .frame(width: Design.CardSize.qrSize, height: Design.CardSize.qrSize) + .offset(y: -Design.Spacing.xLarge) + + // Initials + VStack(spacing: Design.Spacing.xxSmall) { + Text(String(initials.prefix(1))) + .font(.system(size: Design.BaseFontSize.display, weight: .light)) + Text(String(initials.dropFirst().prefix(1))) + .font(.system(size: Design.BaseFontSize.display, weight: .light)) + } + .foregroundStyle(Color.white.opacity(Design.Opacity.accent)) + } + .frame(height: Design.CardSize.bannerHeight * 1.5) + } +} + +// MARK: - Tag Pill + +private struct TagPill: View { + let text: String + let onDelete: () -> Void + + var body: some View { + HStack(spacing: Design.Spacing.xSmall) { + Text(text) + .font(.subheadline) + Button { + onDelete() + } label: { + Image(systemName: "xmark") + .font(.caption2) + } + } + .foregroundStyle(Color.Text.primary) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) + .background(Color.AppBackground.card) + .clipShape(.capsule) + .overlay( + Capsule() + .stroke(Color.Text.tertiary.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin) + ) + } +} + +// MARK: - Contact Info Row + private struct ContactInfoRow: View { - let title: String + let icon: String let value: String - let systemImage: String + let label: String let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: Design.Spacing.medium) { - Image(systemName: systemImage) - .foregroundStyle(Color.Accent.red) - .frame(width: Design.Spacing.xLarge) + // Icon circle + Image(systemName: icon) + .font(.body) + .foregroundStyle(Color.white) + .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) + .background(Color.CardPalette.coral) + .clipShape(.circle) + // Text VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { - Text(title) - .font(.caption) - .foregroundStyle(Color.Text.secondary) Text(value) .font(.body) .foregroundStyle(Color.Text.primary) + Text(label) + .font(.caption) + .foregroundStyle(Color.Text.tertiary) } Spacer() - - Image(systemName: "chevron.right") - .font(.caption) - .foregroundStyle(Color.Text.secondary) } + .padding(Design.Spacing.medium) } .buttonStyle(.plain) } } +// MARK: - Notes Empty State + +private struct NotesEmptyState: View { + var body: some View { + VStack(spacing: Design.Spacing.medium) { + Image(systemName: "note.text") + .font(.system(size: Design.BaseFontSize.display)) + .foregroundStyle(Color.CardPalette.coral.opacity(Design.Opacity.medium)) + + Text("Write down a memorable reminder about your contact") + .font(.subheadline) + .foregroundStyle(Color.Text.tertiary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.vertical, Design.Spacing.xLarge) + .background(Color.AppBackground.card) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } +} + +// MARK: - Bottom Action Bar + +private struct BottomActionBar: View { + let onMore: () -> Void + let onAddTag: () -> Void + let onAddNote: () -> Void + + var body: some View { + HStack(spacing: Design.Spacing.medium) { + Button(action: onMore) { + Text("More...") + .font(.subheadline) + .bold() + .foregroundStyle(Color.Text.primary) + .frame(maxWidth: .infinity) + .padding(.vertical, Design.Spacing.medium) + .background(Color.AppBackground.card) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } + + Button(action: onAddTag) { + Text("Add tag") + .font(.subheadline) + .bold() + .foregroundStyle(Color.AppText.inverted) + .frame(maxWidth: .infinity) + .padding(.vertical, Design.Spacing.medium) + .background(Color.Text.primary) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } + + Button(action: onAddNote) { + Text("Add note") + .font(.subheadline) + .bold() + .foregroundStyle(Color.AppText.inverted) + .frame(maxWidth: .infinity) + .padding(.vertical, Design.Spacing.medium) + .background(Color.Text.primary) + .clipShape(.rect(cornerRadius: Design.CornerRadius.large)) + } + } + .padding(.horizontal, Design.Spacing.large) + .padding(.vertical, Design.Spacing.medium) + .background(Color.AppBackground.secondary) + } +} + +// MARK: - Add Note Sheet + +private struct AddNoteSheet: View { + @Environment(\.dismiss) private var dismiss + @Binding var notes: String + @State private var editedNotes: String = "" + + var body: some View { + NavigationStack { + TextEditor(text: $editedNotes) + .padding(Design.Spacing.medium) + .navigationTitle(String.localized("Notes")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button(String.localized("Cancel")) { + dismiss() + } + } + ToolbarItem(placement: .confirmationAction) { + Button(String.localized("Save")) { + notes = editedNotes + dismiss() + } + .bold() + } + } + .onAppear { + editedNotes = notes + } + } + } +} + #Preview { NavigationStack { ContactDetailView( contact: Contact( - name: "Kevin Lennox", - role: "Branch Manager", - company: "Global Bank", - notes: "Met at the Austin fintech conference", - tags: "finance, potential client", - followUpDate: .now.addingTimeInterval(86400 * 3), - email: "kevin@globalbank.com", - phone: "+1 555 123 4567", - metAt: "Austin Fintech Conference 2026" + name: "Heidi Bruce", + role: "Designer", + company: "Apple", + notes: "", + tags: "", + email: "heidi.h.bruce@gmail.com", + phone: "2145328862" ) ) .environment(AppState(modelContext: try! ModelContainer(for: BusinessCard.self, Contact.self).mainContext)) diff --git a/BusinessCard/Views/RootTabView.swift b/BusinessCard/Views/RootTabView.swift index 31989f1..f5bc3ab 100644 --- a/BusinessCard/Views/RootTabView.swift +++ b/BusinessCard/Views/RootTabView.swift @@ -24,8 +24,8 @@ struct RootTabView: View { } } - // Floating share button - hidden when no cards exist - if !appState.cardStore.cards.isEmpty { + // Floating share button - only shown on cards tab when cards exist + if appState.selectedTab == .cards && !appState.cardStore.cards.isEmpty { FloatingShareButton { showingShareSheet = true } @@ -62,7 +62,7 @@ private struct FloatingShareButton: View { } .accessibilityLabel(String.localized("Share")) .accessibilityHint(String.localized("Opens the share sheet to send your card")) - .padding(.bottom, Design.Spacing.xxLarge + Design.Spacing.xLarge) + .padding(.bottom, Design.CardSize.floatingButtonBottomOffset) } } diff --git a/BusinessCard/Views/Sheets/AddContactSheet.swift b/BusinessCard/Views/Sheets/AddContactSheet.swift index 879badd..40c1014 100644 --- a/BusinessCard/Views/Sheets/AddContactSheet.swift +++ b/BusinessCard/Views/Sheets/AddContactSheet.swift @@ -6,48 +6,70 @@ 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 firstName = "" + @State private var lastName = "" @State private var phone = "" - @State private var metAt = "" + @State private var email = "" + @State private var link = "" + @State private var jobTitle = "" + @State private var company = "" private var canSave: Bool { - !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + !firstName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || + !lastName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + + private var fullName: String { + [firstName, lastName] + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + .joined(separator: " ") } 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) + ScrollView { + VStack(spacing: 0) { + // Name section + VStack(alignment: .leading, spacing: Design.Spacing.medium) { + EditorTextField(placeholder: String.localized("First name"), text: $firstName) + .textContentType(.givenName) + EditorTextField(placeholder: String.localized("Last name"), text: $lastName) + .textContentType(.familyName) + } + .padding(Design.Spacing.large) + .background(Color.AppBackground.elevated) + + // Contact section + VStack(alignment: .leading, spacing: Design.Spacing.medium) { + EditorTextField(placeholder: String.localized("Phone"), text: $phone) + .keyboardType(.phonePad) + .textContentType(.telephoneNumber) + EditorTextField(placeholder: String.localized("Email"), text: $email) + .keyboardType(.emailAddress) + .textContentType(.emailAddress) + .textInputAutocapitalization(.never) + EditorTextField(placeholder: String.localized("Link"), text: $link) + .keyboardType(.URL) + .textContentType(.URL) + .textInputAutocapitalization(.never) + } + .padding(Design.Spacing.large) + .background(Color.AppBackground.elevated) + + // Professional section + VStack(alignment: .leading, spacing: Design.Spacing.medium) { + EditorTextField(placeholder: String.localized("Job title"), text: $jobTitle) + .textContentType(.jobTitle) + EditorTextField(placeholder: String.localized("Company"), text: $company) + .textContentType(.organizationName) + } + .padding(Design.Spacing.large) + .background(Color.AppBackground.elevated) } } - .navigationTitle(String.localized("Add Contact")) + .background(Color.AppBackground.base) + .navigationTitle(String.localized("New contact")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { @@ -59,7 +81,6 @@ struct AddContactSheet: View { Button(String.localized("Save")) { saveContact() } - .bold() .disabled(!canSave) } } @@ -68,17 +89,30 @@ struct AddContactSheet: View { private func saveContact() { appState.contactsStore.createContact( - name: name.trimmingCharacters(in: .whitespacesAndNewlines), - role: role.trimmingCharacters(in: .whitespacesAndNewlines), + name: fullName, + role: jobTitle.trimmingCharacters(in: .whitespacesAndNewlines), company: company.trimmingCharacters(in: .whitespacesAndNewlines), email: email.trimmingCharacters(in: .whitespacesAndNewlines), - phone: phone.trimmingCharacters(in: .whitespacesAndNewlines), - metAt: metAt.trimmingCharacters(in: .whitespacesAndNewlines) + phone: phone.trimmingCharacters(in: .whitespacesAndNewlines) ) dismiss() } } +// MARK: - Shared Editor TextField + +private struct EditorTextField: View { + let placeholder: String + @Binding var text: String + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + TextField(placeholder, text: $text) + Divider() + } + } +} + #Preview { AddContactSheet() .environment(AppState(modelContext: try! ModelContainer(for: BusinessCard.self, Contact.self).mainContext))