A gesture-driven iOS app for discovering, tracking, and organizing movies & TV shows — with social list sharing and cloud sync.
FlickSwiper brings a Tinder-style swipe interface to movie and TV show discovery. Swipe right to mark something as seen, left to skip, or up to save it to your watchlist. The app pulls content from The Movie Database (TMDB) and stores your library locally with SwiftData, with optional Firebase-backed cloud sync and social list sharing.
I wanted a quick, tactile way to log what I've watched without the overhead of full-featured tracking apps. The swipe mechanic makes it feel like a game rather than a chore, and the library features grew organically from there. Social lists and cloud sync came next — sharing a "best sci-fi" list via a Universal Link felt like the right way to extend the concept.
Discover — Browse trending, popular, top-rated, now playing, upcoming, and 11 streaming services. Filter by genre, year range, and content type. Sort streaming catalogs by popularity, rating, release date, or alphabetically.
Swipe & Rate — Swipe right (seen), left (skip), or up (watchlist). Haptic feedback on threshold crossing and velocity-based fly-off animations. After marking something as seen, an inline rating prompt (1–5 stars) appears with spring-animated star selection.
Search — Debounced TMDB search with instant results. Green checkmarks and amber bookmarks indicate items already in your library or watchlist.
Library — Smart collections auto-generated from your data (favorites, genres, platforms, recently added). Create custom lists, add items individually or in bulk via a full-screen selectable grid. Apple-style edit mode with multi-select, bulk delete, and share. Configurable rating display (TMDB, personal, or none) under posters.
Watchlist → Seen — When you finally watch something, tap "I've Watched This" to move it from watchlist to seen with a rating prompt.
Social Lists — Sign in with Apple or Google to publish custom lists as Universal Links. Share via mit112.github.io/FlickSwiper/list/{id}. Friends can follow your lists with real-time sync — when you update a published list, followers see the changes automatically via Firestore snapshot listeners.
Cloud Backup — Optional bidirectional sync backs up your entire library (swipes, ratings, lists, list entries) to Firebase. Push-on-write for instant saves, incremental pull on launch. Merge conflicts resolved by timestamp with direction hierarchy protection (seen > watchlist > skipped). Supports cross-provider account switching with full data isolation.
- SwiftData with Versioned Schema Migration — Four-version migration chain (V1→V2→V3→V4) with frozen versioned schema definitions to prevent hash mismatches. UUID-based join models (
UserList↔ListEntry↔SwipedItem) avoid SwiftData relationship pitfalls. - Firebase Integration — Auth (Apple + Google Sign-In with provider-aware collision handling), Firestore (cloud sync + social list publishing), Security Rules with penetration-tested authorization layer (78 tests).
- Bidirectional Cloud Sync — Push-on-write with incremental pull. Timestamp-based merge with direction hierarchy protection. Chunked batch uploads (400 ops). Re-entrancy guard prevents overlapping sync operations. Cross-provider account switch with data isolation.
- Actor-Isolated Networking —
TMDBServiceis anactor, ensuring thread-safe API access. Handles 429 rate limiting with automatic retry using theRetry-Afterheader. - Gesture-Driven Animation Orchestration — Card swipe animations in three directions with precise timing between fly-off animation (0.2s delay), array mutation, and rating prompt presentation to prevent visual artifacts.
- Real-Time Social Sync — Per-list Firestore snapshot listeners for followed lists. Activate/deactivate lifecycle tied to auth state.
- Image Caching & Prefetch — Enlarged
URLCache(50MB memory / 200MB disk) with a customRetryAsyncImagewrapper that retries failed loads with shimmer loading state and fade-in transitions. Prefetches both w500 posters and w185 thumbnails. - Direction Transition Policy — Hierarchical protection (seen > watchlist > skipped) prevents user data demotions. State-aware undo via
UndoEntrystruct tracking. - Protocol-Oriented Service Layer —
MediaServiceProtocolwith aMockMediaServiceready for unit testing. - Accessibility — Custom accessibility actions on swipe cards, descriptive labels on all interactive elements, proper modifier ordering.
For a deeper look at architecture decisions, see ARCHITECTURE.md. For a history of changes, see CHANGELOG.md.
- UI architecture: SwiftUI + MVVM with
@Observableview models and actor-isolated service calls. - Data ownership:
SwipedItemStorecentralizes write operations with cloud sync hooks so views stay focused on presentation. - Persistence: SwiftData with a four-version migration plan (
FlickSwiperMigrationPlan, V1→V4) and three-tier startup recovery fallback. - Cloud layer: Firebase Auth (Apple + Google) →
CloudSyncService(push-on-write + incremental pull) → Firestore subcollections underusers/{uid}/. - Social layer:
ListPublisher(publish/unpublish lifecycle) +FollowedListSyncService(per-list Firestore snapshot listeners) + Universal Links via GitHub Pages AASA. - Networking:
TMDBServiceactor handles API requests, retry-on-429 behavior, and model mapping toMediaItem. - Testing strategy: XCTest coverage (128 tests) for models, decoding, service/view-model behavior, and persistence mutation paths via in-memory SwiftData. Firestore security rules validated by 78-test penetration testing suite.
FlickSwiper/
├── FlickSwiperApp.swift # App entry, URLCache, ModelContainer, Firebase, environment injection
├── ContentView.swift # Tab nav, auth state observer, deep link handler, periodic sync
├── Config/ # xcconfig-based API key management
├── Models/
│ ├── SwipedItem.swift # SwiftData model — swipes, ratings, genres, platform, sync fields
│ ├── MediaItem.swift # Unified media type for UI consumption
│ ├── TMDBModels.swift # TMDB API response decodables
│ ├── SchemaVersions.swift # Frozen versioned schemas (V1–V4) + migration plan
│ ├── UserList.swift / ListEntry.swift # Custom list models (UUID join) + publish/sync fields
│ ├── FollowedList.swift # SwiftData cache for followed social lists
│ ├── FollowedListItem.swift # Items within a followed list
│ ├── DiscoveryMethod.swift # Discovery methods + streaming provider config
│ ├── Genre.swift # Genre model for filtering
│ ├── StreamingSortOption.swift # Sort options for streaming content
│ └── RatingDisplayOption.swift # User preference for grid card ratings
├── ViewModels/
│ ├── SwipeViewModel.swift # Discovery feed, swipe logic, filtering, cloud sync hooks
│ └── SearchViewModel.swift # Debounced search with Task cancellation
├── Views/
│ ├── Discover/ # Swipe cards, filters, rating prompt, tutorial
│ ├── Library/ # Collections, lists, watchlist, grids, following section
│ ├── Social/ # Sign-in prompt, shared list view, followed list views
│ ├── SearchView.swift # Search tab with library-aware results
│ └── SettingsView.swift # Settings, account management, cloud backup, reset actions
├── Services/
│ ├── TMDBService.swift # Actor-based TMDB API client
│ ├── MediaServiceProtocol.swift # Protocol + mock for testing
│ ├── AuthService.swift # Firebase Auth — Apple + Google Sign-In, account lifecycle + deletion
│ ├── CloudSyncService.swift # Bidirectional Firestore sync, merge, batch upload
│ ├── SwipedItemStore.swift # Centralized write ops with direction policy + cloud sync hooks
│ ├── FirestoreService.swift # Firestore CRUD for social lists
│ ├── ListPublisher.swift # Publish/unpublish/sync lifecycle for shared lists
│ └── FollowedListSyncService.swift # Real-time snapshot listeners for followed lists
└── Utils/
├── Constants.swift # App-wide constants + Firebase/deep link URLs
├── DeepLinkHandler.swift # Universal Link URL parser
├── DisplayNameValidator.swift # Display name format + offensive term validation
├── ShareLinkSheet.swift # UIActivityViewController wrapper
├── NetworkError.swift # Offline detection utility
├── Extensions.swift # Helper extensions
├── GenreMap.swift # TMDB genre ID → name/icon mapping
├── HapticManager.swift # Haptic feedback patterns
└── RetryAsyncImage.swift # AsyncImage with retry + shimmer loading
- iOS 26.0+
- Xcode 26+
- A free TMDB API Key
- A Firebase project (for auth and cloud sync features)
-
Clone the repo:
git clone https://github.com/mit112/FlickSwiper.git cd FlickSwiper -
Configure your API key:
cp FlickSwiper/Config/Secrets.xcconfig.template FlickSwiper/Config/Secrets.xcconfig
Open
Secrets.xcconfigand replaceYOUR_TOKEN_HEREwith your TMDB API Read Access Token (v4 auth). -
Set up Firebase:
- Create a project at Firebase Console
- Register your iOS app (bundle ID:
com.flickswiper.app) - Download
GoogleService-Info.plistand add it to the Xcode project - Enable Apple and Google sign-in providers in Firebase Authentication
-
Open
FlickSwiper.xcodeprojin Xcode and run (⌘R).
Note:
Secrets.xcconfigandGoogleService-Info.plistare gitignored. You need to create them locally before building.
| Layer | Technology |
|---|---|
| UI | SwiftUI |
| Persistence | SwiftData with versioned schema migration (V1–V4) |
| Backend | Firebase Auth (Apple + Google) + Firestore |
| Networking | URLSession + async/await, actor isolation |
| API | TMDB v3 (movies, TV, search, streaming providers) |
| Architecture | MVVM with @Observable |
| Security | Firestore Security Rules, penetration tested (78 tests) |
| Minimum Target | iOS 26.0 |
Core library data is stored locally on device using SwiftData. Accounts are optional — signing in with Apple or Google enables cloud backup and social list sharing via Firebase. Published list data (list name, item metadata, display name) is stored in Firestore and visible to anyone with the share link. No third-party analytics or ad tracking. See the full Privacy Policy.
This project is licensed under the MIT License — see the LICENSE file for details.
- Movie and TV show data provided by The Movie Database (TMDB). This app uses the TMDB API but is not endorsed or certified by TMDB.




