Project Summary · iOS · v1.0

Kino.

TikTok, but for finding tonight's movie.

A native SwiftUI iOS app for full-screen, swipe-up movie discovery. Ships with a local catalog and a recommendation engine that learns from the genres you pick and the movies you like, save, and pass — no backend required.

SwiftUI Swift 6.0 iOS 17+ @Observable XcodeGen Local-first

What it is

A vertical, full-screen feed that ranks movies to you — and gets sharper as you react.

Vertical paging feed — one movie per screen, snap-scrolling like a short-video app.
Local-first engine — ranking runs on-device against a bundled 12-movie catalog.
Like · Save · Pass · Info — four reactions per card, each feeding the ranking.
First-run onboarding — pick your genres before the feed opens.
Saved library — a dedicated shelf for everything you bookmarked.
TMDB-ready — a stubbed API client waiting for a real catalog swap-in.

Architecture

One observable store, a pure ranking function, no network.

Single source of truth

MovieStore

A @MainActor @Observable class holding the catalog + the user profile. Exposes a computed feed, savedMovies, and the like / save / pass / onboarding mutations. Every change persists.

Pure logic

RecommendationEngine

A stateless enum of static functions. Scores and sorts movies from a profile, filters out passed ones, and generates human-readable "why you're seeing this" reasons. No side effects — trivially testable.

Persistence

UserProfile

A Codable struct of preferences and ID sets, saved to UserDefaults as JSON under kino.user-profile.v1. Load is failure-tolerant — a decode error just returns a fresh profile.

The layering is clean: Data (catalog) → Models (Movie, Genre, UserProfile) → Services (engine, TMDB stub) → StoreViews. Views only read the store from the SwiftUI environment and call its methods.

The Recommendation Engine

Score = the movie's rating, nudged by everything you've told it.

Each movie starts at its critic rating, then collects points for matching your taste. Passed movies are removed entirely; ties break toward the higher-rated film.

base
movie.rating — the starting score (e.g. 8.5 for Dune: Part Two)
+2.4
per genre that matches a preferred genre you chose at onboarding
+1.15
per genre shared with each movie you liked
+0.7
per genre shared with each movie you saved
+0.6
if this exact movie is saved  ·  +0.3 if it's liked
hide
movies you passed are filtered out of the feed completely

Reason chips on each card are generated the same way — "More Sci-Fi" from a genre match, "Critic favorite" (≥ 8.2) or "Highly rated" (≥ 7.8) from the score, plus a couple of the movie's mood tags.

The Screens

  1. Onboarding. First launch shows a genre picker. Choosing your genres flips hasCompletedOnboarding and opens the app.
  2. Feed tab. Full-screen paging cards, ranked by the engine. Poster backdrop, rating pill, title, tagline, and reason chips over a dark gradient.
  3. React. The action rail — Like, Save, Info, Pass. Like & Save toggle and clear a prior Pass; Pass clears Like & Save and hides the card.
  4. Detail. Tapping a card (or Info) opens a draggable detail sheet at medium / large detents.
  5. Saved & Prefs tabs. A bookmarked-movies shelf, and a preferences screen to reset onboarding and re-pick genres.

Catalog & Genres

12 hand-picked films across 11 genres.

Each Movie carries a title, year, tagline, overview, genres, rating, runtime, maturity rating, TMDB poster + backdrop URLs, mood tags, and a color palette. The seed catalog spans Arrival, Dune: Part Two, Spider-Verse, and nine more.

ActionAnimationComedy CrimeDramaFantasy HorrorMysteryRomance Sci-FiThriller

Stack & Tooling

SwiftUI · declarative UI Observation · @Observable store Swift 6.0 UserDefaults · JSON persistence XcodeGen · project.yml XCTest · unit + UI iOS 17+ · portrait, iPhone

The Xcode project is generated from a declarative project.yml via XcodeGen — no .xcodeproj to merge-conflict. Three targets: the Kino app, KinoTests (unit), and KinoUITests. Build and test from the terminal with xcodebuild.

~1.3k
Lines of Swift
12
Seed movies
3
Tabs
4
Card actions

Tested Behavior

The recommendation logic is pure, so it's covered directly by RecommendationEngineTests:

Preferred genres lift matches — picking Sci-Fi pushes a Sci-Fi film into the top three.
Passed movies vanish — a passed ID never appears in the ranked feed.
Saves shape neighbors — saving Arrival raises the score of a genre-adjacent film like Dune.
UI smoke testKinoUITests drives the app via accessibility identifiers.
63 Made with Syncric