import 'dart:convert'; import 'package:background_downloader/background_downloader.dart'; import 'package:common_utils/common_utils.dart'; import 'package:ducafe_ui_core/ducafe_ui_core.dart'; import 'dart:io'; 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(); RxInt currentPage = RxInt(1); RxInt totalPage = RxInt(0); RxInt pageSize = RxInt(3); RxList versionList = RxList(); @override void onInit() async { super.onInit(); await getVersionList(); } @override void onClose() {} /// 搜索 Future doPage({int? page}) async { if (page != null) currentPage.value = page; await getVersionList(isLoading: true); } /// 获取版本列表 Future 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 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; } final ext = AppFileTool.getFileExtension(downUrl); final useExt = ObjectUtil.isEmpty(ext) ? ".apk" : ext; final now = DateTime.now(); final dateStr = DateUtil.formatDate( now, format: DateFormats.full, ); final fileName = "pj_xfj_${dateStr}_${now.millisecondsSinceEpoch}$useExt"; final task = DownloadTask( url: downUrl, filename: fileName, updates: Updates.statusAndProgress, baseDirectory: BaseDirectory.applicationDocuments, ); final result = await FileDownloader().download( task, onProgress: (progress) { logger.d("|$_tag|下载进度:$progress"); EasyLoading.showProgress( progress, status: "(${(progress * 100).toStringAsFixed(2)}%)下载中...", ); }, onStatus: (status) => {logger.d("|$_tag|下载状态:$status")}, ); if (result.status == TaskStatus.complete) { final movedPath = await FileDownloader().moveToSharedStorage( task, SharedStorage.downloads, mimeType: useExt == ".apk" ? "application/vnd.android.package-archive" : "application/octet-stream", ); if (ObjectUtil.isNotEmpty(movedPath)) { logger.i("|$_tag|下载完成:$movedPath"); EasyLoading.showSuccess("下载完成"); } else { EasyLoading.showError("移动到下载目录失败"); } } else { EasyLoading.showError("下载失败"); } } else { EasyLoading.showInfo("当前平台不支持系统下载"); } } catch (e) { logger.e("|$_tag|远程下载apk,下载失败:$e"); EasyLoading.showError("下载失败"); } finally { EasyLoading.dismiss(); } } } Widget settingVersionSetting(BuildContext context) { final versionC = Get.put( VersionManageController(), ); return [ 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(); final theme = FTheme.of(context); return [ 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(); 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(); final theme = FTheme.of(context); return [ [ 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), [ 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)), ); }