Files
pj_supermarket_cloud/lib/pages/setting/widgets/setting-version-setting.dart
T
2026-01-30 15:24:09 +08:00

446 lines
14 KiB
Dart

import 'dart:convert';
import 'package:common_utils/common_utils.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:external_path/external_path.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:forui/forui.dart';
import 'package:get/get.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
class VersionManageController extends GetxController {
static const String _tag = "版本管理 Controller";
final appC = Get.find<AppControl>();
RxInt currentPage = RxInt(1);
RxInt totalPage = RxInt(0);
RxInt pageSize = RxInt(3);
RxList<ServiceAppVersion> versionList = RxList();
@override
void onInit() async {
super.onInit();
await getVersionList();
}
@override
void onClose() {}
/// 搜索
Future<void> doPage({int? page}) async {
if (page != null) currentPage.value = page;
await getVersionList(isLoading: true);
}
/// 获取版本列表
Future<void> getVersionList({bool isLoading = false}) async {
if (isLoading) {
EasyLoading.show(
status: '查询中...',
maskType: EasyLoadingMaskType.black,
);
}
try {
final params = {
"appUniqueCode": Constants.deviceUniqueCode,
};
final data = await AppApi.openCardList(params);
logger.d("|$_tag|获取版本列表成功:${json.encode(data)}");
if (data == null || ObjectUtil.isEmptyList(data.records)) {
return;
}
versionList.value = data.records!;
totalPage.value = data.total;
} finally {
if (isLoading) {
Future.delayed(const Duration(seconds: 1), () {
EasyLoading.dismiss();
});
}
}
}
/// 远程下载apk
Future<void> doDownload(String url) async {
if (url.isEmpty) return;
String downUrl = url;
if (!url.startsWith("http") && !url.startsWith("https")) {
downUrl = "${Constants.ossUrl}/api/admin/sys-file/oss/file?fileName=$url";
}
logger.d("|$_tag|远程下载apk,url:$downUrl");
try {
EasyLoading.show(
status: '下载中...',
maskType: EasyLoadingMaskType.black,
);
if (Platform.isAndroid) {
var hasStorage = await Permission.storage.request().isGranted;
if (!hasStorage) {
EasyLoading.showError("存储权限未授权,无法下载");
return;
}
if (await Permission.manageExternalStorage.isDenied) {
final g = await Permission.manageExternalStorage.request();
hasStorage = hasStorage || g.isGranted;
}
const channel =
MethodChannel('com.example.pj_supermarket_cloud/system_download');
final user = await AppSettingTool.getUserSetting();
final headers = <String, String>{};
if (ObjectUtil.isNotEmpty(user.accessToken)) {
headers['Authorization'] = "Bearer ${user.accessToken}";
}
headers['TENANT-ID'] = "${user.tenantId ?? 1}";
headers['CLIENT-TOC'] = "Y";
headers['Accept'] = "*/*";
headers['User-Agent'] = "Mozilla/5.0 (Android) DownloadManager";
final ext = AppFileTool.getFileExtension(downUrl);
final useExt = ObjectUtil.isEmpty(ext) ? ".apk" : ext;
final fileName = "pj_${DateTime.now().millisecondsSinceEpoch}$useExt";
final mimeType = useExt == ".apk"
? "application/vnd.android.package-archive"
: "application/octet-stream";
final res = await channel.invokeMethod<int>('download', {
'url': downUrl,
'fileName': fileName,
'mimeType': mimeType,
'headers': headers,
});
logger.i("|$_tag|系统下载任务创建:${res ?? -1}");
try {
final info = await channel.invokeMethod<Map<dynamic, dynamic>>(
'query',
{'id': res},
);
logger.i("|$_tag|系统下载状态:${info?['status']},原因:${info?['reason']}");
final st = info?['status'] as int?;
if (st == 1 || st == 4) {
final downloadsDir =
await ExternalPath.getExternalStoragePublicDirectory(
"Downloads");
final savePath = "$downloadsDir/$fileName";
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(minutes: 5),
));
dio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
final client = HttpClient(
context: SecurityContext(withTrustedRoots: false),
);
client.badCertificateCallback = (cert, host, port) => true;
return client;
},
);
await dio.download(
downUrl,
savePath,
options: Options(headers: headers),
);
logger.i("|$_tag|直连下载成功:$savePath");
}
} catch (e) {
final downloadsDir =
await ExternalPath.getExternalStoragePublicDirectory("Downloads");
final savePath = "$downloadsDir/$fileName";
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(minutes: 5),
));
dio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
final client = HttpClient(
context: SecurityContext(withTrustedRoots: false),
);
client.badCertificateCallback = (cert, host, port) => true;
return client;
},
);
await dio.download(
downUrl,
savePath,
options: Options(headers: headers),
);
logger.i("|$_tag|系统查询失败降级直连成功:$savePath;error:$e");
}
EasyLoading.showSuccess("已添加到系统下载");
} else {
EasyLoading.showInfo("当前平台不支持系统下载");
}
} catch (e) {
logger.e("|$_tag|远程下载apk,下载失败:$e");
EasyLoading.showError("下载失败");
} finally {
EasyLoading.dismiss();
}
}
}
Widget settingVersionSetting(BuildContext context) {
final versionC = Get.put<VersionManageController>(
VersionManageController(),
);
return <Widget>[
buildSettingVersionSettingSearch(context),
buildSettingVersionSettingList(context).expanded(),
Obx(
() => appPage(
context,
pageNo: versionC.currentPage.value,
pageSize: versionC.pageSize.value,
total: versionC.totalPage.value,
onPageChanged: (pageNo) => versionC.doPage(page: pageNo),
),
)
].toColumn();
}
// 版本更新搜索区域
Widget buildSettingVersionSettingSearch(BuildContext context) {
final versionC = Get.find<VersionManageController>();
final theme = FTheme.of(context);
return <Widget>[
const Spacer(),
FButton(
prefix: FIcon(
FAssets.icons.search,
size: theme.typography.base.fontSize,
),
label: Text(
'搜索',
style: theme.typography.base.copyWith(
color: theme.colorScheme.secondary,
),
),
onPress: () => versionC.doPage(),
),
].toRow();
}
// 版本更新列表区域
Widget buildSettingVersionSettingList(BuildContext context) {
final versionC = Get.find<VersionManageController>();
return Obx(() {
if (versionC.versionList.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.info_outline, size: 48, color: Colors.grey),
const SizedBox(height: 10),
Text("暂无版本信息", style: TextStyle(color: Colors.grey[600])),
],
),
);
}
return GridView.builder(
padding: const EdgeInsets.all(10),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 6,
),
itemCount: versionC.versionList.length,
itemBuilder: (context, index) {
final item = versionC.versionList[index];
return buildSettingVersionSettingCardCopy(context, item);
},
);
});
}
Widget buildSettingVersionSettingCardCopy(
BuildContext context,
ServiceAppVersion item,
) {
final versionC = Get.find<VersionManageController>();
final theme = FTheme.of(context);
return <Widget>[
<Widget>[
Text(
"${AppPlatformEnum.values.fromCode(item.platform ?? '')?.title ?? '未知平台'} - ${item.versionName ?? '未知版本'}",
style: theme.typography.lg.copyWith(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
SizedBox(width: 20.w),
FBadge(
label: Text(
AppVersionStatusEnum.values.fromCode(item.status ?? '')?.title ??
'未知状态',
style: theme.typography.sm.copyWith(height: 1, color: Colors.white),
).padding(),
),
].toRow(),
SizedBox(height: 10.w),
Text(
"更新内容",
style: theme.typography.base.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 10.w),
Text(
item.updateContent ?? '暂无更新内容',
style: theme.typography.base.copyWith(color: Colors.grey),
),
SizedBox(height: 10.w),
<Widget>[
FIcon(
FAssets.icons.sendHorizontal,
size: theme.typography.base.fontSize,
),
SizedBox(width: 10.w),
Text(
item.downloadUrl ?? '未知下载地址',
style: theme.typography.sm.copyWith(color: Colors.grey),
),
const Spacer(),
FIcon(
FAssets.icons.arrowDownToLine,
size: theme.typography.base.fontSize,
),
]
.toRow()
.padding(horizontal: 20.w, vertical: 15.w)
.decorated(
color: theme.colorScheme.secondary,
border: Border.all(
color: theme.colorScheme.primary,
width: 1,
),
borderRadius: theme.style.borderRadius,
)
.onTap(() => versionC.doDownload(item.downloadUrl ?? '')),
]
.toColumn(crossAxisAlignment: CrossAxisAlignment.start)
.padding(horizontal: 20.w, vertical: 15.w)
.decorated(
color: theme.colorScheme.muted,
border: Border.all(
color: theme.colorScheme.primary,
width: 1,
),
borderRadius: theme.style.borderRadius,
);
}
// 版本管理卡片
Widget buildSettingVersionSettingCard(
BuildContext context,
ServiceAppVersion item,
) {
final theme = FTheme.of(context);
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
"${item.platform ?? '未知平台'} - ${item.versionName ?? '未知版本'}",
style:
theme.typography.lg.copyWith(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: item.status == '已发布'
? Colors.green.withOpacity(0.1)
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
item.status ?? '未知状态',
style: theme.typography.sm.copyWith(
color: item.status == '已发布' ? Colors.green : Colors.grey,
),
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Text("Build: ${item.versionCode ?? '-'}",
style: theme.typography.base.copyWith(color: Colors.grey)),
const SizedBox(width: 10),
if (item.isForce == true) _buildTag(context, "强制更新", Colors.red),
if (item.isSilent == true)
_buildTag(context, "静默更新", Colors.orange),
],
),
const Divider(height: 20),
Text("更新内容:",
style: theme.typography.base.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(
item.updateContent ?? '暂无更新内容',
style: theme.typography.base.copyWith(color: Colors.grey[800]),
),
if (item.downloadUrl != null && item.downloadUrl!.isNotEmpty) ...[
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
const Icon(Icons.link, size: 16, color: Colors.blue),
const SizedBox(width: 5),
Expanded(
child: Text(
item.downloadUrl!,
style: theme.typography.sm.copyWith(color: Colors.blue),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
],
],
),
);
}
Widget _buildTag(BuildContext context, String text, Color color) {
return Container(
margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
border: Border.all(color: color),
borderRadius: BorderRadius.circular(4),
),
child: Text(text, style: TextStyle(color: color, fontSize: 10)),
);
}