Player overlay as one piece, Up next always warm and on every source, Jellyfin never unmarks a watched episode

- Overlay: one layout for both designs (top bar with Up next as one of
  its buttons; one action band above the controls for the status pill,
  skips, next episode and end cards), one glass recipe (AppGlass) and
  one pill (FloatingPill); hovering any of them, or the open queue,
  holds the controls up.
- Up next: the first item (or the first continuation episode) is kept
  resolved on the app that puts it up, refreshed before it expires;
  Schedule and drop/paste can queue too.
- Jellyfin: WatchSession (core) counts real watched coverage; an
  episode is watched at 80 % or at its ending after half; a played one
  is re-marked after Playing (10.11 unmarks on open) and never gets a
  position; a peek leaves the old resume point; part-way resume points
  stay under 85 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-11 07:53:42 +03:00
1 parent 6b10cae4e6
commit 771b961b32
27 files changed
+1396 -558

No files matched your search

+7 -22
View File
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:ui' show ImageFilter;
import 'package:flutter/material.dart';
import 'package:sync_relay_core/core.dart';
@@ -7,6 +6,7 @@ import 'package:sync_relay_core/core.dart';
import '../session/health.dart';
import '../session/session_coordinator.dart';
import '../state/session_state.dart';
import '../widgets/app_menu.dart' show AppGlass;
import 'avatar.dart';
import 'motion.dart';
@@ -241,32 +241,17 @@ class _PlayerStatusState extends State<PlayerStatus> {
}
}
/// Frosted dark glass in a pill: the status pill, the unread chip.
/// The player's pill surface (the status pill, the action pills, the
/// unread chip): the app's glass as a stadium ([AppGlass.pill]).
class PlayerGlass extends StatelessWidget {
final Widget child;
const PlayerGlass({super.key, required this.child});
@override
Widget build(BuildContext context) {
final r = BorderRadius.circular(999);
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: ClipRRect(
borderRadius: r,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.55),
borderRadius: r,
border: Border.all(color: Colors.white12, width: 0.5),
),
child: child,
),
),
),
);
}
Widget build(BuildContext context) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 560),
child: AppGlass.pill(child: child),
);
}
class _StatusLine extends StatelessWidget {