web_page.dart 10.7 KB
import 'dart:io';

import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/config/env_config.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  Widget build(BuildContext buildContext) {
    final Map<String, dynamic>? extraData = GoRouterState.of(buildContext).extra as Map<String, dynamic>?;

    var loginOpFlag = true;

    var sessionCode = extraData?['sessionCode'];
    var userCode = extraData?['userCode'];
    var classCode = extraData?['classCode'];
    var userType = extraData?['userType'];
    var stuId = extraData?['stuId'];

    if (sessionCode == null || sessionCode == '') {
      loginOpFlag = false;

      var sharedPreferences = getIt.get<SharedPreferences>();
      sessionCode = sharedPreferences.getString('auth_sessionCode');
      userCode = sharedPreferences.getString('auth_userCode');
      classCode = sharedPreferences.getString('auth_classCode');
      userType = sharedPreferences.getInt('auth_userType');
      stuId = sharedPreferences.getString('auth_stuId');
    }

    return BlocProvider(
      create: (context) {
        final webCubit = WebCubit(
          WebState(
            loginOpFlag: loginOpFlag,
            sessionCode: sessionCode,
            userCode: userCode,
            classCode: classCode,
            userType: userType,
            stuId: stuId,
          ),
        );
        return webCubit;
      },
      child: BlocConsumer<WebCubit, WebState>(
        builder: (ctx, state) {
          final scaffold = Scaffold(
            appBar: state.showAppBar ? _buildAppBar(ctx, state) : null,
            body: Stack(
              children: [
                state.loaded
                    ? WebViewWidget(controller: ctx.read<WebCubit>().controller)
                    : const Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            CircularProgressIndicator(color: Color(0xFF7691fa)),
                            SizedBox(height: 16),
                            Text('加载中...'),
                          ],
                        ),
                      ),
                // 添加升级遮罩层
                if (state.isUpgrading)
                  Container(
                    color: Colors.black54,
                    child: const Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          CircularProgressIndicator(color: Color(0xFF7691fa)),
                          SizedBox(height: 16),
                          Text('资源更新中...', style: TextStyle(color: Colors.white)),
                        ],
                      ),
                    ),
                  ),
                // 连续点击退出登录的隐形热区(左上角 60x60)
                // 用于 H5 因 JS 异常无响应时的兜底退出机制
                // 使用 Listener 而非 GestureDetector:避免 Android 端 WebView 平台视图
                // 在手势竞技场中抢占 onTap,导致点击不生效。
                // Positioned(
                //   left: 0,
                //   top: 0,
                //   width: 80,
                //   height: 80,
                //   child: Listener(
                //     behavior: HitTestBehavior.opaque,
                //     onPointerDown: (_) {
                //       final triggered = ctx.read<WebCubit>().onQuickLogoutTap();
                //       if (triggered) {
                //         _showQuickLogoutDialog(ctx);
                //       }
                //     },
                //     child: const SizedBox.expand(),
                //   ),
                // ),
              ],
            ),
            bottomNavigationBar: state.showBottomNavBar
                ? BottomNavigationBar(
                    type: BottomNavigationBarType.fixed,
                    currentIndex: state.selectedIndex,
                    selectedItemColor: Color(0xFF7691fa),
                    unselectedItemColor: Color(0xFF969799),
                    onTap: (index) {
                      // 更新选中索引
                      ctx.read<WebCubit>().updateSelectedIndex(index);
                      // 根据 index 执行相应的操作
                    },
                    items: const [
                      BottomNavigationBarItem(
                        icon: Icon(Icons.home, size: 32),
                        label: '我的班级',
                      ),
                      BottomNavigationBarItem(
                        icon: Icon(Icons.contact_page, size: 32),
                        label: '通讯录',
                      ),
                      BottomNavigationBarItem(
                        icon: Icon(Icons.find_in_page, size: 32),
                        label: '发现',
                      ),
                      BottomNavigationBarItem(
                        icon: Icon(Icons.person, size: 32),
                        label: '我的',
                      ),
                    ],
                  )
                : null,
          );

          // 不论 showAppBar 是否为 true,都直接使用 scaffold。
          // 原因:Android 平台上 setSystemUIOverlayStyle 会修改 decorView 的
          // systemUiVisibility flag,污染 H5 <video> 全屏插件赖赖的 flag,
          // 导致全屏画面卡死。状态栏样式应在 main.dart 启动时一次性设置。
          final child = scaffold;

          // ios 不使用 PopScope
          if (Platform.isIOS) {
            return child;
          }

          return PopScope(
            canPop: false,
            onPopInvokedWithResult: (didPop, result) {
              if (didPop) {
                return;
              }
              ctx.read<WebCubit>().handleBack();
            },
            child: child,
          );
        },
        listener: (context, state) {
          if (state.suggestUpgrade) {
            context.read<WebCubit>().suggestUpgrade(context);
          } else if (state.orientationCmdFlag) {
            context.read<WebCubit>().getOrientation(context);
          } else if (state.windowInfoCmdFlag) {
            context.read<WebCubit>().getWindowInfo(context);
          } else if (state.chooseImageCmdFlag) {
            context.read<WebCubit>().chooseImage(context);
          } else if (state.chooseVideoCmdFlag) {
            context.read<WebCubit>().chooseVideo(context);
          }
        },
      ),
    );
  }

  AppBar _buildAppBar(BuildContext ctx, WebState state) {
    return AppBar(
      title: EnvConfig.isDev()
          ? Text(state.title + state.testMsg, style: TextStyle(color: Color(state.titleColor), fontSize: 16))
          : Text(state.title, style: TextStyle(color: Color(state.titleColor), fontSize: 16)),
      centerTitle: true,
      automaticallyImplyLeading: false,
      backgroundColor: Color(state.bgColor),
      actionsIconTheme: IconThemeData(color: Color(state.titleColor)),
      leading: state.opIcon == 'back'
          ? IconButton(
              icon: Icon(Icons.arrow_back, color: Color(state.titleColor)),
              onPressed: () {
                ctx.read<WebCubit>().handleBack();
              },
            )
          : (state.opIcon == 'home'
              ? IconButton(
                  icon: Icon(Icons.home, color: Color(state.titleColor)),
                  onPressed: () {
                    ctx.read<WebCubit>().handleHome();
                  },
                )
              : null),
      actions: [
        IconButton(
          icon: const Icon(Icons.settings),
          onPressed: () {
            router.push('/setting');
          },
        ),
      ],
      toolbarHeight: 40.0,
    );
  }

  /// 连续点击达阈后弹出的退出登录确认对话框
  /// 样式参照 login_main_page_v3 中的 _showAgreementDialog
  Future<void> _showQuickLogoutDialog(BuildContext ctx) async {
    final webCubit = ctx.read<WebCubit>();
    final result = await showDialog(
      context: ctx,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return AlertDialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(
              Radius.circular(5),
            ),
          ),
          title: Text(
            '退出登录',
            style: TextStyle(
              fontSize: 17,
              color: Color(0xFF000000),
            ),
            textAlign: TextAlign.center,
          ),
          content: Text(
            '检测到连续点击了屏幕,是否退出登录?',
            style: TextStyle(color: Color(0xFF666666), fontSize: 14),
          ),
          actions: [
            Table(
              children: [
                TableRow(
                  children: [
                    TableCell(
                      child: TextButton(
                        onPressed: () {
                          Navigator.of(context).pop();
                        },
                        style: TextButton.styleFrom(
                          foregroundColor: Color(0xFF666666),
                          textStyle: TextStyle(fontSize: 17),
                          padding: EdgeInsets.zero,
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.zero,
                          ),
                        ),
                        child: Text('取消'),
                      ),
                    ),
                    TableCell(
                      child: TextButton(
                        onPressed: () {
                          Navigator.of(context).pop('OK');
                        },
                        style: TextButton.styleFrom(
                          foregroundColor: Color(0xFF7691FA),
                          textStyle: TextStyle(fontSize: 17),
                          minimumSize: Size.fromHeight(40),
                          padding: EdgeInsets.zero,
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.zero,
                          ),
                        ),
                        child: Text('确认'),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ],
        );
      },
    );
    if (result != null) {
      webCubit.handleQuickLogout();
    }
  }
}