Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ lib/

**Security.** `features/security/`. `AppLockGate` in `MaterialApp.builder` shows `LockScreen` while `appLockedProvider` is true, and a `PrivacyCover` while the app is inactive (with the lock on or "hide in app switcher"). Without a delay the lock engages as the app is hidden, not on return. `LockScreen` asks for biometrics only once the app is resumed (iOS cancels a prompt while still inactive), again after the app was in the background but not after its own prompt. `confirmPin`/`showCreatePin` push full-screen routes on the root navigator, so they cover the app above the settings sheet too. `AppLockSettings.hidesApp` (lock on or "hide in app switcher"; the switch is fixed on with the lock) drives `SecureWindow`: Android's FLAG_SECURE, and on iOS `PrivacyCoverController` (AppDelegate.swift), a native cover added on `UIScene.willDeactivateNotification`, because the app is in the background before Flutter draws another frame (locking the phone showed the old content on return). Whether to cover it reads from UserDefaults itself (`flutter.lock_enabled`, `flutter.lock_hide_in_switcher`): a channel message sent at startup can arrive before the handler exists. Dart removes it (`SecureWindow.uncover`) once a frame was drawn after resuming; a 2 s fallback removes it otherwise. The PIN is stored only as a salted SHA-256 hash, as `<salt>:<hash>` under `lock_pin` in the `SecretStore` (older versions' SharedPreferences values are moved there). `PinThrottle` blocks the PIN pad for a growing time after 5 wrong PINs (stored, so restarts don't reset it); `LockScreen` then shows `PinBlockedView`. While locked, `AppLockGate` excludes focus and semantics, and `lockBackGuard` (registered in `main()` before `runApp`) turns back into leaving the app. The main player keeps playing (background playback, PiP); the shorts stop. Native channels: `stash/privacy` (Android `FLAG_SECURE`) and `stash/appicon` (Android activity-aliases / iOS alternate icons). On Android the launcher entries are the `activity-alias`es in `src/main/AndroidManifest.xml`. MainActivity keeps a MAIN/LAUNCHER filter there only because `flutter run` looks for it; the debug/profile/release manifests remove that filter again (`tools:node="remove"`). Android uses `FlutterFragmentActivity` and an AppCompat launch theme for `local_auth`.

**Animated previews.** Stash's looping WebP previews, switched separately (`widgets/animated_previews.dart`, on by default; `previewsPlay` also checks reduced motion): `markerPreviewsProvider` for marker tiles (`Marker.previewUrl`), `feedPreviewsProvider` for the first fully shown video in `SceneFeedView`: `AutoPreviewScope` around the list picks it `feedPreviewDelayProvider` (1.5 s by default) after scrolling stopped, and the `AutoPreview` in `SceneThumbnail` plays `Scene.webpUrl` over the still. Scrolling stops it. Thumbnails outside a scope (the player's "Up next") never play.
**Animated previews.** Stash's looping WebP previews, switched separately (`widgets/animated_previews.dart`, on by default; `previewsPlay` also checks reduced motion): `markerPreviewsProvider` for marker tiles (`Marker.previewUrl`), `feedPreviewsProvider` for the first fully shown video in `SceneFeedView`: `AutoPreviewScope` around the list picks it `feedPreviewDelayProvider` (0.5 s by default) after scrolling stopped, and the `AutoPreview` in `SceneThumbnail` plays `Scene.webpUrl` over the still. Scrolling stops it. Thumbnails outside a scope (the player's "Up next") never play. `previewSourceProvider` switches the lists' previews to Stash's MP4 preview (`Scene.previewUrl`), played by `PreviewVideo` in a media_kit player of its own (one at a time; the settings warn about data, battery and memory); markers always play the WebP.

**Server tasks.** `features/server_tasks/` (Settings → Server tasks, pushed inside the settings sheet; its own `ScaffoldMessenger` so snack bars show above the sheet). Starts Stash's scan, generate, auto tag and clean (`StashRepository.scanLibrary/generate/autoTag/clean`; `ScanOption`/`GenerateOption` are the input keys), shows `jobQueueProvider` (polls `jobQueue` every 2 s while watched; `stopJob`) and `previewCheckProvider`: `checkPreviews` fetches one byte of the WebP/MP4 preview URLs of a few random scenes and markers (Stash answers 404 for missing files) and runs again when a job finishes. The demo accepts the mutations and has an empty queue.

**Haptics.** Use `Haptics.selection/light/medium/heavy/error` (`core/config/haptics.dart`), never `HapticFeedback` directly: they follow `hapticLevelProvider` (off; light = only `medium`/`heavy`/`error`, the noticeable ones; normal = also the small `selection`/`light` taps), which `StashApp` watches to apply the stored level. Wrap the `onChanged` of switches, checkboxes and filter chips in `withHaptic(...)`.

Expand Down
5 changes: 3 additions & 2 deletions docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Eine wartbare Basis, damit Features unabhängig voneinander wachsen können.
| 3.1 | Als Nutzer möchte ich auf der Startseite einen endlosen Feed von Szenen mit großen 16:9-Vorschaubildern sehen. | Karten mit Thumbnail, Dauer-Badge, Auflösungs-Badge, Kanal-Avatar, Titel (2 Zeilen) und Metazeile. Die Kopfzeile verschwindet beim Herunterscrollen und kommt beim Hochscrollen wieder; sie liegt über der Liste, damit ihre Knöpfe immer reagieren. | MVP | ✅ |
| 3.2 | Als Nutzer möchte ich den Feed über Filter-Chips umschalten („Neu hinzugefügt“, „Neueste“, „Zufällig“, „Top bewertet“, „Meistgesehen“). | Chip-Leiste wie bei YouTube. Der Wechsel lädt den Feed neu. „Zufällig“ ist über alle Seiten stabil (Seed). | MVP | ✅ |
| 3.3 | Als Nutzer möchte ich per Pull-to-Refresh aktualisieren – auch wenn nur wenige Einträge da sind. | `AlwaysScrollableScrollPhysics`. Der bekannte Bug „Refresh geht nicht bei kurzem Inhalt“ ist behoben. | MVP | ✅ |
| 3.4 | Als Nutzer möchte ich beim Gedrückthalten bzw. Scrollen eine animierte Vorschau (Stash-`preview`) sehen. | Autoplay des Preview-MP4 (stumm) für die sichtbare Karte. | Next | ⬜ |
| 3.4 | Als Nutzer möchte ich beim Gedrückthalten bzw. Scrollen eine animierte Vorschau (Stash-`preview`) sehen. | Autoplay des Preview-MP4 (stumm) für die sichtbare Karte. Umgesetzt als Vorschau-Format „Video (MP4)“ der Listenvorschauen (13.16), mit Hinweis auf den höheren Daten-, Akku- und Speicherbedarf; es spielt immer nur eine Vorschau in einem eigenen Player. | Next | ✅ |
| 3.5 | Als Nutzer möchte ich auf der Startseite Abschnitte sehen („Weiterschauen“, „Neu von Favoriten“). | Horizontale Reihen oberhalb des Feeds. „Weiterschauen“: Szenen mit `resume_time > 0`, zuletzt gesehen zuerst, lädt nach dem ersten gespeicherten Fortschritt neu. „Neu von Favoriten“: `performer_favorite`. Leere Reihen werden ausgeblendet. | Next | ✅ |
| 3.6 | Als Nutzer möchte ich über das „⋮“-Menü einer Karte Aktionen ausführen (Zur Playlist, Zum Studio, Teilen). | Bottom Sheet mit Aktionen. | Later | 🟡 (zu Studio/Performer) |

Expand Down Expand Up @@ -169,8 +169,9 @@ Eine wartbare Basis, damit Features unabhängig voneinander wachsen können.
| 13.13 | Als Nutzer möchte ich nach dem ersten Login das Design wählen. | Einmaliger Dialog „Wähle dein Design“ nach dem Hinzufügen des ersten Servers: Hell/System/Dunkel und Akzentfarbe, sofort sichtbar; bleibt bis zum Anzeigen gespeichert (auch über einen Neustart). Bestehende Nutzer und weitere Server sehen ihn nicht. Dieselben Bedienelemente wie in den Einstellungen (`appearance_picker.dart`). | Next | ✅ |
| 13.14 | Als Nutzer möchte ich spürbares haptisches Feedback und dessen Stärke wählen. | Einstellung „Haptisches Feedback“: Aus, Wenig (nur die spürbaren Rückmeldungen: Favorit, O-Zähler, Langdruck-Menü, Aktualisieren, Entsperren, falsche PIN), Normal (zusätzlich Tabs, Sortier- und Filter-Chips, Schalter, Play/Pause, Wischen in den Shorts, Später ansehen, Theme/Farbe). | Next | ✅ |
| 13.15 | Als Nutzer möchte ich durch erneutes Tippen auf den aktiven Tab nach oben scrollen. | Aus einer Unterseite geht es zurück zur Startseite des Tabs, dort scrollen die Listen sanft nach oben (wie YouTube). | Next | ✅ |
| 13.16 | Als Nutzer möchte ich in Listen eine bewegte Vorschau sehen. | Ruht das Scrollen eine Weile (Standard 1,5 s, einstellbar 0,5–3 s), spielt das oberste vollständig sichtbare Video seine WebP-Vorschau (Stash `paths.webp`) über dem Standbild in Dauerschleife, wie YouTube; Scrollen stoppt sie. Gilt für alle Szenenlisten (Start, Bibliothek, Suche, Verlauf, Kanäle, Gruppen). Einstellung „Vorschauen in Listen abspielen“, bei „Bewegung reduzieren“ aus. | Next | ✅ |
| 13.16 | Als Nutzer möchte ich in Listen eine bewegte Vorschau sehen. | Ruht das Scrollen eine Weile (Standard 0,5 s, einstellbar 0,5–3 s), spielt das oberste vollständig sichtbare Video seine WebP-Vorschau (Stash `paths.webp`) über dem Standbild in Dauerschleife, wie YouTube; Scrollen stoppt sie. Gilt für alle Szenenlisten (Start, Bibliothek, Suche, Verlauf, Kanäle, Gruppen). Einstellung „Vorschauen in Listen abspielen“, bei „Bewegung reduzieren“ aus. Vorschau-Format wählbar: animiertes Bild (WebP, Standard) oder Video (MP4, 3.4). | Next | ✅ |
| 13.17 | Als Nutzer möchte ich die App-Version und die Open-Source-Lizenzen sehen. | Einstellungen → „Über“: Version mit Build-Nummer (`package_info_plus`) und „Open-Source-Lizenzen“ (Flutters Lizenzseite mit allen Paketen, dazu StashTubes eigene AGPL-3.0 und die nativen Bibliotheken des Videoplayers aus media_kits libmpv-Build: mpv, FFmpeg, libass, FreeType, FriBidi, HarfBuzz, dav1d, Mbed TLS, libxml2, unter iOS zusätzlich libpng, uchardet, Protocol Buffers; Lizenztexte in `assets/licenses/`). | Next | ✅ |
| 13.18 | Als Nutzer möchte ich Stashs Server-Aufgaben aus der App starten und sehen, ob die Vorschauen erzeugt sind. | Einstellungen → „Server-Aufgaben“ (auch „Vorschauen auf dem Server prüfen“ bei den Vorschau-Einstellungen): Bibliothek scannen (mit Auswahl, was für neue Dateien erzeugt wird), Generieren (Cover, Video- und animierte Vorschauen, Sprites, Marker-Vorschauen und -Standbilder, pHashes; nur Fehlendes), Automatisch taggen, Aufräumen (mit Bestätigung). Laufende Aufgaben aus Stashs Warteschlange mit Fortschritt, alle 2 s aktualisiert, abbrechbar. Vorschau-Check: Stichprobe zufälliger Szenen und Marker, ob WebP- und MP4-Vorschauen vorhanden sind; fehlen die WebP-Vorschauen, ein Hinweis und „Animierte Vorschauen erzeugen“. Nach einer beendeten Aufgabe prüft er erneut. | Next | ✅ |

## Epic 14 – Plattform & Release

Expand Down
35 changes: 35 additions & 0 deletions lib/core/api/documents/tasks.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Server tasks (Settings → Server tasks): start Stash's jobs and follow the
# job queue. The mutations return the new job's id.

fragment JobFields on Job {
id
status
description
subTasks
progress
error
}

query JobQueue {
jobQueue { ...JobFields }
}

mutation StopJob($job_id: ID!) {
stopJob(job_id: $job_id)
}

mutation MetadataScan($input: ScanMetadataInput!) {
metadataScan(input: $input)
}

mutation MetadataGenerate($input: GenerateMetadataInput!) {
metadataGenerate(input: $input)
}

mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
metadataAutoTag(input: $input)
}

mutation MetadataClean($input: CleanMetadataInput!) {
metadataClean(input: $input)
}
Loading
Loading