diff --git a/example/lib/main.dart b/example/lib/main.dart index d89ed5b..956c79e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,9 +1,9 @@ import 'dart:async'; +import 'dart:io'; -import 'package:yx_app_upgrade_flutter/app_upgrade_plugin.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:yx_app_upgrade_flutter/app_upgrade_plugin.dart'; void main() { // 确保Flutter绑定已初始化 @@ -50,13 +50,9 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - String _platformVersion = 'Unknown'; - final _appUpgradePlugin = AppUpgradePlugin(); - @override void initState() { super.initState(); - initPlatformState(); // Use addPostFrameCallback to ensure the context is valid and mounted // after the first frame has been rendered. WidgetsBinding.instance.addPostFrameCallback((_) { @@ -70,46 +66,78 @@ class _HomePageState extends State { await AppUpgradeSimple.instance.checkUpdate( context: context, future: () async { - // 模拟获取服务器版本信息 - // 实际使用时,您应该调用您的API,并返回 AppUpgradeVersion 对象 - // final response = await myApi.checkVersion(); - // return AppUpgradeVersion(...); + final updateAppEvent = await _getUpdateAppEvent(); + print("获取最新版本: $updateAppEvent"); + if (updateAppEvent == null) return null; - // 这里为了演示,我们手动构造一个版本信息 - return AppUpgradeVersion( - versionName: '1.0.1', - versionBuildNumber: 11, - isForce: true, - updateContent: '修复了一些Bug\n优化了用户体验', - downloadUrl: 'https://example.com/app.apk', - supportedMethods: [AppUpgradeMethod.browser, AppUpgradeMethod.inApp, AppUpgradeMethod.market], - ); + return _convertToAppUpgradeVersion(updateAppEvent); }, - showNoUpdateToast: false, // 禁用"已是最新版本"的提示 - autoInstall: true, + onComplete: (bool val) { + print("更新插件执行完成....: $val"); + }, + config: UpgradeConfig.development, + // showNoUpdateToast: false, + // autoDownload: false, + // autoInstall: true, ); - debugPrint('=== 网络功能测试完成 ==='); } - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String platformVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. - try { - platformVersion = await _appUpgradePlugin.getPlatformVersion() ?? 'Unknown platform version'; - } on PlatformException { - platformVersion = 'Failed to get platform version.'; - } + /// 获取最新版本 + Future?> _getUpdateAppEvent() async { + // 获取设备信息 + // String deviceInfo; + /// 1:安卓,2:IOS + int? deviceType = Platform.isAndroid + ? 1 + : Platform.isIOS + ? 2 + : null; + if (deviceType == null) return null; - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; + /// 支持更新内容 加粗、高亮、斜体、等 + return { + "id": 708608950206533, + "version": 307, + "versionName": "1.0.9", + "remark": """ + **布置工作:**上级可以向分属团队`布置任务`,设定类型和时间。\n + **工作管理上:**__级可集中查看下级`所有工作`的详情与进度。__\n + **学生详情:**__在[学生管理]中添加详情页,支持记录家长信息、学生备注等,完善学生档案。__\n""", + "imageBase": null, + "updatetype": 1, + "isActive": 1, + "fileid": 708608942190661, + "fileName": "app-release(62).apk", + "filePath": AppUpgradeVersion.getAppStoreByUrl('6747421483'), + // "filePath": + // "https://quanxue-oa.oss-cn-chengdu.aliyuncs.com/20251106/1762422545956.apk.1", + "fileSize": 144214, + "isforce": true + }; + } - setState(() { - _platformVersion = platformVersion; - }); + /// 将 UpdateappResult 转换为 AppUpgradeVersion + AppUpgradeVersion _convertToAppUpgradeVersion(Map model) { + // 将文件大小从 KB 转换为字节 + final int? apkSizeBytes = model['fileSize'] != null ? model['fileSize'] * 1024 : null; + final filePath = model['filePath']; + final appUpgradeVersion = AppUpgradeVersion( + versionName: model['versionName'], + versionBuildNumber: model['version'], + isForce: model['isforce'], + updateContent: model['remark'], + downloadUrl: filePath, + appStoreUrl: filePath, + apkSize: apkSizeBytes, + apkMd5: null, // UpdateappResult 中没有 MD5 字段 + // appMarkets: null, // UpdateappResult 中没有应用商店列表字段 + supportedMethods: [AppUpgradeMethod.browser, AppUpgradeMethod.inApp, AppUpgradeMethod.market], + // appMarkets: [ + // AppMarket.huawei, + // // AppMarket.xiaomi, + // ], + ); + return appUpgradeVersion; } @override @@ -123,24 +151,30 @@ class _HomePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Running on: $_platformVersion\n'), const SizedBox(height: 16), ElevatedButton( - onPressed: () { - AppUpgradeSimple.instance.checkUpdate( - context: context, + onPressed: () async { + // 1. 静默检查更新(不显示任何 UI) + final upgradeInfo = await AppUpgradeSimple.instance.silentCheckUpdate( future: () async { - // 模拟获取服务器版本信息 - return AppUpgradeVersion( - versionName: '1.0.1', - versionBuildNumber: 11, - updateContent: '这是一个新版本', - downloadUrl: 'https://example.com/app.apk', - ); + final updateAppEvent = await _getUpdateAppEvent(); + print("获取最新版本: $updateAppEvent"); + if (updateAppEvent == null) return null; + + return _convertToAppUpgradeVersion(updateAppEvent); }, - showNoUpdateToast: false, // 禁用"已是最新版本"的提示 - autoInstall: false, ); + // 2. 根据结果处理 + if (upgradeInfo != null && upgradeInfo.hasUpdate) { + // 有新版本,可以显示红点或在用户点击时调用弹窗 + print('发现新版本: ${upgradeInfo.versionName}'); + + // 在需要展示弹窗的时机(如用户点击按钮): + AppUpgradeSimple.instance.showPreparedUpgrade( + context: context, + info: upgradeInfo, // 传入刚才获取的 info + ); + } }, child: const Text('检查更新'), ), diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..3c74ba5 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,557 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.8" + dio: + dependency: transitive + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.20.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + permission_handler: + dependency: transitive + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + process: + dependency: transitive + description: + name: process + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.5" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.flutter-io.cn" + source: hosted + version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + yx_app_upgrade_flutter: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "1.0.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 81c6f9d..69c9b4f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -32,7 +32,6 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - permission_handler: ^11.3.1 dev_dependencies: integration_test: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 745cf61..777ede7 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -5,104 +5,97 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:yx_app_upgrade_flutter/app_upgrade_plugin.dart'; -import 'package:yx_app_upgrade_flutter/app_upgrade_plugin_enhanced.dart'; import 'package:app_upgrade_plugin_example/main.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:yx_app_upgrade_flutter/app_upgrade_plugin.dart'; +import 'package:yx_app_upgrade_flutter/app_upgrade_plugin_platform_interface.dart'; -class MockAppUpgradePluginEnhanced implements AppUpgradePluginEnhanced { - // 在这里 mock AppUpgradePluginEnhanced 的所有方法和属性 - // ... - // 你可以根据测试需要返回特定的值 +/// Mock implementation of AppUpgradePluginPlatform for testing +class MockAppUpgradePluginPlatform extends AppUpgradePluginPlatform { + MockAppUpgradePluginPlatform() : super(); @override - void addDownloadCallback(DownloadCallback callback) {} + Future getPlatformVersion() async => 'Mock Platform 1.0.0'; + @override - void addErrorCallback(ErrorCallback callback) {} + Future getAndroidSdkVersion() async => 33; + @override - void addUpgradeCallback(UpgradeCallback callback) {} + Future openInstallPermissionSettings() async => true; + + @override + Future?> getDeviceInfo() async => { + 'manufacturer': 'Mock Manufacturer', + 'model': 'Mock Model', + 'androidVersion': '13', + }; + + @override + Future> getInstalledMarkets() async => ['huawei', 'xiaomi']; + + @override + void configureHttp(HttpConfig config) { + // Mock implementation + } + + @override + Future> getAppInfo() async => { + 'appName': 'app_upgrade_plugin_example', + 'packageName': 'com.example.app_upgrade_plugin_example', + 'version': '1.0.0', + 'buildNumber': '1', + }; + + @override + Future checkUpdate(String url, {Map? params}) async { + // Return null to simulate no update available + return null; + } + + @override + Future downloadApk( + String url, { + Function(DownloadProgress)? onProgress, + String? savePath, + }) async { + // Simulate download progress + if (onProgress != null) { + onProgress(DownloadProgress(received: 50, total: 100)); + await Future.delayed(const Duration(milliseconds: 10)); + onProgress(DownloadProgress(received: 100, total: 100)); + } + return '/mock/path/to/downloaded.apk'; + } + + @override + Future installApk(String filePath) async => true; + + @override + Future installApkWithSystemFlow(String filePath) async => true; + + @override + Future installApkWithConfig(String filePath, InstallConfig config) async => true; + + @override + Future goToAppStore(String url, {required BuildContext context}) async => true; + + @override + Future getDownloadPath({bool checkPermission = true}) async => '/mock/download/path'; + @override Future checkApkExists(String version, String? md5) async => false; - @override - Future checkUpdateSmart(String url, - {Map? params, bool forceRefresh = false, Duration? cacheDuration}) async => - null; - @override - Future clearCache() async {} - @override - UpgradeConfig get config => UpgradeConfig.instance; - @override - void configure( - {bool? debugMode, - int? checkIntervalHours, - bool? autoCheck, - bool? wifiOnly, - int? downloadTimeout, - int? connectTimeout, - int? maxRetryCount, - bool? supportBreakpoint, - bool? verifyIntegrity, - VersionCompareStrategy? versionStrategy, - Map? customHeaders}) {} - @override - void dispose() {} - @override - Future downloadApkSmart(String url, - {String? versionName, - Function(DownloadProgress p1)? onProgress, - String? savePath, - String? md5, - String? sha256, - bool resumeIfExists = true}) async => - null; - @override - Future> getAppInfo() async => {}; - @override - Future> getCacheStats() async => {}; - @override - DownloadTask? getCurrentDownloadTask() => null; - @override - Future getPlatformVersion() async => 'mock'; - @override - Future goToAppStore(String url) async => false; - @override - Future installApkSmart(String filePath) async => false; - @override - NetworkStatus? get networkStatus => NetworkStatus( - type: NetworkType.wifi, - quality: NetworkQuality.good, - isConnected: true, - isMetered: false, - ); - @override - void pauseDownload() {} - @override - void removeDownloadCallback(DownloadCallback callback) {} - @override - void removeErrorCallback(ErrorCallback callback) {} - @override - void removeUpgradeCallback(UpgradeCallback callback) {} - @override - Future resumeDownload() async => false; - @override - Future retryDownload() async => false; - @override - Future refreshNetworkStatus() async {} - @override - VersionComparator get versionComparator => VersionComparator(); - @override - void cancelDownload() {} } void main() { - // 关键修复:确保测试绑定已初始化 + // Ensure test bindings are initialized TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { - // 为所有方法通道设置一个默认的 mock 处理器,防止未 mock 的调用抛出异常 + // Set up mock method channel handlers for platform channels TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('plugins.flutter.io/package_info'), + const MethodChannel('plugins.flutter.io/package_info_plus'), (MethodCall methodCall) async { if (methodCall.method == 'getAll') { return { @@ -116,45 +109,177 @@ void main() { }, ); + // Mock local notifications channel TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('dexterous.com/flutter/local_notifications'), - (MethodCall methodCall) async => null, // 对通知插件的所有调用都返回 null + (MethodCall methodCall) async => null, ); + // Mock connectivity channel TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('dev.flutter.plugins/connectivity'), (MethodCall methodCall) async { if (methodCall.method == 'check') { - return 'wifi'; // 模拟网络状态为 WiFi + return 'wifi'; } return null; }, ); + + // Set mock platform implementation + AppUpgradePluginPlatform.instance = MockAppUpgradePluginPlatform(); }); - testWidgets('Verify Platform version', (WidgetTester tester) async { - // 使用 Mock enhanced plugin - AppUpgradeSimple.instance = AppUpgradeSimple.private(plugin: MockAppUpgradePluginEnhanced()); - - await tester.pumpWidget(const MyApp()); - - // 只验证应用能正常启动,不验证具体的UI内容 - expect(find.byType(MyApp), findsOneWidget); + tearDown(() { + // Clean up method channel handlers + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('plugins.flutter.io/package_info_plus'), + null, + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('dexterous.com/flutter/local_notifications'), + null, + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('dev.flutter.plugins/connectivity'), + null, + ); }); - testWidgets('Verify Network Status Detection', (WidgetTester tester) async { - // 使用 Mock enhanced plugin - final mockPlugin = MockAppUpgradePluginEnhanced(); - AppUpgradeSimple.instance = AppUpgradeSimple.private(plugin: mockPlugin); + group('App Upgrade Plugin Widget Tests', () { + testWidgets('App should launch successfully', (WidgetTester tester) async { + // Build the app + await tester.pumpWidget(const MyApp()); - await tester.pumpWidget(const MyApp()); + // Verify that the app widget is present + expect(find.byType(MyApp), findsOneWidget); + expect(find.byType(HomePage), findsOneWidget); - // 验证网络状态检测功能 - final networkStatus = mockPlugin.networkStatus; + // Verify that the app bar is present + expect(find.text('App Upgrade Plugin 示例'), findsOneWidget); - expect(networkStatus, isNotNull); - expect(networkStatus!.isConnected, isTrue); - expect(networkStatus.type, equals(NetworkType.wifi)); - expect(networkStatus.isMetered, isFalse); + // Verify that the check update button is present + expect(find.text('检查更新'), findsOneWidget); + }); + + testWidgets('App should display home page correctly', (WidgetTester tester) async { + await tester.pumpWidget(const MyApp()); + await tester.pumpAndSettle(); + + // Verify UI elements + expect(find.byType(Scaffold), findsOneWidget); + expect(find.byType(AppBar), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + }); + + testWidgets('Check update button should be tappable', (WidgetTester tester) async { + await tester.pumpWidget(const MyApp()); + await tester.pumpAndSettle(); + + // Find and tap the check update button + final button = find.text('检查更新'); + expect(button, findsOneWidget); + + // Tap the button + await tester.tap(button); + await tester.pump(); + + // Button should still be present after tap + expect(button, findsOneWidget); + }); + }); + + group('App Upgrade Plugin Platform Tests', () { + test('getPlatformVersion should return mock version', () async { + final platform = AppUpgradePluginPlatform.instance; + final version = await platform.getPlatformVersion(); + expect(version, isNotNull); + expect(version, equals('Mock Platform 1.0.0')); + }); + + test('getAppInfo should return mock app info', () async { + final platform = AppUpgradePluginPlatform.instance; + final appInfo = await platform.getAppInfo(); + expect(appInfo, isNotNull); + expect(appInfo['appName'], equals('app_upgrade_plugin_example')); + expect(appInfo['version'], equals('1.0.0')); + }); + + test('checkUpdate should return null when no update available', () async { + final platform = AppUpgradePluginPlatform.instance; + final upgradeInfo = await platform.checkUpdate('https://example.com/check'); + expect(upgradeInfo, isNull); + }); + + test('getDownloadPath should return mock path', () async { + final platform = AppUpgradePluginPlatform.instance; + final path = await platform.getDownloadPath(); + expect(path, isNotNull); + expect(path, equals('/mock/download/path')); + }); + + test('checkApkExists should return false for non-existent APK', () async { + final platform = AppUpgradePluginPlatform.instance; + final exists = await platform.checkApkExists('1.0.0', null); + expect(exists, isFalse); + }); + }); + + group('AppUpgradeVersion Model Tests', () { + test('AppUpgradeVersion should be created correctly', () { + final version = AppUpgradeVersion( + versionName: '1.0.1', + versionBuildNumber: 2, + updateContent: 'Test update content', + downloadUrl: 'https://example.com/app.apk', + isForce: false, + ); + + expect(version.versionName, equals('1.0.1')); + expect(version.versionBuildNumber, equals(2)); + expect(version.updateContent, equals('Test update content')); + expect(version.downloadUrl, equals('https://example.com/app.apk')); + expect(version.isForce, isFalse); + }); + + test('AppUpgradeVersion with force update should work', () { + final version = AppUpgradeVersion( + versionName: '1.0.1', + versionBuildNumber: 2, + updateContent: 'Force update', + isForce: true, + ); + + expect(version.isForce, isTrue); + }); + + test('AppUpgradeVersion.getAppStoreByUrl should generate correct URL', () { + final url = AppUpgradeVersion.getAppStoreByUrl('123456'); + expect(url, equals('https://apps.apple.com/cn/app/123456')); + }); + + test('AppUpgradeVersion.getAppStoreByItunes should generate correct URL', () { + final url = AppUpgradeVersion.getAppStoreByItunes('123456'); + expect(url, equals('itms-apps://itunes.apple.com/app/id123456')); + }); + }); + + group('DownloadProgress Model Tests', () { + test('DownloadProgress should calculate progress correctly', () { + final progress = DownloadProgress(received: 50, total: 100); + expect(progress.progress, equals(0.5)); + expect(progress.percentage, equals(50)); + }); + + test('DownloadProgress with zero total should handle gracefully', () { + final progress = DownloadProgress(received: 0, total: 0); + expect(progress.progress, equals(0.0)); + expect(progress.percentage, equals(0)); + }); + + test('DownloadProgress should calculate percentage correctly', () { + final progress = DownloadProgress(received: 75, total: 100); + expect(progress.percentage, equals(75)); + }); }); }