From 3b5c76cac4b97d1fbf6679f144ea8b49d0e985f5 Mon Sep 17 00:00:00 2001 From: KIMI Date: Sat, 15 Aug 2026 14:54:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E8=BF=9E=E4=B8=8D=E4=B8=8A=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=97=B6?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E9=A1=B5=E6=97=A0=E9=99=90=E8=BD=AC=E5=9C=88?= =?UTF-8?q?=EF=BC=88B-58=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 启动页先读本地凭证:无凭证(首次打开)直接进登录页,不再先等 SDK 初始化 - SDK init/login 加 20s 超时;init 用共享 Future 防止超时后重复触发 initSDK - 登录页对 IM 登录超时给出明确提示(确认手机连着公司内网 WiFi) - 修复 flutter create 模板遗留的 widget_test(引用不存在的 MyApp),改为登录页渲染冒烟测试 - 版本号 1.0.1+2 --- mobile/lib/main.dart | 26 +++++++++++++++---------- mobile/lib/screens/login_screen.dart | 7 +++++-- mobile/lib/services/im_service.dart | 21 +++++++++++++++++--- mobile/pubspec.yaml | 2 +- mobile/test/widget_test.dart | 29 +++++----------------------- 5 files changed, 45 insertions(+), 40 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index e8d1fa5..8e4481a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -77,18 +77,24 @@ class _SplashGateState extends State { Future _boot() async { final im = IMService.instance; + // 先读本地凭证:没有凭证(首次打开)直接去登录页, + // 不在启动页等 SDK 初始化——服务器连不上时 initSDK 可能长时间不返回, + // 不能让启动页一直转圈。 + final credential = await LoginScreen.readCredential(); + if (credential == null) { + if (!mounted) return; + Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const LoginScreen())); + return; + } try { - await im.init(); - final credential = await LoginScreen.readCredential(); - if (credential != null) { - await im.login(userID: credential.$1, token: credential.$2); - CallService.instance.start(); - if (!mounted) return; - Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const HomeScreen())); - return; - } + await im.init().timeout(const Duration(seconds: 20)); + await im.login(userID: credential.$1, token: credential.$2).timeout(const Duration(seconds: 20)); + CallService.instance.start(); + if (!mounted) return; + Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const HomeScreen())); + return; } catch (_) { - // 自动登录失败(token 失效等):清凭证回登录页 + // 自动登录失败(token 失效、服务器连不上等):清凭证回登录页 await LoginScreen.clearCredential(); } if (!mounted) return; diff --git a/mobile/lib/screens/login_screen.dart b/mobile/lib/screens/login_screen.dart index f2ccaac..2618147 100644 --- a/mobile/lib/screens/login_screen.dart +++ b/mobile/lib/screens/login_screen.dart @@ -78,8 +78,11 @@ class _LoginScreenState extends State { password: password, platformID: IMService.instance.platformID, ); - // 2. 登录 OpenIM SDK - await IMService.instance.login(userID: result.userID, token: result.token); + // 2. 登录 OpenIM SDK(服务器连不上时这一步可能长时间不返回,加超时兜底) + await IMService.instance.login(userID: result.userID, token: result.token).timeout( + const Duration(seconds: 20), + onTimeout: () => throw AuthException('连不上消息服务器,请确认手机连着公司内网 WiFi'), + ); // 3. 启动通话信令监听 CallService.instance.start(); // 4. 存凭证用于下次自动登录 diff --git a/mobile/lib/services/im_service.dart b/mobile/lib/services/im_service.dart index d8cee1a..2bd398d 100644 --- a/mobile/lib/services/im_service.dart +++ b/mobile/lib/services/im_service.dart @@ -75,9 +75,24 @@ class IMService extends ChangeNotifier { int get platformID => Platform.isIOS ? IMPlatform.ios : IMPlatform.android; - /// App 启动时初始化 SDK(只做一次) - Future init() async { - if (sdkReady) return; + /// App 启动时初始化 SDK(只做一次)。 + /// 用共享 Future 防止超时后重复触发 initSDK:第一次调用还在跑时, + /// 后续调用直接复用同一个 Future;失败了则允许下次重试。 + Future? _initFuture; + + Future init() { + if (sdkReady) return Future.value(); + final inFlight = _initFuture; + if (inFlight != null) return inFlight; + final future = _doInit(); + _initFuture = future; + return future.whenComplete(() { + // 失败的初始化允许重试;成功的保留 sdkReady 标记即可 + if (!sdkReady && identical(_initFuture, future)) _initFuture = null; + }); + } + + Future _doInit() async { final dir = await getApplicationDocumentsDirectory(); final dataDir = '${dir.path}/openim'; await Directory(dataDir).create(recursive: true); diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index aa76749..fc48522 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -1,7 +1,7 @@ name: changlian description: 畅联 —— 公司内部通讯 App(手机端) publish_to: "none" -version: 1.0.0+1 +version: 1.0.1+2 environment: sdk: ">=3.6.0 <4.0.0" diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart index f383406..6c849bf 100644 --- a/mobile/test/widget_test.dart +++ b/mobile/test/widget_test.dart @@ -1,30 +1,11 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - +import 'package:changlian/screens/login_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:changlian/main.dart'; - void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + testWidgets('登录页能正常渲染', (tester) async { + await tester.pumpWidget(const MaterialApp(home: LoginScreen())); + expect(find.text('畅联'), findsOneWidget); + expect(find.text('登录'), findsOneWidget); }); }