Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a164669cf2
commit
fadb46e9cd
@ -532,7 +532,7 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = BusinessCard/Info.plist;
|
INFOPLIST_FILE = BusinessCard/Info.plist;
|
||||||
INFOPLIST_KEY_NSContactsUsageDescription = "for testing purposes";
|
INFOPLIST_KEY_NSContactsUsageDescription = "for testing purposes";
|
||||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "";
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "BusinessCard uses your photo library to add a profile photo to your business card.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
@ -568,7 +568,7 @@
|
|||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = BusinessCard/Info.plist;
|
INFOPLIST_FILE = BusinessCard/Info.plist;
|
||||||
INFOPLIST_KEY_NSContactsUsageDescription = "for testing purposes";
|
INFOPLIST_KEY_NSContactsUsageDescription = "for testing purposes";
|
||||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "";
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "BusinessCard uses your photo library to add a profile photo to your business card.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
|
|||||||
@ -7,12 +7,12 @@
|
|||||||
<key>BusinessCard.xcscheme_^#shared#^_</key>
|
<key>BusinessCard.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>1</integer>
|
<integer>2</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>BusinessCardWatch.xcscheme_^#shared#^_</key>
|
<key>BusinessCardWatch.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@ -98,6 +98,7 @@ struct BusinessCardApp: App {
|
|||||||
WindowGroup {
|
WindowGroup {
|
||||||
RootTabView()
|
RootTabView()
|
||||||
.environment(appState)
|
.environment(appState)
|
||||||
|
.preferredColorScheme(.light)
|
||||||
}
|
}
|
||||||
.modelContainer(modelContainer)
|
.modelContainer(modelContainer)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>BusinessCard uses the camera to scan QR codes on other people's business cards.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>BusinessCard uses your photo library to add a profile photo to your business card.</string>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
|
|||||||
@ -21,7 +21,19 @@ enum CardTheme: String, CaseIterable, Identifiable, Hashable, Sendable {
|
|||||||
|
|
||||||
static var all: [CardTheme] { allCases }
|
static var all: [CardTheme] { allCases }
|
||||||
|
|
||||||
// RGB values - nonisolated
|
// MARK: - Theme Brightness
|
||||||
|
|
||||||
|
/// Whether this theme requires dark text for proper contrast.
|
||||||
|
/// Light backgrounds need dark text; dark backgrounds need light text.
|
||||||
|
var requiresDarkText: Bool {
|
||||||
|
switch self {
|
||||||
|
case .lime: return true
|
||||||
|
case .coral, .midnight, .ocean, .violet: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - RGB Values (nonisolated)
|
||||||
|
|
||||||
private var primaryRGB: (Double, Double, Double) {
|
private var primaryRGB: (Double, Double, Double) {
|
||||||
switch self {
|
switch self {
|
||||||
case .coral: return (0.95, 0.35, 0.33)
|
case .coral: return (0.95, 0.35, 0.33)
|
||||||
@ -52,7 +64,14 @@ enum CardTheme: String, CaseIterable, Identifiable, Hashable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colors - computed from RGB
|
private var textRGB: (Double, Double, Double) {
|
||||||
|
requiresDarkText
|
||||||
|
? (0.14, 0.14, 0.17) // Dark text for light backgrounds
|
||||||
|
: (0.98, 0.98, 0.98) // Light text for dark backgrounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Colors (MainActor)
|
||||||
|
|
||||||
@MainActor var primaryColor: Color {
|
@MainActor var primaryColor: Color {
|
||||||
Color(red: primaryRGB.0, green: primaryRGB.1, blue: primaryRGB.2)
|
Color(red: primaryRGB.0, green: primaryRGB.1, blue: primaryRGB.2)
|
||||||
}
|
}
|
||||||
@ -64,4 +83,9 @@ enum CardTheme: String, CaseIterable, Identifiable, Hashable, Sendable {
|
|||||||
@MainActor var accentColor: Color {
|
@MainActor var accentColor: Color {
|
||||||
Color(red: accentRGB.0, green: accentRGB.1, blue: accentRGB.2)
|
Color(red: accentRGB.0, green: accentRGB.1, blue: accentRGB.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The appropriate text color for content displayed on this theme's background.
|
||||||
|
@MainActor var textColor: Color {
|
||||||
|
Color(red: textRGB.0, green: textRGB.1, blue: textRGB.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1013,9 +1013,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Record who received your card" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Reviews" : {
|
"Reviews" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
@ -1553,9 +1550,6 @@
|
|||||||
},
|
},
|
||||||
"This person will appear in your Contacts tab so you can track who has your card." : {
|
"This person will appear in your Contacts tab so you can track who has your card." : {
|
||||||
|
|
||||||
},
|
|
||||||
"Track this share" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Track who receives your card" : {
|
"Track who receives your card" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
|
|||||||
@ -47,7 +47,7 @@ private struct StackedCardLayout: View {
|
|||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
CardHeaderView(card: card)
|
CardHeaderView(card: card)
|
||||||
Divider()
|
Divider()
|
||||||
.overlay(Color.Text.inverted.opacity(Design.Opacity.medium))
|
.overlay(card.theme.textColor.opacity(Design.Opacity.medium))
|
||||||
CardDetailsView(card: card)
|
CardDetailsView(card: card)
|
||||||
if card.hasSocialLinks {
|
if card.hasSocialLinks {
|
||||||
SocialLinksRow(card: card)
|
SocialLinksRow(card: card)
|
||||||
@ -69,7 +69,7 @@ private struct SplitCardLayout: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(minLength: Design.Spacing.medium)
|
Spacer(minLength: Design.Spacing.medium)
|
||||||
AccentBlockView(color: card.theme.accentColor)
|
AccentBlockView(color: card.theme.accentColor, textColor: card.theme.textColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +90,8 @@ private struct PhotoCardLayout: View {
|
|||||||
AvatarBadgeView(
|
AvatarBadgeView(
|
||||||
systemName: card.avatarSystemName,
|
systemName: card.avatarSystemName,
|
||||||
accentColor: card.theme.accentColor,
|
accentColor: card.theme.accentColor,
|
||||||
photoData: card.photoData
|
photoData: card.photoData,
|
||||||
|
borderColor: card.theme.textColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,58 +101,63 @@ private struct PhotoCardLayout: View {
|
|||||||
|
|
||||||
private struct CardHeaderView: View {
|
private struct CardHeaderView: View {
|
||||||
let card: BusinessCard
|
let card: BusinessCard
|
||||||
|
|
||||||
|
private var textColor: Color { card.theme.textColor }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
AvatarBadgeView(
|
AvatarBadgeView(
|
||||||
systemName: card.avatarSystemName,
|
systemName: card.avatarSystemName,
|
||||||
accentColor: card.theme.accentColor,
|
accentColor: card.theme.accentColor,
|
||||||
photoData: card.photoData
|
photoData: card.photoData,
|
||||||
|
borderColor: textColor
|
||||||
)
|
)
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
HStack(spacing: Design.Spacing.xSmall) {
|
HStack(spacing: Design.Spacing.xSmall) {
|
||||||
Text(card.displayName)
|
Text(card.displayName)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.bold()
|
.bold()
|
||||||
.foregroundStyle(Color.Text.inverted)
|
.foregroundStyle(textColor)
|
||||||
|
|
||||||
if !card.pronouns.isEmpty {
|
if !card.pronouns.isEmpty {
|
||||||
Text("(\(card.pronouns))")
|
Text("(\(card.pronouns))")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.strong))
|
.foregroundStyle(textColor.opacity(Design.Opacity.strong))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(card.role)
|
Text(card.role)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.almostFull))
|
.foregroundStyle(textColor.opacity(Design.Opacity.almostFull))
|
||||||
Text(card.company)
|
Text(card.company)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.medium))
|
.foregroundStyle(textColor.opacity(Design.Opacity.medium))
|
||||||
}
|
}
|
||||||
Spacer(minLength: Design.Spacing.small)
|
Spacer(minLength: Design.Spacing.small)
|
||||||
LabelBadgeView(label: card.label, accentColor: card.theme.accentColor)
|
LabelBadgeView(label: card.label, accentColor: card.theme.accentColor, textColor: textColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CardDetailsView: View {
|
private struct CardDetailsView: View {
|
||||||
let card: BusinessCard
|
let card: BusinessCard
|
||||||
|
|
||||||
|
private var textColor: Color { card.theme.textColor }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
if !card.email.isEmpty {
|
if !card.email.isEmpty {
|
||||||
IconRowView(systemImage: "envelope", text: card.email)
|
IconRowView(systemImage: "envelope", text: card.email, textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.phone.isEmpty {
|
if !card.phone.isEmpty {
|
||||||
IconRowView(systemImage: "phone", text: card.phone)
|
IconRowView(systemImage: "phone", text: card.phone, textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.website.isEmpty {
|
if !card.website.isEmpty {
|
||||||
IconRowView(systemImage: "link", text: card.website)
|
IconRowView(systemImage: "link", text: card.website, textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.bio.isEmpty {
|
if !card.bio.isEmpty {
|
||||||
Text(card.bio)
|
Text(card.bio)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.strong))
|
.foregroundStyle(textColor.opacity(Design.Opacity.strong))
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
.padding(.top, Design.Spacing.xxSmall)
|
.padding(.top, Design.Spacing.xxSmall)
|
||||||
}
|
}
|
||||||
@ -162,25 +168,27 @@ private struct CardDetailsView: View {
|
|||||||
private struct SocialLinksRow: View {
|
private struct SocialLinksRow: View {
|
||||||
let card: BusinessCard
|
let card: BusinessCard
|
||||||
|
|
||||||
|
private var textColor: Color { card.theme.textColor }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: Design.Spacing.small) {
|
HStack(spacing: Design.Spacing.small) {
|
||||||
if !card.linkedIn.isEmpty {
|
if !card.linkedIn.isEmpty {
|
||||||
SocialIconView(systemImage: "link")
|
SocialIconView(systemImage: "link", textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.twitter.isEmpty {
|
if !card.twitter.isEmpty {
|
||||||
SocialIconView(systemImage: "at")
|
SocialIconView(systemImage: "at", textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.instagram.isEmpty {
|
if !card.instagram.isEmpty {
|
||||||
SocialIconView(systemImage: "camera")
|
SocialIconView(systemImage: "camera", textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.facebook.isEmpty {
|
if !card.facebook.isEmpty {
|
||||||
SocialIconView(systemImage: "person.2")
|
SocialIconView(systemImage: "person.2", textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.tiktok.isEmpty {
|
if !card.tiktok.isEmpty {
|
||||||
SocialIconView(systemImage: "play.rectangle")
|
SocialIconView(systemImage: "play.rectangle", textColor: textColor)
|
||||||
}
|
}
|
||||||
if !card.github.isEmpty {
|
if !card.github.isEmpty {
|
||||||
SocialIconView(systemImage: "chevron.left.forwardslash.chevron.right")
|
SocialIconView(systemImage: "chevron.left.forwardslash.chevron.right", textColor: textColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, Design.Spacing.xxSmall)
|
.padding(.top, Design.Spacing.xxSmall)
|
||||||
@ -191,19 +199,21 @@ private struct SocialLinksRow: View {
|
|||||||
|
|
||||||
private struct SocialIconView: View {
|
private struct SocialIconView: View {
|
||||||
let systemImage: String
|
let systemImage: String
|
||||||
|
var textColor: Color = Color.Text.inverted
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Image(systemName: systemImage)
|
Image(systemName: systemImage)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.strong))
|
.foregroundStyle(textColor.opacity(Design.Opacity.strong))
|
||||||
.frame(width: Design.Spacing.xLarge, height: Design.Spacing.xLarge)
|
.frame(width: Design.Spacing.xLarge, height: Design.Spacing.xLarge)
|
||||||
.background(Color.Text.inverted.opacity(Design.Opacity.hint))
|
.background(textColor.opacity(Design.Opacity.hint))
|
||||||
.clipShape(.circle)
|
.clipShape(.circle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct AccentBlockView: View {
|
private struct AccentBlockView: View {
|
||||||
let color: Color
|
let color: Color
|
||||||
|
var textColor: Color = Color.Text.inverted
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
@ -211,7 +221,7 @@ private struct AccentBlockView: View {
|
|||||||
.frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize)
|
.frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize)
|
||||||
.overlay(
|
.overlay(
|
||||||
Image(systemName: "bolt.fill")
|
Image(systemName: "bolt.fill")
|
||||||
.foregroundStyle(Color.Text.inverted)
|
.foregroundStyle(textColor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -294,19 +294,23 @@ private struct PhotoPickerRow: View {
|
|||||||
let avatarSystemName: String
|
let avatarSystemName: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let hasPhoto = photoData != nil
|
||||||
|
let labelText = hasPhoto ? String.localized("Change Photo") : String.localized("Add Photo")
|
||||||
|
let accentColor = Color.Accent.red
|
||||||
|
|
||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
AvatarBadgeView(
|
AvatarBadgeView(
|
||||||
systemName: avatarSystemName,
|
systemName: avatarSystemName,
|
||||||
accentColor: Color.Accent.red,
|
accentColor: accentColor,
|
||||||
photoData: photoData
|
photoData: photoData
|
||||||
)
|
)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
PhotosPicker(selection: $selectedPhoto, matching: .images) {
|
PhotosPicker(selection: $selectedPhoto, matching: .images) {
|
||||||
Text(photoData == nil ? String.localized("Add Photo") : String.localized("Change Photo"))
|
Text(labelText)
|
||||||
.foregroundStyle(Color.Accent.red)
|
.foregroundStyle(accentColor)
|
||||||
}
|
}
|
||||||
if photoData != nil {
|
if hasPhoto {
|
||||||
Button(String.localized("Remove Photo"), role: .destructive) {
|
Button(String.localized("Remove Photo"), role: .destructive) {
|
||||||
photoData = nil
|
photoData = nil
|
||||||
selectedPhoto = nil
|
selectedPhoto = nil
|
||||||
@ -349,21 +353,24 @@ private struct EditorCardPreview: View {
|
|||||||
let theme: CardTheme
|
let theme: CardTheme
|
||||||
let photoData: Data?
|
let photoData: Data?
|
||||||
|
|
||||||
|
private var textColor: Color { theme.textColor }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
HStack(spacing: Design.Spacing.medium) {
|
HStack(spacing: Design.Spacing.medium) {
|
||||||
AvatarBadgeView(
|
AvatarBadgeView(
|
||||||
systemName: avatarSystemName,
|
systemName: avatarSystemName,
|
||||||
accentColor: theme.accentColor,
|
accentColor: theme.accentColor,
|
||||||
photoData: photoData
|
photoData: photoData,
|
||||||
|
borderColor: textColor
|
||||||
)
|
)
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
Text(displayName).font(.headline).bold().foregroundStyle(Color.Text.inverted)
|
Text(displayName).font(.headline).bold().foregroundStyle(textColor)
|
||||||
Text(role).font(.subheadline).foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.almostFull))
|
Text(role).font(.subheadline).foregroundStyle(textColor.opacity(Design.Opacity.almostFull))
|
||||||
Text(company).font(.caption).foregroundStyle(Color.Text.inverted.opacity(Design.Opacity.medium))
|
Text(company).font(.caption).foregroundStyle(textColor.opacity(Design.Opacity.medium))
|
||||||
}
|
}
|
||||||
Spacer(minLength: Design.Spacing.small)
|
Spacer(minLength: Design.Spacing.small)
|
||||||
LabelBadgeView(label: label, accentColor: theme.accentColor)
|
LabelBadgeView(label: label, accentColor: theme.accentColor, textColor: textColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
|
|||||||
@ -8,17 +8,20 @@ struct AvatarBadgeView: View {
|
|||||||
let accentColor: Color
|
let accentColor: Color
|
||||||
let photoData: Data?
|
let photoData: Data?
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
|
let borderColor: Color
|
||||||
|
|
||||||
init(
|
init(
|
||||||
systemName: String = "person.crop.circle",
|
systemName: String = "person.crop.circle",
|
||||||
accentColor: Color = Color.Accent.red,
|
accentColor: Color = Color.Accent.red,
|
||||||
photoData: Data? = nil,
|
photoData: Data? = nil,
|
||||||
size: CGFloat = Design.CardSize.avatarSize
|
size: CGFloat = Design.CardSize.avatarSize,
|
||||||
|
borderColor: Color = Color.Text.inverted
|
||||||
) {
|
) {
|
||||||
self.systemName = systemName
|
self.systemName = systemName
|
||||||
self.accentColor = accentColor
|
self.accentColor = accentColor
|
||||||
self.photoData = photoData
|
self.photoData = photoData
|
||||||
self.size = size
|
self.size = size
|
||||||
|
self.borderColor = borderColor
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -30,11 +33,11 @@ struct AvatarBadgeView: View {
|
|||||||
.clipShape(.circle)
|
.clipShape(.circle)
|
||||||
.overlay(
|
.overlay(
|
||||||
Circle()
|
Circle()
|
||||||
.stroke(Color.Text.inverted.opacity(Design.Opacity.medium), lineWidth: Design.LineWidth.thin)
|
.stroke(borderColor.opacity(Design.Opacity.medium), lineWidth: Design.LineWidth.thin)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(Color.Text.inverted)
|
.fill(borderColor)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.overlay(
|
.overlay(
|
||||||
Image(systemName: systemName)
|
Image(systemName: systemName)
|
||||||
@ -42,7 +45,7 @@ struct AvatarBadgeView: View {
|
|||||||
)
|
)
|
||||||
.overlay(
|
.overlay(
|
||||||
Circle()
|
Circle()
|
||||||
.stroke(Color.Text.inverted.opacity(Design.Opacity.medium), lineWidth: Design.LineWidth.thin)
|
.stroke(borderColor.opacity(Design.Opacity.medium), lineWidth: Design.LineWidth.thin)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +61,8 @@ struct AvatarBadgeView: View {
|
|||||||
AvatarBadgeView(
|
AvatarBadgeView(
|
||||||
systemName: "briefcase.fill",
|
systemName: "briefcase.fill",
|
||||||
accentColor: Color.CardPalette.ocean,
|
accentColor: Color.CardPalette.ocean,
|
||||||
size: 80
|
size: 80,
|
||||||
|
borderColor: Color.Text.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import SwiftUI
|
|||||||
import Bedrock
|
import Bedrock
|
||||||
|
|
||||||
/// A row with an icon and text, used for contact info display.
|
/// A row with an icon and text, used for contact info display.
|
||||||
/// Supports both inverted (on dark) and standard (on light) styles.
|
/// Accepts a text color for flexibility with different background themes.
|
||||||
struct IconRowView: View {
|
struct IconRowView: View {
|
||||||
let systemImage: String
|
let systemImage: String
|
||||||
let text: String
|
let text: String
|
||||||
var inverted: Bool = true
|
var textColor: Color = Color.Text.inverted
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: Design.Spacing.xSmall) {
|
HStack(spacing: Design.Spacing.xSmall) {
|
||||||
@ -19,17 +19,13 @@ struct IconRowView: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var textColor: Color {
|
|
||||||
inverted ? Color.Text.inverted : Color.Text.primary
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||||
IconRowView(systemImage: "envelope", text: "hello@example.com")
|
IconRowView(systemImage: "envelope", text: "hello@example.com")
|
||||||
IconRowView(systemImage: "phone", text: "+1 555 123 4567")
|
IconRowView(systemImage: "phone", text: "+1 555 123 4567")
|
||||||
IconRowView(systemImage: "link", text: "example.com", inverted: false)
|
IconRowView(systemImage: "link", text: "example.com", textColor: Color.Text.primary)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.CardPalette.midnight)
|
.background(Color.CardPalette.midnight)
|
||||||
|
|||||||
@ -5,12 +5,13 @@ import Bedrock
|
|||||||
struct LabelBadgeView: View {
|
struct LabelBadgeView: View {
|
||||||
let label: String
|
let label: String
|
||||||
let accentColor: Color
|
let accentColor: Color
|
||||||
|
var textColor: Color = Color.Text.inverted
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(String.localized(label))
|
Text(String.localized(label))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.bold()
|
.bold()
|
||||||
.foregroundStyle(Color.Text.inverted)
|
.foregroundStyle(textColor)
|
||||||
.padding(.horizontal, Design.Spacing.small)
|
.padding(.horizontal, Design.Spacing.small)
|
||||||
.padding(.vertical, Design.Spacing.xxSmall)
|
.padding(.vertical, Design.Spacing.xxSmall)
|
||||||
.background(accentColor.opacity(Design.Opacity.medium))
|
.background(accentColor.opacity(Design.Opacity.medium))
|
||||||
@ -21,7 +22,7 @@ struct LabelBadgeView: View {
|
|||||||
#Preview {
|
#Preview {
|
||||||
HStack {
|
HStack {
|
||||||
LabelBadgeView(label: "Work", accentColor: Color.CardPalette.coral)
|
LabelBadgeView(label: "Work", accentColor: Color.CardPalette.coral)
|
||||||
LabelBadgeView(label: "Personal", accentColor: Color.CardPalette.ocean)
|
LabelBadgeView(label: "Personal", accentColor: Color.CardPalette.ocean, textColor: Color.Text.primary)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.CardPalette.midnight)
|
.background(Color.CardPalette.midnight)
|
||||||
|
|||||||
@ -81,6 +81,8 @@ struct ShareCardView: View {
|
|||||||
|
|
||||||
private struct QRCodeCardView: View {
|
private struct QRCodeCardView: View {
|
||||||
let card: BusinessCard
|
let card: BusinessCard
|
||||||
|
|
||||||
|
private var textColor: Color { card.theme.textColor }
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
@ -92,7 +94,7 @@ private struct QRCodeCardView: View {
|
|||||||
|
|
||||||
Text("Point your camera at the QR code to receive the card")
|
Text("Point your camera at the QR code to receive the card")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(Color.Text.secondary)
|
.foregroundStyle(textColor.opacity(Design.Opacity.strong))
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
}
|
}
|
||||||
.padding(Design.Spacing.large)
|
.padding(Design.Spacing.large)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user