Protocol 10 presence gate, drop/paste anywhere, quick UI fixes
Ready gate: whoever is in the server while it is not playing counts (joining while playing does not pause anyone); a swap counts everyone present and keeps ready flags; the quorum rule is gone. Optional 'Unready when the video changes'. No 'Skip 1:30' guess when the media has chapters. Drag & drop and Ctrl+V anywhere (fox_utils): files, folders and links get a small card asking how to open them. Files series start collapsed. UI: Tab never moves focus and Space is always ready outside text fields; chat focuses its field; unread badge on uosc and a chip in fullscreen; people-only chat with an Activity toggle; skip pills show 5 s then with the controls; volume up to 150%; own avatar same size as the party. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f22be6c20
commit
8effa573a0
38 files changed
+1529
-303
No files matched your search
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:signals/signals_flutter.dart';
|
||||
import 'package:sync_relay_core/core.dart' show HelloMedia, JellyfinException, JellyfinItem;
|
||||
|
||||
@@ -189,32 +188,6 @@ class _LibraryTabState extends State<LibraryTab> {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- paste
|
||||
|
||||
KeyEventResult _onKey(FocusNode node, KeyEvent e) {
|
||||
final ctrl = HardwareKeyboard.instance.isControlPressed || HardwareKeyboard.instance.isMetaPressed;
|
||||
if (e is! KeyDownEvent || !ctrl || e.logicalKey != LogicalKeyboardKey.keyV) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
// A focused text field handles its own paste.
|
||||
final inField =
|
||||
FocusManager.instance.primaryFocus?.context?.findAncestorStateOfType<EditableTextState>() != null;
|
||||
if (inField) return KeyEventResult.ignored;
|
||||
unawaited(_pasteLink());
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
/// Ctrl+V outside a field: an http(s) link goes to the Link source.
|
||||
Future<void> _pasteLink() async {
|
||||
String? text;
|
||||
try {
|
||||
text = (await Clipboard.getData(Clipboard.kTextPlain))?.text?.trim();
|
||||
} catch (_) {}
|
||||
if (text == null || !(text.startsWith('http://') || text.startsWith('https://'))) return;
|
||||
_c.link.text = text;
|
||||
_c.source = MediaKind.link;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- build
|
||||
|
||||
@override
|
||||
@@ -306,10 +279,10 @@ class _LibraryTabState extends State<LibraryTab> {
|
||||
child: AnimatedOpacity(duration: easeTime, curve: ease, opacity: open ? 1 : 0, child: child),
|
||||
);
|
||||
|
||||
// Holds focus so the shell's shortcuts (Ctrl+,) work.
|
||||
return Focus(
|
||||
key: const ValueKey('library-tab'),
|
||||
autofocus: true,
|
||||
onKeyEvent: _onKey,
|
||||
child: LayoutBuilder(builder: (context, box) {
|
||||
if (box.maxWidth >= detailsDockFrom) {
|
||||
return Row(
|
||||
|
||||
Reference in new issue
Block a user