import 'dart:convert'; import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:common_utils/common_utils.dart'; import 'package:ducafe_ui_core/ducafe_ui_core.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:permission_handler/permission_handler.dart'; import 'package:pj_supermarket_cloud/common/index.dart'; import 'setting_components.dart'; class VersionManageController extends GetxController { static const String _tag = '版本管理 Controller'; final currentPage = 1.obs; final totalPage = 0.obs; final pageSize = 3.obs; final versionList = [].obs; final isLoading = false.obs; final errorMessage = ''.obs; @override void onInit() { super.onInit(); getVersionList(); } Future doPage({int? page}) async { if (page != null) currentPage.value = page; await getVersionList(showLoading: true); } Future getVersionList({bool showLoading = false}) async { isLoading.value = true; errorMessage.value = ''; if (showLoading) { EasyLoading.show(status: '查询中...', maskType: EasyLoadingMaskType.black); } try { final data = await AppApi.openCardList({ 'appUniqueCode': Constants.deviceUniqueCode, 'current': currentPage.value, 'size': pageSize.value, }); logger.d('|$_tag|获取版本列表成功:${json.encode(data)}'); versionList.assignAll(data?.records ?? const []); totalPage.value = data?.total ?? 0; } catch (e, stack) { versionList.clear(); errorMessage.value = '版本列表加载失败,请检查网络后重试'; logger.e('|$_tag|获取版本列表失败error:|$e|stack:|$stack|'); } finally { isLoading.value = false; if (showLoading) EasyLoading.dismiss(); } } Future doDownload(String url) async { if (url.isEmpty) { Toast.error(msg: '当前版本没有可用下载地址'); return; } var downUrl = url; if (!url.startsWith('http://') && !url.startsWith('https://')) { downUrl = '${Constants.ossUrl}/api/admin/sys-file/oss/file?fileName=$url'; } if (!Platform.isAndroid) { EasyLoading.showInfo('当前平台不支持系统下载'); return; } EasyLoading.show(status: '下载中...', maskType: EasyLoadingMaskType.black); try { var granted = await Permission.storage.request().isGranted; if (!granted && await Permission.manageExternalStorage.isDenied) { granted = await Permission.manageExternalStorage.request().isGranted; } if (!granted) { EasyLoading.showError('存储权限未授权,无法下载'); return; } final ext = AppFileTool.getFileExtension(downUrl); final useExt = ObjectUtil.isEmpty(ext) ? '.apk' : ext; final now = DateTime.now(); final fileName = 'pj_xfj_${now.millisecondsSinceEpoch}$useExt'; final task = DownloadTask( url: downUrl, filename: fileName, updates: Updates.statusAndProgress, baseDirectory: BaseDirectory.applicationDocuments, ); final result = await FileDownloader().download( task, onProgress: (progress) => EasyLoading.showProgress( progress, status: '(${(progress * 100).toStringAsFixed(0)}%) 下载中...', ), onStatus: (status) => logger.d('|$_tag|下载状态:$status'), ); if (result.status != TaskStatus.complete) { EasyLoading.showError('下载失败'); return; } final movedPath = await FileDownloader().moveToSharedStorage( task, SharedStorage.downloads, mimeType: useExt == '.apk' ? 'application/vnd.android.package-archive' : 'application/octet-stream', ); if (ObjectUtil.isEmpty(movedPath)) { EasyLoading.showError('移动到下载目录失败'); return; } logger.i('|$_tag|下载完成:$movedPath'); EasyLoading.showSuccess('下载完成'); } catch (e, stack) { logger.e('|$_tag|下载失败error:|$e|stack:|$stack|'); EasyLoading.showError('下载失败'); } finally { EasyLoading.dismiss(); } } } Widget settingVersionSetting(BuildContext context) { final controller = Get.isRegistered() ? Get.find() : Get.put(VersionManageController()); return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SettingActionButton( label: '刷新版本列表', icon: FAssets.icons.refreshCw, primary: true, onTap: controller.doPage, ), ], ), SizedBox(height: 20.h), Expanded( child: Obx(() { if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } if (controller.errorMessage.isNotEmpty) { return SettingEmpty(message: controller.errorMessage.value); } if (controller.versionList.isEmpty) { return const SettingEmpty(message: '暂无版本信息'); } return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: XfjDesignTokens.cardGap.w, mainAxisSpacing: XfjDesignTokens.cardGap.h, childAspectRatio: 1.25, ), itemCount: controller.versionList.length, itemBuilder: (context, index) => _VersionCard( item: controller.versionList[index], recommended: index == 0, onDownload: () => controller.doDownload( controller.versionList[index].downloadUrl ?? '', ), ), ); }), ), SizedBox(height: XfjDesignTokens.cardGap.h), Obx( () => appPage( context, pageNo: controller.currentPage.value, pageSize: controller.pageSize.value, total: controller.totalPage.value, onPageChanged: (pageNo) => controller.doPage(page: pageNo), ), ), SizedBox(height: XfjDesignTokens.cardGap.h), SettingNotice( text: '下载需要 Android 存储权限。下载完成后仍需按照终端发布流程确认版本来源并执行安装。', background: Color(0xFFFFF9E8), borderColor: Color(0x4DF2A817), icon: FAssets.icons.triangleAlert, ), ], ); } class _VersionCard extends StatelessWidget { const _VersionCard({ required this.item, required this.recommended, required this.onDownload, }); final ServiceAppVersion item; final bool recommended; final VoidCallback onDownload; @override Widget build(BuildContext context) { final theme = FTheme.of(context); final platform = AppPlatformEnum.values.fromCode(item.platform ?? '')?.title ?? '未知平台'; final status = AppVersionStatusEnum.values.fromCode(item.status ?? '')?.title ?? '未知状态'; return Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: XfjDesignTokens.surface, border: Border.all(color: XfjDesignTokens.line), borderRadius: BorderRadius.circular(XfjDesignTokens.cardRadius), boxShadow: XfjDesignTokens.surfaceShadow, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: EdgeInsets.all(20.w), decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: XfjDesignTokens.line)), ), child: Row( children: [ Container( width: 52.w, height: 52.w, alignment: Alignment.center, decoration: BoxDecoration( color: XfjDesignTokens.primarySoft, borderRadius: BorderRadius.circular( XfjDesignTokens.controlRadius, ), ), child: FIcon( FAssets.icons.rocket, color: XfjDesignTokens.primary, size: 28.sp, ), ), SizedBox(width: 14.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '版本 ${item.versionName ?? '--'}', maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.typography.xl.copyWith( color: XfjDesignTokens.ink, fontWeight: FontWeight.w800, ), ), SizedBox(height: 4.h), Text( '$platform · Build ${item.versionCode ?? '--'}', style: theme.typography.sm.copyWith( color: XfjDesignTokens.muted, ), ), ], ), ), Container( padding: EdgeInsets.symmetric( horizontal: 10.w, vertical: 5.h, ), decoration: BoxDecoration( color: recommended ? XfjDesignTokens.primarySoft : XfjDesignTokens.control, borderRadius: BorderRadius.circular(999), ), child: Text( recommended ? '推荐更新' : status, style: theme.typography.sm.copyWith( color: recommended ? XfjDesignTokens.primary : XfjDesignTokens.muted, ), ), ), ], ), ), Expanded( child: Padding( padding: EdgeInsets.all(20.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '更新内容', style: theme.typography.base.copyWith( color: XfjDesignTokens.ink, fontWeight: FontWeight.w700, ), ), SizedBox(height: 10.h), Expanded( child: Text( item.updateContent ?? '暂无更新内容', maxLines: 5, overflow: TextOverflow.ellipsis, style: theme.typography.sm.copyWith( color: XfjDesignTokens.muted, height: 1.5, ), ), ), SizedBox(height: 12.h), SizedBox( width: double.infinity, child: SettingActionButton( label: '下载 APK', icon: FAssets.icons.arrowDownToLine, primary: recommended, enabled: ObjectUtil.isNotEmpty(item.downloadUrl), onTap: onDownload, ), ), ], ), ), ), ], ), ); } }