splash_page.dart 10.7 KB
import 'dart:async';
import 'dart:io';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:limiting_direction_csx/limiting_direction_csx.dart';
import 'package:umeng_common_sdk/umeng_common_sdk.dart';
import 'package:wow_english/common/core/app_config_helper.dart';
import 'package:wow_english/common/core/user_util.dart';
import 'package:wow_english/common/extension/string_extension.dart';
import 'package:wow_english/common/request/config.dart';
import 'package:wow_english/common/request/dao/user_dao.dart';
import 'package:wow_english/models/user_entity.dart';
import 'package:wow_english/route/route.dart';
import 'package:wow_english/utils/audio_player_util.dart';
import 'package:wow_english/utils/log_util.dart';
import 'package:wow_english/utils/sp_util.dart';

import '../common/core/app_consts.dart';
import '../common/core/module_cache.dart';
import '../common/widgets/webview_dialog.dart';
import '../common/utils/staticstring/userregister.dart';

class SplashPage extends StatelessWidget {
  const SplashPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const TransitionView();
  }
}

class TransitionView extends StatefulWidget {
  const TransitionView({super.key});

  @override
  State<StatefulWidget> createState() {
    return _TransitionViewState();
  }
}

class _TransitionViewState extends State<TransitionView> {
  bool _isPrivacyDecisionInProgress = false;
  bool _isInitializing = false;

  Future startTime() async {
    // 判断是否登录
    UserEntity? userEntity = UserUtil.getUser();
    Log.d(
        '当前模式:kDebugMode=$kDebugMode, kProfileMode=$kProfileMode, kReleaseMode=$kReleaseMode');
    Log.d('当前API地址:${RequestConfig.baseUrl}');
    //BuildContext context = Navigator.of(context).context;
    //var currentPageName = ModalRoute.of(Navigator.of(AppRouter.context))?.settings.name;
    //Log.d('Splash读当前页面:$currentPageName');
    Log.d('Splash读本地, userEntity: $userEntity');
    int apartInMilliseconds = 0;
    // 获取系统配置信息
    AppConfigHelper.getAppConfig();
    // 调一下接口判断一下有效性再往下
    if (userEntity != null && userEntity.token != null) {
      String token = userEntity.token!;
      DateTime startTime = DateTime.now();
      await fetchNecessaryData(token);
      apartInMilliseconds = DateTime.now().difference(startTime).inMilliseconds;
    }
    int duration = max(1500 - apartInMilliseconds, 0);
    Log.d(
        'Splash getUserInfo 耗时:${apartInMilliseconds}ms, 最终duration=$duration');
    Timer(Duration(milliseconds: duration), () {
      pushNamedAndRemoveUntil(AppRouteName.home, (route) => false);
    });
  }

  void _showPrivacyDialog() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return PopScope(
          canPop: false,
          onPopInvoked: (didPop) {
            Log.d('WQF privacy dialog onPopInvoked didPop=$didPop');
          },
          child: child(),
        );
      },
    );
  }

  Widget child() {
    if (Platform.isIOS) {
      return AlertDialog(
        title: const Center(
          child: Text("服务条款及隐私政策"),
        ),
        content: SizedBox(
          width: MediaQuery.of(context).size.height,
          height: MediaQuery.of(context).size.height,
          child: const SingleChildScrollView(
            child: Text(UserRegisterString.userPrivacyPolicyStr),
          ),
        ),
        actions: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              TextButton(
                onPressed: _acceptPrivacyAgreement,
                child: const Text("同意并继续",
                    style: TextStyle(color: Color(0xFFFBB621))),
              ),
              TextButton(
                onPressed: _rejectPrivacyAgreement,
                child: const Text("不同意,退出应用"),
              ),
            ],
          )
        ],
      );
    }
    return WebviewDialog(
      title: "服务条款及隐私政策",
      webUrl: AppConsts.userPrivacyPolicyUrl,
      leftButtonText: '同意并继续',
      rightButtonText: '不同意,退出应用',
      leftTap: _acceptPrivacyAgreement,
      rightTap: _rejectPrivacyAgreement,
    );
  }

  Future<void> _acceptPrivacyAgreement() async {
    if (_isPrivacyDecisionInProgress) return;
    _isPrivacyDecisionInProgress = true;

    await AppConfigHelper.saveAgreementAccepted(true);
    if (!mounted) return;

    // 先关闭隐私政策弹窗,再进行可能耗时的 SDK 和业务初始化。
    Navigator.of(context, rootNavigator: true).pop();
    await _startInitialization();
    _isPrivacyDecisionInProgress = false;
  }

  Future<void> _rejectPrivacyAgreement() async {
    if (_isPrivacyDecisionInProgress) return;
    _isPrivacyDecisionInProgress = true;

    // 显式撤销本地授权并等待写入完成,避免退出后残留 true。
    await AppConfigHelper.saveAgreementAccepted(false);
    AppConfigHelper.exitApp();
  }

  Future<void> _startInitialization() async {
    if (_isInitializing) return;
    _isInitializing = true;
    try {
      await _initData();
    } catch (error, stackTrace) {
      Log.d('Splash初始化失败: $error\n$stackTrace');
      _isInitializing = false;
      if (mounted) {
        _showInitializationErrorDialog();
      }
    }
  }

  void _showInitializationErrorDialog() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (dialogContext) => AlertDialog(
        title: const Text('温馨提示'),
        content: const Text('初始化失败,请检查网络后重试'),
        actions: [
          const TextButton(
            onPressed: AppConfigHelper.exitApp,
            child: Text('退出'),
          ),
          TextButton(
            onPressed: () {
              Navigator.of(dialogContext).pop();
              _startInitialization();
            },
            child: const Text('重试'),
          ),
        ],
      ),
    );
  }

  ///初始化数据
  Future<void> _initData() async {
    // 用户同意后,先准备 Native APM,再正式初始化 Common SDK。
    if (Platform.isAndroid) {
      const MethodChannel umengMethodChannel =
          MethodChannel('wow_english/umeng_method_channel');
      await umengMethodChannel.invokeMethod('prepareAfterPrivacyConsent');
    }
    // Android 插件执行 initCommon 后不会回调 MethodChannel result,返回的
    // Future 永远不结束,因此这里只触发初始化,不能 await。
    unawaited(UmengCommonSdk.initCommon(
            "663b66b0b3362515012f4ea5", "663b66ecf32cc41105ae74b7", "official")
        .catchError((error, stackTrace) {
      Log.d('友盟Common SDK初始化失败: $error\n$stackTrace');
    }));
    await AudioPlayerUtil.getInstance()
        .playAudio(AudioPlayerUtilType.welcomeToWow)
        .timeout(const Duration(seconds: 10), onTimeout: () {
      Log.d('欢迎音频播放超时,继续进入首页');
    });
    await startTime();
  }

  Future<void> fetchNecessaryData(String userToken,
      {Completer<void>? completer}) async {
    completer ??= Completer<void>();
    // 获取用户信息
    try {
      UserEntity? userEntity = await UserDao.getUserInfo();
      Log.d('Splash在线更新, userEntity: $userEntity');
      if (userEntity != null) {
        userEntity.token = userToken;
        Log.d('Splash重设token, userEntity: $userEntity');
        UserUtil.saveUser(userEntity);
      }
      //todo 如果为null是否需要清除用户信息?
      completer.complete();
    } catch (e) {
      debugPrint('Splash获取用户信息异常:$e');
      if (!mounted) {
        if (!completer.isCompleted) {
          completer.complete();
        }
        return;
      }
      //异常的话弹窗提示重试
      showDialog(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return PopScope(
            canPop: false,
            onPopInvoked: (didPop) {
              Log.d('WQF fetchNecessaryData onPopInvoked didPop=$didPop');
            },
            child: AlertDialog(
              title: const Text('温馨提示'),
              content: const Text('网络异常,请检查网络后重试'),
              actions: <Widget>[
                TextButton(
                    child: const Text('退出'),
                    onPressed: () {
                      // 关闭对话框并退出应用
                      Navigator.of(context).pop();
                      AppConfigHelper.exitApp();
                      // 只有在 completer 还没有完成时才完成它
                      if (completer != null && !completer.isCompleted) {
                        completer.complete();
                      }
                    }),
                TextButton(
                  child: const Text('重试'),
                  onPressed: () async {
                    // 关闭对话框并重试
                    Navigator.of(context).pop();
                    await fetchNecessaryData(userToken, completer: completer);
                    // 不需要再次调用 completer.complete(),因为 fetchNecessaryData 内部已经处理了
                  },
                ),
              ],
            ),
          );
        },
      );
      e.logE();
    }
    // 等待completer完成,这会阻塞后续代码的执行,直到用户做出选择
    await completer.future;
  }

  @override
  void initState() {
    super.initState();
    init();
  }

  void init() async {
    changeDevice();
    await SpUtil.preInit();
    ModuleCache.instance.init();
    if (AppConfigHelper.getAgreementAccepted()) {
      await _startInitialization();
    } else {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) {
          _showPrivacyDialog();
        }
      });
    }
  }

  void changeDevice() async {
    ///设置设备默认方向
    WidgetsFlutterBinding.ensureInitialized();
    if (Platform.isIOS) {
      await LimitingDirectionCsx.setScreenDirection(
          DeviceDirectionMask.Landscape);
    } else {
      await SystemChrome.setPreferredOrientations(
          [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
    }
    await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          decoration: BoxDecoration(
              image: DecorationImage(
                  image: AssetImage(
                    'splash'.assetGif,
                  ),
                  fit: BoxFit.fill)),
        ),
      ),
    );
  }
}