splash_controller.dart 3.42 KB
import 'package:doublefeel_flutter/core/logging/app_diagnostic_log_store.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:flutter/scheduler.dart';
import 'package:get/get.dart';

import '../../../routes/app_pages.dart';
import '../../../../core/error/app_error.dart';
import '../../../../core/network/api/user_api.dart';
import '../../../../core/network/api/vip_api.dart';
import '../../../../core/result/app_result.dart';
import '../../../../core/services/user_state_service.dart';
import '../../../../data/local/user_preferences_storage.dart';
import '../../../../data/models/local/user_preferences.dart';

/// Cold-start routing: restore session or open login.
class SplashController extends GetxController {
  SplashController(
    this._userPrefs,
    this._userApi,
    this._vipApi,
    this._userStateService,
  );

  final UserPreferencesStorage _userPrefs;
  final UserApi _userApi;
  final VipApi _vipApi;
  final UserStateService _userStateService;
  final HealthRawDataCoreService _healthRawDataCoreService =
      Get.find<HealthRawDataCoreService>();

  @override
  void onInit() {
    super.onInit();
    SchedulerBinding.instance.addPostFrameCallback((_) {
      _resolveInitialRoute();
    });
  }

  Future<void> _resolveInitialRoute() async {
    final token = _userPrefs.accessToken;
    if (token.isEmpty) {
      final splashDelay =
          Get.testMode ? Duration.zero : const Duration(milliseconds: 800);
      await Future<void>.delayed(splashDelay);
      AppDiagnosticLogStore.instance.log(
        category: 'Splash',
        action: 'navigate_to_login_due_to_empty_token',
        details: {},
      );
      Get.offAllNamed(AppRoutes.login);
      return;
    }

    final ok = await _refreshSession();
    if (ok) {
      await _userStateService.onLogin();
      try {
        _healthRawDataCoreService.startCoreCaculate();
      } catch (_) {}
      await Get.offAllNamed(AppRoutes.home);
    } else {
      AppDiagnosticLogStore.instance.log(
        category: 'Splash',
        action: 'navigate_to_login_due_to_session_refresh_failed',
        details: {
          'tokenLength': token.length,
        },
      );
      Get.offAllNamed(AppRoutes.login);
    }
  }

  Future<bool> _refreshSession() async {
    final userResult = await _userApi.getUserInfo(errorHandlingPolicy: null);

    switch (userResult) {
      case AppSuccess(:final data):
        try {
          await _userPrefs.updateMeUserInfo(data);
        } catch (_) {}

        // 刷新 VIP 信息缓存(失败不影响进入主页)
        final vipResult = await _vipApi.getVipInfo();
        if (vipResult case AppSuccess(data: final vip)) {
          await _userPrefs
              .updateVipInfo(UserPreferencesVipInfo.fromVipInfo(vip));
        }
        return true;

      case AppFailure(:final error):
        AppDiagnosticLogStore.instance.log(
          category: 'Splash',
          action: 'refresh_session_failure',
          details: {
            'errorType': error.runtimeType.toString(),
            'displayMessage': error.displayMessage,
          },
        );
        if (error.isAccessTokenInvalid) {
          return false;
        }
        AppToast.show(
            l10n.noInternetConnectionPleaseCheckYourInternetConnection);
        return true;
    }
  }
}