import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:guru_app/account/account_data_store.dart'; import 'package:guru_app/account/model/account.dart'; import 'package:guru_app/account/model/account_profile.dart'; import 'package:guru_app/account/model/user.dart'; import 'package:guru_app/analytics/guru_analytics.dart'; import 'package:guru_app/api/guru_api.dart'; import 'package:guru_app/firebase/firebase.dart'; import 'package:guru_app/firebase/firestore/firestore_manager.dart'; import 'package:guru_app/property/app_property.dart'; import 'package:guru_app/property/settings/guru_settings.dart'; import 'package:guru_utils/collection/collectionutils.dart'; import 'package:guru_utils/core/ext.dart'; import 'package:guru_utils/datetime/datetime_utils.dart'; import 'package:guru_utils/device/device_info.dart'; import 'package:guru_utils/device/device_utils.dart'; import 'package:guru_utils/log/log.dart'; import 'package:guru_utils/network/network_utils.dart'; /// Created by Haoyi on 6/3/21 /// /// part "account_service_extension.dart"; part "account_auth_extension.dart"; class ModifyNicknameException implements Exception { final String? message; final dynamic cause; ModifyNicknameException(this.message, {this.cause}); @override String toString() { return "ModifyNicknameException: $message cause:$cause"; } } class ModifyLevelException implements Exception { final String? message; final dynamic cause; ModifyLevelException(this.message, {this.cause}); @override String toString() { return "ModifyLevelException: $message cause:$cause"; } } class AccountManager { final AccountDataStore accountDataStore; // final FirestoreService firestoreService; Timer? retryTimer; static AccountManager instance = AccountManager(); AccountManager() : accountDataStore = AccountDataStore.instance; Future init({Completer? completer}) async { try { final result = accountDataStore.transitionTo(AccountDataStatus.initializing); if (!result) { Log.w( "init account error, current initializing! please wait result! retry[${accountDataStore.initRetryCount}]", tag: "Account"); return; } retryTimer?.cancel(); final account = await AppProperty.getInstance().loadAccount(); final restoreResult = await _restoreAccount(account); if (!restoreResult) { Log.v("init account error: restoreAccount error! retry[${accountDataStore.initRetryCount}]", tag: "Account"); _retry(); } else { accountDataStore.initRetryCount = 0; accountDataStore.transitionTo(AccountDataStatus.initialized); Log.v("init account success!", tag: "Account"); } } catch (error, stacktrace) { completer?.complete(error); Log.v("init account error retry[${accountDataStore.initRetryCount}]:$error $stacktrace", tag: "Account"); _retry(); } completer?.complete(true); } void _retry() { final intervalSeconds = (accountDataStore.initRetryCount * 2 + 8).clamp(8, 30); retryTimer?.cancel(); accountDataStore.transitionTo(AccountDataStatus.waiting); retryTimer = Timer(Duration(seconds: intervalSeconds), () { init(); accountDataStore.initRetryCount++; }); } Future updateLocalProfile(Map modifiedJson) async { modifiedJson[AccountProfile.dirtyField] = true; final dirtyAccountProfile = accountDataStore.accountProfile?.merge(modifiedJson) ?? AccountProfile.fromJson(modifiedJson); AppProperty.getInstance().setAccountProfile(dirtyAccountProfile); accountDataStore.updateAccountProfile(dirtyAccountProfile); } Future modifyProfile( {String? nickname, String? avatar, String? countryCode, Map userData = const {}}) async { int retryCount = 2; Log.i("modifyProfile $nickname $avatar $countryCode", syncFirebase: true, tag: "Account"); if (nickname == null && avatar == null && countryCode == null && userData.isEmpty) { return false; } final now = DateTimeUtils.currentTimeInMillis(); final modifiedJson = CollectionUtils.filterOutNulls({ AccountProfile.uidField: accountDataStore.uid, AccountProfile.nicknameField: nickname, AccountProfile.countryField: countryCode?.toLowerCase(), AccountProfile.avatarField: avatar, AccountProfile.updateAtField: now, AccountProfile.versionField: GuruSettings.instance.version.get(), AccountProfile.roleField: GuruSettings.instance.debugMode.get() == true ? UserAttr.tester : UserAttr.real, AccountProfile.dirtyField: true, ...userData }); await updateLocalProfile(modifiedJson); while ((await NetworkUtils.isNetworkConnected()) && retryCount-- > 0) { final accountProfile = await FirestoreManager.instance.modifyProfile(modifiedJson).onError((error, stackTrace) { Log.i("modifyProfile error!:$error"); GuruAnalytics.instance.logException(ModifyLevelException("modifyProfile error!:$error"), stacktrace: stackTrace); return null; }); if (accountProfile != null) { Log.i("modifyProfile success! $accountProfile", tag: "Account"); AppProperty.getInstance().setAccountProfile(accountProfile); accountDataStore.updateAccountProfile(accountProfile); return true; } else { Log.i("[$retryCount] modify profile error!", tag: "Account"); await authenticate().timeout(const Duration(seconds: 15)).catchError((error, stackTrace) { Log.i("re-authenticate error:$error", stackTrace: stackTrace, tag: "Account"); }); await Future.delayed(const Duration(seconds: 1)); } } return false; } }