diff --git a/lib/app/routes/sc_routes.dart b/lib/app/routes/sc_routes.dart index bb04fd2..e5a09a6 100644 --- a/lib/app/routes/sc_routes.dart +++ b/lib/app/routes/sc_routes.dart @@ -1,11 +1,13 @@ import 'package:fluro/fluro.dart' as fluro; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:yumi/app/routes/sc_router_init.dart'; import 'package:yumi/modules/country/country_route.dart'; import 'package:yumi/modules/index/main_route.dart'; import 'package:yumi/modules/chat/chat_route.dart'; import 'package:yumi/modules/store/store_route.dart'; import 'package:yumi/modules/index/index_page.dart'; +import 'package:yumi/services/room/rc_room_manager.dart'; import 'package:yumi/modules/user/settings/settings_route.dart'; import 'package:yumi/modules/user/task/task_route.dart'; import 'package:yumi/modules/user/vip/vip_route.dart'; @@ -32,7 +34,7 @@ class SCRoutes { handler: fluro.Handler( handlerFunc: (BuildContext? context, Map> params) => - SCIndexPage(), + _buildHomePage(context), ), ); @@ -57,6 +59,13 @@ class SCRoutes { routerProvider.initRouter(router); } } + + static Widget _buildHomePage(BuildContext? context) { + return ChangeNotifierProvider.value( + value: resolveSocialChatRoomManager(context), + child: const SCIndexPage(), + ); + } } // PS:路由使用方法 diff --git a/lib/main.dart b/lib/main.dart index b7ba99e..7c1a6d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -241,9 +241,8 @@ class RootAppWithProviders extends StatelessWidget { ), // 房间与社交功能Provider - 懒加载 - ChangeNotifierProvider( - lazy: true, - create: (context) => SocialChatRoomManager(), + ChangeNotifierProvider.value( + value: socialChatRoomManager, ), // 礼物与动画系统Provider - 懒加载 diff --git a/lib/modules/auth/edit/sc_edit_profile_page.dart b/lib/modules/auth/edit/sc_edit_profile_page.dart index 3537507..5a013f3 100644 --- a/lib/modules/auth/edit/sc_edit_profile_page.dart +++ b/lib/modules/auth/edit/sc_edit_profile_page.dart @@ -640,7 +640,9 @@ class _SCEditProfilePageState extends State { authType = SCAuthType.GOOGLE.name; idToken = googleUid; } - } catch (e) {} + } catch (_) { + // Fallback auth lookup is best effort; the form will ask to sign in again below. + } } if (authType.isEmpty || idToken.isEmpty) { SCLoadingManager.hide(); diff --git a/lib/modules/index/index_page.dart b/lib/modules/index/index_page.dart index bb872e9..b7e06a2 100644 --- a/lib/modules/index/index_page.dart +++ b/lib/modules/index/index_page.dart @@ -563,7 +563,8 @@ class _SCIndexPageState extends State return; } await SCEntryPopupCoordinator.openFirstPartyRoomRandomGame(context); - } catch (error) { + } catch (_) { + // The entry popup is optional; failure should not block the home page. } finally { _isOpeningFirstRegisterRoomGame = false; } diff --git a/lib/services/audio/rtc_manager.dart b/lib/services/audio/rtc_manager.dart index a894f59..7dad717 100644 --- a/lib/services/audio/rtc_manager.dart +++ b/lib/services/audio/rtc_manager.dart @@ -2708,13 +2708,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { return; } _startRoomStatePolling(); - final currentContext = context; - if (currentContext != null && currentContext.mounted) { - Provider.of( - currentContext, - listen: false, - ).fetchContributionLevelData(roomId); - } + socialChatRoomManager.fetchContributionLevelData(roomId); failureType = RoomStartupFailureType.rtc; final rtcTokenResult = await rtcTokenFuture; @@ -3400,13 +3394,7 @@ class RealTimeCommunicationManager extends ChangeNotifier { void _clearRoomContributionLevelData([BuildContext? targetContext]) { final currentContext = targetContext ?? context; - if (currentContext == null || !currentContext.mounted) { - return; - } - Provider.of( - currentContext, - listen: false, - ).clearContributionLevelData(); + resolveSocialChatRoomManager(currentContext).clearContributionLevelData(); } @override diff --git a/lib/services/auth/authentication_manager.dart b/lib/services/auth/authentication_manager.dart index 133692c..97d425c 100644 --- a/lib/services/auth/authentication_manager.dart +++ b/lib/services/auth/authentication_manager.dart @@ -32,8 +32,12 @@ class SocialChatAuthenticationManager extends ChangeNotifier { } Future _initializeAuthentication() async { - await SCGoogleAuthService.initialize(); - await _verifyExistingSession(); + try { + await SCGoogleAuthService.initialize(); + await _verifyExistingSession(); + } catch (_) { + // Login buttons should remain usable even if silent Google restore fails. + } } Future _verifyExistingSession() async { @@ -51,10 +55,11 @@ class SocialChatAuthenticationManager extends ChangeNotifier { if (authType == SCAuthType.GOOGLE.name) { this.authType = authType; try { - _user ??= await SCGoogleAuthService.signInWithGoogle(); - if (_user == null) { + final signedInUser = await SCGoogleAuthService.signInWithGoogle(); + if (signedInUser == null) { return; } + _user = signedInUser; uid = _user!.uid; SCLoadingManager.show(); if (uid.isNotEmpty) { diff --git a/lib/services/room/rc_room_manager.dart b/lib/services/room/rc_room_manager.dart index 60d88a4..c6f40a5 100644 --- a/lib/services/room/rc_room_manager.dart +++ b/lib/services/room/rc_room_manager.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; @@ -9,6 +10,25 @@ import 'package:yumi/shared/business_logic/models/res/sc_edit_room_info_res.dart import 'package:yumi/shared/business_logic/models/res/my_room_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_contribute_level_res.dart'; +final SocialChatRoomManager socialChatRoomManager = SocialChatRoomManager(); + +SocialChatRoomManager resolveSocialChatRoomManager(BuildContext? context) { + if (context != null && context.mounted) { + try { + final manager = Provider.of( + context, + listen: false, + ); + if (manager != null) { + return manager; + } + } catch (_) { + // A stale route context can be outside the provider tree after logout. + } + } + return socialChatRoomManager; +} + class SocialChatRoomManager extends ChangeNotifier { MyRoomRes? myRoom; SCRoomContributeLevelRes? roomContributeLevelRes; diff --git a/lib/shared/data_sources/sources/remote/net/api.dart b/lib/shared/data_sources/sources/remote/net/api.dart index 7ef499b..d404e80 100644 --- a/lib/shared/data_sources/sources/remote/net/api.dart +++ b/lib/shared/data_sources/sources/remote/net/api.dart @@ -107,6 +107,7 @@ Future _isCurrentSessionStillValid(DioException e) async { class BaseNetworkClient { final Dio dio; static const String silentErrorToastKey = 'silentErrorToast'; + static const String suppressAuthLogoutKey = 'suppressAuthLogout'; static const String baseUrlOverrideKey = 'baseUrlOverride'; BaseNetworkClient() : dio = Dio() { @@ -292,6 +293,13 @@ class BaseNetworkClient { SCLoadingManager.hide(); final bool silentErrorToast = e.requestOptions.extra[BaseNetworkClient.silentErrorToastKey] == true; + final handledBusinessError = await _handleBusinessErrorResponse( + e, + silentErrorToast: silentErrorToast, + ); + if (handledBusinessError != null) { + return handledBusinessError; + } switch (e.type) { case DioExceptionType.connectionTimeout: return DioException(requestOptions: e.requestOptions, error: '连接超时'); @@ -300,50 +308,10 @@ class BaseNetworkClient { case DioExceptionType.receiveTimeout: return DioException(requestOptions: e.requestOptions, error: '接收超时'); case DioExceptionType.badResponse: - final responseData = e.response?.data; - final errorCode = - responseData is Map ? responseData["errorCode"] : null; - final errorMsg = - responseData is Map - ? responseData["errorMsg"] - : responseData?.toString(); - if (errorCode == SCErroCode.userNotRegistered.code) { - //用户还没有注册 - SCTts.show("Please register an account first."); - BuildContext? context = navigatorKey.currentContext; - if (context != null) { - SCNavigatorUtils.push( - context, - LoginRouter.editProfile, - replace: false, - ); - } - } else if (errorCode == SCErroCode.authUnauthorized.code) { - //token过期 - final shouldLogout = - !SCNavigatorUtils.inLoginPage && - !await _isCurrentSessionStillValid(e); - final BuildContext? context = navigatorKey.currentContext; - if (context != null && context.mounted && shouldLogout) { - AccountStorage().logout(context); - SCNavigatorUtils.inLoginPage = true; - } - } else { - if (errorMsg.toString().endsWith("balance not made")) { - BuildContext? context = navigatorKey.currentContext; - if (context != null) { - SCRoomUtils.goRecharge(context); - } - } else { - if (!silentErrorToast) { - SCTts.show(errorMsg); - } - } - } return DioException( requestOptions: e.requestOptions, response: e.response, - error: '服务器错误: $errorCode ${errorMsg ?? ""}'.trim(), + error: e.error ?? e.message ?? 'Server fail', ); case DioExceptionType.cancel: return DioException( @@ -362,6 +330,95 @@ class BaseNetworkClient { ); } } + + Future _handleBusinessErrorResponse( + DioException e, { + required bool silentErrorToast, + }) async { + final responseData = e.response?.data; + if (responseData == null) { + return null; + } + + final errorCode = _readErrorCode(responseData); + final errorMsg = _readErrorMessage(responseData); + if (errorCode == null && errorMsg == null) { + return null; + } + + if (errorCode == SCErroCode.userNotRegistered.code) { + // 用户还没有注册:Google/Apple 登录后需要继续补充资料。 + SCTts.show("Please register an account first."); + final BuildContext? context = navigatorKey.currentContext; + if (context != null && context.mounted) { + SCNavigatorUtils.push(context, LoginRouter.editProfile, replace: false); + } + } else if (errorCode == SCErroCode.authUnauthorized.code) { + // token过期 + if (e.requestOptions.extra[BaseNetworkClient.suppressAuthLogoutKey] == + true) { + return DioException( + requestOptions: e.requestOptions, + response: e.response, + type: e.type, + error: '服务器错误: ${errorCode ?? ""} ${errorMsg ?? ""}'.trim(), + message: e.message, + ); + } + final shouldLogout = + !SCNavigatorUtils.inLoginPage && + !await _isCurrentSessionStillValid(e); + final BuildContext? context = navigatorKey.currentContext; + if (context != null && context.mounted && shouldLogout) { + AccountStorage().logout(context); + SCNavigatorUtils.inLoginPage = true; + } + } else { + final message = errorMsg ?? ""; + if (message.endsWith("balance not made")) { + final BuildContext? context = navigatorKey.currentContext; + if (context != null && context.mounted) { + SCRoomUtils.goRecharge(context); + } + } else if (!silentErrorToast && message.isNotEmpty) { + SCTts.show(message); + } + } + + return DioException( + requestOptions: e.requestOptions, + response: e.response, + type: e.type, + error: '服务器错误: ${errorCode ?? ""} ${errorMsg ?? ""}'.trim(), + message: e.message, + ); + } + + int? _readErrorCode(dynamic responseData) { + if (responseData is! Map) { + return null; + } + final value = responseData["errorCode"] ?? responseData["code"]; + if (value is int) { + return value; + } + if (value is num) { + return value.toInt(); + } + if (value is String) { + return int.tryParse(value); + } + return null; + } + + String? _readErrorMessage(dynamic responseData) { + if (responseData is Map) { + return responseData["errorMsg"]?.toString() ?? + responseData["message"]?.toString(); + } + final message = responseData?.toString().trim(); + return message == null || message.isEmpty ? null : message; + } } class NotSuccessException implements Exception { diff --git a/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart b/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart index 6409d13..63c9c09 100644 --- a/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart +++ b/lib/shared/data_sources/sources/repositories/sc_user_repository_impl.dart @@ -213,6 +213,10 @@ class SCAccountRepository implements SocialChatUserRepository { final result = await http.post( "58f9485b5b9f401c6a14bc1e6534122c7a085bb5f6a613094188ca56fe8bc03d", data: parm, + extra: const { + BaseNetworkClient.silentErrorToastKey: true, + BaseNetworkClient.suppressAuthLogoutKey: true, + }, fromJson: (json) => {}, ); return result; @@ -352,6 +356,10 @@ class SCAccountRepository implements SocialChatUserRepository { final result = await http.get( "d28c7d4eeb945fb765c8206c8f79d77f", queryParams: {"roomId": roomId}, + extra: const { + BaseNetworkClient.silentErrorToastKey: true, + BaseNetworkClient.suppressAuthLogoutKey: true, + }, fromJson: (json) => {}, ); return result;