初步修改
This commit is contained in:
parent
644b02cc12
commit
a9e7102fde
35
README.md
35
README.md
|
|
@ -101,7 +101,8 @@ AppUpgradeSimple.instance.configure(const UpgradeConfig(
|
|||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<!-- Android 13+ 通知权限(可选) -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<!-- Android 9 写储存权限 sdk是28 否无无法写入内存 -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<application>
|
||||
<!-- FileProvider(Android 7.0+ APK 安装必需) -->
|
||||
<provider
|
||||
|
|
@ -114,6 +115,23 @@ AppUpgradeSimple.instance.configure(const UpgradeConfig(
|
|||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Android 11+ 查询声明:允许打开浏览器处理 HTTP/HTTPS URL -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
<!-- 允许打开浏览器处理 HTTP/HTTPS 链接(用于"前往浏览器下载"功能) -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
```
|
||||
|
||||
|
|
@ -124,11 +142,20 @@ AppUpgradeSimple.instance.configure(const UpgradeConfig(
|
|||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- External storage -->
|
||||
<external-path name="external_files" path="." />
|
||||
<!-- Internal app storage -->
|
||||
<files-path name="files" path="." />
|
||||
<!-- Cache directory -->
|
||||
<cache-path name="cache" path="." />
|
||||
<!-- External cache -->
|
||||
<external-cache-path name="external_cache" path="." />
|
||||
<!-- Downloads directory -->
|
||||
<external-path name="downloads" path="Download/" />
|
||||
<!-- External app-specific files directory (Android/data/包名/files/) -->
|
||||
<!-- This is accessible without WRITE_EXTERNAL_STORAGE permission on Android 10+ -->
|
||||
<!-- 用于权限被拒绝时,使用应用私有目录存储下载的 APK -->
|
||||
<external-files-path name="external_app_files" path="." />
|
||||
</paths>
|
||||
```
|
||||
|
||||
|
|
@ -229,10 +256,13 @@ await AppUpgradePlugin().installApkWithConfig(
|
|||
|
||||
## 🐛 常见问题
|
||||
|
||||
- 安装失败显示“解析包时出现问题”:检查 APK 完整性、签名与架构匹配
|
||||
- 安装失败显示"解析包时出现问题":检查 APK 完整性、签名与架构匹配
|
||||
- 权限申请失败:确认 Manifest 权限、FileProvider 配置、在 MaterialApp 环境调用
|
||||
- 下载失败/进度不更新:检查网络、下载 URL 可用性、服务端是否支持断点
|
||||
- iOS 不跳转:确认 `appStoreUrl` 为有效的 App Store 链接
|
||||
- "前往浏览器下载"无反应:确认已在 AndroidManifest.xml 中添加 `<queries>` 声明(Android 11+ 必需)
|
||||
- FileProvider 配置错误:确认 `file_paths.xml` 中已添加 `<external-files-path>` 配置,用于权限被拒绝时的备用存储路径
|
||||
- Android 9 下载权限错误:插件会自动检测权限,无权限时使用应用私有目录,无需额外配置
|
||||
|
||||
## 📚 主要 API 清单
|
||||
|
||||
|
|
@ -253,6 +283,7 @@ await AppUpgradePlugin().installApkWithConfig(
|
|||
- `openInstallPermissionSettings()`:跳转安装权限设置(Android)
|
||||
- `getDeviceInfo()`、`getAndroidSdkVersion()`(Android)
|
||||
- `goToAppStore(url)`:跳转到应用商店
|
||||
- `checkMarketAvailable({packageName, marketPackage, url})`:检查应用市场是否可用(Android),用于判断设备是否有可用的应用市场
|
||||
|
||||
### PermissionHelper(Android)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.math.BigInteger
|
|||
import java.security.MessageDigest
|
||||
import android.os.Environment
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.PackageManager
|
||||
|
||||
/** AppUpgradePlugin */
|
||||
class AppUpgradePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
||||
|
|
@ -101,6 +102,12 @@ class AppUpgradePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
result.error("INVALID_ARGUMENT", "URL is null", null)
|
||||
}
|
||||
}
|
||||
"checkMarketAvailable" -> {
|
||||
val packageName = call.argument<String>("packageName")
|
||||
val marketPackage = call.argument<String>("marketPackage")
|
||||
val url = call.argument<String>("url")
|
||||
checkMarketAvailable(packageName, marketPackage, url, result)
|
||||
}
|
||||
"getAndroidSdkVersion" -> {
|
||||
result.success(Build.VERSION.SDK_INT)
|
||||
}
|
||||
|
|
@ -201,6 +208,55 @@ class AppUpgradePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
activity?.startActivity(intent)
|
||||
}
|
||||
|
||||
/// 检查应用市场是否可用
|
||||
/// 返回 true 表示有应用可以处理 market:// 链接或指定的应用市场已安装
|
||||
private fun checkMarketAvailable(packageName: String?, marketPackage: String?, url: String?, result: Result) {
|
||||
if (context == null) {
|
||||
result.success(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val finalPackageName = packageName ?: context!!.packageName
|
||||
|
||||
// 如果指定了特定的应用市场包名,检查该应用是否已安装
|
||||
if (marketPackage != null && marketPackage.isNotEmpty()) {
|
||||
val pm = context!!.packageManager
|
||||
try {
|
||||
pm.getPackageInfo(marketPackage, PackageManager.GET_ACTIVITIES)
|
||||
// 应用市场已安装
|
||||
result.success(true)
|
||||
return
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
// 指定的应用市场未安装
|
||||
result.success(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有应用可以处理 market://details?id=包名 的 Intent
|
||||
val marketIntent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$finalPackageName"))
|
||||
val resolveInfo = context!!.packageManager.queryIntentActivities(marketIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
|
||||
if (resolveInfo.isNotEmpty()) {
|
||||
// 有应用可以处理 market:// 链接
|
||||
result.success(true)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有应用可以处理 market:// 链接,但有 URL,检查是否可以打开 URL
|
||||
if (url != null && url.isNotEmpty()) {
|
||||
val urlIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
val urlResolveInfo = context!!.packageManager.queryIntentActivities(urlIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
result.success(urlResolveInfo.isNotEmpty())
|
||||
} else {
|
||||
result.success(false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
result.error("CHECK_MARKET_ERROR", "Failed to check market availability", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInstallPermissionSettings(result: Result) {
|
||||
if (activity == null) {
|
||||
result.error("NO_ACTIVITY", "Activity is not available", null)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
<!-- Permissions -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility?hl=en and
|
||||
|
|
@ -57,5 +58,14 @@
|
|||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
<!-- Allow opening browser for HTTP/HTTPS URLs -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -10,4 +10,7 @@
|
|||
<external-cache-path name="external_cache" path="." />
|
||||
<!-- Downloads directory -->
|
||||
<external-path name="downloads" path="Download/" />
|
||||
<!-- External app-specific files directory (Android/data/包名/files/) -->
|
||||
<!-- This is accessible without WRITE_EXTERNAL_STORAGE permission on Android 10+ -->
|
||||
<external-files-path name="external_app_files" path="." />
|
||||
</paths>
|
||||
|
|
@ -213,10 +213,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1"
|
||||
sha256: "144ddd74d49c865eba47abe31cbc746c7b311c82d6c32e571fd73c4264b740e2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "8.2.12"
|
||||
version: "9.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import 'package:dio/dio.dart';
|
|||
import 'package:dio/io.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'app_upgrade_plugin_platform_interface.dart';
|
||||
|
|
@ -513,10 +515,12 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
|
|||
Future<bool> goToAppStore(String url) async {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
final bool flag = await canLaunchUrl(uri);
|
||||
if (flag) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
return true;
|
||||
}
|
||||
Fluttertoast.showToast(msg: '当前APP没有上架当前设备对应的应用市场');
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('跳转应用商店失败: $e');
|
||||
|
|
@ -525,13 +529,50 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> getDownloadPath() async {
|
||||
Future<String?> getDownloadPath({bool checkPermission = true}) async {
|
||||
if (Platform.isAndroid && checkPermission) {
|
||||
// 检查存储权限状态
|
||||
final sdkVersion = await _getAndroidSdkVersion();
|
||||
|
||||
// Android 9 及以下需要 WRITE_EXTERNAL_STORAGE 权限来写入公共目录
|
||||
if (sdkVersion <= 28) {
|
||||
final permission = await Permission.storage.status;
|
||||
if (!permission.isGranted) {
|
||||
debugPrint('存储权限未授予,使用应用私有目录');
|
||||
// 权限未授予,直接使用应用私有目录
|
||||
return await _getAppPrivateDownloadPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 首先尝试使用原生方法获取下载路径
|
||||
// 首先尝试使用原生方法获取下载路径(公共 Download 目录)
|
||||
final nativePath = await methodChannel.invokeMethod<String>('getDownloadPath');
|
||||
if (nativePath != null && nativePath.isNotEmpty) {
|
||||
debugPrint('使用原生下载路径: $nativePath');
|
||||
return nativePath;
|
||||
// 验证路径是否可写(对于 Android 9 及以下)
|
||||
if (Platform.isAndroid) {
|
||||
final sdkVersion = await _getAndroidSdkVersion();
|
||||
if (sdkVersion <= 28) {
|
||||
try {
|
||||
if (await _canWriteToDirectory(nativePath)) {
|
||||
debugPrint('使用原生下载路径: $nativePath');
|
||||
return nativePath;
|
||||
} else {
|
||||
debugPrint('无法写入公共下载目录,使用应用私有目录');
|
||||
return await _getAppPrivateDownloadPath();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('检查下载路径权限失败: $e,使用应用私有目录');
|
||||
return await _getAppPrivateDownloadPath();
|
||||
}
|
||||
} else {
|
||||
debugPrint('使用原生下载路径: $nativePath');
|
||||
return nativePath;
|
||||
}
|
||||
} else {
|
||||
debugPrint('使用原生下载路径: $nativePath');
|
||||
return nativePath;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('获取原生下载路径失败: $e');
|
||||
|
|
@ -548,7 +589,12 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
|
|||
// 创建 Download 子目录
|
||||
final downloadDir = Directory('${directory.path}/Download');
|
||||
if (!await downloadDir.exists()) {
|
||||
await downloadDir.create(recursive: true);
|
||||
try {
|
||||
await downloadDir.create(recursive: true);
|
||||
} catch (e) {
|
||||
debugPrint('无法创建外部存储下载目录: $e,使用应用私有目录');
|
||||
return await _getAppPrivateDownloadPath();
|
||||
}
|
||||
}
|
||||
debugPrint('使用外部存储下载路径: ${downloadDir.path}');
|
||||
return downloadDir.path;
|
||||
|
|
@ -556,16 +602,95 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
|
|||
}
|
||||
|
||||
// 如果外部存储不可用,使用应用文档目录
|
||||
directory = await getApplicationDocumentsDirectory();
|
||||
final downloadDir = Directory('${directory.path}/downloads');
|
||||
return await _getAppPrivateDownloadPath();
|
||||
} catch (e) {
|
||||
debugPrint('获取备用下载路径失败: $e');
|
||||
return await _getAppPrivateDownloadPath();
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取应用私有下载路径(不需要权限)
|
||||
/// 使用外部存储的应用私有目录(Android/data/包名/files/),因为它在 file_paths.xml 中已配置
|
||||
/// 这个目录在 Android 10+ 不需要 WRITE_EXTERNAL_STORAGE 权限
|
||||
Future<String> _getAppPrivateDownloadPath() async {
|
||||
try {
|
||||
// 使用外部存储的应用私有目录(/storage/emulated/0/Android/data/包名/files/)
|
||||
// 这个目录在 Android 10+ 不需要 WRITE_EXTERNAL_STORAGE 权限
|
||||
// 在 Android 9 及以下,如果无法写入会自动抛出异常,会被下面的 catch 捕获
|
||||
// 并且在 file_paths.xml 中通过 external-files-path 配置,FileProvider 可以访问
|
||||
final externalDir = await getExternalStorageDirectory();
|
||||
if (externalDir != null) {
|
||||
final downloadDir = Directory('${externalDir.path}/downloads');
|
||||
if (!await downloadDir.exists()) {
|
||||
await downloadDir.create(recursive: true);
|
||||
}
|
||||
debugPrint('使用外部存储应用私有下载路径: ${downloadDir.path}');
|
||||
return downloadDir.path;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('无法使用外部存储应用私有目录: $e');
|
||||
}
|
||||
|
||||
// 备用方案:使用内部 files 目录(/data/data/包名/files/)
|
||||
// 这个目录在 file_paths.xml 中通过 files-path 配置,FileProvider 可以访问
|
||||
try {
|
||||
// 获取应用数据目录的父目录,然后访问 files 子目录
|
||||
final appDataDir = await getApplicationSupportDirectory();
|
||||
final parentDir = Directory(appDataDir.path).parent;
|
||||
final filesPath = '${parentDir.path}/files';
|
||||
final downloadDir = Directory('$filesPath/downloads');
|
||||
if (!await downloadDir.exists()) {
|
||||
await downloadDir.create(recursive: true);
|
||||
}
|
||||
debugPrint('使用应用文档下载路径: ${downloadDir.path}');
|
||||
debugPrint('使用内部 files 下载路径: ${downloadDir.path}');
|
||||
return downloadDir.path;
|
||||
} catch (e) {
|
||||
debugPrint('获取备用下载路径失败: $e');
|
||||
return null;
|
||||
debugPrint('无法使用内部 files 目录: $e');
|
||||
// 最后的备用方案:使用 cache 目录(/data/data/包名/cache/)
|
||||
// 这个目录在 file_paths.xml 中通过 cache-path 配置,FileProvider 可以访问
|
||||
try {
|
||||
final cacheDir = await getTemporaryDirectory();
|
||||
final downloadDir = Directory('${cacheDir.path}/downloads');
|
||||
if (!await downloadDir.exists()) {
|
||||
await downloadDir.create(recursive: true);
|
||||
}
|
||||
debugPrint('使用 cache 下载路径: ${downloadDir.path}');
|
||||
return downloadDir.path;
|
||||
} catch (e) {
|
||||
debugPrint('无法使用 cache 目录: $e');
|
||||
// 如果所有方案都失败,抛出异常
|
||||
throw Exception('无法获取可用的下载路径');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查是否可以写入目录
|
||||
Future<bool> _canWriteToDirectory(String path) async {
|
||||
try {
|
||||
final dir = Directory(path);
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
// 尝试创建一个测试文件
|
||||
final testFile = File('$path/.test_write_${DateTime.now().millisecondsSinceEpoch}');
|
||||
await testFile.writeAsString('test');
|
||||
await testFile.delete();
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('无法写入目录 $path: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取 Android SDK 版本
|
||||
Future<int> _getAndroidSdkVersion() async {
|
||||
if (!Platform.isAndroid) return 0;
|
||||
try {
|
||||
final sdkVersion = await methodChannel.invokeMethod<int>('getAndroidSdkVersion');
|
||||
return sdkVersion ?? 0;
|
||||
} catch (e) {
|
||||
debugPrint('获取 Android SDK 版本失败: $e');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -579,6 +704,26 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
|
|||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> checkMarketAvailable({
|
||||
String? packageName,
|
||||
String? marketPackage,
|
||||
String? url,
|
||||
}) async {
|
||||
if (!Platform.isAndroid) return false;
|
||||
try {
|
||||
final result = await methodChannel.invokeMethod<bool>('checkMarketAvailable', {
|
||||
'packageName': packageName,
|
||||
'marketPackage': marketPackage,
|
||||
'url': url,
|
||||
});
|
||||
return result ?? false;
|
||||
} catch (e) {
|
||||
debugPrint('检查应用市场可用性失败: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 比较版本号
|
||||
/// 返回值:1表示v1大于v2,0表示相等,-1表示v1小于v2
|
||||
int _compareVersion(int version1, int version2) {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,10 @@ abstract class AppUpgradePluginPlatform extends PlatformInterface {
|
|||
}
|
||||
|
||||
/// 获取下载目录路径
|
||||
Future<String?> getDownloadPath() {
|
||||
///
|
||||
/// [checkPermission] 是否检查存储权限(默认 true)
|
||||
/// 如果为 true 且权限未授予,将使用应用私有目录
|
||||
Future<String?> getDownloadPath({bool checkPermission = true}) {
|
||||
throw UnimplementedError('getDownloadPath() has not been implemented.');
|
||||
}
|
||||
|
||||
|
|
@ -95,4 +98,19 @@ abstract class AppUpgradePluginPlatform extends PlatformInterface {
|
|||
Future<bool> checkApkExists(String version, String? md5) {
|
||||
throw UnimplementedError('checkApkExists() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 检查应用市场是否可用(仅Android)
|
||||
///
|
||||
/// [packageName] 应用包名,如果为 null 则使用当前应用包名
|
||||
/// [marketPackage] 指定的应用市场包名(可选)
|
||||
/// [url] 备用 URL(可选)
|
||||
///
|
||||
/// 返回 true 表示有应用可以处理应用市场链接
|
||||
Future<bool> checkMarketAvailable({
|
||||
String? packageName,
|
||||
String? marketPackage,
|
||||
String? url,
|
||||
}) {
|
||||
throw UnimplementedError('checkMarketAvailable() has not been implemented.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'app_upgrade_plugin_platform_interface.dart';
|
||||
import 'core/permission_helper.dart';
|
||||
|
|
@ -612,6 +613,63 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 前往浏览器
|
||||
void _goToBrowser() async {
|
||||
final downloadApkUrl = info.downloadUrl;
|
||||
if (!Platform.isAndroid || downloadApkUrl == null) {
|
||||
showToast('下载地址为空');
|
||||
return;
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(downloadApkUrl);
|
||||
|
||||
// 对于 APK 下载链接,直接尝试打开,不先检查 canLaunchUrl
|
||||
// 因为 canLaunchUrl 可能无法正确识别 APK 下载链接
|
||||
try {
|
||||
final launched = await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
if (!launched) {
|
||||
// 如果 launchUrl 返回 false,尝试使用 platformDefault 模式
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.platformDefault,
|
||||
);
|
||||
}
|
||||
// 关闭对话框
|
||||
if (mounted && Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
// 不需要关闭弹窗
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
onComplete?.call();
|
||||
});
|
||||
} catch (launchError) {
|
||||
debugPrint('launchUrl 失败: $launchError');
|
||||
// 如果 launchUrl 失败,尝试检查是否可以启动
|
||||
final canLaunch = await canLaunchUrl(uri);
|
||||
if (canLaunch) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
if (mounted && Navigator.canPop(context)) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
// 不需要关闭弹窗
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
onComplete?.call();
|
||||
});
|
||||
} else {
|
||||
showToast('无法打开下载链接,请检查是否安装了浏览器');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('打开浏览器失败: $e');
|
||||
showToast('打开浏览器失败: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startDownloadAndInstall() async {
|
||||
if (!Platform.isAndroid || info.downloadUrl == null) return;
|
||||
if (!mounted) return;
|
||||
|
|
@ -1709,10 +1767,16 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
|
|||
|
||||
// Option 2: Direct Download
|
||||
ListTile(
|
||||
leading: const Icon(Icons.download_for_offline_outlined),
|
||||
title: const Text('直接下载安装包'),
|
||||
leading: const Icon(Icons.system_update),
|
||||
title: const Text('APP内更新'),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
onTap: () => Navigator.of(ctx).pop('download'),
|
||||
onTap: () => Navigator.of(ctx).pop('update_within_APP'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.download_for_offline_outlined),
|
||||
title: const Text('前往浏览器下载安装包'),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
onTap: () => Navigator.of(ctx).pop('go_to_browser'),
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
|
@ -1755,8 +1819,14 @@ mixin _UpgradeDialogLogic<T extends StatefulWidget> on State<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (choice == 'download' && !_isDownloading) {
|
||||
if (choice == 'update_within_APP' && !_isDownloading) {
|
||||
await _startDownloadAndInstall();
|
||||
return;
|
||||
}
|
||||
|
||||
/// 前往浏览器更新
|
||||
if (choice == 'go_to_browser' && !_isDownloading) {
|
||||
_goToBrowser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ dependencies:
|
|||
shared_preferences: ^2.3.3
|
||||
flutter_local_notifications: ^18.0.1
|
||||
device_info_plus: ^11.2.0
|
||||
fluttertoast: ^8.2.11
|
||||
fluttertoast: ^9.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in New Issue