Commit 8556a0326b7109f713bdb5c0f852062117d89024

Authored by 权海
1 parent f027d9ea

feat(ui):修复登录卡住的Bug

... ... @@ -10,10 +10,7 @@ import 'package:doublefeel_flutter/core/constants/app_const.dart';
import 'package:doublefeel_flutter/core/logging/app_logger.dart';
import 'package:doublefeel_flutter/core/services/app_config_service.dart';
import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
import 'package:doublefeel_flutter/l10n/gen/app_localizations.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/pigeon/platform_api.g.dart';
import 'package:doublefeel_flutter/pigeon/wechat_api.g.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
... ... @@ -23,23 +20,14 @@ import 'package:doublefeel_flutter/core/network/api/user_api.dart';
import 'package:doublefeel_flutter/core/network/api/vip_api.dart';
import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/services/app_config_service.dart';
import 'package:doublefeel_flutter/core/services/loading_service.dart';
import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
import 'package:doublefeel_flutter/core/services/user_state_service.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/data/local/local_storage.dart';
import 'package:doublefeel_flutter/data/local/user_account_storage.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
import 'package:doublefeel_flutter/data/models/user/user_models.dart';
import 'package:doublefeel_flutter/data/models/vip/vip_info.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/pigeon/wechat_api.g.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:get/get.dart';
class LoginController extends GetxController {
final UserApi _userApi = Get.find<UserApi>();
... ... @@ -381,21 +369,39 @@ class LoginController extends GetxController {
}
isLoggingIn.value = true;
final loginStopwatch = Stopwatch()..start();
_logLoginTiming('phone_login.start');
try {
await LoadingService.instance.run(() async {
final loginRes = await _userApi.login(
telephone: cleanPhone,
verifyCode: codeInput.value,
final loginRes = await _measureLoginStep(
'phone_login.api',
() => _userApi.login(
telephone: cleanPhone,
verifyCode: codeInput.value,
),
);
if (loginRes is AppSuccess<LoginResponse>) {
await _handleLoginSuccess(loginRes.data);
await Get.find<LocalStorage>().setLastLoginMethod('phone');
await environmentConfig.updateHideChooseRegion(true);
await _measureLoginStep(
'phone_login.handle_success',
() => _handleLoginSuccess(loginRes.data),
);
await _measureLoginStep(
'phone_login.persist_login_method',
() => Get.find<LocalStorage>().setLastLoginMethod('phone'),
);
await _measureLoginStep(
'phone_login.persist_region_choice',
() => environmentConfig.updateHideChooseRegion(true),
);
}
});
} finally {
isLoggingIn.value = false;
loginStopwatch.stop();
_logLoginTiming(
'phone_login.finish total_ms=${loginStopwatch.elapsedMilliseconds}',
);
}
}
... ... @@ -595,7 +601,10 @@ class LoginController extends GetxController {
}
}
final regRes = await _userApi.register(registerData);
final regRes = await _measureLoginStep(
'login_success.register',
() => _userApi.register(registerData),
);
if (regRes is AppSuccess<RegisterResponse>) {
accessToken = regRes.data.accessToken;
} else {
... ... @@ -605,12 +614,21 @@ class LoginController extends GetxController {
}
// Fetch user info and VIP info
final userInfoResFuture = _userApi.getUserInfo(accessToken: accessToken);
final userInfoResFuture = _measureLoginStep(
'login_success.get_user_info',
() => _userApi.getUserInfo(accessToken: accessToken),
);
final vipInfoResFuture = isRegister
? Future.value(null)
: _vipApi.getVipInfo(accessToken: accessToken);
: _measureLoginStep(
'login_success.get_vip_info',
() => _vipApi.getVipInfo(accessToken: accessToken),
);
final results = await Future.wait([userInfoResFuture, vipInfoResFuture]);
final results = await _measureLoginStep(
'login_success.wait_user_and_vip',
() => Future.wait([userInfoResFuture, vipInfoResFuture]),
);
final userInfoRes = results[0];
final vipInfoRes = results[1];
... ... @@ -631,51 +649,36 @@ class LoginController extends GetxController {
vip = vipInfoRes.data;
}
await _userPrefs.updateFromLogin(
accessToken: accessToken,
me: me,
partner: partner,
vip: vip,
await _measureLoginStep(
'login_success.persist_user_preferences',
() => _userPrefs.updateFromLogin(
accessToken: accessToken,
me: me,
partner: partner,
vip: vip,
),
);
try {
if (Get.isRegistered<AppEnvironmentConfig>()) {
var env = Get.find<AppEnvironmentConfig>();
unawaited(
AppPlatformHostApi()
.updateLoginInfo(
jsonEncode(UserPreferences(
accessToken: accessToken,
meUserInfo: me,
partnerUserInfo: partner,
vipInfo: vip != null
? UserPreferencesVipInfo.fromVipInfo(vip)
: null,
)),
env.serverBaseUrl,
)
.catchError((Object error, StackTrace stackTrace) {
AppLogger.e('Native login info sync failed', error, stackTrace);
}),
);
}
} on Exception catch (e) {
AppLogger.e(e);
}
await _userStateService.onLogin();
await _measureLoginStep(
'login_success.user_state_on_login',
_userStateService.onLogin,
);
if (isRegister) {
ta.track('success_regist');
}
// 用户登录成功即代表同意了协议,持久化到本地供冷启动时 SDK 初始化判断使用
await Get.find<LocalStorage>().setTermsAgreed(true);
await _measureLoginStep(
'login_success.persist_terms_agreed',
() => Get.find<LocalStorage>().setTermsAgreed(true),
);
// 根据引导完成状态决定跳转目标
final userId = me.id ?? 0;
if (_userAccount.hasCompletedOnboarding(userId) ||
me.isPassNoviceGuide == 1) {
// 该账号已完成引导,直接进主页
_logLoginTiming('login_success.route_home');
Get.offAllNamed(AppRoutes.home);
} else {
// 新用户或未完成引导,进入引导页(支持断点续做)
... ... @@ -689,4 +692,30 @@ class LoginController extends GetxController {
// userInfoRes 失败,外层 finally 会重置 isLoggingIn
}
}
Future<T> _measureLoginStep<T>(
String name,
Future<T> Function() operation,
) async {
if (!environmentConfig.isDebug) return operation();
final stopwatch = Stopwatch()..start();
AppLogger.i('[LoginTiming] $name.start');
try {
return await operation();
} catch (error, stackTrace) {
AppLogger.e('[LoginTiming] $name.error', error, stackTrace);
rethrow;
} finally {
stopwatch.stop();
AppLogger.i(
'[LoginTiming] $name.finish elapsed_ms=${stopwatch.elapsedMilliseconds}',
);
}
}
void _logLoginTiming(String message) {
if (environmentConfig.isDebug) {
AppLogger.i('[LoginTiming] $message');
}
}
}
... ...
... ... @@ -50,18 +50,85 @@ class UserStateService {
}
Future<void> onLogin() async {
await initializeSdks();
final totalStopwatch = Stopwatch()..start();
_logLoginTiming('on_login.start');
await _measureLoginStep('on_login.initialize_sdks', initializeSdks);
_trackLogin();
_setUserProfile();
// The WebSocket handler can immediately start a calculation. Ensure its
// local result store is ready before subscribing and connecting.
await _healthRawDataCoreService.openDatabase();
// Opening/migrating the local health SQLite database can contend with the
// native uploader. Do not make the login route wait for it. The dependent
// realtime session is still started only after the database is ready.
unawaited(_startRealtimeSessionAfterHealthDatabaseReady(totalStopwatch));
_logLoginTiming('on_login.database_initialization_dispatched');
}
Future<void> _startRealtimeSessionAfterHealthDatabaseReady(
Stopwatch totalStopwatch,
) async {
try {
await _measureLoginStep(
'on_login.open_health_database',
_healthRawDataCoreService.openDatabase,
);
} catch (error, stackTrace) {
AppLogger.e(
'[LoginTiming] on_login.open_health_database.failed',
error,
stackTrace,
);
return;
}
// The user may have logged out while an existing database lock cleared.
if (!_userPrefs.isLoggedIn) {
_logLoginTiming('on_login.realtime_session_skipped_logged_out');
return;
}
// Push Kit may wait for its service/network. The token is still reported
// after login, but must not delay the rest of the authenticated session.
unawaited(_pushService.registerPushToken());
await _imService.connect();
try {
await _measureLoginStep('on_login.im_connect', _imService.connect);
} catch (error, stackTrace) {
AppLogger.e(
'[LoginTiming] on_login.im_connect.failed', error, stackTrace);
}
if (!_userPrefs.isLoggedIn) {
_logLoginTiming('on_login.realtime_session_stopped_logged_out');
return;
}
_listenForHealthDataUpdates();
_webSocketService.start();
totalStopwatch.stop();
_logLoginTiming(
'on_login.finish total_ms=${totalStopwatch.elapsedMilliseconds}');
}
Future<T> _measureLoginStep<T>(
String name,
Future<T> Function() operation,
) async {
if (!_environmentConfig.isDebug) return operation();
final stopwatch = Stopwatch()..start();
AppLogger.i('[LoginTiming] $name.start');
try {
return await operation();
} catch (error, stackTrace) {
AppLogger.e('[LoginTiming] $name.error', error, stackTrace);
rethrow;
} finally {
stopwatch.stop();
AppLogger.i(
'[LoginTiming] $name.finish elapsed_ms=${stopwatch.elapsedMilliseconds}',
);
}
}
void _logLoginTiming(String message) {
if (_environmentConfig.isDebug) {
AppLogger.i('[LoginTiming] $message');
}
}
Future<void> onLogout({bool callServerLogout = true}) async {
... ...
... ... @@ -65,26 +65,77 @@ class UserPreferencesStorage {
Future<void> _syncLoginInfoToNative(
{required UserPreferences userInfo}) async {
final totalStopwatch = Stopwatch()..start();
_logLoginTiming('native_login_sync.start');
if (userInfo.accessToken.isEmpty) {
try {
if (Get.isRegistered<AppEnvironmentConfig>()) {
await AppPlatformHostApi().logout();
await _measureLoginStep(
'native_login_sync.logout',
() => AppPlatformHostApi().logout(),
);
}
} on Exception {
return;
} finally {
totalStopwatch.stop();
_logLoginTiming(
'native_login_sync.finish total_ms=${totalStopwatch.elapsedMilliseconds}',
);
}
return;
}
try {
if (Get.isRegistered<AppEnvironmentConfig>()) {
var env = Get.find<AppEnvironmentConfig>();
await AppPlatformHostApi()
.updateLoginInfo(jsonEncode(userInfo), env.serverBaseUrl);
await _syncHealthRawDatabaseToNative();
await _measureLoginStep(
'native_login_sync.update_login_info',
() => AppPlatformHostApi()
.updateLoginInfo(jsonEncode(userInfo), env.serverBaseUrl),
);
await _measureLoginStep(
'native_login_sync.sync_health_database',
_syncHealthRawDatabaseToNative,
);
}
AppLogger.d('Native login info synced for cold start');
} catch (error, stackTrace) {
AppLogger.e('Native login info sync failed', error, stackTrace);
} finally {
totalStopwatch.stop();
_logLoginTiming(
'native_login_sync.finish total_ms=${totalStopwatch.elapsedMilliseconds}',
);
}
}
bool get _isDebug =>
Get.isRegistered<AppEnvironmentConfig>() &&
Get.find<AppEnvironmentConfig>().isDebug;
Future<T> _measureLoginStep<T>(
String name,
Future<T> Function() operation,
) async {
if (!_isDebug) return operation();
final stopwatch = Stopwatch()..start();
AppLogger.i('[LoginTiming] $name.start');
try {
return await operation();
} catch (error, stackTrace) {
AppLogger.e('[LoginTiming] $name.error', error, stackTrace);
rethrow;
} finally {
stopwatch.stop();
AppLogger.i(
'[LoginTiming] $name.finish elapsed_ms=${stopwatch.elapsedMilliseconds}',
);
}
}
void _logLoginTiming(String message) {
if (_isDebug) {
AppLogger.i('[LoginTiming] $message');
}
}
... ...