222 lines
7.0 KiB
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);
|
|
}
|
|
}
|
|
}
|