feat(client): 断开连接前弹二次确认框(避免误触瞬断)
连接键在 on 态点击不再直接断开:先弹危险操作确认框(告知连接已加密, 确认才 toggle 断开);off/connecting 态行为不变直接 toggle。 新增 l10n disconnectTitle/Body/Action(en/zh/es/ja/ko/ru 六语)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
This commit is contained in:
@@ -343,6 +343,9 @@ abstract class AppText {
|
|||||||
String get capOn;
|
String get capOn;
|
||||||
String get connectNow; // 连接键 off 态圆内文字
|
String get connectNow; // 连接键 off 态圆内文字
|
||||||
String get secure; // 连接键圆内「已加密」
|
String get secure; // 连接键圆内「已加密」
|
||||||
|
String get disconnectTitle; // 断开确认弹窗标题
|
||||||
|
String get disconnectBody; // 断开确认弹窗正文
|
||||||
|
String get disconnectAction; // 断开确认弹窗「断开」按钮
|
||||||
|
|
||||||
// ── 底部 / 侧栏导航 ──
|
// ── 底部 / 侧栏导航 ──
|
||||||
String get tabConnect;
|
String get tabConnect;
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class StringsEn extends AppText {
|
|||||||
@override
|
@override
|
||||||
String get secure => 'SECURE';
|
String get secure => 'SECURE';
|
||||||
@override
|
@override
|
||||||
|
String get disconnectTitle => 'Disconnect?';
|
||||||
|
@override
|
||||||
|
String get disconnectBody => 'Your connection is encrypted. Are you sure you want to disconnect?';
|
||||||
|
@override
|
||||||
|
String get disconnectAction => 'Disconnect';
|
||||||
|
@override
|
||||||
String get tabConnect => 'Connect';
|
String get tabConnect => 'Connect';
|
||||||
@override
|
@override
|
||||||
String get tabServers => 'Servers';
|
String get tabServers => 'Servers';
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class StringsEs extends AppText {
|
|||||||
@override
|
@override
|
||||||
String get secure => 'SEGURO';
|
String get secure => 'SEGURO';
|
||||||
@override
|
@override
|
||||||
|
String get disconnectTitle => '¿Desconectar?';
|
||||||
|
@override
|
||||||
|
String get disconnectBody => 'Tu conexión está cifrada. ¿Seguro que quieres desconectar?';
|
||||||
|
@override
|
||||||
|
String get disconnectAction => 'Desconectar';
|
||||||
|
@override
|
||||||
String get tabConnect => 'Conectar';
|
String get tabConnect => 'Conectar';
|
||||||
@override
|
@override
|
||||||
String get tabServers => 'Servidores';
|
String get tabServers => 'Servidores';
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class StringsJa extends AppText {
|
|||||||
@override
|
@override
|
||||||
String get secure => 'セキュア';
|
String get secure => 'セキュア';
|
||||||
@override
|
@override
|
||||||
|
String get disconnectTitle => '切断しますか?';
|
||||||
|
@override
|
||||||
|
String get disconnectBody => '接続は暗号化されています。切断してもよろしいですか?';
|
||||||
|
@override
|
||||||
|
String get disconnectAction => '切断';
|
||||||
|
@override
|
||||||
String get tabConnect => '接続';
|
String get tabConnect => '接続';
|
||||||
@override
|
@override
|
||||||
String get tabServers => 'サーバー';
|
String get tabServers => 'サーバー';
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class StringsKo extends AppText {
|
|||||||
@override
|
@override
|
||||||
String get secure => '보안';
|
String get secure => '보안';
|
||||||
@override
|
@override
|
||||||
|
String get disconnectTitle => '연결 해제하시겠습니까?';
|
||||||
|
@override
|
||||||
|
String get disconnectBody => '연결이 암호화되어 있습니다. 정말 연결을 해제하시겠습니까?';
|
||||||
|
@override
|
||||||
|
String get disconnectAction => '연결 해제';
|
||||||
|
@override
|
||||||
String get tabConnect => '연결';
|
String get tabConnect => '연결';
|
||||||
@override
|
@override
|
||||||
String get tabServers => '서버';
|
String get tabServers => '서버';
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class StringsRu extends AppText {
|
|||||||
@override
|
@override
|
||||||
String get secure => 'ЗАЩИЩЕНО';
|
String get secure => 'ЗАЩИЩЕНО';
|
||||||
@override
|
@override
|
||||||
|
String get disconnectTitle => 'Отключиться?';
|
||||||
|
@override
|
||||||
|
String get disconnectBody => 'Ваше соединение зашифровано. Действительно отключиться?';
|
||||||
|
@override
|
||||||
|
String get disconnectAction => 'Отключить';
|
||||||
|
@override
|
||||||
String get tabConnect => 'Подключение';
|
String get tabConnect => 'Подключение';
|
||||||
@override
|
@override
|
||||||
String get tabServers => 'Серверы';
|
String get tabServers => 'Серверы';
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ class StringsZh extends AppText {
|
|||||||
@override
|
@override
|
||||||
String get secure => '已加密';
|
String get secure => '已加密';
|
||||||
@override
|
@override
|
||||||
|
String get disconnectTitle => '断开连接?';
|
||||||
|
@override
|
||||||
|
String get disconnectBody => '当前连接已加密。确定要断开吗?';
|
||||||
|
@override
|
||||||
|
String get disconnectAction => '断开';
|
||||||
|
@override
|
||||||
String get tabConnect => '连接';
|
String get tabConnect => '连接';
|
||||||
@override
|
@override
|
||||||
String get tabServers => '节点';
|
String get tabServers => '节点';
|
||||||
|
|||||||
@@ -21,6 +21,47 @@ class ConnectPage extends ConsumerWidget {
|
|||||||
final bool isWide;
|
final bool isWide;
|
||||||
final VoidCallback onOpenNodes;
|
final VoidCallback onOpenNodes;
|
||||||
|
|
||||||
|
// 连接键点击:off/connecting 直接 toggle;on 态(已连接)先弹二次确认再断开,
|
||||||
|
// 避免误触瞬断加密连接。
|
||||||
|
Future<void> _handleToggle(BuildContext context, WidgetRef ref, VpnPhase phase, AppText t) async {
|
||||||
|
final notifier = ref.read(connectionProvider.notifier);
|
||||||
|
if (phase != VpnPhase.on) {
|
||||||
|
notifier.toggle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final c = context.pangolin;
|
||||||
|
final ok = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
backgroundColor: c.surface,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(PangolinRadius.xl)),
|
||||||
|
title: Row(children: [
|
||||||
|
Container(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
decoration: BoxDecoration(color: c.dangerSubtle, shape: BoxShape.circle),
|
||||||
|
child: Icon(PangolinIcons.alertTriangle, size: 18, color: c.danger)),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(t.disconnectTitle,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: PangolinText.body.copyWith(color: c.fg1, fontWeight: FontWeight.w700))),
|
||||||
|
]),
|
||||||
|
content: Text(t.disconnectBody, style: PangolinText.sm.copyWith(color: c.fg2, height: 1.5)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, false),
|
||||||
|
child: Text(t.devCancel, style: PangolinText.sm.copyWith(color: c.fg2, fontWeight: FontWeight.w600))),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
|
child: Text(t.disconnectAction,
|
||||||
|
style: PangolinText.sm.copyWith(color: c.danger, fontWeight: FontWeight.w700))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (ok == true) notifier.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final c = context.pangolin;
|
final c = context.pangolin;
|
||||||
@@ -53,7 +94,7 @@ class ConnectPage extends ConsumerWidget {
|
|||||||
elapsed: conn.elapsed,
|
elapsed: conn.elapsed,
|
||||||
offLabel: t.connectNow,
|
offLabel: t.connectNow,
|
||||||
secureLabel: t.secure,
|
secureLabel: t.secure,
|
||||||
onTap: () => ref.read(connectionProvider.notifier).toggle(),
|
onTap: () => _handleToggle(context, ref, conn.phase, t),
|
||||||
);
|
);
|
||||||
|
|
||||||
final captionWidget = Column(mainAxisSize: MainAxisSize.min, children: [
|
final captionWidget = Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
@@ -94,7 +135,7 @@ class ConnectPage extends ConsumerWidget {
|
|||||||
offLabel: t.connectNow,
|
offLabel: t.connectNow,
|
||||||
secureLabel: t.secure,
|
secureLabel: t.secure,
|
||||||
size: 176,
|
size: 176,
|
||||||
onTap: () => ref.read(connectionProvider.notifier).toggle(),
|
onTap: () => _handleToggle(context, ref, conn.phase, t),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text(caption,
|
Text(caption,
|
||||||
|
|||||||
Reference in New Issue
Block a user