A solo-built, production-shipped iOS social photo app. Swift 6 · Strict Concurrency · SwiftUI · Firebase · WidgetKit · Live Activities
anlık. is a real-time, filter-free social photo app for close friends. Users share unedited "moments" with a small circle, build daily streaks, comment on each other's photos, and add live drawings or text. Position: between Snapchat (private, ephemeral) and BeReal (authentic, in-the-moment) — but built for Turkey-first audiences.
I shipped this app solo as a learning vehicle: ~14,900 lines of Swift covering camera capture, real-time messaging, push notifications, widgets, Live Activities, Watch app, and a Cloud Functions backend.
Screenshots coming soon — clone & build to see it live, or grab the app from the App Store.
| Camera | Friends | History | Memories |
|---|---|---|---|
| capture | list | grid | recap |
This isn't a tutorial app. It's a production codebase that ships to real users, with all the messy edges that come with that:
- Swift 6 strict concurrency across the entire codebase — every service is an
actor, all view models are@Observable, zero data-race warnings. - Repository + Dependency Injection pattern with protocol-based services, swappable for testing.
- Offline-first architecture combining Firestore persistent cache + SwiftData + a JSON file fallback.
- Custom AVFoundation camera with parallel capture + location sampling, safety timeouts, HEVC video recording, and a long-press shutter for video.
- Live Activities + Dynamic Island for upload progress, fully styled.
- Three widget sizes + Lock Screen widget with 3-layered data loading (App Group → cached image → remote).
- Cloud Functions backend (12 functions) handling streak calculation, push notifications, content moderation (Cloud Vision SafeSearch), and weekly summary cron jobs.
- Server-side streak calculation — clients can't fake it. Five tiers, friendship score, leaderboard.
- GDPR/KVKK-compliant cascade delete (9 client steps + 9 server steps).
- Apple Sign-In with nonce replay protection + Keychain-backed credential persistence across reinstalls.
- Firebase App Check (DeviceCheck on iOS) for API abuse prevention.
- Custom rate limiting — client-side (5/min) + server-side (10/min on comments).
┌─────────────────────────────────────────────────────────┐
│ SwiftUI Views (39) │
│ AuthView · MainTabView · CameraView · HistoryView │
├─────────────────────────────────────────────────────────┤
│ @Observable ViewModels (8) │
│ CameraVM · HistoryVM · FriendsListVM · ChatVM · … │
├─────────────────────────────────────────────────────────┤
│ Repository Protocols (DI-friendly) │
│ PhotoRepository · FriendsRepository · … │
├─────────────────────────────────────────────────────────┤
│ Swift Actors (16 services) │
│ AuthService · StorageService · PhotoService · … │
├─────────────────────────────────────────────────────────┤
│ Infrastructure │
│ Firebase · SwiftData · App Group · NWPathMonitor │
├─────────────────────────────────────────────────────────┤
│ Widget Extension · Notification Service │
│ watchOS App · macOS Admin (separate target) │
└─────────────────────────────────────────────────────────┘
Pattern: MVVM + Repository + Dependency Injection
Concurrency: Swift 6 strict, actor for all services, @Observable for view models, OSAllocatedUnfairLock for the DI container
Thread safety: Verified — no @unchecked Sendable shortcuts, no warnings
| Layer | Tech |
|---|---|
| UI | SwiftUI (iOS 17+ APIs), UIKit bridges where needed |
| Concurrency | Swift 6 strict, async/await, actor, @Observable |
| Local persistence | SwiftData (offline cache), Keychain (auth), App Group (widget data) |
| Backend | Firebase — Firestore, Storage, Auth, Cloud Messaging, App Check, Crashlytics, Analytics |
| Cloud Functions | TypeScript/JS, firebase-functions v2, firebase-admin, jimp, @google-cloud/vision |
| Camera | AVFoundation custom pipeline, HEVC video, parallel capture + GPS |
| Widgets | WidgetKit + App Intents (iOS 18 interactive widgets) |
| Live Activity | ActivityKit (upload progress, Dynamic Island) |
| Watch | WatchKit + WatchConnectivity |
| Notifications | UserNotifications + Notification Service Extension (rich previews with avatars + thumbnails) |
| Drawing | Custom Canvas-based overlay engine, undo/redo, image burn |
| Networking | Native URLSession, URLCache (50MB RAM + 150MB disk) |
| Security | App Check (DeviceCheck), per-user FCM token private subcollection, nonce replay protection |
| Feature | Status | Notes |
|---|---|---|
| Camera capture | ✅ | Front/back, flash, exposure, lens zoom, WYSIWYG |
| Video recording | ✅ | Long-press shutter, HEVC, progress ring |
| Drawing overlay | ✅ | Canvas, color/brush, undo/redo |
| Text overlay | ✅ | Draggable, burns into image |
| Photo history | ✅ | Grid + date groups, pagination, offline-first |
| Streak system | ✅ | Server-side, 5 tiers, friendship score |
| Daily prompts | ✅ | 60 prompts, Cloud Function, topic push |
| DM | ✅ | Reply chain, typing, read receipts, reactions, photo/video bubbles |
| Comments | ✅ | Real-time, reply thread, server rate-limited |
| Invite code + QR | ✅ | Camera-based QR detection |
| Widgets | ✅ | Small/Medium/Large + Lock Screen |
| Dynamic Island | ✅ | Upload progress + status |
| Push notifications | ✅ | 6 types, per-user silent hours, rich previews |
| Watch app | ✅ | Recent photos, streak display |
| Apple Sign-In | ✅ | Nonce-protected, Keychain-persisted |
| Content moderation | ✅ | Cloud Vision SafeSearch |
| GDPR/KVKK delete | ✅ | 9-step cascade, client + server |
| In-app banner | ✅ | Custom overlay with deep link |
| Block / report | ✅ | Two-sided, Firestore rules-enforced |
StripMate/
├── App/ # @main entry, lifecycle, root container
├── Core/
│ ├── Views/ # 39 SwiftUI screens
│ ├── ViewModels/ # 8 @Observable view models
│ ├── Services/ # 16 actor-based services
│ └── Camera/ # Custom AVFoundation pipeline
├── Models/ # 15 Codable / SwiftData models
├── Utils/ # Haptics, formatters, image processing
└── Resources/ # Assets, sounds, localization
StripMateWidget/ # Widget Extension (3 sizes + Lock Screen)
StripMateNotificationService/ # Rich notification previews
StripMateWatch Watch App/ # watchOS companion
functions/ # Firebase Cloud Functions (TS)
This repo is published as a portfolio / reference implementation. Running it end-to-end requires your own Firebase project + Apple Developer team.
- Xcode 16+ (Swift 6 toolchain)
- iOS 17+ device or simulator
- Firebase project (Firestore, Storage, Auth, Functions, App Check)
- Apple Developer account (for push, App Check, Sign in with Apple)
- Clone:
git clone https://github.com/clbasaran/anlik.git cd anlik - Create a Firebase project and download your own
GoogleService-Info.plistintoStripMate/App/. - Update bundle identifier and Team ID in Xcode project settings.
- Deploy Cloud Functions:
cd functions npm install firebase deploy --only functions - Open
StripMate.xcodeprojin Xcode 16+ and build.
A few non-obvious choices documented in commit messages and docs:
- Why actors over GCD:
StripMate/Core/Services/— all services are isolated actors, eliminating an entire class of race conditions - Why server-side streak math: clients can lie about the calendar. Daily streak verification happens in a Cloud Function with the server's clock + timezone awareness
- Why Firestore persistent cache + SwiftData: Firestore handles real-time sync; SwiftData mirrors what's been seen for instant offline reads. Different responsibilities, both needed
- Why 1080p resize + JPEG 0.75: ~80% upload size reduction with imperceptible quality loss for social photos
- Why per-user silent hours (default off): the original implementation hard-coded silent hours 23:00–07:00 globally; this killed notifications for night-shift users. Now opt-in per user
See docs/ for deeper writeups (Turkish):
01_TEKNIK_MIMARI.md— Technical architecture05_RELEASE_CHECKLIST.md— Release checklist & PrivacyInfo07_FIRESTORE_CLOUD_FUNCTIONS.md— Backend deep-diveANALYTICS_DASHBOARD.md— Firebase Analytics + Crashlytics setupdesign-tokens/— Design system tokens
- ~993 lines of unit tests, ~47 cases
- Test targets:
StripMateTests(unit),StripMateUITests(UI smoke) - Run:
⌘+Uin Xcode, orxcodebuild test -scheme StripMate -destination 'platform=iOS Simulator,name=iPhone 16'
GoogleService-Info.plist— bring your own- App Store provisioning profiles + signing certs
- Push notification
.p8key
Honest reflection — I shipped this as my first real iOS project. A few non-trivial takeaways:
- Swift 6 strict concurrency forces you to think. When the compiler refuses to ship a data race, you can't paper over it. The mental model shift from "threads + locks" to "isolation domains" took weeks but is worth it.
- You don't need every feature on day one. I shipped widgets, watchOS, and Live Activity before I knew if anyone wanted the core product. Should have stayed focused.
- Server-side truth beats client-side cleverness. Anything users can game (streak, friendship score, prompts) must live on the backend.
- Cloud Vision + App Check + Firestore Rules is a surprisingly small amount of code for a real moderation/abuse stack — but only if you compose them right.
- The hardest part isn't Swift. It's the product decisions: which feature to ship, which to delete, when to listen to the user vs. your own taste.
Built by Celal Başaran — solo iOS developer, Turkey.
Open to junior / mid iOS engineer positions in Turkey (Istanbul, hybrid, or remote).
This repo is published as a portfolio reference. It contains no production secrets — bring your own Firebase / Apple / Maps credentials.
Reporting a vulnerability: see SECURITY.md. Email security reports to celalba78@icloud.com with [SECURITY] in the subject.
Defensive stack:
- Firebase App Check (DeviceCheck / Play Integrity)
- Firestore custom-claims admin model (not self-elevatable)
- Path-based authorization in Storage rules (uid-prefixed filenames)
- Server-side + client-side rate limiting
- Cloud Vision SafeSearch content moderation
- Pre-commit secret scanning (gitleaks + detect-secrets)
- GitHub Secret Scanning + Push Protection enabled
- Dependabot security alerts
MIT — see LICENSE
"Filtreler değil, anlar." Not filters. Moments.