diff --git a/BusinessCard.xcodeproj/project.pbxproj b/BusinessCard.xcodeproj/project.pbxproj index b6fa068..66adfb6 100644 --- a/BusinessCard.xcodeproj/project.pbxproj +++ b/BusinessCard.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + EA69DC822F3C199C00592220 /* Bedrock in Frameworks */ = {isa = PBXBuildFile; productRef = EA69DC812F3C199C00592220 /* Bedrock */; }; EA837E672F107D6800077F87 /* Bedrock in Frameworks */ = {isa = PBXBuildFile; productRef = EA837E662F107D6800077F87 /* Bedrock */; }; EAAE892A2F12DE110075BC8A /* BusinessCardWatch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = EA837F982F11B16400077F87 /* BusinessCardWatch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; EACLIP0012F200000000001 /* BusinessCardClip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = EACLIP0012F200000000002 /* BusinessCardClip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -135,6 +136,7 @@ buildActionMask = 2147483647; files = ( EA837E672F107D6800077F87 /* Bedrock in Frameworks */, + EA69DC822F3C199C00592220 /* Bedrock in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -236,6 +238,7 @@ name = BusinessCard; packageProductDependencies = ( EA837E662F107D6800077F87 /* Bedrock */, + EA69DC812F3C199C00592220 /* Bedrock */, ); productName = BusinessCard; productReference = EA8379232F105F2600077F87 /* BusinessCard.app */; @@ -372,7 +375,7 @@ mainGroup = EA83791A2F105F2600077F87; minimizedProjectReferenceProxies = 1; packageReferences = ( - EA837E652F107D6800077F87 /* XCLocalSwiftPackageReference "../Frameworks/Bedrock" */, + EA69DC802F3C199C00592220 /* XCLocalSwiftPackageReference "../Bedrock" */, ); preferredProjectObjectVersion = 77; productRefGroup = EA8379242F105F2600077F87 /* Products */; @@ -524,7 +527,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = 6R7KLBPBLZ; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -589,7 +592,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = 6R7KLBPBLZ; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -628,8 +631,8 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "BusinessCard uses your photo library to add a profile photo to your business card."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - "INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -665,8 +668,8 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "BusinessCard uses your photo library to add a profile photo to your business card."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - "INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -852,8 +855,8 @@ INFOPLIST_KEY_CFBundleDisplayName = BusinessCard; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - "INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -890,8 +893,8 @@ INFOPLIST_KEY_CFBundleDisplayName = BusinessCard; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_BackgroundColor = LaunchBackground; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - "INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 26.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -973,13 +976,17 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - EA837E652F107D6800077F87 /* XCLocalSwiftPackageReference "../Frameworks/Bedrock" */ = { + EA69DC802F3C199C00592220 /* XCLocalSwiftPackageReference "../Bedrock" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ../Frameworks/Bedrock; + relativePath = ../Bedrock; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + EA69DC812F3C199C00592220 /* Bedrock */ = { + isa = XCSwiftPackageProductDependency; + productName = Bedrock; + }; EA837E662F107D6800077F87 /* Bedrock */ = { isa = XCSwiftPackageProductDependency; productName = Bedrock; diff --git a/BusinessCard.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist b/BusinessCard.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist index 849d755..4b8d1ac 100644 --- a/BusinessCard.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/BusinessCard.xcodeproj/xcuserdata/mattbruce.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,17 +7,17 @@ BusinessCard.xcscheme_^#shared#^_ orderHint - 1 + 2 BusinessCardClip.xcscheme_^#shared#^_ orderHint - 2 + 0 BusinessCardWatch Watch App.xcscheme_^#shared#^_ orderHint - 3 + 1 diff --git a/BusinessCard/Assets.xcassets/AppBackgroundAccent.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppBackgroundAccent.colorset/Contents.json new file mode 100644 index 0000000..46886a4 --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppBackgroundAccent.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.860", + "green" : "0.910", + "red" : "0.950" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.160", + "green" : "0.190", + "red" : "0.230" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppBackgroundBase.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppBackgroundBase.colorset/Contents.json new file mode 100644 index 0000000..820d3cd --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppBackgroundBase.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.940", + "green" : "0.960", + "red" : "0.970" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.160", + "green" : "0.130", + "red" : "0.110" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppBackgroundElevated.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppBackgroundElevated.colorset/Contents.json new file mode 100644 index 0000000..12458a1 --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppBackgroundElevated.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.250", + "green" : "0.200", + "red" : "0.180" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppBackgroundSecondary.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppBackgroundSecondary.colorset/Contents.json new file mode 100644 index 0000000..ade7426 --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppBackgroundSecondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.950", + "green" : "0.950", + "red" : "0.950" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.200", + "green" : "0.160", + "red" : "0.140" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppTextInverted.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppTextInverted.colorset/Contents.json new file mode 100644 index 0000000..ab1b67c --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppTextInverted.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.980", + "green" : "0.980", + "red" : "0.980" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.170", + "green" : "0.140", + "red" : "0.120" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppTextPrimary.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppTextPrimary.colorset/Contents.json new file mode 100644 index 0000000..91835d9 --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppTextPrimary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.170", + "green" : "0.140", + "red" : "0.140" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.960", + "green" : "0.940", + "red" : "0.930" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppTextSecondary.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppTextSecondary.colorset/Contents.json new file mode 100644 index 0000000..3c8c165 --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppTextSecondary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.400", + "green" : "0.340", + "red" : "0.320" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.820", + "green" : "0.770", + "red" : "0.740" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/AppTextTertiary.colorset/Contents.json b/BusinessCard/Assets.xcassets/AppTextTertiary.colorset/Contents.json new file mode 100644 index 0000000..c885ab8 --- /dev/null +++ b/BusinessCard/Assets.xcassets/AppTextTertiary.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.620", + "green" : "0.580", + "red" : "0.560" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.700", + "green" : "0.640", + "red" : "0.600" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/ShareSheetBackground.colorset/Contents.json b/BusinessCard/Assets.xcassets/ShareSheetBackground.colorset/Contents.json new file mode 100644 index 0000000..eb7da9b --- /dev/null +++ b/BusinessCard/Assets.xcassets/ShareSheetBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.970", + "green" : "0.950", + "red" : "0.940" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.230", + "green" : "0.200", + "red" : "0.180" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/ShareSheetCardBackground.colorset/Contents.json b/BusinessCard/Assets.xcassets/ShareSheetCardBackground.colorset/Contents.json new file mode 100644 index 0000000..5bfc227 --- /dev/null +++ b/BusinessCard/Assets.xcassets/ShareSheetCardBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.990", + "red" : "0.980" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.300", + "green" : "0.260", + "red" : "0.240" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/ShareSheetRowBackground.colorset/Contents.json b/BusinessCard/Assets.xcassets/ShareSheetRowBackground.colorset/Contents.json new file mode 100644 index 0000000..1d7e877 --- /dev/null +++ b/BusinessCard/Assets.xcassets/ShareSheetRowBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.920", + "green" : "0.880", + "red" : "0.860" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.360", + "green" : "0.320", + "red" : "0.300" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/ShareSheetSecondaryText.colorset/Contents.json b/BusinessCard/Assets.xcassets/ShareSheetSecondaryText.colorset/Contents.json new file mode 100644 index 0000000..8893f17 --- /dev/null +++ b/BusinessCard/Assets.xcassets/ShareSheetSecondaryText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.470", + "green" : "0.410", + "red" : "0.380" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.750", + "green" : "0.720", + "red" : "0.700" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/Assets.xcassets/ShareSheetText.colorset/Contents.json b/BusinessCard/Assets.xcassets/ShareSheetText.colorset/Contents.json new file mode 100644 index 0000000..a68bd6a --- /dev/null +++ b/BusinessCard/Assets.xcassets/ShareSheetText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.170", + "green" : "0.140", + "red" : "0.120" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.970", + "green" : "0.960", + "red" : "0.960" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BusinessCard/BusinessCardApp.swift b/BusinessCard/BusinessCardApp.swift index bbffc7e..370cb68 100644 --- a/BusinessCard/BusinessCardApp.swift +++ b/BusinessCard/BusinessCardApp.swift @@ -9,6 +9,15 @@ struct BusinessCardApp: App { init() { let schema = Schema([BusinessCard.self, Contact.self, ContactField.self]) + + // Register app theme for Bedrock semantic text/surface colors. + Theme.register( + text: AppThemeText.self, + surface: AppSurface.self, + accent: AppThemeAccent.self, + status: AppStatus.self + ) + Theme.register(border: AppBorder.self) // Primary strategy: App Group for watch sync (without CloudKit for now) // CloudKit can be enabled once properly configured in Xcode @@ -80,7 +89,7 @@ struct BusinessCardApp: App { AppLaunchView(config: .businessCard) { RootTabView() .environment(appState) - .preferredColorScheme(.light) + .preferredColorScheme(appState.preferredColorScheme) } } } diff --git a/BusinessCard/Design/BusinessCardTheme.swift b/BusinessCard/Design/BusinessCardTheme.swift index 2f5b043..400ef41 100644 --- a/BusinessCard/Design/BusinessCardTheme.swift +++ b/BusinessCard/Design/BusinessCardTheme.swift @@ -2,8 +2,8 @@ // BusinessCardTheme.swift // BusinessCard // -// App-specific theme conforming to Bedrock's color protocols. -// This light theme uses warm, professional tones. +// App-specific adaptive theme conforming to Bedrock's color protocols. +// Uses warm light colors and deep slate dark colors. // import SwiftUI @@ -11,39 +11,39 @@ import Bedrock // MARK: - Surface Colors -/// Surface colors with warm off-white tones for a professional light theme. +/// Surface colors with warm off-white light tones and deep slate dark tones. public enum BusinessCardSurfaceColors: SurfaceColorProvider { - /// Primary background - warm off-white base - public static let primary = Color(red: 0.97, green: 0.96, blue: 0.94) - + /// Primary background + public static let primary = Color.AppBackground.base + /// Secondary/elevated surface - public static let secondary = Color(red: 0.95, green: 0.95, blue: 0.95) - + public static let secondary = Color.AppBackground.secondary + /// Tertiary/card surface - most elevated - public static let tertiary = Color(red: 1.0, green: 1.0, blue: 1.0) - + public static let tertiary = Color.AppBackground.elevated + /// Overlay background (for sheets/modals) - public static let overlay = Color(red: 0.97, green: 0.96, blue: 0.94) - + public static let overlay = Color.AppBackground.base + /// Card/grouped element background - public static let card = Color(red: 1.0, green: 1.0, blue: 1.0) - + public static let card = Color.AppBackground.card + /// Subtle fill for grouped content sections - public static let groupedFill = Color(red: 0.95, green: 0.94, blue: 0.92) - + public static let groupedFill = Color.AppBackground.accent + /// Section fill for list sections - public static let sectionFill = Color(red: 0.93, green: 0.92, blue: 0.90) + public static let sectionFill = Color.AppBackground.secondary } // MARK: - Text Colors public enum BusinessCardTextColors: TextColorProvider { - public static let primary = Color(red: 0.14, green: 0.14, blue: 0.17) - public static let secondary = Color(red: 0.32, green: 0.34, blue: 0.40) - public static let tertiary = Color(red: 0.56, green: 0.58, blue: 0.62) - public static let disabled = Color(red: 0.70, green: 0.72, blue: 0.75) - public static let placeholder = Color(red: 0.60, green: 0.62, blue: 0.66) - public static let inverse = Color(red: 0.98, green: 0.98, blue: 0.98) + public static let primary = Color.AppText.primary + public static let secondary = Color.AppText.secondary + public static let tertiary = Color.AppText.tertiary + public static let disabled = Color.AppText.tertiary.opacity(Design.Opacity.strong) + public static let placeholder = Color.AppText.tertiary + public static let inverse = Color.AppText.inverted } // MARK: - Accent Colors @@ -84,9 +84,9 @@ public enum BusinessCardStatusColors: StatusColorProvider { // MARK: - Border Colors public enum BusinessCardBorderColors: BorderColorProvider { - public static let subtle = Color(red: 0.14, green: 0.14, blue: 0.17).opacity(Design.Opacity.subtle) - public static let standard = Color(red: 0.14, green: 0.14, blue: 0.17).opacity(Design.Opacity.hint) - public static let emphasized = Color(red: 0.14, green: 0.14, blue: 0.17).opacity(Design.Opacity.light) + public static let subtle = Color.AppText.tertiary.opacity(Design.Opacity.subtle) + public static let standard = Color.AppText.tertiary.opacity(Design.Opacity.hint) + public static let emphasized = Color.AppText.secondary.opacity(Design.Opacity.light) public static let selected = BusinessCardAccentColors.primary.opacity(Design.Opacity.medium) } diff --git a/BusinessCard/Design/DesignConstants.swift b/BusinessCard/Design/DesignConstants.swift index 81b79ff..f7d49b1 100644 --- a/BusinessCard/Design/DesignConstants.swift +++ b/BusinessCard/Design/DesignConstants.swift @@ -105,15 +105,14 @@ extension Design.Shadow { /// BusinessCard's light theme color palette. /// Uses warm, professional tones suitable for a business card app. extension Color { - - // MARK: - App Backgrounds (Light Theme) + // MARK: - App Backgrounds 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) + static let base = Color("AppBackgroundBase") + static let secondary = Color("AppBackgroundSecondary") + static let elevated = Color("AppBackgroundElevated") + static let card = elevated + static let accent = Color("AppBackgroundAccent") } // MARK: - Card Theme Palette @@ -142,13 +141,13 @@ extension Color { static let slate = Color(red: 0.29, green: 0.33, blue: 0.4) } - // MARK: - App Text Colors (Light Theme) + // MARK: - App Text Colors enum AppText { - static let primary = Color(red: 0.14, green: 0.14, blue: 0.17) - static let secondary = Color(red: 0.32, green: 0.34, blue: 0.4) - static let tertiary = Color(red: 0.56, green: 0.58, blue: 0.62) - static let inverted = Color(red: 0.98, green: 0.98, blue: 0.98) + static let primary = Color("AppTextPrimary") + static let secondary = Color("AppTextSecondary") + static let tertiary = Color("AppTextTertiary") + static let inverted = Color("AppTextInverted") } // MARK: - Badge Colors @@ -158,14 +157,14 @@ extension Color { static let neutral = Color(red: 0.89, green: 0.89, blue: 0.9) } - // MARK: - Share Sheet Dark Theme + // MARK: - Share Sheet Theme enum ShareSheet { - static let background = Color(red: 0.18, green: 0.20, blue: 0.23) - static let cardBackground = Color(red: 0.24, green: 0.26, blue: 0.30) - static let rowBackground = Color(red: 0.30, green: 0.32, blue: 0.36) - static let text = Color(red: 0.96, green: 0.96, blue: 0.97) - static let secondaryText = Color(red: 0.70, green: 0.72, blue: 0.75) + static let background = Color("ShareSheetBackground") + static let cardBackground = Color("ShareSheetCardBackground") + static let rowBackground = Color("ShareSheetRowBackground") + static let text = Color("ShareSheetText") + static let secondaryText = Color("ShareSheetSecondaryText") } // MARK: - Social Media Brand Colors diff --git a/BusinessCard/State/AppState.swift b/BusinessCard/State/AppState.swift index 7f705fd..655c7e2 100644 --- a/BusinessCard/State/AppState.swift +++ b/BusinessCard/State/AppState.swift @@ -1,19 +1,53 @@ import Foundation import Observation import SwiftData +import SwiftUI + +enum AppAppearance: String, CaseIterable, Sendable { + case system + case light + case dark + + var preferredColorScheme: ColorScheme? { + switch self { + case .system: nil + case .light: .light + case .dark: .dark + } + } +} @Observable @MainActor final class AppState { + private enum DefaultsKey { + static let appearance = "appAppearance" + } + var selectedTab: AppTab = .cards var cardStore: CardStore var contactsStore: ContactsStore let shareLinkService: ShareLinkProviding + var appearance: AppAppearance { + didSet { + UserDefaults.standard.set(appearance.rawValue, forKey: DefaultsKey.appearance) + } + } + + var preferredColorScheme: ColorScheme? { + appearance.preferredColorScheme + } init(modelContext: ModelContext) { self.cardStore = CardStore(modelContext: modelContext) self.contactsStore = ContactsStore(modelContext: modelContext) self.shareLinkService = ShareLinkService() + if let rawValue = UserDefaults.standard.string(forKey: DefaultsKey.appearance), + let savedAppearance = AppAppearance(rawValue: rawValue) { + self.appearance = savedAppearance + } else { + self.appearance = .system + } // Clean up expired shared cards on launch (best-effort, non-blocking) Task { diff --git a/BusinessCard/Views/BusinessCardView.swift b/BusinessCard/Views/BusinessCardView.swift index 9952cb4..01f7c1b 100644 --- a/BusinessCard/Views/BusinessCardView.swift +++ b/BusinessCard/Views/BusinessCardView.swift @@ -76,9 +76,9 @@ private struct ProfileBannerContent: View { VStack(spacing: Design.Spacing.xSmall) { Image(systemName: "person.fill") - .font(.system(size: Design.BaseFontSize.display, weight: .bold)) + .typography(.title2Bold) Text("Profile") - .font(.title3) + .typography(.title3) .bold() } .foregroundStyle(card.theme.textColor.opacity(Design.Opacity.medium)) @@ -111,9 +111,9 @@ private struct LogoBannerContent: View { } else { VStack(spacing: Design.Spacing.xSmall) { Image(systemName: "building.2.fill") - .font(.system(size: Design.BaseFontSize.display, weight: .bold)) + .typography(.title2Bold) Text("Logo") - .font(.title3) + .typography(.title3) .bold() } .foregroundStyle(card.theme.textColor.opacity(Design.Opacity.medium)) @@ -146,9 +146,9 @@ private struct CoverBannerContent: View { VStack(spacing: Design.Spacing.xSmall) { Image(systemName: "photo.fill") - .font(.system(size: Design.BaseFontSize.display, weight: .bold)) + .typography(.title2Bold) Text("Cover") - .font(.title3) + .typography(.title3) .bold() } .foregroundStyle(card.theme.textColor.opacity(Design.Opacity.medium)) @@ -173,28 +173,28 @@ private struct CardContentView: View { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { HStack(spacing: Design.Spacing.xSmall) { Text(card.fullName) - .font(.title2) + .typography(.title2) .bold() .foregroundStyle(textColor) if !card.pronouns.isEmpty { Text("(\(card.pronouns))") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) } } Text(card.role) - .font(.headline) + .typography(.heading) .foregroundStyle(textColor) Text(card.company) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) if !card.headline.isEmpty { Text(card.headline) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) .padding(.top, Design.Spacing.xxSmall) } @@ -249,7 +249,7 @@ private struct ProfileAvatarView: View { .scaledToFill() } else { Image(systemName: card.avatarSystemName) - .font(.system(size: Design.BaseFontSize.title)) + .typography(.title3) .foregroundStyle(card.theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(card.theme.accentColor) @@ -277,9 +277,9 @@ private struct LogoBadgeView: View { } else { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "building.2") - .font(.system(size: Design.BaseFontSize.body)) + .typography(.body) Text("Logo") - .font(.caption2) + .typography(.caption2) } .foregroundStyle(card.theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -310,9 +310,9 @@ private struct LogoRectangleView: View { } else { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "building.2") - .font(.system(size: Design.BaseFontSize.body)) + .typography(.body) Text("Logo") - .font(.caption2) + .typography(.caption2) } .foregroundStyle(card.theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -354,7 +354,7 @@ private struct ContactFieldRowView: View { Button(action: action) { HStack(alignment: .top, spacing: Design.Spacing.medium) { field.iconImage() - .font(.body) + .typography(.body) .foregroundStyle(.white) .frame(width: Design.CardSize.socialIconSize, height: Design.CardSize.socialIconSize) .background(themeColor) @@ -362,12 +362,12 @@ private struct ContactFieldRowView: View { VStack(alignment: .leading, spacing: 0) { Text(field.displayValue) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) .multilineTextAlignment(.leading) Text(field.title.isEmpty ? field.displayName : field.title) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) .lineLimit(1) } @@ -375,7 +375,7 @@ private struct ContactFieldRowView: View { Spacer() Image(systemName: "chevron.right") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } .contentShape(.rect) diff --git a/BusinessCard/Views/CardEditorView.swift b/BusinessCard/Views/CardEditorView.swift index 2a9f2dd..f790cab 100644 --- a/BusinessCard/Views/CardEditorView.swift +++ b/BusinessCard/Views/CardEditorView.swift @@ -408,7 +408,7 @@ private struct CustomColorSwatch: View { // Center icon to indicate it's a picker if customColor == nil { Image(systemName: "eyedropper") - .font(.caption) + .typography(.caption) .foregroundStyle(.white) .shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusSmall) } @@ -443,7 +443,7 @@ private struct CustomColorPickerSheet: View { .frame(height: Design.CardSize.bannerHeight) .overlay( Text("Preview") - .font(.headline) + .typography(.heading) .foregroundStyle(selectedColor.contrastingTextColor) ) .padding(.horizontal, Design.Spacing.large) @@ -572,24 +572,24 @@ private struct ImageLayoutRow: View { Button(action: onSelectLayout) { HStack(spacing: Design.Spacing.medium) { Image(systemName: selectedHeaderLayout.iconName) - .font(.title3) + .typography(.title3) .foregroundStyle(Color.accentColor) .frame(width: Design.CardSize.socialIconSize) VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text("Header Layout") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) Text(selectedHeaderLayout.displayName) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) } Spacer() Image(systemName: "chevron.right") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } .padding(.vertical, Design.Spacing.xSmall) @@ -654,9 +654,9 @@ private struct EditorBannerPreviewView: View { VStack(spacing: Design.Spacing.xSmall) { Image(systemName: "person.fill") - .font(.system(size: Design.BaseFontSize.display, weight: .bold)) + .typography(.title2Bold) Text("Profile") - .font(.title3) + .typography(.title3) .bold() } .foregroundStyle(selectedTheme.textColor.opacity(Design.Opacity.medium)) @@ -678,9 +678,9 @@ private struct EditorBannerPreviewView: View { } else { VStack(spacing: Design.Spacing.xSmall) { Image(systemName: "building.2.fill") - .font(.system(size: Design.BaseFontSize.display, weight: .bold)) + .typography(.title2Bold) Text("Logo") - .font(.title3) + .typography(.title3) .bold() } .foregroundStyle(selectedTheme.textColor.opacity(Design.Opacity.medium)) @@ -703,9 +703,9 @@ private struct EditorBannerPreviewView: View { VStack(spacing: Design.Spacing.xSmall) { Image(systemName: "photo.fill") - .font(.system(size: Design.BaseFontSize.display, weight: .bold)) + .typography(.title2Bold) Text("Cover") - .font(.title3) + .typography(.title3) .bold() } .foregroundStyle(selectedTheme.textColor.opacity(Design.Opacity.medium)) @@ -742,7 +742,7 @@ private struct EditorLogoBadgeView: View { .padding(Design.Spacing.small) } else { Image(systemName: "building.2") - .font(.system(size: Design.BaseFontSize.title)) + .typography(.title3) .foregroundStyle(theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) } @@ -781,7 +781,7 @@ private struct EditorLogoRectangleView: View { .padding(Design.Spacing.small) } else { Image(systemName: "building.2") - .font(.system(size: Design.BaseFontSize.title)) + .typography(.title3) .foregroundStyle(theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) } @@ -859,24 +859,24 @@ private struct ImageActionRow: View { Button(action: onTap) { HStack(spacing: Design.Spacing.medium) { Image(systemName: systemImage) - .font(.title3) + .typography(.title3) .foregroundStyle(hasImage ? Color.accentColor : Color.Text.secondary) .frame(width: Design.CardSize.socialIconSize) VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(title) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) Text(subtitle) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) } Spacer() Image(systemName: "chevron.right") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } .padding(.vertical, Design.Spacing.xSmall) @@ -909,7 +909,7 @@ private struct ProfilePhotoView: View { .scaledToFill() } else { Image(systemName: avatarSystemName) - .font(.title) + .typography(.title) .foregroundStyle(theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(theme.accentColor) @@ -933,7 +933,7 @@ private struct ContactFieldRowView: View { HStack(spacing: Design.Spacing.medium) { // Drag handle Image(systemName: "line.3.horizontal") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.tertiary) .accessibilityHidden(true) @@ -943,19 +943,19 @@ private struct ContactFieldRowView: View { .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .overlay( field.fieldType.iconImage() - .font(.title3) + .typography(.title3) .foregroundStyle(.white) ) // Content VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(field.value.isEmpty ? field.fieldType.valuePlaceholder : field.shortDisplayValue) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(field.value.isEmpty ? Color.Text.secondary : Color.Text.primary) .lineLimit(1) Text(field.title.isEmpty ? field.fieldType.displayName : field.title) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) .lineLimit(1) } @@ -992,7 +992,7 @@ private struct AccreditationsRow: View { addAccreditation() } label: { Image(systemName: "plus.circle.fill") - .font(.title2) + .typography(.title2) .foregroundStyle(Color.accentColor) } .buttonStyle(.plain) @@ -1006,12 +1006,12 @@ private struct AccreditationsRow: View { ForEach(accreditationsList, id: \.self) { tag in HStack(spacing: Design.Spacing.xSmall) { Text(tag) - .font(.subheadline) + .typography(.subheading) Button { removeAccreditation(tag) } label: { Image(systemName: "xmark.circle.fill") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.secondary) } .buttonStyle(.plain) @@ -1052,7 +1052,7 @@ private struct PreviewCardButton: View { var body: some View { Button(action: action) { Text("Preview card") - .font(.headline) + .typography(.heading) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(Design.Spacing.medium) diff --git a/BusinessCard/Views/CardsHomeView.swift b/BusinessCard/Views/CardsHomeView.swift index f7ce4c1..5b179eb 100644 --- a/BusinessCard/Views/CardsHomeView.swift +++ b/BusinessCard/Views/CardsHomeView.swift @@ -118,17 +118,17 @@ private struct EmptyCardsView: View { Spacer() Image(systemName: "rectangle.stack.badge.plus") - .font(.system(size: Design.BaseFontSize.display)) + .typography(.title2) .foregroundStyle(Color.Text.secondary) VStack(spacing: Design.Spacing.small) { Text("Create your first card") - .font(.title2) + .typography(.title2) .bold() .foregroundStyle(Color.Text.primary) Text("Design and share polished digital business cards for every context.") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) .multilineTextAlignment(.center) } diff --git a/BusinessCard/Views/Components/ActionRowView.swift b/BusinessCard/Views/Components/ActionRowView.swift index fa0d7e4..793fe87 100644 --- a/BusinessCard/Views/Components/ActionRowView.swift +++ b/BusinessCard/Views/Components/ActionRowView.swift @@ -49,12 +49,12 @@ struct ActionRowContent: View { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(title) - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.primary) if let subtitle { Text(subtitle) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) } } diff --git a/BusinessCard/Views/Components/AddedContactFieldsView.swift b/BusinessCard/Views/Components/AddedContactFieldsView.swift index 3ac900f..e4d81a8 100644 --- a/BusinessCard/Views/Components/AddedContactFieldsView.swift +++ b/BusinessCard/Views/Components/AddedContactFieldsView.swift @@ -105,19 +105,19 @@ private struct FieldRowPreview: View { .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .overlay( field.fieldType.iconImage() - .font(.title3) + .typography(.title3) .foregroundStyle(.white) ) VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(field.value.isEmpty ? field.fieldType.displayName : field.shortDisplayValue) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) .lineLimit(1) if !field.title.isEmpty { Text(field.title) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) .lineLimit(1) } @@ -141,7 +141,7 @@ private struct FieldRow: View { HStack(spacing: Design.Spacing.medium) { // Drag handle Image(systemName: "line.3.horizontal") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) .frame(width: Design.Spacing.large) @@ -151,7 +151,7 @@ private struct FieldRow: View { .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .overlay( field.fieldType.iconImage() - .font(.title3) + .typography(.title3) .foregroundStyle(.white) ) @@ -159,12 +159,12 @@ private struct FieldRow: View { Button(action: onTap) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(field.value.isEmpty ? field.fieldType.valuePlaceholder : field.shortDisplayValue) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(field.value.isEmpty ? Color.Text.secondary : Color.Text.primary) .lineLimit(1) Text(field.title.isEmpty ? field.fieldType.displayName : field.title) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) .lineLimit(1) } @@ -175,7 +175,7 @@ private struct FieldRow: View { // Delete button Button(action: onDelete) { Image(systemName: "xmark.circle.fill") - .font(.title3) + .typography(.title3) .foregroundStyle(Color.Text.secondary) } .buttonStyle(.plain) diff --git a/BusinessCard/Views/Components/AddressEditorView.swift b/BusinessCard/Views/Components/AddressEditorView.swift index 93aa760..0d929f5 100644 --- a/BusinessCard/Views/Components/AddressEditorView.swift +++ b/BusinessCard/Views/Components/AddressEditorView.swift @@ -68,7 +68,7 @@ private struct AddressTextField: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { Text(label) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) TextField(placeholder, text: $text) @@ -98,7 +98,7 @@ private struct AddressTextField: View { Section("Preview") { Text(address.formattedString) - .font(.subheadline) + .typography(.subheading) } } } diff --git a/BusinessCard/Views/Components/ContactFieldPickerView.swift b/BusinessCard/Views/Components/ContactFieldPickerView.swift index b45c7e2..8c18a79 100644 --- a/BusinessCard/Views/Components/ContactFieldPickerView.swift +++ b/BusinessCard/Views/Components/ContactFieldPickerView.swift @@ -12,13 +12,13 @@ struct ContactFieldPickerView: View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { HStack { Text("Tap a field below to add it") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) Spacer() Image(systemName: "plus") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) } .padding(.horizontal, Design.Spacing.medium) @@ -52,12 +52,12 @@ private struct FieldTypeButton: View { .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .overlay( fieldType.iconImage() - .font(.title3) + .typography(.title3) .foregroundStyle(.white) ) Text(fieldType.displayName) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.primary) .multilineTextAlignment(.center) .lineLimit(2) diff --git a/BusinessCard/Views/Components/HeaderLayoutPickerView.swift b/BusinessCard/Views/Components/HeaderLayoutPickerView.swift index 57d6adf..e0583b7 100644 --- a/BusinessCard/Views/Components/HeaderLayoutPickerView.swift +++ b/BusinessCard/Views/Components/HeaderLayoutPickerView.swift @@ -88,7 +88,7 @@ struct HeaderLayoutPickerView: View { dismiss() } label: { Text("Confirm layout") - .font(.headline) + .typography(.heading) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, Design.Spacing.large) @@ -107,7 +107,7 @@ struct HeaderLayoutPickerView: View { dismiss() } label: { Image(systemName: "xmark") - .font(.body) + .typography(.body) .foregroundStyle(Color.Text.primary) } } @@ -206,9 +206,9 @@ private struct LayoutPreviewCard: View { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "person.fill") - .font(.system(size: Design.BaseFontSize.title)) + .typography(.title3) Text("Profile") - .font(.caption) + .typography(.caption) .bold() } .foregroundStyle(theme.textColor.opacity(Design.Opacity.medium)) @@ -227,9 +227,9 @@ private struct LayoutPreviewCard: View { } else { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "building.2.fill") - .font(.system(size: Design.BaseFontSize.title)) + .typography(.title3) Text("Logo") - .font(.caption) + .typography(.caption) .bold() } .foregroundStyle(theme.textColor.opacity(Design.Opacity.medium)) @@ -249,9 +249,9 @@ private struct LayoutPreviewCard: View { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "photo.fill") - .font(.system(size: Design.BaseFontSize.title)) + .typography(.title3) Text("Cover") - .font(.caption) + .typography(.caption) .bold() } .foregroundStyle(Color.Text.tertiary) @@ -351,9 +351,9 @@ private struct LayoutPreviewCard: View { } else { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "building.2") - .font(.caption) + .typography(.caption) Text("Logo") - .font(.system(size: 8)) + .typography(.caption2) } .foregroundStyle(theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -375,9 +375,9 @@ private struct LayoutPreviewCard: View { } else { VStack(spacing: Design.Spacing.xxSmall) { Image(systemName: "building.2") - .font(.caption) + .typography(.caption) Text("Logo") - .font(.system(size: 8)) + .typography(.caption2) } .foregroundStyle(theme.textColor) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -411,9 +411,9 @@ private struct LayoutBadge: View { var body: some View { HStack(spacing: Design.Spacing.xSmall) { Image(systemName: iconName) - .font(.caption2) + .typography(.caption2) Text(text) - .font(.caption2) + .typography(.caption2) .bold() } .padding(.horizontal, Design.Spacing.small) diff --git a/BusinessCard/Views/Components/IconRowView.swift b/BusinessCard/Views/Components/IconRowView.swift index 8da29c7..7863516 100644 --- a/BusinessCard/Views/Components/IconRowView.swift +++ b/BusinessCard/Views/Components/IconRowView.swift @@ -11,10 +11,10 @@ struct IconRowView: View { var body: some View { HStack(spacing: Design.Spacing.xSmall) { Image(systemName: systemImage) - .font(.caption) + .typography(.caption) .foregroundStyle(textColor.opacity(Design.Opacity.heavy)) Text(text) - .font(.caption) + .typography(.caption) .foregroundStyle(textColor) .lineLimit(1) } diff --git a/BusinessCard/Views/Components/ImageEditorFlow.swift b/BusinessCard/Views/Components/ImageEditorFlow.swift index 4b3c050..511774d 100644 --- a/BusinessCard/Views/Components/ImageEditorFlow.swift +++ b/BusinessCard/Views/Components/ImageEditorFlow.swift @@ -121,7 +121,7 @@ struct ImageEditorFlow: View { onComplete(nil) } label: { Image(systemName: "xmark") - .font(.body.bold()) + .typography(.bodyEmphasis) .foregroundStyle(Color.Text.primary) } } @@ -319,12 +319,12 @@ private struct OptionRow: View { Button(action: action) { HStack(spacing: Design.Spacing.medium) { Image(systemName: icon) - .font(.body) + .typography(.body) .foregroundStyle(isDestructive ? Color.red : Color.Text.secondary) .frame(width: Design.CardSize.socialIconSize) Text(title) - .font(.body) + .typography(.body) .foregroundStyle(isDestructive ? Color.red : Color.Text.primary) Spacer() diff --git a/BusinessCard/Views/Components/LabelBadgeView.swift b/BusinessCard/Views/Components/LabelBadgeView.swift index 4660950..3050c8b 100644 --- a/BusinessCard/Views/Components/LabelBadgeView.swift +++ b/BusinessCard/Views/Components/LabelBadgeView.swift @@ -9,7 +9,7 @@ struct LabelBadgeView: View { var body: some View { Text(String.localized(label)) - .font(.caption) + .typography(.caption) .bold() .foregroundStyle(textColor) .padding(.horizontal, Design.Spacing.small) diff --git a/BusinessCard/Views/Components/PhotoSourcePicker.swift b/BusinessCard/Views/Components/PhotoSourcePicker.swift index 09c68a7..8a49e97 100644 --- a/BusinessCard/Views/Components/PhotoSourcePicker.swift +++ b/BusinessCard/Views/Components/PhotoSourcePicker.swift @@ -116,7 +116,7 @@ struct PhotoSourcePicker: View { dismiss() } label: { Image(systemName: "xmark") - .font(.body.bold()) + .typography(.bodyEmphasis) .foregroundStyle(Color.Text.primary) } } @@ -139,12 +139,12 @@ private struct OptionRow: View { Button(action: action) { HStack(spacing: Design.Spacing.medium) { Image(systemName: icon) - .font(.body) + .typography(.body) .foregroundStyle(isDestructive ? Color.red : Color.Text.secondary) .frame(width: Design.CardSize.socialIconSize) Text(title) - .font(.body) + .typography(.body) .foregroundStyle(isDestructive ? Color.red : Color.Text.primary) Spacer() diff --git a/BusinessCard/Views/ContactDetailView.swift b/BusinessCard/Views/ContactDetailView.swift index b9caf6f..19af5bf 100644 --- a/BusinessCard/Views/ContactDetailView.swift +++ b/BusinessCard/Views/ContactDetailView.swift @@ -40,21 +40,21 @@ struct ContactDetailView: View { VStack(alignment: .leading, spacing: Design.Spacing.large) { // Name Text(contact.name.isEmpty ? String.localized("Contact") : contact.name) - .font(.largeTitle) + .typography(.hero) .bold() .foregroundStyle(Color.Text.primary) // Connection details VStack(alignment: .leading, spacing: Design.Spacing.small) { Text("Connection details") - .font(.subheadline) + .typography(.subheading) .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) + .typography(.subheading) .foregroundStyle(Color.Text.primary) } } @@ -71,7 +71,7 @@ struct ContactDetailView: View { showingAddTag = true } label: { Label(String.localized("Add tag"), systemImage: "plus") - .font(.subheadline) + .typography(.subheading) .bold() .foregroundStyle(Color.AppText.inverted) .padding(.horizontal, Design.Spacing.medium) @@ -87,7 +87,7 @@ struct ContactDetailView: View { // Notes section VStack(alignment: .leading, spacing: Design.Spacing.medium) { Text("Notes") - .font(.headline) + .typography(.heading) .bold() .foregroundStyle(Color.Text.primary) @@ -95,7 +95,7 @@ struct ContactDetailView: View { NotesEmptyState() } else { Text(contact.notes) - .font(.body) + .typography(.body) .foregroundStyle(Color.Text.secondary) .frame(maxWidth: .infinity, alignment: .leading) .padding(Design.Spacing.medium) @@ -107,7 +107,7 @@ struct ContactDetailView: View { showingAddNote = true } label: { Label(String.localized("Add note"), systemImage: "plus") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) .frame(maxWidth: .infinity) .padding(.vertical, Design.Spacing.medium) @@ -314,9 +314,9 @@ private struct ContactBannerView: View { // Initials VStack(spacing: Design.Spacing.xxSmall) { Text(String(initials.prefix(1))) - .font(.system(size: Design.BaseFontSize.display, weight: .light)) + .typography(.title2) Text(String(initials.dropFirst().prefix(1))) - .font(.system(size: Design.BaseFontSize.display, weight: .light)) + .typography(.title2) } .foregroundStyle(Color.white.opacity(Design.Opacity.accent)) } @@ -327,7 +327,7 @@ private struct ContactBannerView: View { Spacer() Button(action: onEditPhoto) { Image(systemName: contact.photoData == nil ? "camera.fill" : "pencil") - .font(.body) + .typography(.body) .foregroundStyle(.white) .padding(Design.Spacing.medium) .background(.ultraThinMaterial) @@ -354,12 +354,12 @@ private struct TagPill: View { var body: some View { HStack(spacing: Design.Spacing.xSmall) { Text(text) - .font(.subheadline) + .typography(.subheading) Button { onDelete() } label: { Image(systemName: "xmark") - .font(.caption2) + .typography(.caption2) } } .foregroundStyle(Color.Text.primary) @@ -461,7 +461,7 @@ private struct ContactFieldInfoRow: View { HStack(alignment: .top, spacing: Design.Spacing.medium) { // Icon circle field.iconImage() - .font(.body) + .typography(.body) .foregroundStyle(Color.white) .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .background(Color.CardPalette.coral) @@ -470,12 +470,12 @@ private struct ContactFieldInfoRow: View { // Text VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(field.displayValue) - .font(.body) + .typography(.body) .foregroundStyle(Color.Text.primary) .multilineTextAlignment(.leading) Text(field.title.isEmpty ? field.displayName : field.title) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } @@ -500,7 +500,7 @@ private struct ContactInfoRow: View { HStack(spacing: Design.Spacing.medium) { // Icon circle Image(systemName: icon) - .font(.body) + .typography(.body) .foregroundStyle(Color.white) .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .background(Color.CardPalette.coral) @@ -509,10 +509,10 @@ private struct ContactInfoRow: View { // Text VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(value) - .font(.body) + .typography(.body) .foregroundStyle(Color.Text.primary) Text(label) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } @@ -530,11 +530,11 @@ private struct NotesEmptyState: View { var body: some View { VStack(spacing: Design.Spacing.medium) { Image(systemName: "note.text") - .font(.system(size: Design.BaseFontSize.display)) + .typography(.title2) .foregroundStyle(Color.CardPalette.coral.opacity(Design.Opacity.medium)) Text("Write down a memorable reminder about your contact") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.tertiary) .multilineTextAlignment(.center) } @@ -556,7 +556,7 @@ private struct BottomActionBar: View { HStack(spacing: Design.Spacing.medium) { Button(action: onMore) { Text("More...") - .font(.subheadline) + .typography(.subheading) .bold() .foregroundStyle(Color.Text.primary) .frame(maxWidth: .infinity) @@ -567,7 +567,7 @@ private struct BottomActionBar: View { Button(action: onAddTag) { Text("Add tag") - .font(.subheadline) + .typography(.subheading) .bold() .foregroundStyle(Color.AppText.inverted) .frame(maxWidth: .infinity) @@ -578,7 +578,7 @@ private struct BottomActionBar: View { Button(action: onAddNote) { Text("Add note") - .font(.subheadline) + .typography(.subheading) .bold() .foregroundStyle(Color.AppText.inverted) .frame(maxWidth: .infinity) diff --git a/BusinessCard/Views/ContactsView.swift b/BusinessCard/Views/ContactsView.swift index f670e60..ef6d331 100644 --- a/BusinessCard/Views/ContactsView.swift +++ b/BusinessCard/Views/ContactsView.swift @@ -53,15 +53,15 @@ private struct EmptyContactsView: View { var body: some View { VStack(spacing: Design.Spacing.large) { Image(systemName: "person.2.slash") - .font(.system(size: Design.BaseFontSize.display)) + .typography(.title2) .foregroundStyle(Color.Text.secondary) Text("No contacts yet") - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.primary) Text("Tap + to add a contact, scan a QR code, or track who you share your card with.") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) .multilineTextAlignment(.center) .padding(.horizontal, Design.Spacing.xLarge) @@ -126,7 +126,7 @@ private struct ContactsListView: View { } } header: { Text("Shared With") - .font(.headline) + .typography(.heading) .bold() } } @@ -149,25 +149,25 @@ private struct ContactRowView: View { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { HStack(spacing: Design.Spacing.xSmall) { Text(contact.name) - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.primary) if contact.isReceivedCard { Image(systemName: "arrow.down.circle.fill") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Accent.mint) } if contact.hasFollowUp { Image(systemName: contact.isFollowUpOverdue ? "exclamationmark.circle.fill" : "clock.fill") - .font(.caption) + .typography(.caption) .foregroundStyle(contact.isFollowUpOverdue ? Color.Accent.red : Color.Accent.gold) } } if !contact.role.isEmpty || !contact.company.isEmpty { Text("\(contact.role)\(contact.role.isEmpty || contact.company.isEmpty ? "" : " ยท ")\(contact.company)") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) .lineLimit(1) } @@ -176,7 +176,7 @@ private struct ContactRowView: View { HStack(spacing: Design.Spacing.xSmall) { ForEach(contact.tagList.prefix(2), id: \.self) { tag in Text(tag) - .font(.caption2) + .typography(.caption2) .padding(.horizontal, Design.Spacing.xSmall) .padding(.vertical, Design.Spacing.xxSmall) .background(Color.AppBackground.accent) @@ -190,10 +190,10 @@ private struct ContactRowView: View { VStack(alignment: .trailing, spacing: Design.Spacing.xxSmall) { Text(relativeDate) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) Text(String.localized(contact.cardLabel)) - .font(.caption) + .typography(.caption) .padding(.horizontal, Design.Spacing.small) .padding(.vertical, Design.Spacing.xxSmall) .background(Color.AppBackground.base) @@ -218,7 +218,7 @@ private struct ContactAvatarView: View { .clipShape(.rect(cornerRadius: Design.CornerRadius.medium)) } else { Image(systemName: contact.avatarSystemName) - .font(.title2) + .typography(.title2) .foregroundStyle(Color.Accent.red) .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .background(Color.AppBackground.accent) diff --git a/BusinessCard/Views/EmptyStateView.swift b/BusinessCard/Views/EmptyStateView.swift index 91c2a50..e6fb711 100644 --- a/BusinessCard/Views/EmptyStateView.swift +++ b/BusinessCard/Views/EmptyStateView.swift @@ -8,10 +8,10 @@ struct EmptyStateView: View { var body: some View { VStack(spacing: Design.Spacing.small) { Text(title) - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.primary) Text(message) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) .multilineTextAlignment(.center) } diff --git a/BusinessCard/Views/QRScannerView.swift b/BusinessCard/Views/QRScannerView.swift index 6e160c4..c5daee4 100644 --- a/BusinessCard/Views/QRScannerView.swift +++ b/BusinessCard/Views/QRScannerView.swift @@ -106,7 +106,7 @@ private struct ScannerOverlayView: View { Spacer() Text("Point at a QR code") - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.inverted) .padding(Design.Spacing.medium) .background(Color.black.opacity(Design.Opacity.medium)) @@ -140,25 +140,25 @@ private struct ScannedResultView: View { var body: some View { VStack(spacing: Design.Spacing.xLarge) { Image(systemName: isVCard ? "person.crop.circle.badge.checkmark" : "qrcode") - .font(.system(size: Design.BaseFontSize.display * 2)) + .font(.system(size: Design.IconSize.xxxLarge)) .foregroundStyle(Color.Accent.red) if isVCard { VStack(spacing: Design.Spacing.small) { Text("Card Found!") - .font(.title2) + .typography(.title2) .bold() .foregroundStyle(Color.Text.primary) if let name = parsedName { Text(name) - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.secondary) } } } else { Text("QR Code Scanned") - .font(.title2) + .typography(.title2) .bold() .foregroundStyle(Color.Text.primary) } @@ -172,7 +172,7 @@ private struct ScannedResultView: View { .controlSize(.large) } else { Text("This doesn't appear to be a business card QR code.") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) .multilineTextAlignment(.center) .padding(.horizontal, Design.Spacing.xLarge) diff --git a/BusinessCard/Views/RootTabView.swift b/BusinessCard/Views/RootTabView.swift index 86387fe..a93e4b7 100644 --- a/BusinessCard/Views/RootTabView.swift +++ b/BusinessCard/Views/RootTabView.swift @@ -50,7 +50,7 @@ private struct FloatingShareButton: View { var body: some View { Button(action: action) { Image(systemName: "qrcode") - .font(.title2) + .typography(.title2) .fontWeight(.semibold) .foregroundStyle(.white) .frame(width: Design.CardSize.floatingButtonSize, height: Design.CardSize.floatingButtonSize) diff --git a/BusinessCard/Views/SettingsView.swift b/BusinessCard/Views/SettingsView.swift index 69acc83..e535c6b 100644 --- a/BusinessCard/Views/SettingsView.swift +++ b/BusinessCard/Views/SettingsView.swift @@ -2,13 +2,16 @@ // SettingsView.swift // BusinessCard // -// App settings screen using Bedrock components. +// App settings screen using Bedrock settings layout contract: +// SettingsCard owns horizontal inset, custom rows use SettingsCardRow, +// and in-card separators use SettingsDivider. // import SwiftUI import Bedrock struct SettingsView: View { + @Environment(AppState.self) private var appState @State private var settingsState = SettingsState() var body: some View { @@ -17,7 +20,7 @@ struct SettingsView: View { VStack(spacing: Design.Spacing.large) { // MARK: - About Section - + appearanceSection aboutSection // MARK: - Debug Section @@ -31,12 +34,44 @@ struct SettingsView: View { .padding(.horizontal, Design.Spacing.large) .padding(.top, Design.Spacing.medium) } - .background(Color(.systemGroupedBackground)) + .background(Color.AppBackground.base) .navigationTitle(String.localized("Settings")) .navigationBarTitleDisplayMode(.large) } } - + + // MARK: - Appearance Section + + private var appearanceSection: some View { + VStack(alignment: .leading, spacing: Design.Spacing.small) { + SettingsSectionHeader( + title: "Appearance", + systemImage: "paintbrush", + accentColor: AppThemeAccent.primary + ) + + SettingsCard( + backgroundColor: Color.AppBackground.secondary, + borderColor: .clear + ) { + SettingsSegmentedPicker( + title: "Theme", + subtitle: "Choose app theme", + options: [ + ("System", AppAppearance.system), + ("Light", AppAppearance.light), + ("Dark", AppAppearance.dark) + ], + selection: Binding( + get: { appState.appearance }, + set: { appState.appearance = $0 } + ), + accentColor: AppThemeAccent.primary + ) + } + } + } + // MARK: - About Section private var aboutSection: some View { @@ -48,43 +83,49 @@ struct SettingsView: View { ) SettingsCard( - backgroundColor: Color(.secondarySystemGroupedBackground), + backgroundColor: Color.AppBackground.secondary, borderColor: .clear ) { - VStack(alignment: .leading, spacing: Design.Spacing.medium) { - // App Name + SettingsCardRow { HStack { Text(settingsState.appName) - .font(.system(size: Design.BaseFontSize.title, weight: .semibold)) - .foregroundStyle(.primary) + .typography(.title3Bold) + .foregroundStyle(Color.AppText.primary) Spacer() } - - // Version + } + + SettingsDivider(color: AppBorder.subtle) + + SettingsCardRow { HStack { Text(String.localized("Version")) - .font(.system(size: Design.BaseFontSize.body, weight: .medium)) - .foregroundStyle(.primary) + .typography(.bodyEmphasis) + .foregroundStyle(Color.AppText.primary) Spacer() Text(settingsState.versionString) - .font(.system(size: Design.BaseFontSize.body, design: .monospaced)) - .foregroundStyle(.secondary) + .typography(.body) + .fontDesign(.monospaced) + .foregroundStyle(Color.AppText.secondary) } - - // Developer + } + + SettingsDivider(color: AppBorder.subtle) + + SettingsCardRow { HStack { Text(String.localized("Developer")) - .font(.system(size: Design.BaseFontSize.body, weight: .medium)) - .foregroundStyle(.primary) + .typography(.bodyEmphasis) + .foregroundStyle(Color.AppText.primary) Spacer() Text("Matt Bruce") - .font(.system(size: Design.BaseFontSize.body)) - .foregroundStyle(.secondary) + .typography(.body) + .foregroundStyle(Color.AppText.secondary) } } } @@ -103,39 +144,41 @@ struct SettingsView: View { ) SettingsCard( - backgroundColor: Color(.secondarySystemGroupedBackground), + backgroundColor: Color.AppBackground.secondary, borderColor: .clear ) { - VStack(spacing: Design.Spacing.medium) { - SettingsToggle( - title: "Enable Debug Premium", - subtitle: "Unlock all premium features for testing", - isOn: Binding( - get: { settingsState.isDebugPremiumEnabled }, - set: { settingsState.isDebugPremiumEnabled = $0 } - ), - accentColor: AppStatus.warning + SettingsToggle( + title: "Enable Debug Premium", + subtitle: "Unlock all premium features for testing", + isOn: Binding( + get: { settingsState.isDebugPremiumEnabled }, + set: { settingsState.isDebugPremiumEnabled = $0 } + ), + accentColor: AppStatus.warning + ) + + SettingsDivider(color: AppBorder.subtle) + + SettingsNavigationRow( + title: "Icon Generator", + subtitle: "Generate and save app icon to Files", + backgroundColor: .clear + ) { + IconGeneratorView(config: .businessCard, appName: "BusinessCard") + } + + SettingsDivider(color: AppBorder.subtle) + + SettingsNavigationRow( + title: "Branding Preview", + subtitle: "Preview app icon and launch screen", + backgroundColor: .clear + ) { + BrandingPreviewView( + iconConfig: .businessCard, + launchConfig: .businessCard, + appName: "BusinessCard" ) - - SettingsNavigationRow( - title: "Icon Generator", - subtitle: "Generate and save app icon to Files", - backgroundColor: Color(.tertiarySystemGroupedBackground) - ) { - IconGeneratorView(config: .businessCard, appName: "BusinessCard") - } - - SettingsNavigationRow( - title: "Branding Preview", - subtitle: "Preview app icon and launch screen", - backgroundColor: Color(.tertiarySystemGroupedBackground) - ) { - BrandingPreviewView( - iconConfig: .businessCard, - launchConfig: .businessCard, - appName: "BusinessCard" - ) - } } } } diff --git a/BusinessCard/Views/ShareCardView.swift b/BusinessCard/Views/ShareCardView.swift index 7525e78..4e2e026 100644 --- a/BusinessCard/Views/ShareCardView.swift +++ b/BusinessCard/Views/ShareCardView.swift @@ -105,7 +105,7 @@ private struct QRCodeSection: View { // Instruction text Text("Point your camera at the QR code to receive the card") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.ShareSheet.secondaryText) .multilineTextAlignment(.center) } @@ -127,10 +127,10 @@ private struct AppClipSection: View { // Header HStack { Image(systemName: "app.gift") - .font(.headline) + .typography(.heading) .foregroundStyle(Color.ShareSheet.text) Text("App Clip (includes photo)") - .font(.headline) + .typography(.heading) .foregroundStyle(Color.ShareSheet.text) } @@ -150,7 +150,7 @@ private struct AppClipSection: View { // Expiration notice Text("Expires in 7 days") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.ShareSheet.secondaryText) // Reset button @@ -158,7 +158,7 @@ private struct AppClipSection: View { appClipState.reset() } label: { Text("Generate New Link") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.ShareSheet.text) } } else { @@ -170,7 +170,7 @@ private struct AppClipSection: View { Image(systemName: "qrcode") Text("Generate App Clip Link") } - .font(.headline) + .typography(.heading) .foregroundStyle(Color.ShareSheet.background) .padding(.horizontal, Design.Spacing.xLarge) .padding(.vertical, Design.Spacing.medium) @@ -180,7 +180,7 @@ private struct AppClipSection: View { // Description Text("Creates a link that opens a mini-app for recipients to preview and save your card with photo.") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.ShareSheet.secondaryText) .multilineTextAlignment(.center) } @@ -188,7 +188,7 @@ private struct AppClipSection: View { // Error message if let error = appClipState.errorMessage { Text(error) - .font(.caption) + .typography(.caption) .foregroundStyle(.red) .multilineTextAlignment(.center) } @@ -279,15 +279,15 @@ private struct EmptyShareState: View { var body: some View { VStack(spacing: Design.Spacing.large) { Image(systemName: "rectangle.on.rectangle.slash") - .font(.system(size: Design.BaseFontSize.display)) + .typography(.title2) .foregroundStyle(Color.ShareSheet.secondaryText) Text("No card selected") - .font(.headline) + .typography(.heading) .foregroundStyle(Color.ShareSheet.text) Text("Choose a card in the My Cards tab to start sharing.") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.ShareSheet.secondaryText) .multilineTextAlignment(.center) } @@ -305,7 +305,7 @@ private struct RowContent: View { var body: some View { HStack(spacing: Design.Spacing.medium) { Image(systemName: systemImage) - .font(.body) + .typography(.body) .foregroundStyle(iconColor) .frame(width: Design.Spacing.xLarge) diff --git a/BusinessCard/Views/Sheets/AddContactSheet.swift b/BusinessCard/Views/Sheets/AddContactSheet.swift index 33dbe40..af883bc 100644 --- a/BusinessCard/Views/Sheets/AddContactSheet.swift +++ b/BusinessCard/Views/Sheets/AddContactSheet.swift @@ -280,7 +280,7 @@ private struct ContactPhotoRow: View { .scaledToFill() } else { Image(systemName: "person.crop.circle.fill") - .font(.system(size: Design.BaseFontSize.display)) + .typography(.title2) .foregroundStyle(Color.Text.tertiary) } } @@ -290,18 +290,18 @@ private struct ContactPhotoRow: View { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { Text("Profile Photo") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) Text(photoData == nil ? String.localized("Add a photo") : String.localized("Tap to change")) - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) } Spacer() Image(systemName: "chevron.right") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } .padding(.vertical, Design.Spacing.xSmall) @@ -342,7 +342,7 @@ private struct LabeledFieldRow: View { Text(entry.label) .foregroundStyle(Color.accentColor) Image(systemName: "chevron.up.chevron.down") - .font(.caption2) + .typography(.caption2) .foregroundStyle(Color.secondary) } } diff --git a/BusinessCard/Views/Sheets/ContactFieldEditorSheet.swift b/BusinessCard/Views/Sheets/ContactFieldEditorSheet.swift index 381b34a..43f07d0 100644 --- a/BusinessCard/Views/Sheets/ContactFieldEditorSheet.swift +++ b/BusinessCard/Views/Sheets/ContactFieldEditorSheet.swift @@ -76,7 +76,7 @@ struct ContactFieldEditorSheet: View { } else { VStack(alignment: .leading, spacing: Design.Spacing.small) { Text(fieldType.valueLabel) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) TextField(fieldType.valuePlaceholder, text: $value) @@ -91,7 +91,7 @@ struct ContactFieldEditorSheet: View { // Title field VStack(alignment: .leading, spacing: Design.Spacing.small) { Text("Title (optional)") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) TextField(String(localized: "e.g. Work, Personal"), text: $title) @@ -102,7 +102,7 @@ struct ContactFieldEditorSheet: View { if !fieldType.titleSuggestions.isEmpty { VStack(alignment: .leading, spacing: Design.Spacing.small) { Text("Here are some suggestions for your title:") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) FlowLayout(spacing: Design.Spacing.small) { @@ -191,12 +191,12 @@ private struct FieldHeaderView: View { .frame(width: Design.CardSize.avatarSize, height: Design.CardSize.avatarSize) .overlay( fieldType.iconImage() - .font(.title3) + .typography(.title3) .foregroundStyle(.white) ) Text(fieldType.displayName) - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.primary) Spacer() @@ -215,7 +215,7 @@ private struct SuggestionChip: View { var body: some View { Button(action: action) { Text(text) - .font(.subheadline) + .typography(.subheading) .padding(.horizontal, Design.Spacing.medium) .padding(.vertical, Design.Spacing.small) .background(Color.AppBackground.elevated) diff --git a/BusinessCard/Views/Sheets/LogoEditorSheet.swift b/BusinessCard/Views/Sheets/LogoEditorSheet.swift index 86b95e7..79f31a6 100644 --- a/BusinessCard/Views/Sheets/LogoEditorSheet.swift +++ b/BusinessCard/Views/Sheets/LogoEditorSheet.swift @@ -47,7 +47,7 @@ struct LogoEditorSheet: View { onComplete(nil) } label: { Image(systemName: "xmark") - .font(.body.bold()) + .typography(.bodyEmphasis) .foregroundStyle(Color.Text.primary) } } @@ -106,19 +106,19 @@ struct LogoEditorSheet: View { private var zoomSliderSection: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { Text("Zoom in/out") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) HStack(spacing: Design.Spacing.medium) { Image(systemName: "minus.magnifyingglass") - .font(.body) + .typography(.body) .foregroundStyle(Color.Text.tertiary) Slider(value: $zoomScale, in: minZoom...maxZoom) .tint(Color.accentColor) Image(systemName: "plus.magnifyingglass") - .font(.body) + .typography(.body) .foregroundStyle(Color.Text.tertiary) } } @@ -129,13 +129,13 @@ struct LogoEditorSheet: View { private var backgroundColorSection: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { Text("Change background color") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) HStack(spacing: Design.Spacing.large) { // Suggested label and colors Text("Suggested") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) HStack(spacing: Design.Spacing.small) { @@ -158,7 +158,7 @@ struct LogoEditorSheet: View { } label: { HStack(spacing: Design.Spacing.small) { Text("Custom color") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.primary) ColorSwatchButton( @@ -171,7 +171,7 @@ struct LogoEditorSheet: View { Spacer() Image(systemName: "chevron.right") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.tertiary) } .contentShape(.rect) diff --git a/BusinessCard/Views/Sheets/RecordContactSheet.swift b/BusinessCard/Views/Sheets/RecordContactSheet.swift index cb9b90f..405d9ee 100644 --- a/BusinessCard/Views/Sheets/RecordContactSheet.swift +++ b/BusinessCard/Views/Sheets/RecordContactSheet.swift @@ -29,7 +29,7 @@ struct RecordContactSheet: View { Section { Text("This person will appear in your Contacts tab so you can track who has your card.") - .font(.footnote) + .typography(.footnote) .foregroundStyle(Color.Text.secondary) } } diff --git a/BusinessCard/Views/WidgetsView.swift b/BusinessCard/Views/WidgetsView.swift index 477b19e..61c41f4 100644 --- a/BusinessCard/Views/WidgetsView.swift +++ b/BusinessCard/Views/WidgetsView.swift @@ -10,7 +10,7 @@ struct WidgetsView: View { ScrollView { VStack(spacing: Design.Spacing.large) { Text("Share using widgets on your phone or watch") - .font(.title2) + .typography(.title2) .bold() .foregroundStyle(Color.Text.primary) @@ -44,7 +44,7 @@ private struct PhoneWidgetPreview: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { Text("Phone Widget") - .font(.headline) + .typography(.heading) .bold() .foregroundStyle(Color.Text.primary) @@ -54,13 +54,13 @@ private struct PhoneWidgetPreview: View { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { Text(card.fullName) - .font(.headline) + .typography(.heading) .foregroundStyle(Color.Text.primary) Text(card.role) - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) Text("Tap to share") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) } } @@ -77,7 +77,7 @@ private struct WatchWidgetPreview: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { Text("Watch Widget") - .font(.headline) + .typography(.heading) .bold() .foregroundStyle(Color.Text.primary) @@ -87,10 +87,10 @@ private struct WatchWidgetPreview: View { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { Text("Ready to scan") - .font(.subheadline) + .typography(.subheading) .foregroundStyle(Color.Text.secondary) Text("Open on Apple Watch") - .font(.caption) + .typography(.caption) .foregroundStyle(Color.Text.secondary) } } diff --git a/README.md b/README.md index 67d9aff..9e374c1 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,15 @@ The app uses the [Bedrock](ssh://git@192.168.1.128:220/mbrucedogs/Bedrock.git) p App-specific extensions are in `Design/DesignConstants.swift`. +### Settings Layout Contract + +`SettingsCard` is the single owner of horizontal row inset in `SettingsView`. + +- Use `SettingsCardRow` for custom in-card rows (`HStack`, status/info rows, custom content). +- Use `SettingsDivider` between in-card rows (instead of `Divider`/manual lines). +- Use `SettingsNavigationRow(..., backgroundColor: .clear)` for standard in-card navigation rows. +- Avoid child `.padding(.horizontal, ...)` inside `SettingsCard` unless intentional indentation is required. + ## Project Structure ```