yumi-flutter/lib/services/auth/authentication_manager.dart
2026-06-05 14:47:02 +08:00

230 lines
7.2 KiB
Dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:yumi/app/routes/sc_routes.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/shared/tools/sc_google_auth_utils.dart';
import 'package:yumi/shared/tools/sc_loading_manager.dart';
import 'package:yumi/shared/tools/sc_version_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:provider/provider.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/services/audio/rtm_manager.dart';
import '../../shared/data_sources/models/enum/sc_auth_type.dart';
class SocialChatAuthenticationManager extends ChangeNotifier {
String authType = "";
String uid = "";
User? _user;
User? get user => _user;
bool get isSignedIn => _user != null;
SocialChatAuthenticationManager() {
_initializeAuthentication();
}
Future<void> _initializeAuthentication() async {
try {
await SCGoogleAuthService.initialize();
await _verifyExistingSession();
} catch (_) {
// Login buttons should remain usable even if silent Google restore fails.
}
}
Future<void> _verifyExistingSession() async {
final user = await SCGoogleAuthService.signInSilently();
_user = user;
if (user != null) {
authType = SCAuthType.GOOGLE.name;
uid = user.uid;
await DataPersistence.setPendingChannelAuth(authType, uid);
}
notifyListeners();
}
Future<void> authenticateUser(BuildContext context, String authType) async {
if (authType == SCAuthType.GOOGLE.name) {
this.authType = authType;
try {
final signedInUser = await SCGoogleAuthService.signInWithGoogle();
if (signedInUser == null) {
return;
}
_user = signedInUser;
uid = _user!.uid;
SCLoadingManager.show();
if (uid.isNotEmpty) {
await DataPersistence.setPendingChannelAuth(authType, uid);
var user = await SCAccountRepository().loginForChannel(authType, uid);
if (!context.mounted) {
SCLoadingManager.hide();
return;
}
AccountStorage().setCurrentUser(user);
await DataPersistence.clearPendingChannelAuth();
if (!context.mounted) {
SCLoadingManager.hide();
return;
}
final canContinue = await SCVersionUtils.checkReview(
context: context,
);
if (!context.mounted || !canContinue) {
SCLoadingManager.hide();
return;
}
await Provider.of<RtcProvider>(
context,
listen: false,
).resetLocalRoomState(
fallbackRtmProvider: Provider.of<RtmProvider>(
context,
listen: false,
),
);
if (!context.mounted) {
return;
}
SCLoadingManager.hide();
notifyListeners();
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
}
} catch (e) {
SCLoadingManager.hide();
_showAuthError(e);
}
} else if (authType == SCAuthType.APPLE.name) {
this.authType = authType;
try {
_authDebugLog('apple sign-in start');
final isAvailable = await SignInWithApple.isAvailable();
_authDebugLog('apple sign-in availability=$isAvailable');
if (!isAvailable) {
SCTts.show("Sign in with Apple is not available on this device.");
return;
}
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName, // 请求姓名权限
],
);
uid = appleCredential.userIdentifier ?? "";
_authDebugLog(
'apple credential success userIdentifier=${_maskForDebug(uid)} '
'hasAuthorizationCode=${appleCredential.authorizationCode.isNotEmpty} '
'hasIdentityToken=${(appleCredential.identityToken ?? "").isNotEmpty} '
'hasEmail=${(appleCredential.email ?? "").isNotEmpty}',
);
if (uid.isEmpty) {
SCTts.show("Apple authorization failed. Please try again.");
return;
}
SCLoadingManager.show();
await DataPersistence.setPendingChannelAuth(authType, uid);
var user = await SCAccountRepository().loginForChannel(authType, uid);
if (!context.mounted) {
SCLoadingManager.hide();
return;
}
AccountStorage().setCurrentUser(user);
await DataPersistence.clearPendingChannelAuth();
if (!context.mounted) {
SCLoadingManager.hide();
return;
}
final canContinue = await SCVersionUtils.checkReview(context: context);
if (!context.mounted || !canContinue) {
SCLoadingManager.hide();
return;
}
await Provider.of<RtcProvider>(
context,
listen: false,
).resetLocalRoomState(
fallbackRtmProvider: Provider.of<RtmProvider>(context, listen: false),
);
if (!context.mounted) {
return;
}
SCLoadingManager.hide();
notifyListeners();
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
} catch (e) {
SCLoadingManager.hide();
_showAuthError(e);
}
}
}
Future<void> logoutUser() async {
await SCGoogleAuthService.signOut();
await DataPersistence.clearPendingChannelAuth();
_user = null;
notifyListeners();
}
void _showAuthError(Object error) {
_authDebugLog('authenticateUser error: $error');
if (error is DioException) {
return;
}
if (error is SignInWithAppleAuthorizationException) {
_showAppleAuthorizationError(error);
return;
}
if (error is SignInWithAppleNotSupportedException) {
SCTts.show("Sign in with Apple is not available on this device.");
return;
}
final message = error.toString().replaceFirst("Exception: ", "").trim();
if (message.isNotEmpty) {
SCTts.show(message);
}
}
void _showAppleAuthorizationError(
SignInWithAppleAuthorizationException error,
) {
if (error.code == AuthorizationErrorCode.canceled) {
return;
}
if (error.code == AuthorizationErrorCode.unknown) {
SCTts.show(
"Apple sign-in is not available. Please check the app signing profile.",
);
return;
}
SCTts.show("Apple authorization failed. Please try again.");
}
void _authDebugLog(String message) {
if (kDebugMode) {
debugPrint('[Auth][Apple] $message');
}
}
String _maskForDebug(String value) {
if (value.isEmpty) {
return '<empty>';
}
if (value.length <= 8) {
return '***';
}
return '${value.substring(0, 4)}...${value.substring(value.length - 4)}';
}
}