This commit is contained in:
roxy 2026-05-12 10:56:18 +08:00
parent 92bbbcb0eb
commit f2e9ac3c60
9 changed files with 154 additions and 65 deletions

View File

@ -1,11 +1,13 @@
import 'package:fluro/fluro.dart' as fluro; import 'package:fluro/fluro.dart' as fluro;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:yumi/app/routes/sc_router_init.dart'; import 'package:yumi/app/routes/sc_router_init.dart';
import 'package:yumi/modules/country/country_route.dart'; import 'package:yumi/modules/country/country_route.dart';
import 'package:yumi/modules/index/main_route.dart'; import 'package:yumi/modules/index/main_route.dart';
import 'package:yumi/modules/chat/chat_route.dart'; import 'package:yumi/modules/chat/chat_route.dart';
import 'package:yumi/modules/store/store_route.dart'; import 'package:yumi/modules/store/store_route.dart';
import 'package:yumi/modules/index/index_page.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/settings/settings_route.dart';
import 'package:yumi/modules/user/task/task_route.dart'; import 'package:yumi/modules/user/task/task_route.dart';
import 'package:yumi/modules/user/vip/vip_route.dart'; import 'package:yumi/modules/user/vip/vip_route.dart';
@ -32,7 +34,7 @@ class SCRoutes {
handler: fluro.Handler( handler: fluro.Handler(
handlerFunc: handlerFunc:
(BuildContext? context, Map<String, List<String>> params) => (BuildContext? context, Map<String, List<String>> params) =>
SCIndexPage(), _buildHomePage(context),
), ),
); );
@ -57,6 +59,13 @@ class SCRoutes {
routerProvider.initRouter(router); routerProvider.initRouter(router);
} }
} }
static Widget _buildHomePage(BuildContext? context) {
return ChangeNotifierProvider<SocialChatRoomManager>.value(
value: resolveSocialChatRoomManager(context),
child: const SCIndexPage(),
);
}
} }
// PS使 // PS使

View File

@ -241,9 +241,8 @@ class RootAppWithProviders extends StatelessWidget {
), ),
// Provider - // Provider -
ChangeNotifierProvider<SocialChatRoomManager>( ChangeNotifierProvider<SocialChatRoomManager>.value(
lazy: true, value: socialChatRoomManager,
create: (context) => SocialChatRoomManager(),
), ),
// Provider - // Provider -

View File

@ -640,7 +640,9 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
authType = SCAuthType.GOOGLE.name; authType = SCAuthType.GOOGLE.name;
idToken = googleUid; 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) { if (authType.isEmpty || idToken.isEmpty) {
SCLoadingManager.hide(); SCLoadingManager.hide();

View File

@ -563,7 +563,8 @@ class _SCIndexPageState extends State<SCIndexPage>
return; return;
} }
await SCEntryPopupCoordinator.openFirstPartyRoomRandomGame(context); await SCEntryPopupCoordinator.openFirstPartyRoomRandomGame(context);
} catch (error) { } catch (_) {
// The entry popup is optional; failure should not block the home page.
} finally { } finally {
_isOpeningFirstRegisterRoomGame = false; _isOpeningFirstRegisterRoomGame = false;
} }

View File

@ -2708,13 +2708,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
return; return;
} }
_startRoomStatePolling(); _startRoomStatePolling();
final currentContext = context; socialChatRoomManager.fetchContributionLevelData(roomId);
if (currentContext != null && currentContext.mounted) {
Provider.of<SocialChatRoomManager>(
currentContext,
listen: false,
).fetchContributionLevelData(roomId);
}
failureType = RoomStartupFailureType.rtc; failureType = RoomStartupFailureType.rtc;
final rtcTokenResult = await rtcTokenFuture; final rtcTokenResult = await rtcTokenFuture;
@ -3400,13 +3394,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
void _clearRoomContributionLevelData([BuildContext? targetContext]) { void _clearRoomContributionLevelData([BuildContext? targetContext]) {
final currentContext = targetContext ?? context; final currentContext = targetContext ?? context;
if (currentContext == null || !currentContext.mounted) { resolveSocialChatRoomManager(currentContext).clearContributionLevelData();
return;
}
Provider.of<SocialChatRoomManager>(
currentContext,
listen: false,
).clearContributionLevelData();
} }
@override @override

View File

@ -32,8 +32,12 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
} }
Future<void> _initializeAuthentication() async { Future<void> _initializeAuthentication() async {
await SCGoogleAuthService.initialize(); try {
await _verifyExistingSession(); await SCGoogleAuthService.initialize();
await _verifyExistingSession();
} catch (_) {
// Login buttons should remain usable even if silent Google restore fails.
}
} }
Future<void> _verifyExistingSession() async { Future<void> _verifyExistingSession() async {
@ -51,10 +55,11 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
if (authType == SCAuthType.GOOGLE.name) { if (authType == SCAuthType.GOOGLE.name) {
this.authType = authType; this.authType = authType;
try { try {
_user ??= await SCGoogleAuthService.signInWithGoogle(); final signedInUser = await SCGoogleAuthService.signInWithGoogle();
if (_user == null) { if (signedInUser == null) {
return; return;
} }
_user = signedInUser;
uid = _user!.uid; uid = _user!.uid;
SCLoadingManager.show(); SCLoadingManager.show();
if (uid.isNotEmpty) { if (uid.isNotEmpty) {

View File

@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:yumi/app_localizations.dart'; import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/shared/tools/sc_loading_manager.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/my_room_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_contribute_level_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<SocialChatRoomManager?>(
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 { class SocialChatRoomManager extends ChangeNotifier {
MyRoomRes? myRoom; MyRoomRes? myRoom;
SCRoomContributeLevelRes? roomContributeLevelRes; SCRoomContributeLevelRes? roomContributeLevelRes;

View File

@ -107,6 +107,7 @@ Future<bool> _isCurrentSessionStillValid(DioException e) async {
class BaseNetworkClient { class BaseNetworkClient {
final Dio dio; final Dio dio;
static const String silentErrorToastKey = 'silentErrorToast'; static const String silentErrorToastKey = 'silentErrorToast';
static const String suppressAuthLogoutKey = 'suppressAuthLogout';
static const String baseUrlOverrideKey = 'baseUrlOverride'; static const String baseUrlOverrideKey = 'baseUrlOverride';
BaseNetworkClient() : dio = Dio() { BaseNetworkClient() : dio = Dio() {
@ -292,6 +293,13 @@ class BaseNetworkClient {
SCLoadingManager.hide(); SCLoadingManager.hide();
final bool silentErrorToast = final bool silentErrorToast =
e.requestOptions.extra[BaseNetworkClient.silentErrorToastKey] == true; e.requestOptions.extra[BaseNetworkClient.silentErrorToastKey] == true;
final handledBusinessError = await _handleBusinessErrorResponse(
e,
silentErrorToast: silentErrorToast,
);
if (handledBusinessError != null) {
return handledBusinessError;
}
switch (e.type) { switch (e.type) {
case DioExceptionType.connectionTimeout: case DioExceptionType.connectionTimeout:
return DioException(requestOptions: e.requestOptions, error: '连接超时'); return DioException(requestOptions: e.requestOptions, error: '连接超时');
@ -300,50 +308,10 @@ class BaseNetworkClient {
case DioExceptionType.receiveTimeout: case DioExceptionType.receiveTimeout:
return DioException(requestOptions: e.requestOptions, error: '接收超时'); return DioException(requestOptions: e.requestOptions, error: '接收超时');
case DioExceptionType.badResponse: 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( return DioException(
requestOptions: e.requestOptions, requestOptions: e.requestOptions,
response: e.response, response: e.response,
error: '服务器错误: $errorCode ${errorMsg ?? ""}'.trim(), error: e.error ?? e.message ?? 'Server fail',
); );
case DioExceptionType.cancel: case DioExceptionType.cancel:
return DioException( return DioException(
@ -362,6 +330,95 @@ class BaseNetworkClient {
); );
} }
} }
Future<DioException?> _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 { class NotSuccessException implements Exception {

View File

@ -213,6 +213,10 @@ class SCAccountRepository implements SocialChatUserRepository {
final result = await http.post( final result = await http.post(
"58f9485b5b9f401c6a14bc1e6534122c7a085bb5f6a613094188ca56fe8bc03d", "58f9485b5b9f401c6a14bc1e6534122c7a085bb5f6a613094188ca56fe8bc03d",
data: parm, data: parm,
extra: const {
BaseNetworkClient.silentErrorToastKey: true,
BaseNetworkClient.suppressAuthLogoutKey: true,
},
fromJson: (json) => {}, fromJson: (json) => {},
); );
return result; return result;
@ -352,6 +356,10 @@ class SCAccountRepository implements SocialChatUserRepository {
final result = await http.get( final result = await http.get(
"d28c7d4eeb945fb765c8206c8f79d77f", "d28c7d4eeb945fb765c8206c8f79d77f",
queryParams: {"roomId": roomId}, queryParams: {"roomId": roomId},
extra: const {
BaseNetworkClient.silentErrorToastKey: true,
BaseNetworkClient.suppressAuthLogoutKey: true,
},
fromJson: (json) => {}, fromJson: (json) => {},
); );
return result; return result;