yumi-flutter/lib/services/auth/authentication_manager.dart

222 lines
7.0 KiB
Dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.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/business_logic/models/req/sc_user_profile_cmd.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.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';
import '../../shared/data_sources/models/enum/sc_erro_code.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 {
final appleCredential = await SignInWithApple.getAppleIDCredential(
// 审核要求 Apple 登录不能依赖姓名或邮箱授权。
scopes: const [],
);
uid = appleCredential.userIdentifier ?? "";
SCLoadingManager.show();
if (uid.isNotEmpty) {
await DataPersistence.setPendingChannelAuth(authType, uid);
final user = await _loginOrRegisterApple(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<SocialChatLoginRes> _loginOrRegisterApple(
String authType,
String openId,
) async {
try {
return await SCAccountRepository().loginForChannel(
authType,
openId,
suppressRegistrationRedirect: true,
);
} on DioException catch (error) {
if (!_isUserNotRegistered(error)) {
rethrow;
}
// Apple 首登自动建立最小资料:昵称由服务端随机生成,未向 Apple 请求姓名或邮箱。
final defaultBirthday = DateTime(2000, 1, 1);
final defaultProfile = SCUserProfileCmd(
userNickname: '',
userAvatar: '',
userSex: 0,
bornYear: defaultBirthday.year,
bornMonth: defaultBirthday.month,
bornDay: defaultBirthday.day,
age: DateTime.now().year - defaultBirthday.year,
countryCode: 'US',
countryName: 'United States',
);
return SCAccountRepository().regist(authType, openId, defaultProfile);
}
}
bool _isUserNotRegistered(DioException error) {
final data = error.response?.data;
if (data is! Map) {
return false;
}
final rawCode = data['errorCode'] ?? data['code'];
final code =
rawCode is num
? rawCode.toInt()
: int.tryParse(rawCode?.toString() ?? '');
return code == SCErroCode.userNotRegistered.code;
}
Future<void> logoutUser() async {
await SCGoogleAuthService.signOut();
await DataPersistence.clearPendingChannelAuth();
_user = null;
notifyListeners();
}
void _showAuthError(Object error) {
if (error is DioException) {
return;
}
final message = error.toString().replaceFirst("Exception: ", "").trim();
if (message.isNotEmpty) {
SCTts.show(message);
}
}
}