Desktop session batch 09-12: frameless chrome, bisect removal, chat/idle fixes

Frameless (window_manager, hidden titlebar): chrome bar on welcome,
drag-to-move appbar + window buttons on connect, drag layer + window
buttons in session controls; min size 160x90; volume-scroll off for
drag; session maximize = fullscreen toggle. Verified on KDE Wayland.
Delete legacy/minimal/nosheet bisect screens; session routes direct
(black-screen fix verified incl. WAN).
Avatar: pipeline now 128px aspect-kept PNG (was cropped JPEG);
profile card centered 64px hover-to-change; member avatar drops
ResizeImage downscale. NOTE open: pipeline 512 KiB check vs server
64 KiB enforcement mismatch.
Fixes: idle warn/unready skipped while PLAYING; sendChat drops
blanks; one chat row per send (drop textless server feed echo);
chat field keeps focus after send. Playback libass true; sdk floor
3.13. Handoff 9-11: 9+10 fixed, 11 jumpy seek still open.
Docs: handoff/roadmap/protocol record all of the above; few-commits
preference going forward.
This commit is contained in:
foxyz committed 2026-09-12 11:42:18 +03:00
1 parent 3303d88a5b
commit ff154c5030
26 files changed
+1147 -881

No files matched your search

@@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import '../state/session_state.dart';
class WindowButtons extends StatefulWidget {
final FocusNode? focusNode;
final bool fullscreen;
final VoidCallback? onToggleFullscreen;
const WindowButtons({
super.key,
this.focusNode,
this.fullscreen = false,
this.onToggleFullscreen,
});
@override
State<WindowButtons> createState() => _WindowButtonsState();
}
class _WindowButtonsState extends State<WindowButtons> with WindowListener {
bool _maximized = false;
@override
void initState() {
super.initState();
windowManager.addListener(this);
windowManager.isMaximized().then((v) {
if (mounted) setState(() => _maximized = v);
});
}
@override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}
@override
void onWindowMaximize() {
if (mounted) setState(() => _maximized = true);
}
@override
void onWindowUnmaximize() {
if (mounted) setState(() => _maximized = false);
}
Future<void> _toggleMaximize() async {
if (widget.onToggleFullscreen != null) {
widget.onToggleFullscreen!();
return;
}
if (await windowManager.isMaximized()) {
await windowManager.unmaximize();
} else {
await windowManager.maximize();
}
}
Future<void> _close() async {
try {
await SessionState.instance.coordinator.leave();
await SessionState.instance.stopServer();
} catch (_) {}
await windowManager.close();
}
@override
Widget build(BuildContext context) {
final focus = widget.focusNode;
final restored = widget.fullscreen || _maximized;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_WinBtn(
icon: Icons.remove,
tooltip: 'Minimize',
focusNode: focus,
onPressed: () => windowManager.minimize(),
),
_WinBtn(
icon: restored ? Icons.filter_none : Icons.crop_square,
tooltip: restored ? 'Restore' : 'Maximize',
focusNode: focus,
onPressed: _toggleMaximize,
),
_WinBtn(
icon: Icons.close,
tooltip: 'Close',
focusNode: focus,
hoverColor: Colors.red,
onPressed: _close,
),
],
);
}
}
class _WinBtn extends StatelessWidget {
final IconData icon;
final String tooltip;
final VoidCallback onPressed;
final FocusNode? focusNode;
final Color? hoverColor;
const _WinBtn({
required this.icon,
required this.tooltip,
required this.onPressed,
this.focusNode,
this.hoverColor,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 40,
height: 32,
child: IconButton(
focusNode: focusNode,
tooltip: tooltip,
padding: EdgeInsets.zero,
constraints: const BoxConstraints.tightFor(width: 40, height: 32),
iconSize: 15,
splashRadius: 12,
hoverColor: (hoverColor ?? Colors.white).withValues(alpha: 0.12),
highlightColor: Colors.transparent,
icon: Icon(icon, size: 15),
onPressed: onPressed,
),
);
}
}