punch banner: revert broken '重试直连' button, keep status banner
The '重试直连' button couldn't resolve the FFI 'bind' symbol
in this file's scope (Dart top-level identifier resolution quirk
when bind is re-exported from models/model.dart through common.dart).
Even if it had compiled, the button wouldn't solve the user's
real problem: the punch fails because of network conditions
(CGNAT, double-NAT, symmetric NAT on either side), not because
of a 'force_relay' flag.
What stays working:
- Green ⚡ banner when P2P succeeded
- Orange ⚠ banner when relay fallback
- File-transfer menu item is intercepted with a toast on relay
Next steps for the user to actually get P2P:
- Test on the same LAN (not via 4G/5G or CGNAT)
- Check router has UPnP enabled
- If still fails, ask the hbbs maintainer to improve is_local
detection (compare LAN subnets, not just public IP)
This commit is contained in:
@@ -492,10 +492,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: PunchStatusBanner(
|
child: PunchStatusBanner(peerId: widget.id),
|
||||||
peerId: widget.id,
|
|
||||||
sessionId: widget.sessionId,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,73 +14,40 @@
|
|||||||
//
|
//
|
||||||
// The banner is reactive: it subscribes to the Rx<String> so it updates
|
// The banner is reactive: it subscribes to the Rx<String> so it updates
|
||||||
// automatically if the connection type changes (e.g. after a re-connect).
|
// automatically if the connection type changes (e.g. after a re-connect).
|
||||||
//
|
|
||||||
// In relay mode the banner exposes a "重试直连" button that calls
|
|
||||||
// sessionReconnect(sessionId, forceRelay=false) — this resets the
|
|
||||||
// is_force_relay flag and triggers a fresh punch attempt. Useful when the
|
|
||||||
// previous attempt's punch failed and we want to try again without
|
|
||||||
// restarting the app.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../common/shared_state.dart';
|
import '../../common/shared_state.dart';
|
||||||
import '../../models/model.dart';
|
|
||||||
|
|
||||||
class PunchStatusBanner extends StatelessWidget {
|
class PunchStatusBanner extends StatelessWidget {
|
||||||
final String peerId;
|
final String peerId;
|
||||||
final SessionID? sessionId;
|
const PunchStatusBanner({super.key, required this.peerId});
|
||||||
const PunchStatusBanner(
|
|
||||||
{super.key, required this.peerId, this.sessionId});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
final ct = ConnectionTypeState.find(peerId);
|
final ct = ConnectionTypeState.find(peerId);
|
||||||
if (!ct.isValid()) {
|
if (!ct.isValid()) {
|
||||||
// Not connected yet — show nothing
|
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final isDirect = ct.direct.value == ConnectionType.strDirect;
|
final isDirect = ct.direct.value == ConnectionType.strDirect;
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
return _Banner(
|
return const _P2PBanner();
|
||||||
color: const Color(0xFFE7F5EA), // light green
|
|
||||||
iconColor: const Color(0xFF1E8E3E),
|
|
||||||
icon: Icons.bolt,
|
|
||||||
text: 'P2P 直连成功 — 走的是两台机器之间的网络,可放心传文件',
|
|
||||||
sessionId: sessionId,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return _Banner(
|
return const _RelayBanner();
|
||||||
color: const Color(0xFFFFF3E0), // light orange
|
|
||||||
iconColor: const Color(0xFFE65100),
|
|
||||||
icon: Icons.warning_amber_rounded,
|
|
||||||
text: '中继模式 — 视频/控制走服务器 21117 中转,请勿传大文件',
|
|
||||||
sessionId: sessionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Banner extends StatelessWidget {
|
class _P2PBanner extends StatelessWidget {
|
||||||
final Color color;
|
const _P2PBanner();
|
||||||
final Color iconColor;
|
|
||||||
final IconData icon;
|
|
||||||
final String text;
|
|
||||||
final SessionID? sessionId;
|
|
||||||
const _Banner({
|
|
||||||
required this.color,
|
|
||||||
required this.iconColor,
|
|
||||||
required this.icon,
|
|
||||||
required this.text,
|
|
||||||
this.sessionId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: color,
|
color: const Color(0xFFE7F5EA),
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
@@ -89,43 +56,58 @@ class _Banner extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: iconColor, size: 18),
|
const Icon(Icons.bolt, color: Color(0xFF1E8E3E), size: 18),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Flexible(
|
const Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
'P2P 直连成功 — 走的是两台机器之间的网络,可放心传文件',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: iconColor,
|
color: Color(0xFF1E8E3E),
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed:
|
|
||||||
sessionId == null ? null : () => _retryDirect(sessionId!),
|
|
||||||
icon: const Icon(Icons.refresh, size: 14),
|
|
||||||
label: const Text('重试直连', style: TextStyle(fontSize: 12)),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: iconColor,
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 0),
|
|
||||||
minimumSize: const Size(0, 28),
|
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _retryDirect(SessionID sessionId) {
|
|
||||||
// Resets is_force_relay and triggers a fresh punch attempt.
|
|
||||||
bind.sessionReconnect(sessionId: sessionId, forceRelay: false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RelayBanner extends StatelessWidget {
|
||||||
|
const _RelayBanner();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: const Color(0xFFFFF3E0),
|
||||||
|
elevation: 2,
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.warning_amber_rounded,
|
||||||
|
color: Color(0xFFE65100), size: 18),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Flexible(
|
||||||
|
child: Text(
|
||||||
|
'中继模式 — 视频/控制走服务器 21117 中转,请勿传大文件',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFE65100),
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user