Restructure step 3: your server, Invite, version gate, AppGraph
ci / core (push) Successful in 27s
ci / server (push) Successful in 1m7s
ci / updater-windows (push) Failing after 6m4s

- One server per app run (loopback, no password); Invite attaches an
  all-interfaces listener to the same server and Stop sharing detaches
  it (guests told with 4016); members, media and Up next never reset.
  No solo mode: you're home on your own server unless in a friend's.
- Navigation: none / yours / friend; a join passes a version gate on
  /health first (clear "they/you need to update", update offer).
- Invite as a value (host, port, name, password flag, version, fp
  reserved); addresses filtered (loopback, link-local, 198.18/15,
  bridges) and ranked; Sharing chip, Copy invite, Stop sharing.
- Alone looks like a plain player (alone replaces solo).
- AppGraph builds and disposes systems; widgets use AppScope;
  SessionState deleted; a test caps the remaining singletons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-15 22:52:52 +03:00
1 parent 629be0d9ed
commit 4d00b7455e
86 files changed
+2993 -2071

No files matched your search

+20 -11
View File
@@ -1,8 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../session/notifier.dart' show AppEvent, Copied, CopyFailed, noticeFor;
import '../state/session_state.dart';
import '../app/app_graph.dart';
// Reusing what the app shows elsewhere: copying a link or a title, opening
// a page in the browser. One wording and one confirmation (a small toast)
@@ -21,27 +21,36 @@ abstract final class ReuseText {
@visibleForTesting
void Function(String text)? debugReuseToast;
void _notify(AppEvent event) {
void Function(AppEvent event) _notifier(BuildContext context) {
final debug = debugReuseToast;
if (debug != null) return debug(noticeFor(event).toast ?? '');
SessionState.instance.coordinator.notify(event);
if (debug != null) return (event) => debug(noticeFor(event).toast ?? '');
return AppScope.of(context).notifier.notify;
}
/// Puts [text] on the clipboard and confirms it with a toast ([done]:
/// its text, e.g. "Link copied").
Future<void> copyToClipboard(String text, {String done = 'Copied'}) async {
Future<void> copyToClipboard(BuildContext context, String text, {String done = 'Copied'}) async {
final notify = _notifier(context);
try {
await Clipboard.setData(ClipboardData(text: text));
} catch (_) {
_notify(const CopyFailed());
notify(const CopyFailed());
return;
}
_notify(Copied(done));
notify(Copied(done));
}
/// Opens [url] in the browser; when none opens, copies it instead and
/// says so.
Future<void> openLink(String url) async {
if (await SessionState.instance.ports.urls.open(url)) return;
await copyToClipboard(url, done: "Couldn't open a browser — link copied");
Future<void> openLink(BuildContext context, String url) async {
final urls = AppScope.of(context).ports.urls;
final notify = _notifier(context);
if (await urls.open(url)) return;
try {
await Clipboard.setData(ClipboardData(text: url));
} catch (_) {
notify(const CopyFailed());
return;
}
notify(const Copied("Couldn't open a browser — link copied"));
}