Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
499889d373
commit
8f01d78146
1
PRD.md
1
PRD.md
@ -44,6 +44,7 @@ SelfieCam is a professional-grade selfie camera app featuring a customizable scr
|
||||
1. **Protocol-Oriented Programming (POP)** - All shared capabilities defined via protocols before concrete types
|
||||
2. **MVVM-lite** - Views are "dumb" renderers; all logic lives in `@Observable` view models
|
||||
3. **Bedrock Design System** - Centralized design tokens, no magic numbers
|
||||
- Settings layout contract: `SettingsCard` owns horizontal insets, custom rows use `SettingsCardRow`, and in-card separators use `SettingsDivider`
|
||||
4. **Full Accessibility** - Dynamic Type, VoiceOver labels/hints/traits/announcements
|
||||
5. **Modern Swift & SwiftUI** - Swift 6 concurrency, `@MainActor`, modern APIs
|
||||
6. **Testable & Reusable Design** - Protocols enable mocking and future package extraction
|
||||
|
||||
@ -53,6 +53,7 @@ Perfect for low-light selfies, content creation, video calls, makeup application
|
||||
- Dynamic Type and ScaledMetric for readable text at all sizes
|
||||
- String Catalog localization ready (`.xcstrings`)
|
||||
- Consistent design system using Bedrock framework
|
||||
- Settings cards follow Bedrock’s row contract (`SettingsCard` + `SettingsCardRow` + `SettingsDivider`) for consistent insets and row rhythm
|
||||
- Prevents screen dimming during camera use
|
||||
|
||||
## Screenshots
|
||||
|
||||
@ -442,7 +442,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(PRODUCT_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
@ -472,7 +472,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(PRODUCT_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
|
||||
Binary file not shown.
@ -39,6 +39,7 @@ struct OnboardingSettingsView: View {
|
||||
isOn: $viewModel.isRingLightEnabled,
|
||||
accentColor: AppAccent.primary
|
||||
)
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Camera Position using Bedrock SettingsSegmentedPicker
|
||||
SettingsSegmentedPicker(
|
||||
|
||||
@ -43,12 +43,17 @@ struct SettingsView: View {
|
||||
accentColor: AppAccent.primary
|
||||
)
|
||||
.accessibilityHint(String(localized: "Enables or disables the ring light overlay"))
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Ring Size Slider
|
||||
ringSizeSlider
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Color Preset
|
||||
colorPresetSection
|
||||
SettingsCardRow {
|
||||
colorPresetSection
|
||||
}
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Ring Light Brightness
|
||||
ringLightBrightnessSlider
|
||||
@ -61,9 +66,11 @@ struct SettingsView: View {
|
||||
SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) {
|
||||
// Camera Position
|
||||
cameraPositionPicker
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Flash Mode
|
||||
flashModePicker
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Flash Sync (premium)
|
||||
premiumToggle(
|
||||
@ -72,9 +79,11 @@ struct SettingsView: View {
|
||||
isOn: $viewModel.isFlashSyncedWithRingLight,
|
||||
accessibilityHint: String(localized: "Syncs flash color with ring light color")
|
||||
)
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// HDR Mode
|
||||
hdrModePicker
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Center Stage (premium feature)
|
||||
premiumToggle(
|
||||
@ -83,9 +92,12 @@ struct SettingsView: View {
|
||||
isOn: $viewModel.isCenterStageEnabled,
|
||||
accessibilityHint: String(localized: "Automatically adjusts camera to keep subject centered")
|
||||
)
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Photo Quality
|
||||
photoQualityPicker
|
||||
SettingsCardRow {
|
||||
photoQualityPicker
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Display Section
|
||||
@ -101,6 +113,7 @@ struct SettingsView: View {
|
||||
accentColor: AppAccent.primary
|
||||
)
|
||||
.accessibilityHint(String(localized: "Shows a grid overlay to help compose your shot"))
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// True Mirror (premium)
|
||||
premiumToggle(
|
||||
@ -109,6 +122,7 @@ struct SettingsView: View {
|
||||
isOn: $viewModel.isMirrorFlipped,
|
||||
accessibilityHint: String(localized: "Flips the camera preview horizontally")
|
||||
)
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Skin Smoothing (premium)
|
||||
premiumToggle(
|
||||
@ -132,9 +146,12 @@ struct SettingsView: View {
|
||||
accentColor: AppAccent.primary
|
||||
)
|
||||
.accessibilityHint(String(localized: "When enabled, photos and videos are saved immediately after capture"))
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Timer Selection
|
||||
timerPicker
|
||||
SettingsCardRow {
|
||||
timerPicker
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -258,7 +275,6 @@ struct SettingsView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
}
|
||||
|
||||
// MARK: - Flash Mode Picker
|
||||
@ -585,40 +601,44 @@ struct SettingsView: View {
|
||||
// MARK: - Branding Debug Section
|
||||
|
||||
#if DEBUG
|
||||
@ViewBuilder
|
||||
private var brandingDebugSection: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Debug Premium Toggle
|
||||
SettingsToggle(
|
||||
title: "Enable Debug Premium",
|
||||
subtitle: "Unlock all premium features for testing",
|
||||
isOn: $viewModel.isDebugPremiumEnabled,
|
||||
accentColor: AppAccent.primary
|
||||
)
|
||||
|
||||
// Reset Onboarding Button
|
||||
// Debug Premium Toggle
|
||||
SettingsToggle(
|
||||
title: "Enable Debug Premium",
|
||||
subtitle: "Unlock all premium features for testing",
|
||||
isOn: $viewModel.isDebugPremiumEnabled,
|
||||
accentColor: AppAccent.primary
|
||||
)
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Reset Onboarding Button
|
||||
SettingsCardRow {
|
||||
ResetOnboardingButton()
|
||||
|
||||
// Icon Generator
|
||||
SettingsNavigationRow(
|
||||
title: "Icon Generator",
|
||||
subtitle: "Generate and save app icon to Files",
|
||||
backgroundColor: AppSurface.primary
|
||||
) {
|
||||
IconGeneratorView(config: .selfieCam, appName: "SelfieCam")
|
||||
}
|
||||
|
||||
// Branding Preview
|
||||
SettingsNavigationRow(
|
||||
title: "Branding Preview",
|
||||
subtitle: "Preview app icon and launch screen",
|
||||
backgroundColor: AppSurface.primary
|
||||
) {
|
||||
BrandingPreviewView(
|
||||
iconConfig: .selfieCam,
|
||||
launchConfig: .selfieCam,
|
||||
appName: "SelfieCam"
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Icon Generator
|
||||
SettingsNavigationRow(
|
||||
title: "Icon Generator",
|
||||
subtitle: "Generate and save app icon to Files",
|
||||
backgroundColor: .clear
|
||||
) {
|
||||
IconGeneratorView(config: .selfieCam, appName: "SelfieCam")
|
||||
}
|
||||
SettingsDivider(color: AppBorder.subtle)
|
||||
|
||||
// Branding Preview
|
||||
SettingsNavigationRow(
|
||||
title: "Branding Preview",
|
||||
subtitle: "Preview app icon and launch screen",
|
||||
backgroundColor: .clear
|
||||
) {
|
||||
BrandingPreviewView(
|
||||
iconConfig: .selfieCam,
|
||||
launchConfig: .selfieCam,
|
||||
appName: "SelfieCam"
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user