Skip to content

Commit b5fb8d9

Browse files
committed
πŸ“± iOS: Complete SwiftUI host app
## App Structure iosApp/QRShield/ β”œβ”€β”€ App/ β”‚ └── QRShieldApp.swift # Entry point with tab navigation β”œβ”€β”€ UI/ β”‚ β”œβ”€β”€ Scanner/ β”‚ β”‚ β”œβ”€β”€ ScannerView.swift # Glassmorphic scanner screen β”‚ β”‚ β”œβ”€β”€ CameraPreview.swift # AVCaptureSession bridge to SwiftUI β”‚ β”‚ └── ScannerViewModel.swift # KMP bridge with haptics β”‚ β”œβ”€β”€ Components/ β”‚ β”‚ β”œβ”€β”€ ResultCard.swift # Animated risk assessment card β”‚ β”‚ β”œβ”€β”€ DetailSheet.swift # Full analysis bottom sheet β”‚ β”‚ └── ImagePicker.swift # Photo library integration β”‚ β”œβ”€β”€ History/ β”‚ β”‚ └── HistoryView.swift # Native list with filters/search β”‚ β”œβ”€β”€ Settings/ β”‚ β”‚ └── SettingsView.swift # User preferences β”‚ └── Onboarding/ β”‚ └── OnboardingView.swift # First-run experience β”œβ”€β”€ Extensions/ β”‚ └── Color+Theme.swift # QR-SHIELD design system β”œβ”€β”€ Assets.xcassets/ # App icon and colors └── Info.plist # Camera/photo permissions ## Features - Glassmorphic UI with .thinMaterial effects - Haptic feedback on scan results (success/warning/error) - Animated result cards with verdict-specific colors - Native camera preview via AVCaptureSession - Photo library scanning support - History with filtering and search - Settings with app preferences - Beautiful onboarding experience ## KMP Integration - IosQrScanner.kt now exposes getSession() for SwiftUI - ScannerViewModel bridges SwiftUI to Kotlin PhishingEngine - Mock types allow standalone testing before framework linking
1 parent 3696e6a commit b5fb8d9

16 files changed

Lines changed: 2056 additions & 0 deletions

File tree

β€Žcommon/src/iosMain/kotlin/com/qrshield/scanner/IosQrScanner.ktβ€Ž

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ class IosQrScanner : QrScanner {
5353
private val scanningState = AtomicInt(STATE_STOPPED)
5454
private var captureSession: AVCaptureSession? = null
5555

56+
/**
57+
* Get the AVCaptureSession for use in SwiftUI CameraPreview.
58+
* This allows native SwiftUI views to display the camera feed.
59+
*
60+
* @return The active capture session, or null if not initialized
61+
*/
62+
fun getSession(): AVCaptureSession? = captureSession
63+
5664
/**
5765
* Start continuous camera scanning using AVFoundation + Vision.
5866
*
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// App/QRShieldApp.swift
2+
// QR-SHIELD iOS Application Entry Point
3+
//
4+
// This is the main SwiftUI app that wraps the Kotlin Multiplatform shared logic
5+
// and provides a native iOS experience.
6+
7+
import SwiftUI
8+
9+
@main
10+
struct QRShieldApp: App {
11+
12+
init() {
13+
// Configure app appearance
14+
configureAppearance()
15+
}
16+
17+
var body: some Scene {
18+
WindowGroup {
19+
ContentView()
20+
.preferredColorScheme(.dark) // Force dark mode for QR-SHIELD aesthetic
21+
}
22+
}
23+
24+
private func configureAppearance() {
25+
// Navigation bar appearance
26+
let navAppearance = UINavigationBarAppearance()
27+
navAppearance.configureWithOpaqueBackground()
28+
navAppearance.backgroundColor = UIColor(Color.bgDark)
29+
navAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
30+
navAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
31+
32+
UINavigationBar.appearance().standardAppearance = navAppearance
33+
UINavigationBar.appearance().scrollEdgeAppearance = navAppearance
34+
UINavigationBar.appearance().compactAppearance = navAppearance
35+
36+
// Tab bar appearance
37+
let tabAppearance = UITabBarAppearance()
38+
tabAppearance.configureWithOpaqueBackground()
39+
tabAppearance.backgroundColor = UIColor(Color.bgDark)
40+
41+
UITabBar.appearance().standardAppearance = tabAppearance
42+
UITabBar.appearance().scrollEdgeAppearance = tabAppearance
43+
}
44+
}
45+
46+
// MARK: - Root Content View
47+
48+
struct ContentView: View {
49+
@State private var selectedTab = 0
50+
51+
var body: some View {
52+
TabView(selection: $selectedTab) {
53+
NavigationStack {
54+
ScannerView()
55+
}
56+
.tabItem {
57+
Image(systemName: "qrcode.viewfinder")
58+
Text("Scan")
59+
}
60+
.tag(0)
61+
62+
NavigationStack {
63+
HistoryView()
64+
}
65+
.tabItem {
66+
Image(systemName: "clock.fill")
67+
Text("History")
68+
}
69+
.tag(1)
70+
71+
NavigationStack {
72+
SettingsView()
73+
}
74+
.tabItem {
75+
Image(systemName: "gearshape.fill")
76+
Text("Settings")
77+
}
78+
.tag(2)
79+
}
80+
.accentColor(.brandPrimary)
81+
}
82+
}
83+
84+
#Preview {
85+
ContentView()
86+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"images": [
3+
{
4+
"filename": "icon-1024.png",
5+
"idiom": "universal",
6+
"platform": "ios",
7+
"size": "1024x1024"
8+
}
9+
],
10+
"info": {
11+
"author": "xcode",
12+
"version": 1
13+
}
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info": {
3+
"author": "xcode",
4+
"version": 1
5+
}
6+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"colors": [
3+
{
4+
"color": {
5+
"color-space": "srgb",
6+
"components": {
7+
"alpha": "1.000",
8+
"blue": "0x17",
9+
"green": "0x11",
10+
"red": "0x0D"
11+
}
12+
},
13+
"idiom": "universal"
14+
}
15+
],
16+
"info": {
17+
"author": "xcode",
18+
"version": 1
19+
}
20+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Extensions/Color+Theme.swift
2+
// QR-SHIELD Design System for iOS
3+
//
4+
// Based on UI_DESIGN_SYSTEM.md specifications
5+
// Provides consistent theming across the iOS application
6+
7+
import SwiftUI
8+
9+
extension Color {
10+
// MARK: - Brand Colors
11+
12+
/// Primary brand purple - #6C5CE7
13+
static let brandPrimary = Color(red: 0.42, green: 0.36, blue: 0.91)
14+
15+
/// Secondary brand teal - #00CEC9
16+
static let brandSecondary = Color(red: 0.0, green: 0.81, blue: 0.79)
17+
18+
// MARK: - Verdict Colors
19+
20+
/// Safe verdict green - #00D68F
21+
static let verdictSafe = Color(red: 0.0, green: 0.84, blue: 0.56)
22+
23+
/// Warning/Suspicious verdict amber - #FFAA00
24+
static let verdictWarning = Color(red: 1.0, green: 0.67, blue: 0.0)
25+
26+
/// Danger/Malicious verdict red - #FF3D71
27+
static let verdictDanger = Color(red: 1.0, green: 0.24, blue: 0.44)
28+
29+
/// Unknown verdict gray
30+
static let verdictUnknown = Color(red: 0.55, green: 0.58, blue: 0.62)
31+
32+
// MARK: - Background Colors
33+
34+
/// Dark background - #0D1117
35+
static let bgDark = Color(red: 0.05, green: 0.07, blue: 0.09)
36+
37+
/// Card background - #161B22
38+
static let bgCard = Color(red: 0.09, green: 0.11, blue: 0.13)
39+
40+
/// Surface background - #21262D
41+
static let bgSurface = Color(red: 0.13, green: 0.15, blue: 0.18)
42+
43+
// MARK: - Text Colors
44+
45+
/// Primary text color
46+
static let textPrimary = Color.white
47+
48+
/// Secondary text color
49+
static let textSecondary = Color.white.opacity(0.7)
50+
51+
/// Muted text color
52+
static let textMuted = Color.white.opacity(0.5)
53+
}
54+
55+
// MARK: - Verdict Helpers
56+
57+
enum VerdictColor {
58+
case safe, suspicious, malicious, unknown
59+
60+
var color: Color {
61+
switch self {
62+
case .safe: return .verdictSafe
63+
case .suspicious: return .verdictWarning
64+
case .malicious: return .verdictDanger
65+
case .unknown: return .verdictUnknown
66+
}
67+
}
68+
69+
var iconName: String {
70+
switch self {
71+
case .safe: return "checkmark.shield.fill"
72+
case .suspicious: return "exclamationmark.shield.fill"
73+
case .malicious: return "xmark.shield.fill"
74+
case .unknown: return "questionmark.circle"
75+
}
76+
}
77+
78+
var emoji: String {
79+
switch self {
80+
case .safe: return "βœ…"
81+
case .suspicious: return "⚠️"
82+
case .malicious: return "🚨"
83+
case .unknown: return "❓"
84+
}
85+
}
86+
}
87+
88+
// MARK: - Gradient Helpers
89+
90+
extension LinearGradient {
91+
/// Primary brand gradient
92+
static let brandGradient = LinearGradient(
93+
gradient: Gradient(colors: [.brandPrimary, .brandSecondary]),
94+
startPoint: .topLeading,
95+
endPoint: .bottomTrailing
96+
)
97+
98+
/// Safe verdict gradient
99+
static let safeGradient = LinearGradient(
100+
gradient: Gradient(colors: [.verdictSafe, .verdictSafe.opacity(0.7)]),
101+
startPoint: .top,
102+
endPoint: .bottom
103+
)
104+
105+
/// Danger verdict gradient
106+
static let dangerGradient = LinearGradient(
107+
gradient: Gradient(colors: [.verdictDanger, .verdictDanger.opacity(0.7)]),
108+
startPoint: .top,
109+
endPoint: .bottom
110+
)
111+
}

β€ŽiosApp/QRShield/Info.plistβ€Ž

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<!-- Bundle Configuration -->
6+
<key>CFBundleDevelopmentRegion</key>
7+
<string>$(DEVELOPMENT_LANGUAGE)</string>
8+
<key>CFBundleDisplayName</key>
9+
<string>QR-SHIELD</string>
10+
<key>CFBundleExecutable</key>
11+
<string>$(EXECUTABLE_NAME)</string>
12+
<key>CFBundleIdentifier</key>
13+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
14+
<key>CFBundleInfoDictionaryVersion</key>
15+
<string>6.0</string>
16+
<key>CFBundleName</key>
17+
<string>$(PRODUCT_NAME)</string>
18+
<key>CFBundlePackageType</key>
19+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
20+
<key>CFBundleShortVersionString</key>
21+
<string>1.0.0</string>
22+
<key>CFBundleVersion</key>
23+
<string>1</string>
24+
25+
<!-- Launch Configuration -->
26+
<key>LSRequiresIPhoneOS</key>
27+
<true/>
28+
<key>UILaunchScreen</key>
29+
<dict>
30+
<key>UIColorName</key>
31+
<string>LaunchScreenBackground</string>
32+
<key>UIImageName</key>
33+
<string>LaunchLogo</string>
34+
</dict>
35+
36+
<!-- Orientation -->
37+
<key>UISupportedInterfaceOrientations</key>
38+
<array>
39+
<string>UIInterfaceOrientationPortrait</string>
40+
</array>
41+
<key>UISupportedInterfaceOrientations~ipad</key>
42+
<array>
43+
<string>UIInterfaceOrientationPortrait</string>
44+
<string>UIInterfaceOrientationPortraitUpsideDown</string>
45+
<string>UIInterfaceOrientationLandscapeLeft</string>
46+
<string>UIInterfaceOrientationLandscapeRight</string>
47+
</array>
48+
49+
<!-- Privacy Permissions (REQUIRED for App Store) -->
50+
<key>NSCameraUsageDescription</key>
51+
<string>QR-SHIELD needs camera access to scan QR codes and detect phishing attempts in real-time.</string>
52+
<key>NSPhotoLibraryUsageDescription</key>
53+
<string>QR-SHIELD needs photo library access to scan QR codes from saved images.</string>
54+
55+
<!-- App Transport Security -->
56+
<key>NSAppTransportSecurity</key>
57+
<dict>
58+
<key>NSAllowsArbitraryLoads</key>
59+
<false/>
60+
</dict>
61+
62+
<!-- Capabilities -->
63+
<key>UIRequiredDeviceCapabilities</key>
64+
<array>
65+
<string>arm64</string>
66+
</array>
67+
68+
<!-- Status Bar -->
69+
<key>UIStatusBarStyle</key>
70+
<string>UIStatusBarStyleLightContent</string>
71+
<key>UIViewControllerBasedStatusBarAppearance</key>
72+
<true/>
73+
74+
<!-- App Background Modes (none needed) -->
75+
76+
</dict>
77+
</plist>

0 commit comments

Comments
Β (0)