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
```