更新首页数据滚动效果

This commit is contained in:
wyf
2026-01-12 15:16:36 +08:00
parent 0ef8ddee8a
commit e841ee887a
19 changed files with 1160 additions and 400 deletions

View File

@@ -1,284 +1,24 @@
// import 'dart:async';
// import 'dart:io';
// import 'package:ef/ef.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';
// import 'package:flutter_svg/svg.dart';
// import 'package:get_storage/get_storage.dart';
// import 'package:vbvs_app/common/color/appConstants.dart';
// import 'package:vbvs_app/common/util/FitTool.dart';
// import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
// import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
// import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart';
// import 'package:vbvs_app/controller/message/message_controller.dart';
// import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
// import 'package:vbvs_app/controller/user_info_controller.dart';
// import 'package:vbvs_app/enum/LoginStatus.dart';
// import 'package:vbvs_app/pages/common/selectDialog.dart';
// import 'package:vbvs_app/pages/main_bottom/e_page.dart';
// import 'package:vbvs_app/pages/main_bottom/home_page.dart';
// import 'package:vbvs_app/pages/main_bottom/message_page.dart';
// import 'package:vbvs_app/pages/main_bottom/mine_page.dart';
// class MainPageBottomChange extends GetView<MainPageController> {
// GlobalController globalController = Get.find();
// ThemeController themeController = Get.find();
// MessageController messageController = Get.find();
// getBottomNavigationBarItem(String svgPath, String actSvgPath, String label,
// {double size = 0, bool isEmpty = false, bool showBadge = false}) {
// if (size == 0) {
// size = 36.rpx;
// }
// Widget buildIcon(String path) {
// return Padding(
// padding: EdgeInsets.only(bottom: 6.rpx),
// child: isEmpty
// ? Container()
// : Stack(
// clipBehavior: Clip.none,
// children: [
// SvgPicture.asset(
// path,
// width: size,
// height: size,
// ),
// if (showBadge)
// Positioned(
// right: -20.rpx,
// top: -2.rpx,
// child: Container(
// width: 14.rpx,
// height: 14.rpx,
// decoration: BoxDecoration(
// color: Colors.red,
// shape: BoxShape.circle,
// ),
// ),
// )
// ],
// ),
// );
// }
// return BottomNavigationBarItem(
// icon: buildIcon(actSvgPath),
// activeIcon: buildIcon(svgPath),
// label: label,
// );
// }
// List<Widget> arr = [
// HomePage(),
// // SleepReportPage(),
// // EPage(),
// EPage(sleepUri: "https://xiaoe.he-info.cn/"),
// MessagePage(),
// MinePage(),
// ];
// DateTime? _lastBackPressedTime; // 记录上一次返回的时间
// final getStorage = GetStorage();
// @override
// Widget build(BuildContext context) {
// Future.delayed(const Duration(milliseconds: 0), () {
// String? isShowYingShiDialog = getStorage.read("isShowYingShiDialog");
// if (isShowYingShiDialog == null || isShowYingShiDialog != "true") {
// String btnName = "同意".tr;
// String cancelName = "取消".tr;
// if (Platform.isAndroid) {
// cancelName = "退出".tr;
// }
// showCustomConfirmOfWebViewDialog(context, "隐私协议".tr, getPrivacy(1),
// btnName: btnName, showCancel: true, cancelName: cancelName)
// .then((e) {
// if (e == "confirm") {
// getStorage.write("isShowYingShiDialog", "true");
// } else {
// if (cancelName == "退出") {
// SystemNavigator.pop();
// }
// }
// });
// }
// });
// return PopScope(
// canPop: false,
// // onPopInvokedWithResult: (disposition, result) async {
// // if (Platform.isAndroid) {
// // var flag = await _handleBackPressed(context); // 自定义返回逻辑
// // if (flag) {
// // SystemNavigator.pop();
// // }
// // }
// // },
// onPopInvokedWithResult: (disposition, result) async {
// UserInfoController userInfoController = Get.find();
// if (userInfoController.model.isProgrammaticPop) {
// // 如果是程序触发,重置标记并忽略
// userInfoController.model.isProgrammaticPop = false;
// return; // 阻止处理
// }
// if (Platform.isAndroid) {
// // var flag = await _handleBackPressed(context); // 自定义返回逻辑
// // if (flag) {
// // SystemNavigator.pop();
// // }
// Get.back();
// }
// },
// child: Obx(
// () {
// if (globalController.model.hideBottomNavigationBar == true) {
// return Scaffold(
// // body: arr[controller.model.currentIndex],
// body: IndexedStack(
// // ✅ 改成 IndexedStack
// index: controller.model.currentIndex,
// children:
// arr.map((page) => SizedBox.expand(child: page)).toList(),
// ),
// floatingActionButtonAnimator:
// FloatingActionButtonAnimator.noAnimation,
// floatingActionButton: Container(),
// );
// } else {
// return Container(
// decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage('assets/img/bgImage.png'), // 本地图片
// fit: BoxFit.fill, // 填满整个 Container
// ),
// ),
// child: Scaffold(
// backgroundColor: Colors.transparent,
// // body: arr[controller.model.currentIndex],
// body: IndexedStack(
// // ✅ 改成 IndexedStack
// index: controller.model.currentIndex??3,
// children: arr
// .map((page) => SizedBox.expand(child: page))
// .toList(),
// ),
// floatingActionButtonAnimator:
// FloatingActionButtonAnimator.noAnimation,
// floatingActionButtonLocation:
// FloatingActionButtonLocation.centerDocked,
// bottomNavigationBar: Theme(
// data: Theme.of(context).copyWith(
// splashFactory: NoSplash.splashFactory,
// splashColor: Colors.transparent,
// highlightColor: Colors.transparent,
// ),
// child: Container(
// decoration: BoxDecoration(
// border: Border(
// top: BorderSide(
// color: themeController.currentColor.sc4
// .withOpacity(0.5),
// width: AppConstants().border_width,
// ),
// ),
// ),
// child: BottomNavigationBar(
// unselectedItemColor: themeController.currentColor.sc4,
// selectedItemColor: themeController.currentColor.sc1,
// backgroundColor: themeController.currentColor.sc5,
// selectedFontSize: 26.rpx,
// unselectedFontSize: 26.rpx,
// type: BottomNavigationBarType.fixed,
// currentIndex: controller.model.currentIndex,
// onTap: (index) {
// Future.delayed(const Duration(milliseconds: 100), () {
// UserInfoController userInfoController = Get.find();
// bool isLoggedIn = userInfoController.model.login ==
// LoginStatus.LOGIN.code;
// // if ((index == 1 || index == 2) && !isLoggedIn) {
// if ((index == 2) && !isLoggedIn) {
// TopSlideNotification.show(
// context,
// text: "必须登录提示".tr,
// textColor: themeController.currentColor.sc9,
// );
// Future.delayed(Duration(milliseconds: 50), () {
// if (Get.currentRoute == '/ePage' ||
// Get.currentRoute == '/messagePage') {
// Get.back();
// }
// Future.delayed(Duration(milliseconds: 100), () {
// Get.toNamed("/otherLoginPage");
// });
// });
// return;
// }
// if (controller.model.currentIndex != index) {
// globalController.model.hideBottomNavigationBar =
// false;
// globalController.updateAll();
// }
// controller.model.currentIndex = index;
// controller.updateAll();
// });
// },
// items: [
// getBottomNavigationBarItem("assets/img/menu/home.svg",
// "assets/img/menu/n_home.svg", "菜单.首页".tr),
// getBottomNavigationBarItem("assets/img/menu/e.svg",
// "assets/img/menu/n_e.svg", "菜单.小e".tr),
// getBottomNavigationBarItem(
// "assets/img/menu/message.svg",
// "assets/img/menu/n_message.svg",
// "菜单.消息".tr,
// showBadge: (messageController
// .model.body_message_read ==
// 1 ||
// messageController.model.system_message_read ==
// 1)),
// getBottomNavigationBarItem("assets/img/menu/mine.svg",
// "assets/img/menu/n_mine.svg", "菜单.我的".tr),
// ],
// ),
// ),
// ),
// ));
// }
// },
// ),
// );
// }
// Future<bool> _handleBackPressed(BuildContext context) async {
// final currentTime = DateTime.now();
// // 如果上次点击返回键时间为空,或者间隔超过 1 秒
// if (_lastBackPressedTime == null ||
// currentTime.difference(_lastBackPressedTime!) > Duration(seconds: 2)) {
// _lastBackPressedTime = currentTime;
// // showToast("再按一次退出程序", color: color_warning, closeTime: 2);
// TopSlideNotification.show(context, text: "滑动退出提醒".tr);
// return false; // 阻止退出程序
// } else {
// return true; // 允许退出程序
// }
// }
// }
import 'dart:async';
import 'dart:io';
// 添加新的导入
import 'package:dio/dio.dart';
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:get_storage/get_storage.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/CustomCard.dart';
import 'package:vbvs_app/component/tool/FrostedDialog.dart';
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart';
@@ -294,6 +34,48 @@ import 'package:vbvs_app/pages/main_bottom/home_page.dart';
import 'package:vbvs_app/pages/main_bottom/message_page.dart';
import 'package:vbvs_app/pages/main_bottom/mine_page.dart';
// 创建一个专门管理下载状态的Controller
class DownloadController extends GetxController {
RxDouble downloadProgress = 0.0.obs;
RxBool isDownloading = false.obs;
RxString downloadStatus = '等待下载'.obs;
CancelToken? cancelToken;
String? downloadFilePath;
void startDownload() {
isDownloading.value = true;
downloadProgress.value = 0.0;
downloadStatus.value = '开始下载';
}
void updateProgress(double progress) {
downloadProgress.value = progress;
downloadStatus.value = '下载中: ${(progress * 100).toStringAsFixed(1)}%';
}
void completeDownload(String filePath) {
isDownloading.value = false;
downloadProgress.value = 1.0;
downloadStatus.value = '下载完成';
downloadFilePath = filePath;
}
void cancelDownload() {
if (cancelToken != null) {
cancelToken!.cancel();
cancelToken = null;
}
isDownloading.value = false;
downloadProgress.value = 0.0;
downloadStatus.value = '下载已取消';
}
void errorDownload(String error) {
isDownloading.value = false;
downloadStatus.value = '下载失败: $error';
}
}
class MainPageBottomChange extends GetView<MainPageController> {
GlobalController globalController = Get.find();
ThemeController themeController = Get.find();
@@ -304,6 +86,10 @@ class MainPageBottomChange extends GetView<MainPageController> {
late final List<BottomNavigationBarItem> bottomItems;
DateTime? _lastBackPressedTime;
LanguageController languageController = Get.find();
bool select = false; // 是否已经提示过升级弹窗
// 使用GetX Controller来管理下载状态
final DownloadController downloadController = Get.put(DownloadController());
MainPageBottomChange({super.key}) {
// ✅ 根据是否测试账号动态生成页面
@@ -344,6 +130,393 @@ class MainPageBottomChange extends GetView<MainPageController> {
}
}
// iOS跳转AppStore方法
Future<void> _openAppStore() async {
const appStoreId = '6746246837'; // 你的 App ID
// 优先尝试使用 App Store 应用打开
final directUrl =
Uri.parse('itms-apps://itunes.apple.com/app/id$appStoreId');
final webUrl = Uri.parse('https://apps.apple.com/app/id$appStoreId');
try {
// 使用新版本的 canLaunchUrl 和 launchUrl
if (await canLaunchUrl(directUrl)) {
await launchUrl(
directUrl,
mode: LaunchMode.externalApplication, // 重要:使用外部应用打开
);
} else if (await canLaunchUrl(webUrl)) {
// 备用:使用网页版
await launchUrl(
webUrl,
mode: LaunchMode.externalApplication,
);
} else {
// 如果都无法打开
NewTopSlideNotification.show(
text: "无法打开AppStore链接".tr,
textColor: themeController.currentColor.sc9,
);
}
} catch (e) {
print('打开 App Store 失败: $e');
NewTopSlideNotification.show(
text: "打开AppStore失败".tr,
textColor: themeController.currentColor.sc9,
);
}
}
// 检查并请求存储权限Android
Future<bool> _requestStoragePermission() async {
return true;
if (!Platform.isAndroid) return true;
var status = await Permission.storage.status;
if (!status.isGranted) {
status = await Permission.storage.request();
}
if (Platform.isAndroid && status.isGranted) {
// Android 11及以上需要请求管理外部存储权限
if (await Permission.manageExternalStorage.isRestricted) {
status = await Permission.manageExternalStorage.request();
}
}
return status.isGranted;
}
// 下载APK文件
Future<void> _downloadApk({
required String url,
required String version,
}) async {
try {
// 请求存储权限
bool hasPermission = await _requestStoragePermission();
if (!hasPermission) {
downloadController.errorDownload("需要存储权限才能下载应用更新");
NewTopSlideNotification.show(
text: "需要存储权限才能下载应用更新".tr,
textColor: themeController.currentColor.sc9);
return;
}
// 创建下载目录
Directory? externalDir = await getExternalStorageDirectory();
String downloadDir = '${externalDir!.path}/Download';
// 确保目录存在
await Directory(downloadDir).create(recursive: true);
// 生成文件名
String fileName = 'app_update_$version.apk';
String filePath = '$downloadDir/$fileName';
// 如果文件已存在,先删除
File existingFile = File(filePath);
if (await existingFile.exists()) {
await existingFile.delete();
}
// 创建Dio实例
Dio dio = Dio();
downloadController.cancelToken = CancelToken();
downloadController.startDownload();
// 开始下载
try {
await dio.download(
url,
filePath,
cancelToken: downloadController.cancelToken,
onReceiveProgress: (received, total) {
if (total != -1) {
double progress = received / total;
downloadController.updateProgress(progress);
}
},
options: Options(
headers: {
'User-Agent': 'Dart/2.18 (dart:io)',
},
receiveTimeout: const Duration(minutes: 5),
),
);
} catch (e) {
ef.log("msg");
}
downloadController.completeDownload(filePath);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
downloadController.errorDownload("下载已取消");
} else {
downloadController.errorDownload("下载失败: ${e.toString()}");
}
NewTopSlideNotification.show(
text: "服务器错误,请稍后再试".tr, textColor: themeController.currentColor.sc9);
} finally {
downloadController.cancelToken = null;
}
}
// 安装APK文件Android
Future<void> _installApk(String filePath) async {
if (!Platform.isAndroid) return;
try {
// Android 8.0+ 需要「允许安装未知来源应用」权限
var status = await Permission.requestInstallPackages.status;
if (!status.isGranted) {
status = await Permission.requestInstallPackages.request();
if (!status.isGranted) {
Get.snackbar("权限被拒绝", "需要安装应用的权限");
return;
}
}
// 使用 open_file 打开 APK系统安装器
final result = await OpenFile.open(
filePath,
type: "application/vnd.android.package-archive",
);
if (result.type == ResultType.done) {
// Get.snackbar("提示", "正在打开安装程序...");
} else {
Get.defaultDialog(
title: "安装提示".tr,
middleText: "无法自动安装,请手动安装\n路径:\n$filePath".tr,
textConfirm: "确定".tr,
onConfirm: () => Get.back(),
);
}
} catch (e) {
// Get.snackbar("安装失败", "错误: $e\n请手动安装: $filePath");
// SystemNavigator.pop();
}
}
// 显示更新对话框
void _showUpdateDialog(
BuildContext context,
String newVersion,
String updateContent,
String updateUrl,
) {
select = true;
// 使用Obx来包装对话框内容
Get.dialog(
WillPopScope(
onWillPop: () async => false, // 禁止返回键关闭
child: FrostedDialog(
blurSigma: 3.0,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
padding: EdgeInsetsDirectional.fromSTEB(64.rpx, 0, 64.rpx, 0),
child: Container(
width: double.infinity,
constraints: BoxConstraints(
maxHeight: MediaQuery.sizeOf(context).height * 0.656,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 使用Obx监听下载状态
Obx(() {
return Align(
alignment: AlignmentDirectional(0, 0),
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0.rpx, 93.rpx, 0, 74.rpx),
child: Column(
children: [
/// 上半部分:居中对齐
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 94.rpx,
height: 70.rpx,
child: SvgPicture.asset(
'assets/img/icon/upgrade.svg',
fit: BoxFit.cover,
),
),
SizedBox(height: 37.rpx),
Text(
downloadController.isDownloading.value
? "正在下载更新...".tr
: "检测到新版本!".tr,
style: TextStyle(
color: stringToColor("#333333"),
fontSize:
AppConstants().title_text_fontSize,
),
),
],
),
SizedBox(height: 37.rpx),
/// 显示下载进度
if (downloadController.isDownloading.value)
Column(
children: [
LinearProgressIndicator(
value: downloadController
.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(
Colors.blue),
),
SizedBox(height: 10),
Text(
downloadController.downloadStatus.value,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
SizedBox(height: 20),
],
),
/// 更新信息
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("当前版本号".tr +
"" +
AppConstants.theh_app_version),
Text("升级后版本".tr + "" + newVersion),
SizedBox(height: 10),
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Text(
"升级内容".tr + "" + updateContent,
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
),
],
),
],
),
),
);
}),
// 按钮区域
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 19.rpx, 0, 60.rpx),
child: Obx(() {
return Column(
children: [
// 主要按钮(升级/下载按钮)
CustomCard(
gradientDirection: GradientDirection.vertical,
borderRadius:
AppConstants().button_container_radius,
onTap: () async {
if (Platform.isIOS) {
// 跳转AppStore
await _openAppStore();
Get.back();
} else if (!downloadController
.isDownloading.value) {
// 开始下载
_downloadApk(
url: updateUrl,
version: newVersion,
);
}
},
colors: [
themeController.currentColor.sc1,
themeController.currentColor.sc2,
],
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height * 0.055,
constraints: BoxConstraints(
minWidth: 500.rpx,
minHeight: 90.rpx,
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
downloadController.isDownloading.value
? "下载中...".tr
: Platform.isIOS
? "前往AppStore".tr
: "升级".tr,
style: TextStyle(
color: Colors.white,
fontFamily: 'Inter',
fontSize:
AppConstants().normal_text_fontSize,
letterSpacing: 0.0,
),
),
].divide(SizedBox(width: 17.rpx)),
),
),
),
],
);
}),
),
],
),
),
),
),
),
);
// 监听下载完成
ever(downloadController.isDownloading, (isDownloading) async {
if (!isDownloading && downloadController.downloadProgress.value >= 1.0) {
// 下载完成,关闭对话框并安装
Get.back();
// Get.defaultDialog(
// title: "下载完成".tr,
// middleText: "新版本已下载完成,是否立即安装?".tr,
// textConfirm: "安装".tr,
// textCancel: "稍后".tr,
// onConfirm: () {
// Get.back();
// _installApk(downloadController.downloadFilePath!);
// },
// onCancel: () {
// Get.back();
// Get.snackbar(
// "提示", "安装文件保存在: ${downloadController.downloadFilePath}");
// },
// );
await Future.delayed(Duration(milliseconds: 2000));
_installApk(downloadController.downloadFilePath!);
}
});
}
getBottomNavigationBarItem(String svgPath, String actSvgPath, String label,
{double size = 0, bool isEmpty = false, bool showBadge = false}) {
if (size == 0) size = 36.rpx;
@@ -402,13 +575,62 @@ class MainPageBottomChange extends GetView<MainPageController> {
showCustomConfirmOfWebViewDialog(context, "隐私协议".tr, getPrivacy(1),
btnName: btnName, showCancel: true, cancelName: cancelName)
.then((e) {
.then((e) async {
if (e == "confirm") {
getStorage.write("isShowYingShiDialog", "true");
await Future.delayed(const Duration(milliseconds: 1000));
if (select != true) {
// 判断APP是否需要更新
UserInfoController userInfoController = Get.find();
userInfoController
.checkAPPUpdate(AppConstants.theh_app_version)
.then((data) {
if (data != null) {
if (data['update'] != null && data['update'] == true) {
// 需要更新
String updateUrl = data['file']['path'];
String currentVersion = AppConstants.theh_app_version;
String newVersion = data['file']['version'] ?? "1.2.0";
String updateContent =
data['file']['desc'] ?? "修复已知bug".tr;
try {
_showUpdateDialog(
context, newVersion, updateContent, updateUrl);
} catch (e) {
ef.log("更新对话框显示失败: $e");
}
}
}
});
}
} else {
if (cancelName == "退出") SystemNavigator.pop();
}
});
} else {
if (select != true) {
// 判断APP是否需要更新
UserInfoController userInfoController = Get.find();
userInfoController
.checkAPPUpdate(AppConstants.theh_app_version)
.then((data) {
if (data != null) {
if (data['update'] != null && data['update'] == true) {
// 需要更新
String updateUrl = data['file']['path'];
String currentVersion = AppConstants.theh_app_version;
String newVersion = data['file']['version'] ?? "1.2.0";
String updateContent = data['file']['desc'] ?? "修复已知bug".tr;
try {
_showUpdateDialog(
context, newVersion, updateContent, updateUrl);
} catch (e) {
ef.log("更新对话框显示失败: $e");
}
}
}
});
}
}
});
}
@@ -425,9 +647,6 @@ class MainPageBottomChange extends GetView<MainPageController> {
return;
}
// if (Platform.isAndroid) {
// Get.back();
// }
if (Platform.isAndroid) {
var flag = await _handleBackPressed(context);
if (flag) {