Restructure step 0: test safety net, AppEvent/Notifier seam, platform ports, protocol 16 planned

- Tests: the four known failures fixed (test-side causes), one duplicate
  file and four detail-pinning cases removed, every test that pins
  behaviour the restructure changes tagged with its step.
- Every toast, sound and activity-log line goes through Notifier.notify
  (AppEvent), mapped one-to-one to today's behaviour.
- Platform ports (shell, URLs, folders, link resolver, system
  notifications) with desktop implementations; window_manager, Process
  and file_picker now live only in platform/desktop/.
- ROADMAP: protocol 16 planned; app id dev.foxyz.starsync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-15 19:24:11 +03:00
1 parent 7e98ebd1db
commit a1b270fd4e
63 files changed
+1231 -434

No files matched your search

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import '../state/session_state.dart';
@@ -18,43 +17,37 @@ class WindowButtons extends StatefulWidget {
State<WindowButtons> createState() => _WindowButtonsState();
}
class _WindowButtonsState extends State<WindowButtons> with WindowListener {
class _WindowButtonsState extends State<WindowButtons> {
final _shell = SessionState.instance.ports.shell;
bool _maximized = false;
late final void Function() _stopWatch;
@override
void initState() {
super.initState();
windowManager.addListener(this);
windowManager.isMaximized().then((v) {
_stopWatch = _shell.watchMaximized((v) {
if (mounted) setState(() => _maximized = v);
});
_shell.isMaximized().then((v) {
if (mounted) setState(() => _maximized = v);
});
}
@override
void dispose() {
windowManager.removeListener(this);
_stopWatch();
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();
if (await _shell.isMaximized()) {
await _shell.unmaximize();
} else {
await windowManager.maximize();
await _shell.maximize();
}
}
@@ -63,7 +56,7 @@ class _WindowButtonsState extends State<WindowButtons> with WindowListener {
await SessionState.instance.coordinator.leave();
await SessionState.instance.stopServer();
} catch (_) {}
await windowManager.close();
await _shell.requestClose();
}
@override
@@ -77,7 +70,7 @@ class _WindowButtonsState extends State<WindowButtons> with WindowListener {
icon: Icons.remove,
tooltip: 'Minimize',
focusNode: focus,
onPressed: () => windowManager.minimize(),
onPressed: () => _shell.minimize(),
),
_WinBtn(
icon: restored ? Icons.filter_none : Icons.crop_square,
@@ -131,3 +124,27 @@ class _WinBtn extends StatelessWidget {
);
}
}
/// The title area: drag moves the window, a double click maximizes or
/// restores it (window_manager's DragToMoveArea, through [ShellPort]).
class WindowDragArea extends StatelessWidget {
final Widget child;
const WindowDragArea({super.key, required this.child});
@override
Widget build(BuildContext context) {
final shell = SessionState.instance.ports.shell;
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (_) => shell.startDragging(),
onDoubleTap: () async {
if (await shell.isMaximized()) {
await shell.unmaximize();
} else {
await shell.maximize();
}
},
child: child,
);
}
}