Files
tuiche/lib/pages/main_bottom/main_page_bottom_change.dart
2026-02-10 14:30:16 +08:00

788 lines
30 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
import 'package:vbvs_app/controller/message/message_controller.dart';
import 'package:vbvs_app/controller/setting/language/language_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/APPPackageType.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';
// 创建一个专门管理下载状态的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();
MessageController messageController = Get.find();
final getStorage = GetStorage();
late final List<Widget> arr;
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}) {
// ✅ 根据是否测试账号动态生成页面
if (!AppConstants.is_test_account) {
arr = [
HomePage(),
EPage(sleepUri: "https://xiaoe.he-info.cn/"),
MessagePage(),
MinePage(),
];
bottomItems = [
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: true),
getBottomNavigationBarItem("assets/img/menu/mine.svg",
"assets/img/menu/n_mine.svg", "菜单.我的".tr),
];
} else {
arr = [
HomePage(),
MessagePage(),
MinePage(),
];
bottomItems = [
getBottomNavigationBarItem("assets/img/menu/home.svg",
"assets/img/menu/n_home.svg", "菜单.首页".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),
];
}
}
// 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;
Widget buildIcon(String path) {
return Obx(() {
int type1 = messageController.model.body_message_read!;
int type2 = messageController.model.system_message_read!;
return Padding(
padding: EdgeInsets.only(bottom: 6.rpx),
child: isEmpty
? Container()
: Stack(
clipBehavior: Clip.none,
children: [
SvgPicture.asset(
path,
width: size,
height: size,
),
if ((type1 == 1 || type2 == 1) && showBadge)
Positioned(
right: -20.rpx,
top: -2.rpx,
child: Container(
width: 14.rpx,
height: 14.rpx,
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
],
),
);
});
}
return BottomNavigationBarItem(
icon: buildIcon(actSvgPath),
activeIcon: buildIcon(svgPath),
label: label,
);
}
@override
Widget build(BuildContext context) {
if (AppConstants().ent_type != APPPackageType.MHT.code && isShowPrivacyOriginFromTHAPP()) {
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) 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");
}
}
}
});
}
}
});
}
return Obx(() {
final currentLanguage =
languageController.selectLanguage.value; // 监听此变量变化
return PopScope(
canPop: false,
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();
}
}
},
child: Obx(() {
int currentIndex = controller.model.currentIndex;
// ✅ 防止 index 超出范围(例如测试账号少一个 tab
if (currentIndex >= arr.length) {
currentIndex = 0;
controller.model.currentIndex = 0;
}
if (globalController.model.hideBottomNavigationBar == true) {
return Scaffold(
body: IndexedStack(
index: currentIndex,
children:
arr.map((page) => SizedBox.expand(child: page)).toList(),
),
);
} else {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(getBackgroundImage()),
fit: BoxFit.fill,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
body: IndexedStack(
index: currentIndex,
children:
arr.map((page) => SizedBox.expand(child: page)).toList(),
),
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: currentIndex,
onTap: (index) {
Future.delayed(const Duration(milliseconds: 100), () {
UserInfoController userInfoController = Get.find();
bool isLoggedIn = userInfoController.model.login ==
LoginStatus.LOGIN.code;
// ✅ 仅非测试账号检查登录状态
if (!AppConstants.is_test_account &&
(index == 2) &&
!isLoggedIn) {
TopSlideNotification.show(
context,
text: "必须登录提示".tr,
textColor: themeController.currentColor.sc9,
);
Future.delayed(const Duration(milliseconds: 50),
() {
if (Get.currentRoute == '/ePage' ||
Get.currentRoute == '/messagePage') {
Get.back();
}
Future.delayed(const 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: bottomItems,
),
),
),
),
);
}
}),
);
});
}
Future<bool> _handleBackPressed(BuildContext context) async {
final currentTime = DateTime.now();
if (_lastBackPressedTime == null ||
currentTime.difference(_lastBackPressedTime!) >
const Duration(seconds: 2)) {
_lastBackPressedTime = currentTime;
TopSlideNotification.show(context, text: "滑动退出提醒".tr);
return false;
} else {
return true;
}
}
//太和e护衍生app是否显示协议
bool isShowPrivacyOriginFromTHAPP() {
if (AppConstants().ent_type == APPPackageType.TH.code) {
return true;
}
if (AppConstants().ent_type == APPPackageType.DONGHUA.code) {
return true;
}
return false;
}
}