Skip to content

Commit 551ddf4

Browse files
committed
Result
1 parent d14455f commit 551ddf4

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Mobile App Implementation Summary
2+
3+
## Overview
4+
5+
Implemented iOS and Android mobile apps using **Hotwire Native** as hybrid wrappers around the existing Symfony web app. The apps reuse 90%+ of the web codebase while having platform-specific billing (App Store for iOS, Play Store for Android, Stripe for web).
6+
7+
## What Was Implemented
8+
9+
### Phase 1: Platform Detection
10+
- **Platform enum** (`src/Value/Platform.php`) - Web/iOS/Android detection
11+
- **PlatformDetector service** - Detects platform from User-Agent header
12+
- **PlatformTwigExtension** - Twig functions `is_web()`, `is_ios()`, `is_android()`, `is_native_app()`
13+
- **JavaScript detection** in `app.js` - Sets `window.isNativeApp` and `window.nativePlatform`
14+
- **CSS classes** - `platform-web`, `platform-ios`, `platform-android`, `native-app`
15+
16+
### Phase 2: Billing Infrastructure
17+
- **Database migration** - Added `platform` column to `membership` table
18+
- **Membership entity** - Added platform property and helper methods
19+
- **Billing interface** (`PlatformBillingInterface`) - Common contract for all platforms
20+
- **Platform billing services** - `WebStripeBilling`, `IosAppStoreBilling`, `AndroidPlayBilling`
21+
- **BillingFactory** - Returns appropriate service based on detected platform
22+
23+
### Phase 3: Platform-Specific UI
24+
- **base.html.twig** - Header/footer wrapped in `{% if is_web() %}` conditionals
25+
- **membership.html.twig** - Platform-specific subscription buttons and management links
26+
- **Translations** - Added keys for all 6 languages (en, cs, de, es, fr, ja)
27+
28+
### Phase 4: Native Scanner Bridge
29+
- **barcode_scanner_controller.js** - Added native bridge methods
30+
- `window.onNativeScanResult(code)` callback
31+
- `window.onNativeScanCancelled()` callback
32+
- Auto-detects native app and uses native scanner instead of web camera
33+
34+
### Phase 5: API Endpoints
35+
- `POST /api/ios/verify-receipt` - iOS App Store receipt verification
36+
- `POST /api/android/verify-purchase` - Android Play Store purchase verification
37+
38+
### Phase 6: iOS App (`ios/`)
39+
- Swift Package Manager project with Hotwire Native iOS dependency
40+
- `WebViewController` with JavaScript bridges for scanner and billing
41+
- `BarcodeScannerBridge` - Native AVFoundation barcode scanner (EAN-8/EAN-13)
42+
- `StoreKitManager` - StoreKit 2 in-app purchases
43+
- `BillingBridge` - JavaScript bridge for purchases
44+
45+
### Phase 7: Android App (`android/`)
46+
- Gradle project with Hotwire Turbo Android dependency
47+
- `TurboWebFragment` with JavaScript interfaces
48+
- `BarcodeScannerBridge` - CameraX + ML Kit barcode scanner
49+
- `BillingManager` - Google Play Billing Library integration
50+
- Material Design UI with scan overlay
51+
52+
### Phase 8: CI/CD
53+
- `.github/workflows/ios.yml` - Builds on macOS, runs tests
54+
- `.github/workflows/android.yml` - Builds APK, uploads artifacts
55+
56+
## Problems Faced & Solutions
57+
58+
### 1. PHPStan Error with `is_array()` Check
59+
**Problem:** PHPStan complained that `is_array()` always evaluates to true when a `@var` annotation was placed before `json_decode()`.
60+
61+
**Solution:** Moved the `@var` annotation to after the `is_array()` check:
62+
```php
63+
// Before (error)
64+
/** @var array<string, mixed> $data */
65+
$data = json_decode($content, true);
66+
67+
// After (fixed)
68+
$data = json_decode($content, true);
69+
if (!is_array($data)) { return error; }
70+
/** @var array<string, mixed> $data */
71+
```
72+
73+
### 2. Duplicate Companion Object in Kotlin
74+
**Problem:** Android `BarcodeScannerBridge.kt` had two `companion object` declarations.
75+
76+
**Solution:** Consolidated all constants into a single companion object at the bottom of the class.
77+
78+
### 3. Turbo Globally Disabled
79+
**Problem:** Turbo is disabled via `data-turbo="false"` on `<html>` element.
80+
81+
**Solution:** This is intentional per project architecture. Native apps use Hotwire Native which handles navigation natively, so Turbo being disabled doesn't affect mobile apps.
82+
83+
## File Structure
84+
85+
```
86+
ios/
87+
├── Package.swift
88+
├── .gitignore
89+
└── MySpeedPuzzling/
90+
├── App/MySpeedPuzzlingApp.swift
91+
├── Navigation/MainNavigationView.swift
92+
├── Web/WebViewController.swift
93+
├── Features/BarcodeScannerBridge.swift
94+
└── Billing/
95+
├── StoreKitManager.swift
96+
└── BillingBridge.swift
97+
98+
android/
99+
├── build.gradle.kts
100+
├── settings.gradle.kts
101+
├── gradlew
102+
└── app/
103+
└── src/main/
104+
├── AndroidManifest.xml
105+
├── java/com/myspeedpuzzling/
106+
│ ├── app/MainActivity.kt
107+
│ ├── app/TurboWebFragment.kt
108+
│ ├── features/BarcodeScannerBridge.kt
109+
│ ├── features/BarcodeScannerActivity.kt
110+
│ └── billing/
111+
│ ├── BillingBridge.kt
112+
│ └── BillingManager.kt
113+
└── res/
114+
```
115+
116+
## Next Steps
117+
118+
### Immediate (Required for Launch)
119+
1. **Run migration:** `docker compose exec web php bin/console doctrine:migrations:migrate`
120+
2. **Implement actual receipt verification** in `IosAppStoreBilling` and `AndroidPlayBilling` (currently stubs)
121+
3. **Set up App Store Connect:** Create app, configure in-app purchase products
122+
4. **Set up Google Play Console:** Create app, configure subscription products
123+
124+
### For Production Deployment
125+
5. **Code signing:**
126+
- iOS: Add certificates and provisioning profiles to GitHub secrets
127+
- Android: Create and secure signing keystore
128+
6. **App icons and launch screens** for both platforms
129+
7. **Webhook endpoints** for subscription status updates from Apple/Google
130+
8. **TestFlight/Internal testing** before public release
131+
132+
### Product IDs
133+
- **iOS:** `com.myspeedpuzzling.premium.monthly`, `com.myspeedpuzzling.premium.yearly`
134+
- **Android:** `premium_monthly`, `premium_yearly`
135+
136+
## Testing
137+
138+
The web app continues to work normally. All PHP checks pass:
139+
- PHPStan: ✅
140+
- PHPCS: ✅
141+
- PHPUnit: ✅
142+
- Schema validation: ✅
143+
- Cache warmup: ✅
144+
145+
Native apps are isolated in `ios/` and `android/` directories and cannot break the web app.

0 commit comments

Comments
 (0)