From e74bb792bd5b47c91c8f666a9cff18614c09e517 Mon Sep 17 00:00:00 2001 From: chuxuehaocai Date: Tue, 29 Sep 2026 13:30:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=99=BB=E5=BD=95=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=AF=B9=E8=AE=A4=E4=B8=8D=E5=87=BA=E7=9A=84=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E7=BB=99=E5=87=BA=E6=8F=90=E7=A4=BA=EF=BC=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E6=97=A0=E5=8F=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SGWCMAID 前缀改成按粘贴原文判断:令牌取的是内容的后 64 位,粘贴内容一旦超过 64 位,前缀就被截掉了,原先对截取结果做 startsWith 会直接 return, 点按钮看着能点却一声不响 - 补 test/login_page_test.dart 覆盖两条输入路径:不认识的令牌给提示, 连接信息把按钮切成「恢复会话」 --- lib/config/strings.dart | 1 + lib/pages/login_page.dart | 71 +++++++++++++++++++++------------------ test/login_page_test.dart | 40 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 32 deletions(-) create mode 100644 test/login_page_test.dart diff --git a/lib/config/strings.dart b/lib/config/strings.dart index 63f0b25..e8d0ccb 100644 --- a/lib/config/strings.dart +++ b/lib/config/strings.dart @@ -21,6 +21,7 @@ class AppStrings { static const qrScanError = '解析失败'; static const forcePreviewApi = '强制使用 Preview API'; static const loginFailed = '登录失败'; + static const qrTokenUnrecognized = '没认出 QR 令牌(内容里应当含 SGWCMAID),也可以粘贴「连接信息」。'; static const requestFailed = '请求失败'; static const settingsTooltip = '设置'; diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 5c910e3..35c62ba 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -23,6 +23,7 @@ class LoginPage extends StatefulWidget { class _LoginPageState extends State { final _qrController = TextEditingController(); String _qrCode = ''; + String _rawInput = ''; SessionShare? _share; bool _loading = false; bool _forcePreviewApi = false; @@ -36,6 +37,7 @@ class _LoginPageState extends State { void _onQRContentChanged(String value) { final text = value.trim(); setState(() { + _rawInput = text; _qrCode = SessionModel.extractQRCode(text); _share = SessionShare.tryDecode(text); }); @@ -51,6 +53,7 @@ class _LoginPageState extends State { if (result != null && result.isNotEmpty) { _qrController.text = result; setState(() { + _rawInput = result.trim(); _qrCode = SessionModel.extractQRCode(result); _share = SessionShare.tryDecode(result); }); @@ -78,43 +81,47 @@ class _LoginPageState extends State { Future _onLogin() async { if (_qrCode.isEmpty) return; - if (_qrCode.startsWith("SGWCMAID")) { - setState(() => _loading = true); + // 令牌是粘贴内容的后 64 位,前缀可能在截取之后就没了,所以要拿原文判断; + // 认不出来时必须给提示,否则点了按钮一声不响,像是坏了。 + if (!_rawInput.contains('SGWCMAID')) { + context.showSnack(AppStrings.qrTokenUnrecognized); + return; + } + setState(() => _loading = true); - try { - final result = await SessionModel.instance.loginWithQr(_qrCode); + try { + final result = await SessionModel.instance.loginWithQr(_qrCode); - if (!mounted) return; + if (!mounted) return; - if (result.success) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => HomePage( - userId: result.userId, - token: result.token, - forcePreviewApi: _forcePreviewApi, - ), + if (result.success) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => HomePage( + userId: result.userId, + token: result.token, + forcePreviewApi: _forcePreviewApi, ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - '${AppStrings.loginFailed} (errorID: ${result.errorId})', - ), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + '${AppStrings.loginFailed} (errorID: ${result.errorId})', ), - ); - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${AppStrings.requestFailed}: $e')), - ); - } - } finally { - if (mounted) { - setState(() => _loading = false); - } + ), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${AppStrings.requestFailed}: $e')), + ); + } + } finally { + if (mounted) { + setState(() => _loading = false); } } } diff --git a/test/login_page_test.dart b/test/login_page_test.dart new file mode 100644 index 0000000..c15b9e9 --- /dev/null +++ b/test/login_page_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ichino/config/strings.dart'; +import 'package:ichino/main.dart'; +import 'package:ichino/models/session_share.dart'; + +/// 登录页的输入既可能是 QR 令牌,也可能是主页导出的「连接信息」, +/// 两条路都要有可见反馈,不能出现点了没反应。 +void main() { + testWidgets('粘贴不认识的令牌时点登录会给出提示', (tester) async { + await tester.pumpWidget(const MyApp()); + await tester.pump(); + + // 72 位:截取的后 64 位里没有 SGWCMAID 前缀 + await tester.enterText(find.byType(TextField), 'ABCDEF' * 12); + await tester.pump(); + + await tester.tap(find.text(AppStrings.login)); + await tester.pump(); + + expect(find.text(AppStrings.qrTokenUnrecognized), findsOneWidget); + }); + + testWidgets('粘贴连接信息时按钮切成「恢复会话」', (tester) async { + await tester.pumpWidget(const MyApp()); + await tester.pump(); + + const share = SessionShare( + userId: 7, + token: 'SGWCMAIDtoken', + cookie: 'JSESSIONID=x', + ); + await tester.enterText(find.byType(TextField), share.encode()); + await tester.pump(); + + expect(find.text(AppStrings.sessionShareDetected), findsOneWidget); + expect(find.text(AppStrings.restoreSession), findsOneWidget); + expect(find.text(AppStrings.login), findsNothing); + }); +}