appbridgenewplus 1.0.0-beta.1 copy "appbridgenewplus: ^1.0.0-beta.1" to clipboard
appbridgenewplus: ^1.0.0-beta.1 copied to clipboard

A versatile Flutter plugin for integrating various platform-specific functionalities and bridging communication with web views. It provides modules for UI, navigation, live activities, events, downloa [...]

example/lib/main.dart

import 'dart:io';
import 'dart:ui';
import 'package:url_launcher/url_launcher.dart';
import 'package:appbridgenewplus/app_bridge_webview.dart';
import 'package:http/http.dart' as http;
import 'package:appbridgenewplus/webrtc_live/webrtc_config.dart';
import 'package:appbridgenewplus/appbridgenew.dart';
import 'package:appbridgenewplus/appbridgenew_platform_interface.dart';
import 'package:appbridgenewplus/src/ui/video_player_page.dart';
import 'package:appbridgenewplus/src/ui/novel_reader_page.dart';
import 'package:appbridgenewplus/src/ui/comics_reader_page.dart';
import 'package:appbridgenewplus/src/ui/post_reader_page.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:isolate';
import 'downloader_io.dart';
import 'package:appbridgenewplus/event_service.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:appbridgenewplus/src/models/bridge_response.dart';

// 用于与下载器隔离区通信的接收端口
final ReceivePort port = ReceivePort();

// 应用程序入口
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 强制锁定竖屏
  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]);

  await FlutterDownloader.initialize(
    debug: true,
  );
  FlutterDownloader.registerCallback(downloadCallback);
  runApp(const MyApp());
}

// MyApp 是整个 Flutter 应用程序的根 widget。
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

// _MyAppState 类管理应用程序的状态和生命周期。
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  String _platformVersion = 'Unknown';
  final _appbridgenewPlugin = Appbridgenew();
  String? _demoHtmlContent;
  String? _initialUrl;
  DateTime? _lastPopTime;
  String? _currentDownloadTaskId;
  final Map<String, int> _retryAttempts = {};
  String? _pendingInstallApkPath;

  ThemeMode _themeMode = ThemeMode.system;

  String _currentAppBarTitle = 'AppBridgeH5 SDK 演示';
  String? _currentAppBarSubtitle;
  bool _isAppBarVisible = true;
  late Color _currentAppBarBackgroundColor;
  late Color _currentAppBarForegroundColor;
  late Brightness _currentAppBarStyle;

  static const platform =
      MethodChannel('com.example.appbridgenew_example/shortcut_channel');

  // 根据主题模式获取AppBar的背景颜色。
  Color _getAppBarBackgroundColor(ThemeMode mode) {
    return mode == ThemeMode.dark ? Colors.grey[900]! : Colors.blue.shade700;
  }

  // 根据主题模式获取 AppBar 的前景颜色。
  Color _getAppBarForegroundColor(ThemeMode mode) {
    return mode == ThemeMode.dark ? Colors.white : Colors.white;
  }

  // 根据主题模式获取 AppBar 的亮度样式。
  Brightness _getAppBarStyle(ThemeMode mode) {
    final Color bgColor = _getAppBarBackgroundColor(mode);
    return bgColor.computeLuminance() > 0.5
        ? Brightness.dark
        : Brightness.light;
  }

  // 获取弹窗标题的文本样式。
  TextStyle _getDialogTitleTextStyle(BuildContext context) {
    final TextStyle? headlineSmall = Theme.of(context).textTheme.headlineSmall;
    return headlineSmall?.copyWith(
            fontSize: (headlineSmall.fontSize ?? 24.0) - 3.0) ??
        const TextStyle(fontSize: 21.0, fontWeight: FontWeight.bold);
  }
  @override
  // 初始化状态,注册生命周期观察者和平台方法调用处理器。
  void initState() {
    super.initState();
    _preWarmSignalingServer(); // 启动即唤醒信令服务器
    WidgetsBinding.instance.addObserver(this);
    _currentAppBarBackgroundColor = _getAppBarBackgroundColor(_themeMode);
    _currentAppBarForegroundColor = _getAppBarForegroundColor(_themeMode);
    _currentAppBarStyle = _getAppBarStyle(_themeMode);

    _requestPermissions();

    WidgetsBinding.instance.addPostFrameCallback((_) {
      _loadDemoHtmlAndInitPlatformState();
    });

    platform.setMethodCallHandler((call) async {
      if (call.method == "receiveShortcutUrl") {
        final String? url = call.arguments;
        if (url != null && url.isNotEmpty) {
          setState(() {
            _initialUrl = url;
          });
        }
      }
    });

    IsolateNameServer.registerPortWithName(
        port.sendPort, 'downloader_send_port');
    port.listen((dynamic data) async {
      final Map<String, dynamic> downloadData = data as Map<String, dynamic>;
      final String id = downloadData['id'] as String;
      final int statusInt = downloadData['status'] as int;
      int progress = downloadData['progress'] as int;
      String? filePath;
      String? errorMessage;

      String formattedSpeed = "0.00KB/s";
      int currentTotalBytes = 0;
      int currentDownloadedBytes = 0;
      final tasks = await FlutterDownloader.loadTasksWithRawQuery(
          query: 'SELECT * FROM task WHERE task_id = "$id"');
      if (tasks != null && tasks.isNotEmpty) {
        final task = tasks.first;
        filePath = '${task.savedDir}/${task.filename}';
      } else {
        errorMessage = 'Download task not found for id: $id';
      }

      if (_appbridgenewPlugin.downloadTaskTotalBytes[id] != null &&
          _appbridgenewPlugin.downloadTaskTotalBytes[id]! > 0) {
        currentTotalBytes = _appbridgenewPlugin.downloadTaskTotalBytes[id]!;
        currentDownloadedBytes = (progress / 100 * currentTotalBytes).round();
        final int currentTime = DateTime.now().millisecondsSinceEpoch;
        final int currentBytes = currentDownloadedBytes;
        final int? lastTime = _appbridgenewPlugin.lastProgressTime[id];
        final int? lastBytes = _appbridgenewPlugin.lastProgressBytes[id];

        if (lastTime != null && lastBytes != null) {
          final timeDelta = currentTime - lastTime;

          final bytesDelta = currentBytes - lastBytes;

          if (timeDelta > 500 && bytesDelta >= 0) {
            final double speedInBps = (bytesDelta * 1000) / timeDelta;

            if (speedInBps >= 1024 * 1024) {
              formattedSpeed =
                  '${(speedInBps / (1024 * 1024)).toStringAsFixed(2)} MB/s';
            } else if (speedInBps >= 1024) {
              formattedSpeed = '${(speedInBps / 1024).toStringAsFixed(2)} KB/s';
            } else {
              formattedSpeed = '${speedInBps.toStringAsFixed(2)} B/s';
            }

            _appbridgenewPlugin.lastProgressTime[id] = currentTime;
            _appbridgenewPlugin.lastProgressBytes[id] = currentBytes;
          }
        } else {
          _appbridgenewPlugin.lastProgressTime[id] = currentTime;
          _appbridgenewPlugin.lastProgressBytes[id] = currentBytes;
        }
      }

      final DownloadTaskStatus downloadStatus =
          DownloadTaskStatus.values[statusInt];
      String statusString = downloadStatus.toString().split('.').last;

      if (downloadStatus == DownloadTaskStatus.failed && progress == -1) {
        final int attempts = _retryAttempts[id] ?? 0;
        if (attempts < 1) {
          _retryAttempts[id] = attempts + 1;

          if (filePath != null) {
            final file = File(filePath);
            if (await file.exists()) {
              try {
                await file.delete();
              } catch (e) {}
            }
          }

          if (tasks != null && tasks.isNotEmpty) {
            final task = tasks.first;
            final newTaskId = await FlutterDownloader.enqueue(
              url: task.url,
              savedDir: task.savedDir,
              fileName: task.filename,
              showNotification: true,
              openFileFromNotification: true,
            );

            if (id == _currentDownloadTaskId) {
              setState(() {
                _currentDownloadTaskId = newTaskId;
              });
            }
          }
          return;
        } else {}
      }

      if (downloadStatus == DownloadTaskStatus.failed &&
          progress == -1 &&
          filePath != null) {
        final file = File(filePath);
        if (await file.exists()) {
          statusString = DownloadTaskStatus.complete.toString().split('.').last;
          errorMessage = null;
          progress = 100;
        } else {
          errorMessage =
              'Download failed or cancelled for task: $id. Path: $filePath';
        }
      } else if (downloadStatus == DownloadTaskStatus.failed ||
          downloadStatus == DownloadTaskStatus.canceled) {
        errorMessage =
            'Download failed or cancelled for task: $id. Path: $filePath';
      }

      final eventPayload = {
        'id': id,
        'status': statusString,
        'progress': progress,
        'path': filePath,
        'error': errorMessage,
        'speed': formattedSpeed,
        'totalBytes': currentTotalBytes,
      };

      if (statusString ==
          DownloadTaskStatus.complete.toString().split('.').last) {
        _appbridgenewPlugin.emitEvent('download.completed', eventPayload);
      } else if (statusString ==
          DownloadTaskStatus.failed.toString().split('.').last) {
        _appbridgenewPlugin.emitEvent('download.failed', eventPayload);
      } else if (statusString ==
          DownloadTaskStatus.canceled.toString().split('.').last) {
        _appbridgenewPlugin.emitEvent('download.canceled', eventPayload);
      } else {
        _appbridgenewPlugin.emitEvent('download.progress', eventPayload);
      }
    });
  }

  Future<void> _requestPermissions() async {
    print("DEBUG: _requestPermissions method started.");
    // 请求相机权限
    print("DEBUG: Requesting Camera permission. Current status: ${await Permission.camera.status}");
    PermissionStatus cameraStatus = await Permission.camera.request();
    if (cameraStatus.isGranted) {
      print("DEBUG: Camera permission granted");
    } else if (cameraStatus.isDenied) {
      print("DEBUG: Camera permission denied (first time)");
      // 可选:显示对话框解释为何需要权限
    } else if (cameraStatus.isPermanentlyDenied) {
      print("DEBUG: Camera permission permanently denied");
      openAppSettings(); // 打开应用程序设置供用户手动启用
    }
    print("DEBUG: Camera permission final status: $cameraStatus");

    // 请求麦克风权限
    print("DEBUG: Requesting Microphone permission. Current status: ${await Permission.microphone.status}");
    PermissionStatus microphoneStatus = await Permission.microphone.request();
    if (microphoneStatus.isGranted) {
      print("DEBUG: Microphone microphoneStatus granted");
    } else if (microphoneStatus.isDenied) {
      print("DEBUG: Microphone microphoneStatus denied (first time)");
    } else if (microphoneStatus.isPermanentlyDenied) {
      print("DEBUG: Microphone microphoneStatus permanently denied");
      openAppSettings(); // 打开应用程序设置供用户手动启用
    }
    print("DEBUG: Microphone permission final status: $microphoneStatus");

    // 请求蓝牙连接权限(用于直播模块)
    print("DEBUG: Requesting BluetoothConnect permission. Current status: ${await Permission.bluetoothConnect.status}");
    PermissionStatus bluetoothStatus = await Permission.bluetoothConnect.request();
    if (bluetoothStatus.isGranted) {
      print("DEBUG: BluetoothConnect permission granted");
    } else if (bluetoothStatus.isDenied) {
      print("DEBUG: BluetoothConnect permission denied");
    } else if (bluetoothStatus.isPermanentlyDenied) {
      print("DEBUG: BluetoothConnect permission permanently denied");
    }

    // --- 诊断:打印 Info.plist 条目 ---
    try {
      final PackageInfo packageInfo = await PackageInfo.fromPlatform();
      print("APP_VERSION:${packageInfo.version}+${packageInfo.buildNumber}");
    } catch (e) {
      print("Error getting PackageInfo or Info.plist entries: $e");
    }
    print("DEBUG: _requestPermissions method finished.");
  }

  @override
  // 销毁时移除生命周期观察者和端口映射。
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    IsolateNameServer.removePortNameMapping('downloader_send_port');
    super.dispose();
  }

  // 加载 demo HTML 内容并初始化平台状态。
  Future<void> _loadDemoHtmlAndInitPlatformState() async {
    if (_initialUrl == null) {
      try {
        _demoHtmlContent = await rootBundle
            .loadString('packages/appbridgenewplus/assets/demo.html');
      } catch (e) {
        _demoHtmlContent = '<h1>Error loading demo.html</h1><p>$e</p>';
      }
    }

    String platformVersion;
    try {
      await _appbridgenewPlugin.initialize(
        onNavOpen: (url, {title, animated, modal, inExternal}) async {
          if (!mounted) return;
          if (inExternal == true) {
            if (await canLaunchUrl(Uri.parse(url))) {
              await launchUrl(Uri.parse(url),
                  mode: LaunchMode.externalApplication);
            } else {}
          } else {
            Navigator.of(_appbridgenewPlugin.mainContext!).push(
              MaterialPageRoute(
                builder: (context) => Scaffold(
                  appBar: AppBar(title: Text(title ?? '新页面')),
                  body: AppBridgeWebView(
                      initialUrl: url,
                      initialHtmlContent: null,
                      onWebViewCreated: (controller) {
                        _appbridgenewPlugin.emitEvent('theme.change', {
                          'theme':
                              _themeMode == ThemeMode.dark ? 'dark' : 'light'
                        });
                      }),
                ),
                fullscreenDialog: modal ?? false,
              ),
            );
          }
        },
        onNavClose: () {
          EventService().addLog('EVENT: nav.close');
        },
        onNavReplace: (url, title) {
          EventService().addLog('EVENT: nav.replace, URL: $url, Title: $title');
        },
        onAddShortcut: (title, url) async {
          if (!mounted) return BridgeResponse.error(-1, 'Widget not mounted');
          final result = await _appbridgenewPlugin.addShortcuts(title, url);
          return BridgeResponse.success(result);
        },
        onAppIcon: (styleId) async {
          if (!mounted) return BridgeResponse.error(-1, 'Widget not mounted');
          final result = await _appbridgenewPlugin.appIcon(styleId: styleId);
          return BridgeResponse.success(result);
        },
        onAppUpdateCheck: () async {
          if (!mounted) return BridgeResponse.error(-1, 'Widget not mounted');
          final currentContext = _appbridgenewPlugin.mainContext;
          if (currentContext == null || !currentContext.mounted) {
            return BridgeResponse.error(-1, 'No valid context.');
          }

          try {
            const hasUpdate = true;
            const version = '1.1.1';
            const releaseNotes = '更新内容:\n修复了一些已知问题;\n优化了用户体验。';
            const downloadUrl =
                'https://apk.loapk.com/51dongman_android/yjdm_01_2.28.apk';

            final userAction = await showDialog<String>(
              context: currentContext,
              barrierDismissible: false,
              builder: (dialogContext) {
                return AlertDialog(
                  title: const Text('发现新版本'),
                  content: const Column(
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('版本: $version'),
                      SizedBox(height: 10),
                      Text(releaseNotes),
                    ],
                  ),
                  actions: [
                    TextButton(
                      onPressed: () =>
                          Navigator.of(dialogContext).pop('cancel'),
                      child: const Text('稍后再说'),
                    ),
                    ElevatedButton(
                      onPressed: () =>
                          Navigator.of(dialogContext).pop('update'),
                      child: const Text('立即更新'),
                    ),
                  ],
                );
              },
            );

            if (userAction == 'update') {
              return BridgeResponse.success({
                'hasUpdate': hasUpdate,
                'version': version,
                'releaseNotes': releaseNotes,
                'downloadUrl': downloadUrl,
                'shouldUpdate': true,
              });
            }
            return BridgeResponse.success(
                {'hasUpdate': hasUpdate, 'shouldUpdate': false});
          } catch (e) {
            return BridgeResponse.error(-1, e.toString());
          }
        },
      );

      platformVersion = await _appbridgenewPlugin.getPlatformVersion() ??
          'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
      _currentAppBarTitle = 'AppBridgeH5 SDK 演示 ($_platformVersion)';
    });

    try {
      final response = await _appbridgenewPlugin.callModuleMethod(
          'core.has', {'path': 'core.getVersion'}); // 测试 core.getVersion 方法是否存在
      final response2 = await _appbridgenewPlugin.callModuleMethod(
          'core.has', {'path': 'non.existent.method'}); // 测试 一个不存在的方法
    } catch (e) {}

    _appbridgenewPlugin.initialize(
      onNavSetTitle: (title, subtitle) {
        if (mounted) {
          setState(() {
            _currentAppBarTitle = title;
            _currentAppBarSubtitle = subtitle;
          });
        }
      },
      // 处理导航栏设置事件,控制导航栏的显示、颜色和样式。
      onNavSetBars: (hidden, color, style) {
        if (mounted) {
          setState(() {
            _isAppBarVisible = !(hidden ?? false);

            if (color != null) {
              try {
                _currentAppBarBackgroundColor =
                    Color(int.parse(color.replaceFirst('#', '0xff')));
              } catch (e) {
                _currentAppBarBackgroundColor =
                    _getAppBarBackgroundColor(_themeMode);
              }
            } else {
              _currentAppBarBackgroundColor =
                  _getAppBarBackgroundColor(_themeMode);
            }

            _currentAppBarForegroundColor =
                _currentAppBarBackgroundColor.computeLuminance() > 0.5
                    ? Colors.black
                    : Colors.white;

            if (style != null) {
              _currentAppBarStyle =
                  style == 'dark' ? Brightness.dark : Brightness.light;
            } else {
              _currentAppBarStyle =
                  _currentAppBarBackgroundColor.computeLuminance() > 0.5
                      ? Brightness.dark
                      : Brightness.light;
            }
          });
        }
      },
      // 处理导航打开事件,根据 URL 跳转到不同的页面或功能。
      onNavOpen: (url) {
        if (url.isNotEmpty &&
            _appbridgenewPlugin.mainContext != null &&
            _appbridgenewPlugin.mainContext!.mounted) {
          // 处理视频播放请求
          if (url.startsWith('appbridge://video_open')) {
            final Uri uri = Uri.parse(url);
            final String? videoUrl = uri.queryParameters['videoUrl'];
            final String? pageTitle = uri.queryParameters['title'];

            if (videoUrl != null) {
              Navigator.of(_appbridgenewPlugin.mainContext!).push(
                MaterialPageRoute(
                  builder: (context) => VideoPlayerPage(
                      videoId: 'video_123',
                      videoUrl:
                          'https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny.m3u8',
                      title: pageTitle,
                      vttUrl:
                          'https://cdn.theoplayer.com/video/big_buck_bunny/thumbnails.vtt',
                      appBridge: _appbridgenewPlugin,
                      showCachingInfo: true),
                ),
              );
            } else {}
            // 处理小说阅读请求
          } else if (url.startsWith('appbridge://novel_open')) {
            final Uri uri = Uri.parse(url);
            final String? novelId = uri.queryParameters['novelId'];
            final String? novelUrl = uri.queryParameters['novelUrl'];
            final String? pageTitle = uri.queryParameters['title'];
            _appbridgenewPlugin.setNovelReaderActive(true);
            Navigator.of(_appbridgenewPlugin.mainContext!)
                .push(
              MaterialPageRoute(
                builder: (context) => NovelReaderPage(
                  novelId: novelId,
                  novelUrl: novelUrl,
                  novelTitle: pageTitle,
                  appBridge: _appbridgenewPlugin,
                ),
              ),
            )
                .then((_) {
              _appbridgenewPlugin.notifyNovelReaderDismissed();
            });
            // 处理漫画阅读请求
          } else if (url.startsWith('appbridge://comics_open')) {
            final Uri uri = Uri.parse(url);
            final String? comicId = uri.queryParameters['comicId'];
            final String? comicUrl = uri.queryParameters['comicUrl'];
            final String? pageTitle = uri.queryParameters['title'];

            if (comicId != null && pageTitle != null) {
              Navigator.of(_appbridgenewPlugin.mainContext!).push(
                MaterialPageRoute(
                  builder: (context) => ComicsReaderPage(
                    comicId: comicId,
                    comicUrl: comicUrl ?? '',
                    comicTitle: pageTitle,
                  ),
                ),
              );
            } else {}
            // 处理帖子阅读请求
          } else if (url.startsWith('appbridge://post_open')) {
            final Uri uri = Uri.parse(url);
            final String? postId = uri.queryParameters['postId'];
            final String? postUrl = uri.queryParameters['postUrl'];
            final String? pageTitle = uri.queryParameters['title'];

            if (postId != null && pageTitle != null) {
              Navigator.of(_appbridgenewPlugin.mainContext!).push(
                MaterialPageRoute(
                  builder: (context) => PostReaderPage(
                    postId: postId,
                    postUrl: postUrl ?? '',
                    postTitle: pageTitle,
                  ),
                ),
              );
            } else {}
          }

          // 处理事件测试页面
          else if (url.contains('events_test.html')) {
            Navigator.of(_appbridgenewPlugin.mainContext!).push(
              MaterialPageRoute(
                builder: (context) => Scaffold(
                  appBar: AppBar(
                    title: const Text('事件测试'),
                    centerTitle: true,
                    actions: [
                      IconButton(
                        icon: const Icon(Icons.repeat),
                        tooltip: '模拟原生事件 (on)',
                        onPressed: () {
                          _appbridgenewPlugin.emitEvent(
                            'native-to-h5',
                            {
                              'message': '来自 AppBar on 按钮',
                              'timestamp': DateTime.now().millisecondsSinceEpoch
                            },
                          );
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(
                                content: Text('已发送 native-to-h5 事件 (on)')),
                          );
                        },
                      ),
                      IconButton(
                        icon: const Icon(Icons.looks_one),
                        tooltip: '模拟原生事件 (once)',
                        onPressed: () {
                          _appbridgenewPlugin.emitEvent(
                            'native-to-h5',
                            {
                              'message': '来自 AppBar once 按钮',
                              'timestamp': DateTime.now().millisecondsSinceEpoch
                            },
                          );
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(
                                content: Text('已发送 native-to-h5 事件 (once)')),
                          );
                        },
                      ),
                    ],
                  ),
                  body: AppBridgeWebView(
                      initialUrl:
                          'packages/appbridgenewplus/assets/events_test.html',
                      initialHtmlContent: null,
                      onWebViewCreated: (controller) {
                        _appbridgenewPlugin.emitEvent('theme.change', {
                          'theme':
                              _themeMode == ThemeMode.dark ? 'dark' : 'light'
                        });
                      }),
                ),
              ),
            );
          } else {
            Navigator.of(_appbridgenewPlugin.mainContext!).push(
              MaterialPageRoute(
                builder: (context) => Scaffold(
                  appBar: AppBar(title: const Text('新页面')),
                  body: AppBridgeWebView(
                      initialUrl: url,
                      initialHtmlContent: null,
                      onWebViewCreated: (controller) {
                        _appbridgenewPlugin.emitEvent('theme.change', {
                          'theme':
                              _themeMode == ThemeMode.dark ? 'dark' : 'light'
                        });
                      }),
                ),
              ),
            );
          }
        } else {}
      },
      onNavClose: () {
        EventService().addLog('EVENT: nav.close');
      },
      onNavReplace: (url, title) {
        EventService().addLog('EVENT: nav.replace, URL: $url, Title: $title');
      },
      // 处理添加快捷方式事件。
      onAddShortcut: (title, url) async {
        if (!mounted) return BridgeResponse.error(-1, 'Widget not mounted');
        final currentContext = _appbridgenewPlugin.mainContext;
        if (currentContext != null && currentContext.mounted) {
          ScaffoldMessenger.of(currentContext).showSnackBar(
            SnackBar(content: Text('Added shortcut: $title, url: $url')),
          );
        } else {}

        final result = await _appbridgenewPlugin.addShortcuts(title, url);
        return BridgeResponse.success(result);
      },
      // 处理应用图标更改事件。
      onAppIcon: (styleId) async {
        if (!mounted) return BridgeResponse.error(-1, 'Widget not mounted');
        final currentContext = _appbridgenewPlugin.mainContext;
        if (currentContext != null && currentContext.mounted) {
          ScaffoldMessenger.of(currentContext).showSnackBar(SnackBar(
              content: Text('Requested app icon change for style: $styleId')));
        } else {}

        final result = await _appbridgenewPlugin.appIcon(styleId: styleId);
        return BridgeResponse.success(result);
      },
      // 处理应用更新检查事件。
      onAppUpdateCheck: () async {
        if (!mounted) return BridgeResponse.error(-1, 'Widget not mounted');
        final currentContext = _appbridgenewPlugin.mainContext;
        if (currentContext == null || !currentContext.mounted) {
          return BridgeResponse.error(-1, 'No valid context.');
        }

        try {
          String version = '1.1.1';
          String releaseNotes = '更新内容:\n修复了一些已知问题;\n优化了用户体验。';
          String downloadUrl = 'https://apk.loapk.com/51dongman_android/yjdm_01_2.28.apk';
          bool hasUpdate = true;

          // 针对 iOS 的处理逻辑
          if (Platform.isIOS) {
            final iosInfo = await _appbridgenewPlugin.checkUpdate();
            // 生产环境下这里应该从服务器获取最新版本并与 iosInfo['currentVersion'] 比较
            version = '1.1.5'; // 假设服务器最新版本
            // 提示:正式发布时请替换为你的真实 App ID
            downloadUrl = 'https://apps.apple.com/app/id414478124'; // 示例:微信的 AppStore 链接
          }

          final userAction = await showDialog<String>(
            context: currentContext,
            barrierDismissible: false,
            builder: (dialogContext) {
              return Dialog(
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(16.0)),
                elevation: 10.0,
                child: Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.system_update,
                          size: 48,
                          color: Theme.of(dialogContext).primaryColor),
                      const SizedBox(height: 16),
                      Text(
                        hasUpdate ? '发现新版本 V$version' : '当前已是最新版本',
                        style: _getDialogTitleTextStyle(dialogContext)
                            .copyWith(fontSize: 20.0),
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 10),
                      Text(
                        hasUpdate ? releaseNotes : '您当前使用的是最新版本,无需更新。',
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 20),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          TextButton(
                            onPressed: () =>
                                Navigator.of(dialogContext).pop('later'),
                            child: const Text('以后再说'),
                          ),
                          if (hasUpdate)
                            ElevatedButton(
                              onPressed: () =>
                                  Navigator.of(dialogContext).pop('update'),
                              child: const Text('立即更新'),
                            ),
                        ],
                      ),
                    ],
                  ),
                ),
              );
            },
          );

          if (userAction == 'update') {
            await _appbridgenewPlugin.triggerAppUpdateApply(downloadUrl);
            return BridgeResponse.success({'userAction': 'update'});
          } else {
            return BridgeResponse.success({'userAction': 'later'});
          }
        } catch (e) {
          return BridgeResponse.error(-1, 'Update check failed: $e');
        }
      },
      // 处理应用更新应用事件,包括下载和安装 APK。
      onAppUpdateApply: (url) async {
        if (url == null || url.isEmpty) {
          _appbridgenewPlugin.emitEvent('apk.install.failed',
              {'reason': 'Download URL is null or empty.'});
          return BridgeResponse.error(
              -1, 'Download URL cannot be null or empty.');
        }
        if (!mounted) {
          return BridgeResponse.error(-1, 'Widget not mounted');
        }
        final currentContext = _appbridgenewPlugin.mainContext;
        if (currentContext == null || !currentContext.mounted) {
          return BridgeResponse.error(-1, 'No valid context.');
        }

        // 针对 iOS 的特殊处理:直接跳转,不显示下载进度条
        if (Platform.isIOS) {
          try {
            await _appbridgenewPlugin.applyUpdate(url);
            ScaffoldMessenger.of(currentContext).showSnackBar(
                const SnackBar(content: Text('正在为您跳转至 App Store...')));
            return BridgeResponse.success(true);
          } catch (e) {
            ScaffoldMessenger.of(currentContext).showSnackBar(
                SnackBar(content: Text('跳转失败: ${e.toString()}')));
            return BridgeResponse.error(-1, 'Failed to open App Store URL: $e');
          }
        }

        final String localGeneratedDownloadId =
            'update_apk_${DateTime.now().millisecondsSinceEpoch}';

        final Completer<Map<String, dynamic>> downloadCompleter =
            Completer<Map<String, dynamic>>();
        StreamSubscription? progressSubscription;

        double currentProgress = 0.0;
        final String? returnedTaskId = await _appbridgenewPlugin
            .applyUpdate(url, downloadId: localGeneratedDownloadId);
        if (mounted) {
          setState(() {
            _currentDownloadTaskId = returnedTaskId;
          });
        }
        final bool initiationSuccess = _currentDownloadTaskId != null;

        bool isDialogDismissedByCancel = false;

        if (currentContext.mounted) {
          showDialog(
            context: currentContext,
            barrierDismissible: false,
            builder: (dialogContext) {
              return Dialog(
                // 使用 Dialog 代替 AlertDialog
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(16.0)), // 圆角
                elevation: 10.0, // 阴影
                child: Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: StatefulBuilder(
                    builder: (context, setStateInDialog) {
                      String totalSize = '未知';

                      if (progressSubscription == null) {
                        progressSubscription = _appbridgenewPlugin
                            .on('download.progress', (eventData) {
                          final Map<String, dynamic> data =
                              eventData['payload'] as Map<String, dynamic>;
                          if (data['id'] ==
                              (_currentDownloadTaskId ??
                                  localGeneratedDownloadId)) {
                            setStateInDialog(() {
                              currentProgress =
                                  (data['progress'] as int? ?? 0) / 100.0;
                              final int? totalBytesFromEvent =
                                  data['totalBytes'] as int?;
                              if (totalBytesFromEvent != null &&
                                  totalBytesFromEvent > 0) {
                                totalSize =
                                    '${(totalBytesFromEvent / (1024 * 1024)).toStringAsFixed(2)} MB';
                              } else {
                                totalSize = '未知';
                              }
                            });
                          }
                        });
                        _appbridgenewPlugin.on('download.completed',
                            (eventData) {
                          final Map<String, dynamic> data =
                              eventData['payload'] as Map<String, dynamic>;
                          if (data['id'] ==
                              (_currentDownloadTaskId ??
                                  localGeneratedDownloadId)) {
                            progressSubscription?.cancel();
                            if (!downloadCompleter.isCompleted) {
                              downloadCompleter.complete(
                                  {'success': true, 'filePath': data['path']});
                            }
                          }
                        });
                        _appbridgenewPlugin.on('download.failed', (eventData) {
                          final Map<String, dynamic> data =
                              eventData['payload'] as Map<String, dynamic>;
                          if (data['id'] ==
                              (_currentDownloadTaskId ??
                                  localGeneratedDownloadId)) {
                            progressSubscription?.cancel();
                            if (!downloadCompleter.isCompleted) {
                              downloadCompleter.complete(
                                  {'success': false, 'error': data['error']});
                            }
                          }
                        });
                        _appbridgenewPlugin.on('download.canceled',
                            (eventData) {
                          final Map<String, dynamic> data =
                              eventData['payload'] as Map<String, dynamic>;
                          if (data['id'] ==
                              (_currentDownloadTaskId ??
                                  localGeneratedDownloadId)) {
                            progressSubscription?.cancel();
                            if (!downloadCompleter.isCompleted) {
                              downloadCompleter.complete({
                                'success': false,
                                'error': 'Download cancelled by system or user.'
                              });
                            }
                          }
                        });
                      }

                      return Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Icon(Icons.download,
                              size: 48,
                              color:
                                  Theme.of(dialogContext).primaryColor), // 添加图标
                          const SizedBox(height: 16),
                          Text('下载更新',
                              style: _getDialogTitleTextStyle(dialogContext)
                                  .copyWith(fontSize: 20.0)), // 调整标题字号
                          const SizedBox(height: 10),
                          Text(
                              '新版本下载中: ${totalSize == '未知' ? '' : '($totalSize)'}'),
                          const SizedBox(height: 20),
                          LinearProgressIndicator(value: currentProgress),
                          const SizedBox(height: 10),
                          Text('进度: ${(currentProgress * 100).toInt()}%'),
                          const SizedBox(height: 20),
                          TextButton(
                            onPressed: () async {
                              final idToCancel = _currentDownloadTaskId ??
                                  localGeneratedDownloadId;
                              await _appbridgenewPlugin.download
                                  ?.handleMethod('cancel', {'id': idToCancel});
                              progressSubscription?.cancel();
                              if (!downloadCompleter.isCompleted) {
                                downloadCompleter.complete(
                                    {'success': false, 'error': '取消了下载'});
                              }
                              isDialogDismissedByCancel = true;
                              Navigator.of(dialogContext).pop();
                            },
                            child: const Text('取消'),
                          ),
                        ],
                      );
                    },
                  ),
                ),
              );
            },
          );
        }

        if (currentContext.mounted) {
          final Map<String, dynamic> downloadResult =
              await downloadCompleter.future;
          final bool downloadSuccess =
              downloadResult['success'] as bool? ?? false;
          final String? downloadedFilePath =
              downloadResult['filePath'] as String?;
          final String? downloadError = downloadResult['error'] as String?;

          if (!isDialogDismissedByCancel) {
            Navigator.of(currentContext).pop();
          }

          if (downloadSuccess && (initiationSuccess ?? false)) {
            ScaffoldMessenger.of(currentContext)
                .showSnackBar(const SnackBar(content: Text('下载成功,正在准备安装...')));
            if (downloadedFilePath != null && downloadedFilePath.isNotEmpty) {
              _pendingInstallApkPath = downloadedFilePath; // 存储路径,以备在应用恢复时继续安装
              final bool? installInitiated =
                  await _appbridgenewPlugin.apkInstall(downloadedFilePath);
              if (installInitiated != true) {
                ScaffoldMessenger.of(currentContext).showSnackBar(
                    const SnackBar(content: Text('APK安装失败,请尝试从通知栏或文件管理器安装。')));
                _appbridgenewPlugin.emitEvent('apk.install.failed', {
                  'path': downloadedFilePath,
                  'reason': 'Failed to initiate install intent.'
                });
              }
              _pendingInstallApkPath = null; // 尝试安装后清除路径
            } else {
              ScaffoldMessenger.of(currentContext).showSnackBar(
                  const SnackBar(content: Text('更新失败:无法获取文件路径。')));
              _appbridgenewPlugin.emitEvent('apk.install.failed', {
                'path': null,
                'reason': 'Downloaded file path is null or empty.'
              });
            }
          } else {
            ScaffoldMessenger.of(currentContext).showSnackBar(
                SnackBar(content: Text('更新失败: ${downloadError ?? '未知错误'}')));
            _appbridgenewPlugin.emitEvent('download.failed', {
              'url': url,
              'error': downloadError ?? 'Unknown download error.'
            });
          }
        }
        return BridgeResponse.success(initiationSuccess);
      },
    );
    // _runAllApiTests();
  }

  @override
  // 监听应用生命周期变化,用于处理 APK 安装中断后的恢复。
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      if (_pendingInstallApkPath != null) {
        final currentContext = _appbridgenewPlugin.mainContext;
        if (currentContext != null && currentContext.mounted) {
          ScaffoldMessenger.of(currentContext).showSnackBar(
              const SnackBar(content: Text('检测到有未完成的APK安装,尝试继续安装...')));
          final bool? installInitiated =
              await _appbridgenewPlugin.apkInstall(_pendingInstallApkPath!);
          if (installInitiated == true) {
          } else {
            ScaffoldMessenger.of(currentContext).showSnackBar(
                const SnackBar(content: Text('APK安装失败,请尝试从通知栏或文件管理器安装。')));
            _appbridgenewPlugin.emitEvent('apk.install.failed', {
              'path': _pendingInstallApkPath,
              'reason': 'Failed to initiate install intent on resume.'
            });
          }
          _pendingInstallApkPath = null; // 尝试安装后清除路径
        }
      }
    }
  }

  @override
  // 构建应用程序的用户界面。
  Widget build(BuildContext context) {
    // 根据 AppBarTheme 的 textStyle 优先确定基础样式
    final TextStyle baseStyle = Theme.of(context).appBarTheme.titleTextStyle ??
        Theme.of(context).textTheme.titleLarge ??
        const TextStyle(fontSize: 16.0, color: Colors.white); // 默认回退样式

    // 创建最终样式,降低字号并设置正确的颜色
    final TextStyle finalTitleStyle = baseStyle.copyWith(
      fontSize: (baseStyle.fontSize ?? 16.0) - 4.0,
      color: _currentAppBarForegroundColor,
    );

    return MaterialApp(
      navigatorKey: AppbridgenewPlatform.instance.navigatorKey,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue.shade700,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.grey.shade900,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      themeMode: _themeMode,
      // 使用状态变量控制主题模式
      home: Scaffold(
          appBar: _isAppBarVisible
              ? AppBar(
                  title: Text(
                    _currentAppBarTitle,
                    style: finalTitleStyle,
                  ),

                  backgroundColor: _currentAppBarBackgroundColor,
                  foregroundColor: _currentAppBarForegroundColor,
                  // 使用状态变量控制前景颜色
                  centerTitle: true,
                  systemOverlayStyle: _currentAppBarStyle == Brightness.dark
                      ? SystemUiOverlayStyle.dark
                      : SystemUiOverlayStyle.light,
                  actions: [
                    IconButton(
                      icon: Icon(
                        _themeMode == ThemeMode.dark
                            ? Icons.dark_mode
                            : Icons.light_mode,
                        color: _currentAppBarForegroundColor, // 使用状态变量控制图标颜色
                      ),
                      onPressed: () {
                        setState(() {
                          _themeMode = _themeMode == ThemeMode.dark
                              ? ThemeMode.light
                              : ThemeMode.dark;
                          _currentAppBarBackgroundColor =
                              _getAppBarBackgroundColor(_themeMode);
                          _currentAppBarForegroundColor =
                              _getAppBarForegroundColor(_themeMode);
                          _currentAppBarStyle = _getAppBarStyle(_themeMode);
                          _appbridgenewPlugin.emitEvent('theme.change', {
                            'theme':
                                _themeMode == ThemeMode.dark ? 'dark' : 'light'
                          });
                        });
                      },
                    ),
                  ],
                )
              : null,
          body: (_initialUrl != null || _demoHtmlContent != null)
              ? AppBridgeWebView(
                  initialUrl: _initialUrl,
                  initialHtmlContent:
                      _initialUrl == null ? _demoHtmlContent : null)
              : Container(
                  color: Colors.white,
                  child: const Center(child: CircularProgressIndicator()))),
    );
  }

  /*
   * 预热信令服务器 (唤醒 Render 冷启动)
   */
  Future<void> _preWarmSignalingServer() async {
    try {
      const String signalingServerUrl = WebRTCConfig.signalingServerUrl;
      final String httpUrl = '${signalingServerUrl
          .replaceFirst('wss://', 'https://')
          .replaceFirst('ws://', 'http://')}/ping';
      debugPrint('🔥 程序启动:正在预热信令服务器以唤醒冷启动: $httpUrl');
      // 发起一个简单的 GET 请求,无需等待结果
      http
          .get(Uri.parse(httpUrl))
          .timeout(const Duration(seconds: 10))
          .then((_) {
        debugPrint('✅ 信令服务器预热请求已发出');
      }).catchError((e) {
        debugPrint('ℹ️ 信令服务器预热请求已发出或失败 (预期中): $e');
      });
    } catch (e) {
      debugPrint('❌ 预热信令服务器逻辑错误: $e');
    }
  }
}