|
|
@@ -0,0 +1,408 @@
|
|
|
+
|
|
|
+import 'package:clean/base/base_page.dart';
|
|
|
+import 'package:clean/module/analysis/analysis_controller.dart';
|
|
|
+import 'package:clean/utils/expand.dart';
|
|
|
+import 'package:clean/utils/file_utils.dart';
|
|
|
+import 'package:flutter/Material.dart';
|
|
|
+import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
+import 'package:get/get.dart';
|
|
|
+import '../../model/asset_info.dart';
|
|
|
+import '../../resource/assets.gen.dart';
|
|
|
+import '../../router/app_pages.dart';
|
|
|
+import '../../utils/image_util.dart';
|
|
|
+import 'analysis_state.dart';
|
|
|
+import 'dart:typed_data';
|
|
|
+
|
|
|
+class AnalysisPage extends BasePage<AnalysisController> {
|
|
|
+ const AnalysisPage({super.key});
|
|
|
+
|
|
|
+ @override
|
|
|
+ bool immersive() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ bool statusBarDarkFont() => false;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget buildBody(BuildContext context) {
|
|
|
+ return Stack(
|
|
|
+ children: [
|
|
|
+ buildMain(context),
|
|
|
+ IgnorePointer(
|
|
|
+ child: Assets.images.bgHome.image(
|
|
|
+ width: 360.w,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget buildMain(BuildContext context) {
|
|
|
+ return SafeArea(
|
|
|
+ child: Container(
|
|
|
+ padding: EdgeInsets.only(left: 16.w, top: 14.h, right: 16.w),
|
|
|
+ child: Obx(() {
|
|
|
+ return Column(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.start,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ !controller.isEdit.value
|
|
|
+ ? Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ Get.back();
|
|
|
+ },
|
|
|
+ child: Assets.images.iconCommonBack
|
|
|
+ .image(width: 28.w, height: 28.w),
|
|
|
+ ),
|
|
|
+ Obx(() {
|
|
|
+ return Visibility(
|
|
|
+ visible: AnalysisState.imageList.isNotEmpty,
|
|
|
+ child: GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.isEdit.value = true;
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ width: 71.w,
|
|
|
+ height: 30.h,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: "#1F2D3F".color,
|
|
|
+ borderRadius: BorderRadius.all(
|
|
|
+ Radius.circular(15.h),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Center(
|
|
|
+ child: Text(
|
|
|
+ "Select",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: 14.sp,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+ : Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.isEdit.value = false;
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ width: 71.w,
|
|
|
+ height: 30.h,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: "#1F2D3F".color,
|
|
|
+ borderRadius: BorderRadius.all(
|
|
|
+ Radius.circular(15.h),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Center(
|
|
|
+ child: Text(
|
|
|
+ "Cancel",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: 14.sp,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Obx(() {
|
|
|
+ return Visibility(
|
|
|
+ visible: AnalysisState.imageList.isNotEmpty,
|
|
|
+ child: GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.toggleSelectAll();
|
|
|
+ },
|
|
|
+ child: Text(
|
|
|
+ "Select All",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white.withOpacity(0.65),
|
|
|
+ fontSize: 14.sp,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ height: 12.h,
|
|
|
+ ),
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ "Photo Analysis",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white,
|
|
|
+ fontWeight: FontWeight.w700,
|
|
|
+ fontSize: 24.sp,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ AnalysisState.imageList.isEmpty
|
|
|
+ ? _buildEmptyPhotoView(context)
|
|
|
+ : _buildPhotoView(),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ _buildEmptyPhotoView(BuildContext context) {
|
|
|
+ return Center(
|
|
|
+ child: Column(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ SizedBox(
|
|
|
+ height: 130.h,
|
|
|
+ ),
|
|
|
+ Assets.images.iconPrivacyEmptyImage.image(width: 70.w, height: 70.w),
|
|
|
+ SizedBox(
|
|
|
+ height: 22.h,
|
|
|
+ ),
|
|
|
+ Text(
|
|
|
+ "Upload Photos for Analysis",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white.withOpacity(0.9),
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ fontSize: 18.sp,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ height: 116.h,
|
|
|
+ ),
|
|
|
+ GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.uploadBtnClick();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ width: 180.w,
|
|
|
+ height: 48.h,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: "#0279FB".color,
|
|
|
+ borderRadius: BorderRadius.all(
|
|
|
+ Radius.circular(10.r),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Center(
|
|
|
+ child: Text(
|
|
|
+ "Upload",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: 16.sp,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildPhotoView() {
|
|
|
+ return Expanded(
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ SizedBox(
|
|
|
+ height: 20.h,
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ child: ListView.builder(
|
|
|
+ shrinkWrap: true,
|
|
|
+ itemCount: controller.monthCount,
|
|
|
+ itemBuilder: (context, index) {
|
|
|
+ final monthAssets =
|
|
|
+ ImageUtil.getMonthAssets(controller.assetsByMonth, index);
|
|
|
+ return Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ ImageUtil.getMonthText(controller.assetsByMonth, index),
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 16.sp,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ color: Colors.white),
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ height: 11.h,
|
|
|
+ ),
|
|
|
+ GridView.builder(
|
|
|
+ shrinkWrap: true,
|
|
|
+ physics: NeverScrollableScrollPhysics(),
|
|
|
+ itemCount: monthAssets.length,
|
|
|
+ itemBuilder: (context, index) {
|
|
|
+ var asset = monthAssets[index];
|
|
|
+ return _buildAssetItem(asset);
|
|
|
+ },
|
|
|
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
|
+ crossAxisCount: 3, // 每行有 4 列
|
|
|
+ mainAxisSpacing: 8.w, // 主轴(垂直)上的间距
|
|
|
+ crossAxisSpacing: 8.w, // 交叉轴(水平)上的间距
|
|
|
+ childAspectRatio: 1.0, // 子项宽高比
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ height: 24.h,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ !controller.isEdit.value
|
|
|
+ ? GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.uploadBtnClick();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ width: 328.w,
|
|
|
+ height: 48.h,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: "#0279FB".color,
|
|
|
+ borderRadius: BorderRadius.all(
|
|
|
+ Radius.circular(10.r),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Center(
|
|
|
+ child: Text(
|
|
|
+ "Upload",
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: 16.sp,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.deleteBtnClick();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ width: 328.w,
|
|
|
+ height: 48.h,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: "#0279FB".color,
|
|
|
+ borderRadius: BorderRadius.all(
|
|
|
+ Radius.circular(10.r),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Center(
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ Assets.images.iconPrivacyPhotoDelete
|
|
|
+ .image(width: 18.w, height: 18.h),
|
|
|
+ SizedBox(
|
|
|
+ width: 5.w,
|
|
|
+ ),
|
|
|
+ Text(
|
|
|
+ controller.formatFileSize(
|
|
|
+ controller.selectedTotalSize.value),
|
|
|
+ style: TextStyle(
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: 16.sp,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建图片项
|
|
|
+ Widget _buildAssetItem(AssetInfo asset) {
|
|
|
+ return GestureDetector(
|
|
|
+ onTap: () async {
|
|
|
+ // 获取当前资产在列表中的索引
|
|
|
+ final index = AnalysisState.imageList.indexWhere((item) => item.id == asset.id);
|
|
|
+ if (index != -1) { // 确保找到了索引
|
|
|
+ final result = await Get.toNamed(RoutePath.photoInfo, arguments: {
|
|
|
+ "type": "analysis",
|
|
|
+ "list": AnalysisState.imageList,
|
|
|
+ "index": index,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查返回结果并刷新
|
|
|
+ if (result != null && result['deleted'] == true) {
|
|
|
+ controller.loadAssets();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ child: Stack(
|
|
|
+ children: [
|
|
|
+ ClipRRect(
|
|
|
+ borderRadius: BorderRadius.circular(8.r),
|
|
|
+ child: FutureBuilder<Uint8List?>(
|
|
|
+ future: ImageUtil.getImageThumbnail(FileType.analysis, asset),
|
|
|
+ builder: (context, snapshot) {
|
|
|
+ if (snapshot.data != null) {
|
|
|
+ return Image.memory(
|
|
|
+ snapshot.data!,
|
|
|
+ width: double.infinity,
|
|
|
+ height: double.infinity,
|
|
|
+ fit: BoxFit.cover,
|
|
|
+ gaplessPlayback: true,
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ return Container();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ // 删除按钮
|
|
|
+ Visibility(
|
|
|
+ visible: controller.isEdit.value,
|
|
|
+ child: Positioned(
|
|
|
+ right: 4.w,
|
|
|
+ bottom: 4.h,
|
|
|
+ child: GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ controller.toggleSelectAsset(asset.id);
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ child: controller.selectedAssets.contains(asset.id)
|
|
|
+ ? Center(
|
|
|
+ child: Assets.images.iconSelected.image(
|
|
|
+ width: 16.w,
|
|
|
+ height: 16.h,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : Center(
|
|
|
+ child: Assets.images.iconUnselected.image(
|
|
|
+ width: 16.w,
|
|
|
+ height: 16.h,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|