Compare commits

...

4 Commits

9 changed files with 3049 additions and 144 deletions

View File

@ -207,12 +207,13 @@
knownRegions = (
en,
Base,
"fr-CA",
);
mainGroup = EA836AB62F0ACE8A00077F87;
minimizedProjectReferenceProxies = 1;
packageReferences = (
EA836AEE2F0AD00000077F87 /* XCRemoteSwiftPackageReference "purchases-ios-spm" */,
EA836AF72F0AD00000077F87 /* XCLocalSwiftPackageReference "Bedrock" */,
EA836AF72F0AD00000077F87 /* XCLocalSwiftPackageReference "../Bedrock" */,
EA836AF82F0AD00000077F87 /* XCRemoteSwiftPackageReference "MijickCamera" */,
);
preferredProjectObjectVersion = 77;
@ -421,12 +422,13 @@
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSCameraUsageDescription = "SelfieCam needs camera access to show your selfie preview and capture photos and videos.";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SelfieCam needs microphone access to record audio with your videos.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SelfieCam needs photo library access to save your captured photos and videos.";
INFOPLIST_KEY_NSCameraUsageDescription = "SelfieCam needs camera access to display your live selfie preview, apply real-time filters and ring light effects, capture high-quality photos and videos, and enable advanced features like Center Stage auto-framing.";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SelfieCam needs microphone access to record clear audio when capturing videos, ensuring your voiceovers and ambient sounds are captured along with your video content.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SelfieCam needs photo library access to automatically save your captured photos and videos to your device, making them available in the Photos app and other compatible applications.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
@ -457,12 +459,13 @@
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSCameraUsageDescription = "SelfieCam needs camera access to show your selfie preview and capture photos and videos.";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SelfieCam needs microphone access to record audio with your videos.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SelfieCam needs photo library access to save your captured photos and videos.";
INFOPLIST_KEY_NSCameraUsageDescription = "SelfieCam needs camera access to display your live selfie preview, apply real-time filters and ring light effects, capture high-quality photos and videos, and enable advanced features like Center Stage auto-framing.";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "SelfieCam needs microphone access to record clear audio when capturing videos, ensuring your voiceovers and ambient sounds are captured along with your video content.";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "SelfieCam needs photo library access to automatically save your captured photos and videos to your device, making them available in the Photos app and other compatible applications.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
@ -607,6 +610,13 @@
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
EA836AF72F0AD00000077F87 /* XCLocalSwiftPackageReference "../Bedrock" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../Bedrock;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
EA836AEE2F0AD00000077F87 /* XCRemoteSwiftPackageReference "purchases-ios-spm" */ = {
isa = XCRemoteSwiftPackageReference;
@ -616,10 +626,6 @@
minimumVersion = 5.52.1;
};
};
EA836AF72F0AD00000077F87 /* XCLocalSwiftPackageReference "Bedrock" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../Bedrock;
};
EA836AF82F0AD00000077F87 /* XCRemoteSwiftPackageReference "MijickCamera" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "http://192.168.1.128:3000/mbrucedogs/MijickCamera";
@ -643,7 +649,7 @@
};
EA836AF32F0AD00000077F87 /* Bedrock */ = {
isa = XCSwiftPackageProductDependency;
package = EA836AF72F0AD00000077F87 /* XCLocalSwiftPackageReference "Bedrock" */;
package = EA836AF72F0AD00000077F87 /* XCLocalSwiftPackageReference "../Bedrock" */;
productName = Bedrock;
};
EA836AF52F0AD00000077F87 /* MijickCamera */ = {

View File

@ -16,7 +16,9 @@ struct SelfieCamApp: App {
var body: some Scene {
WindowGroup {
ContentView()
AppLaunchView(config: .selfieCam) {
ContentView()
}
}
}
}

View File

@ -31,6 +31,10 @@ struct CustomCameraScreen: MCameraScreen {
@State private var isShowingScreenFlash: Bool = false
@State private var originalBrightness: CGFloat = UIScreen.main.brightness
// Timer countdown state
@State private var countdownSeconds: Int = 0
@State private var isCountdownActive: Bool = false
// Pinch to zoom gesture state
@GestureState private var magnification: CGFloat = 1.0
@State private var lastMagnification: CGFloat = 1.0
@ -95,8 +99,21 @@ struct CustomCameraScreen: MCameraScreen {
isCenterStageActive: cameraSettings.isCenterStageEnabled
)
// Capture Button
// Capture Button with timer indicator badge
CaptureButton(action: { performCapture() })
.overlay(alignment: .bottomTrailing) {
if cameraSettings.selectedTimer.seconds > 0 {
ZStack {
Circle()
.fill(Color.black.opacity(Design.Opacity.accent))
.frame(width: 24, height: 24)
Text(cameraSettings.selectedTimer.displayName)
.font(.system(size: 10, weight: .bold))
.foregroundColor(.white)
}
.offset(x: 4, y: 4)
}
}
.padding(.bottom, Design.Spacing.large)
}
.padding(.horizontal, Design.Spacing.large)
@ -106,6 +123,26 @@ struct CustomCameraScreen: MCameraScreen {
// Countdown overlay
if isCountdownActive {
ZStack {
Color.black.opacity(0.3)
.ignoresSafeArea()
VStack {
Text("\(countdownSeconds)")
.font(.system(size: 120, weight: .bold, design: .rounded))
.foregroundColor(.white)
.shadow(radius: 10)
Text("Get ready!")
.font(.title2)
.foregroundColor(.white.opacity(0.8))
}
}
.transition(.opacity)
}
// Screen flash overlay for front camera
if isShowingScreenFlash {
screenFlashColor
@ -114,6 +151,7 @@ struct CustomCameraScreen: MCameraScreen {
}
}
.animation(.easeInOut(duration: 0.05), value: isShowingScreenFlash)
.animation(.easeInOut(duration: 0.3), value: isCountdownActive)
.onAppear {
// Set flash mode from saved settings
setFlashMode(cameraSettings.flashMode.toMijickFlashMode)
@ -162,9 +200,40 @@ struct CustomCameraScreen: MCameraScreen {
cameraPosition == .front && cameraSettings.flashMode != .off && cameraSettings.isFlashSyncedWithRingLight
}
/// Performs capture with screen flash if needed
/// Performs capture with timer countdown and screen flash if needed
private func performCapture() {
print("performCapture called - shouldUseCustomScreenFlash: \(shouldUseCustomScreenFlash)")
// Check if timer is enabled
let timerSeconds = cameraSettings.selectedTimer.seconds
if timerSeconds > 0 && !isCountdownActive {
// Start countdown
startCountdown(seconds: timerSeconds)
} else if !isCountdownActive {
// No timer or countdown already running, proceed with capture
performActualCapture()
}
}
/// Starts the countdown timer
private func startCountdown(seconds: Int) {
countdownSeconds = seconds
isCountdownActive = true
Task { @MainActor in
while countdownSeconds > 0 {
try? await Task.sleep(for: .seconds(1))
countdownSeconds -= 1
}
// Countdown finished, perform capture
isCountdownActive = false
performActualCapture()
}
}
/// Performs the actual capture with screen flash if needed
private func performActualCapture() {
print("performActualCapture called - shouldUseCustomScreenFlash: \(shouldUseCustomScreenFlash)")
if shouldUseCustomScreenFlash {
// Save original brightness and boost to max
originalBrightness = UIScreen.main.brightness

View File

@ -141,6 +141,14 @@ struct SettingsView: View {
acknowledgmentsSection
// MARK: - Debug Section
#if DEBUG
SettingsSectionHeader(title: "Debug", systemImage: "ant.fill")
brandingDebugSection
#endif
Spacer(minLength: Design.Spacing.xxxLarge)
}
.padding(.horizontal, Design.Spacing.large)
@ -752,6 +760,70 @@ struct SettingsView: View {
.padding(.top, Design.Spacing.large)
.padding(.bottom, Design.Spacing.xSmall)
}
// MARK: - Branding Debug Section
#if DEBUG
private var brandingDebugSection: some View {
VStack(spacing: Design.Spacing.small) {
// Icon Generator
NavigationLink {
IconGeneratorView(config: .selfieCam, appName: "SelfieCam")
} label: {
HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("Icon Generator")
.font(.system(size: Design.BaseFontSize.body, weight: .medium))
.foregroundStyle(.white)
Text("Generate and save app icon to Files")
.font(.system(size: Design.BaseFontSize.caption))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
}
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: Design.BaseFontSize.caption))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
}
.padding(Design.Spacing.medium)
.background(Color.Surface.primary, in: RoundedRectangle(cornerRadius: Design.CornerRadius.medium))
}
.buttonStyle(.plain)
// Branding Preview
NavigationLink {
BrandingPreviewView(
iconConfig: .selfieCam,
launchConfig: .selfieCam,
appName: "SelfieCam"
)
} label: {
HStack {
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("Branding Preview")
.font(.system(size: Design.BaseFontSize.body, weight: .medium))
.foregroundStyle(.white)
Text("Preview app icon and launch screen")
.font(.system(size: Design.BaseFontSize.caption))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
}
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: Design.BaseFontSize.caption))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
}
.padding(Design.Spacing.medium)
.background(Color.Surface.primary, in: RoundedRectangle(cornerRadius: Design.CornerRadius.medium))
}
.buttonStyle(.plain)
}
}
#endif
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.450",
"green" : "0.250",
"red" : "0.850"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
//
// BrandingConfig.swift
// SelfieCam
//
// App-specific branding configurations for icons and launch screens.
//
import SwiftUI
import Bedrock
// MARK: - App Branding Colors
extension Color {
/// SelfieCam branding colors for icon and launch screen.
enum Branding {
/// Primary gradient color - rich magenta/rose for a vibrant, modern selfie aesthetic.
static let primary = Color(red: 0.85, green: 0.25, blue: 0.45)
/// Secondary gradient color - deeper magenta/purple for depth.
static let secondary = Color(red: 0.45, green: 0.12, blue: 0.35)
/// Accent color - soft white/cream for clean, bright text and icons.
static let accent = Color.white
}
}
// MARK: - App Icon Configuration
extension AppIconConfig {
/// SelfieCam app icon configuration.
/// A vibrant, modern icon perfect for a selfie camera app.
static let selfieCam = AppIconConfig(
title: "SELFIE",
subtitle: "CAM",
iconSymbol: "camera.fill",
primaryColor: Color.Branding.primary,
secondaryColor: Color.Branding.secondary,
accentColor: Color.Branding.accent
)
}
// MARK: - Launch Screen Configuration
extension LaunchScreenConfig {
/// SelfieCam launch screen configuration.
/// Animated launch screen with camera-focused visuals and radial glow.
static let selfieCam = LaunchScreenConfig(
title: "SELFIE CAM",
tagline: "Look Your Best",
iconSymbols: ["camera.fill", "sparkles"],
cornerSymbol: "sparkle",
decorativeSymbol: "circle.fill",
patternStyle: .radial,
layoutStyle: .iconAboveTitle,
primaryColor: Color.Branding.primary,
secondaryColor: Color.Branding.secondary,
accentColor: Color.Branding.accent,
titleColor: .white,
iconSize: 52,
titleSize: 38,
subtitleSize: 72,
iconSpacing: 12,
animationDuration: 0.6,
showLoadingIndicator: false
)
}