Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
8c40e638e5
commit
954f21616e
@ -51,6 +51,14 @@ final class ContactField {
|
||||
fieldType?.displayName ?? typeId
|
||||
}
|
||||
|
||||
/// Formatted display value using the field type's formatter
|
||||
/// For addresses, this returns a multi-line formatted string
|
||||
/// For other fields, returns the value as-is
|
||||
@MainActor
|
||||
var displayValue: String {
|
||||
fieldType?.formattedDisplayValue(value) ?? value
|
||||
}
|
||||
|
||||
/// Returns an Image view for this field's icon
|
||||
@MainActor
|
||||
func iconImage() -> Image {
|
||||
|
||||
@ -39,6 +39,7 @@ struct ContactFieldType: Identifiable, Hashable, Sendable {
|
||||
let keyboardType: UIKeyboardType
|
||||
let autocapitalization: TextInputAutocapitalization
|
||||
let urlBuilder: @Sendable (String) -> URL?
|
||||
let displayValueFormatter: @Sendable (String) -> String
|
||||
|
||||
init(
|
||||
id: String,
|
||||
@ -52,7 +53,8 @@ struct ContactFieldType: Identifiable, Hashable, Sendable {
|
||||
titleSuggestions: [String],
|
||||
keyboardType: UIKeyboardType,
|
||||
autocapitalization: TextInputAutocapitalization = .never,
|
||||
urlBuilder: @escaping @Sendable (String) -> URL?
|
||||
urlBuilder: @escaping @Sendable (String) -> URL?,
|
||||
displayValueFormatter: @escaping @Sendable (String) -> String = { $0 }
|
||||
) {
|
||||
self.id = id
|
||||
self.displayName = displayName
|
||||
@ -66,6 +68,7 @@ struct ContactFieldType: Identifiable, Hashable, Sendable {
|
||||
self.keyboardType = keyboardType
|
||||
self.autocapitalization = autocapitalization
|
||||
self.urlBuilder = urlBuilder
|
||||
self.displayValueFormatter = displayValueFormatter
|
||||
}
|
||||
|
||||
/// Returns an Image view for this field type's icon
|
||||
@ -93,6 +96,13 @@ struct ContactFieldType: Identifiable, Hashable, Sendable {
|
||||
func buildURL(value: String) -> URL? {
|
||||
urlBuilder(value)
|
||||
}
|
||||
|
||||
// MARK: - Display Value Formatting
|
||||
|
||||
/// Returns a formatted display value for the given raw value
|
||||
func formattedDisplayValue(_ value: String) -> String {
|
||||
displayValueFormatter(value)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - All Field Types
|
||||
@ -183,7 +193,8 @@ extension ContactFieldType {
|
||||
urlBuilder: { value in
|
||||
let encoded = value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? value
|
||||
return URL(string: "maps://?q=\(encoded)")
|
||||
}
|
||||
},
|
||||
displayValueFormatter: formatAddressForDisplay
|
||||
)
|
||||
|
||||
// MARK: - Social Media
|
||||
@ -641,6 +652,26 @@ extension ContactFieldType {
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Display Value Formatters
|
||||
|
||||
/// Formats an address for multi-line display
|
||||
/// Splits on commas and puts each component on a new line
|
||||
nonisolated private func formatAddressForDisplay(_ value: String) -> String {
|
||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return value }
|
||||
|
||||
// Split by comma, trim each component, and join with newlines
|
||||
let components = trimmed
|
||||
.split(separator: ",")
|
||||
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
// If only one component, return as-is
|
||||
guard components.count > 1 else { return trimmed }
|
||||
|
||||
return components.joined(separator: "\n")
|
||||
}
|
||||
|
||||
// MARK: - URL Helper Functions
|
||||
|
||||
nonisolated private func buildWebURL(_ value: String) -> URL? {
|
||||
|
||||
@ -606,6 +606,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"The default card is used for sharing and widgets." : {
|
||||
|
||||
},
|
||||
"This doesn't appear to be a business card QR code." : {
|
||||
|
||||
|
||||
@ -44,6 +44,9 @@ struct CardEditorView: View {
|
||||
@State private var coverPhotoData: Data?
|
||||
@State private var logoData: Data?
|
||||
|
||||
// Default card
|
||||
@State private var isDefault = false
|
||||
|
||||
// Photo editor state - just one variable!
|
||||
@State private var editingImageType: ImageType?
|
||||
|
||||
@ -106,6 +109,15 @@ struct CardEditorView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
// Default card toggle
|
||||
Section {
|
||||
Toggle(isOn: $isDefault) {
|
||||
Label(String.localized("Default Card"), systemImage: "checkmark.seal.fill")
|
||||
}
|
||||
} footer: {
|
||||
Text("The default card is used for sharing and widgets.")
|
||||
}
|
||||
|
||||
// Card Style section
|
||||
Section {
|
||||
CardStylePicker(selectedTheme: $selectedTheme)
|
||||
@ -651,6 +663,7 @@ private extension CardEditorView {
|
||||
bio = card.bio
|
||||
accreditations = card.accreditations
|
||||
avatarSystemName = card.avatarSystemName
|
||||
isDefault = card.isDefault
|
||||
|
||||
// Load contact fields from the array
|
||||
contactFields = card.orderedContactFields.compactMap { $0.toAddedContactField() }
|
||||
@ -693,9 +706,19 @@ private extension CardEditorView {
|
||||
if let existingCard = card {
|
||||
updateCard(existingCard)
|
||||
onSave(existingCard)
|
||||
|
||||
// Handle default card toggle
|
||||
if isDefault {
|
||||
appState.cardStore.setDefaultCard(existingCard)
|
||||
}
|
||||
} else {
|
||||
let newCard = createCard()
|
||||
onSave(newCard)
|
||||
|
||||
// Handle default card toggle for new cards
|
||||
if isDefault {
|
||||
appState.cardStore.setDefaultCard(newCard)
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@ -27,12 +27,8 @@ struct CardsHomeView: View {
|
||||
// Full-screen swipeable cards
|
||||
TabView(selection: $cardStore.selectedCardID) {
|
||||
ForEach(cardStore.cards) { card in
|
||||
CardPageView(
|
||||
card: card,
|
||||
isDefault: card.isDefault,
|
||||
onSetDefault: { cardStore.setDefaultCard(card) }
|
||||
)
|
||||
.tag(Optional(card.id))
|
||||
CardPageView(card: card)
|
||||
.tag(Optional(card.id))
|
||||
}
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .automatic))
|
||||
@ -97,23 +93,11 @@ struct CardsHomeView: View {
|
||||
|
||||
private struct CardPageView: View {
|
||||
let card: BusinessCard
|
||||
let isDefault: Bool
|
||||
let onSetDefault: () -> Void
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: Design.Spacing.large) {
|
||||
BusinessCardView(card: card)
|
||||
|
||||
// Default card toggle
|
||||
Button(
|
||||
isDefault ? String.localized("Default card") : String.localized("Set as default"),
|
||||
systemImage: isDefault ? "checkmark.seal.fill" : "checkmark.seal",
|
||||
action: onSetDefault
|
||||
)
|
||||
.buttonStyle(.bordered)
|
||||
.tint(Color.Accent.red)
|
||||
.accessibilityHint(String.localized("Sets this card as your default sharing card"))
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.padding(.vertical, Design.Spacing.xLarge)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user