From d344486644fb36dc8482ee9b97ceb4480979e2f2 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 16 Jul 2025 22:21:23 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=88=86=E4=BA=AB=E7=BB=9F=E8=AE=A1=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bottomSheet/statistics_bottom_sheet.dart | 442 ++++++++++-------- pubspec.yaml | 2 +- 2 files changed, 250 insertions(+), 194 deletions(-) diff --git a/lib/modules/meeting/views/bottomSheet/statistics_bottom_sheet.dart b/lib/modules/meeting/views/bottomSheet/statistics_bottom_sheet.dart index e9cd37d2a..1926ea821 100644 --- a/lib/modules/meeting/views/bottomSheet/statistics_bottom_sheet.dart +++ b/lib/modules/meeting/views/bottomSheet/statistics_bottom_sheet.dart @@ -1,8 +1,15 @@ import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:image_gallery_saver_plus/image_gallery_saver_plus.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; import '../../../profile/controllers/profile_controller.dart'; import '../../controllers/meeting_mine_controller.dart'; @@ -18,6 +25,8 @@ class _StatisticsBottomSheetState extends State { final _mineController = Get.find(); final _profileCtrl = Get.find(); + final GlobalKey _posterKey = GlobalKey(); + @override Widget build(BuildContext context) { return Stack( @@ -30,225 +39,263 @@ class _StatisticsBottomSheetState extends State { height: 1.sh, ), ), - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 20.w), - margin: EdgeInsets.symmetric(horizontal: 10.w), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16.w), - image: const DecorationImage( - alignment: Alignment.topCenter, - image: AssetImage('assets/images/mine-background.png'), - fit: BoxFit.fitWidth, - ), - ), - child: Column( - children: [ - Container( - width: 50.w, - height: 50.w, - margin: EdgeInsets.only(bottom: 10.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.w), - ), - child: CircleAvatar( - radius: 25.w, - backgroundImage: _profileCtrl.localAvatar.value.isNotEmpty - ? FileImage(File(_profileCtrl.localAvatar.value)) - : (_profileCtrl.userInfo.value.avatar.isNotEmpty - ? NetworkImage(_profileCtrl.userInfo.value.avatar) - : null) as ImageProvider?, - backgroundColor: Colors.grey[200], - child: (_profileCtrl.localAvatar.value.isEmpty && - _profileCtrl.userInfo.value.avatar.isEmpty) - ? Icon( - Icons.person, - size: 25.r, - color: Colors.grey[600], - ) - : null, - ), - ), - Text( - _profileCtrl.userInfo.value.name, - style: TextStyle( - color: Colors.black87, - fontSize: 20.sp, - fontWeight: FontWeight.bold, + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RepaintBoundary( + key: _posterKey, + child: Container( + padding: + EdgeInsets.symmetric(horizontal: 15.w, vertical: 20.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16.w), + image: const DecorationImage( + alignment: Alignment.topCenter, + image: AssetImage('assets/images/mine-background.png'), + fit: BoxFit.fitWidth, ), ), - Padding( - padding: EdgeInsets.symmetric(vertical: 20.w), - child: Wrap( - spacing: 5.w, - runSpacing: 5.w, - children: [ - _item( - '天', - '${_mineController.days.value}', - Colors.amber, - ), - _item( - '文件', - '${_mineController.numberFiles.value}', - Colors.blueGrey, - ), - _item( - '小时', - '${_mineController.hourage.value}', - Colors.cyan, - ), - _item( - '连续录音天数', - '${_mineController.continuousDays.value}', - Colors.deepOrangeAccent, - ), - _item( - '单日最多录音', - '${_mineController.maxNumberFiles.value}', - Colors.teal, - ), - _item( - '单日最长录音', - '${_mineController.maxHourage.value}', - Colors.pink, - ), - ], - ), - ), - Row( + child: Column( children: [ - Text( - '过去12个月的热力图', - style: TextStyle( - color: Colors.grey, - fontSize: 12.sp, + Container( + width: 50.w, + height: 50.w, + margin: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.w), + ), + child: CircleAvatar( + radius: 25.w, + backgroundImage: _profileCtrl + .localAvatar.value.isNotEmpty + ? FileImage(File(_profileCtrl.localAvatar.value)) + : (_profileCtrl.userInfo.value.avatar.isNotEmpty + ? NetworkImage( + _profileCtrl.userInfo.value.avatar) + : null) as ImageProvider?, + backgroundColor: Colors.grey[200], + child: (_profileCtrl.localAvatar.value.isEmpty && + _profileCtrl.userInfo.value.avatar.isEmpty) + ? Icon( + Icons.person, + size: 25.r, + color: Colors.grey[600], + ) + : null, ), ), - const Spacer(), Text( - '较冷', + _profileCtrl.userInfo.value.name, style: TextStyle( - color: Colors.grey, - fontSize: 12.sp, + color: Colors.black87, + fontSize: 20.sp, + fontWeight: FontWeight.bold, ), ), - 5.horizontalSpace, - Container( - width: 10.w, - height: 10.w, - margin: EdgeInsets.symmetric(horizontal: 1.w), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(2.w), + Padding( + padding: EdgeInsets.symmetric(vertical: 20.w), + child: Wrap( + spacing: 5.w, + runSpacing: 5.w, + children: [ + _item( + '天', + '${_mineController.days.value}', + Colors.amber, + ), + _item( + '文件', + '${_mineController.numberFiles.value}', + Colors.blueGrey, + ), + _item( + '小时', + '${_mineController.hourage.value}', + Colors.cyan, + ), + _item( + '连续录音天数', + '${_mineController.continuousDays.value}', + Colors.deepOrangeAccent, + ), + _item( + '单日最多录音', + '${_mineController.maxNumberFiles.value}', + Colors.teal, + ), + _item( + '单日最长录音', + '${_mineController.maxHourage.value}', + Colors.pink, + ), + ], ), ), - Container( - width: 10.w, - height: 10.w, - margin: EdgeInsets.symmetric(horizontal: 1.w), - decoration: BoxDecoration( - color: Colors.red[100], - borderRadius: BorderRadius.circular(2.w), - ), + Row( + children: [ + Text( + '过去12个月的热力图', + style: TextStyle( + color: Colors.grey, + fontSize: 12.sp, + ), + ), + const Spacer(), + Text( + '较冷', + style: TextStyle( + color: Colors.grey, + fontSize: 12.sp, + ), + ), + 5.horizontalSpace, + Container( + width: 10.w, + height: 10.w, + margin: EdgeInsets.symmetric(horizontal: 1.w), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(2.w), + ), + ), + Container( + width: 10.w, + height: 10.w, + margin: EdgeInsets.symmetric(horizontal: 1.w), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: BorderRadius.circular(2.w), + ), + ), + Container( + width: 10.w, + height: 10.w, + margin: EdgeInsets.symmetric(horizontal: 1.w), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(2.w), + ), + ), + 5.horizontalSpace, + Text( + '较热', + style: TextStyle( + color: Colors.grey, + fontSize: 12.sp, + ), + ), + ], ), Container( - width: 10.w, - height: 10.w, - margin: EdgeInsets.symmetric(horizontal: 1.w), - decoration: BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.circular(2.w), + height: 50.w, + alignment: Alignment.center, + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 52, + mainAxisSpacing: 1.w, + crossAxisSpacing: 1.w, + ), + itemCount: 364, + itemBuilder: (BuildContext context, int index) { + final col = index % 52; + final row = index ~/ 52; + final vIndex = row + col * 7; + + int type = 0; + if (_mineController.statistics + .containsKey(vIndex + 1)) { + if (_mineController.statistics[vIndex + 1] + ['count'] <= + _mineController.averageNumberFiles) { + type = 1; + } else { + type = 2; + } + } + return Container( + color: type == 0 + ? Colors.grey[200] + : type == 1 + ? Colors.red[100] + : Colors.red, + ); + }, ), ), - 5.horizontalSpace, - Text( - '较热', + DefaultTextStyle( style: TextStyle( color: Colors.grey, - fontSize: 12.sp, + fontSize: 10.sp, + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text('Jan'), + Text('Feb'), + Text('Mar'), + Text('Apr'), + Text('May'), + Text('Jun'), + Text('Jul'), + Text('Aug'), + Text('Sep'), + Text('Oct'), + Text('Nov'), + Text('Dec'), + ], ), ), ], ), - Container( - height: 50.w, - alignment: Alignment.center, - child: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 52, - mainAxisSpacing: 1.w, - crossAxisSpacing: 1.w, - ), - itemCount: 364, - itemBuilder: (BuildContext context, int index) { - final col = index % 52; - final row = index ~/ 52; - final vIndex = row + col * 7; - - int type = 0; - if (_mineController.statistics - .containsKey(vIndex + 1)) { - if (_mineController.statistics[vIndex + 1]['count'] <= - _mineController.averageNumberFiles) { - type = 1; - } else { - type = 2; - } - } - return Container( - color: type == 0 - ? Colors.grey[200] - : type == 1 - ? Colors.red[100] - : Colors.red, - ); - }, - ), - ), - DefaultTextStyle( - style: TextStyle( - color: Colors.grey, - fontSize: 10.sp, - ), - child: const Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('Jan'), - Text('Feb'), - Text('Mar'), - Text('Apr'), - Text('May'), - Text('Jun'), - Text('Jul'), - Text('Aug'), - Text('Sep'), - Text('Oct'), - Text('Nov'), - Text('Dec'), - ], - ), - ), - ], + ), ), - ), - SizedBox(height: 10.w), - Padding( - padding: EdgeInsets.symmetric(horizontal: 10.w), - child: Row( + SizedBox(height: 10.w), + Row( children: [ - _button(Icons.share, '分享图片', Colors.deepOrange, () {}), + _button(Icons.share, '分享图片', Colors.deepOrange, () async { + EasyLoading.show(); + Uint8List pngBytes = await _generate(); + final tempDir = await getTemporaryDirectory(); + final String fileName = + 'share_${DateTime.now().millisecondsSinceEpoch}.png'; + final file = File('${tempDir.path}/$fileName'); + await file.writeAsBytes(pngBytes); + final result = await SharePlus.instance.share(ShareParams( + text: '分享 DEAPSOUND', + files: [XFile(file.path)], + )); + if (result.status == ShareResultStatus.success) { + Get.back(); + EasyLoading.showToast('分享完成!'); + } + EasyLoading.dismiss(); + }), 10.horizontalSpace, - _button(Icons.download, '保存图片', Colors.cyan, () {}), + _button(Icons.download, '保存图片', Colors.cyan, () async { + EasyLoading.show(); + Uint8List pngBytes = await _generate(); + final result = await ImageGallerySaverPlus.saveImage( + pngBytes, + quality: 100, + ); + if (result['isSuccess']) { + Get.back(); + EasyLoading.showToast('保存成功'); + } else { + EasyLoading.showToast('保存失败'); + } + EasyLoading.dismiss(); + }), ], ), - ), - ], + ], + ), ), ], ); @@ -324,4 +371,13 @@ class _StatisticsBottomSheetState extends State { ), ); } + + Future _generate() async { + RenderRepaintBoundary boundary = + _posterKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; + ui.Image image = await boundary.toImage(pixelRatio: 6.0); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + return pngBytes; + } } diff --git a/pubspec.yaml b/pubspec.yaml index fd133e15c..011abdb79 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,7 +69,7 @@ dependencies: flutter_facebook_auth: ^7.1.1 flutter_image_compress: ^2.4.0 common_utils: ^2.1.0 - # image_cropper: ^9.0.0 + image_gallery_saver_plus: ^4.0.1 dio: ^5.8.0+1 sqflite: ^2.4.2 path: ^1.9.1 From b7788fe39577e07fbbd1b85d916b54aef9491107 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Thu, 17 Jul 2025 11:52:31 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/meeting_home_controller.dart | 4 ++-- .../controllers/meeting_mine_controller.dart | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/modules/meeting/controllers/meeting_home_controller.dart b/lib/modules/meeting/controllers/meeting_home_controller.dart index cb91fdde9..3c1150630 100644 --- a/lib/modules/meeting/controllers/meeting_home_controller.dart +++ b/lib/modules/meeting/controllers/meeting_home_controller.dart @@ -36,7 +36,7 @@ class MeetingHomeController extends GetxController { TextEditingController titleController = TextEditingController(); // 录音标题 String titleCSuffix = ""; // 录音标题后缀 - List originalDataList = []; + RxList originalDataList = [].obs; RxList dataList = [].obs; RxString filtrateType = ''.obs; bool isAsc = false; @@ -82,7 +82,7 @@ class MeetingHomeController extends GetxController { // 请求本地数据 void _getDataList() async { List list = await SqfliteApi.getMeetingList(); - originalDataList = List.from(list); + originalDataList.value = List.from(list); dataList.value = List.from(list); } diff --git a/lib/modules/meeting/controllers/meeting_mine_controller.dart b/lib/modules/meeting/controllers/meeting_mine_controller.dart index 9e0fe905d..5e2fa8d7d 100644 --- a/lib/modules/meeting/controllers/meeting_mine_controller.dart +++ b/lib/modules/meeting/controllers/meeting_mine_controller.dart @@ -4,12 +4,17 @@ import 'package:get/get.dart'; import '../../../data/services/db/sqflite_api.dart'; import '../../profile/controllers/profile_controller.dart'; +import 'meeting_controller.dart'; +import 'meeting_home_controller.dart'; class MeetingMineController extends GetxController { + final _meetingController = Get.find(); + final _homeController = Get.find(); final profileCtrl = Get.find(); final ScrollController scrollController = ScrollController(); + bool isStatistics = true; Map statistics = {}; RxInt days = 0.obs; @@ -33,6 +38,14 @@ class MeetingMineController extends GetxController { } }); _getStatistics(); + ever(_meetingController.pageIndex, (value) { + if (isStatistics && value == 2) { + _getStatistics(); + } + }); + ever(_homeController.originalDataList, (value) { + isStatistics = true; + }); } @override @@ -42,6 +55,7 @@ class MeetingMineController extends GetxController { } void _getStatistics() async { + if (!isStatistics) return; List dataList = await SqfliteApi.getMeetingList(); numberFiles.value = dataList.length; statistics = {}; @@ -97,5 +111,6 @@ class MeetingMineController extends GetxController { maxNumberFiles.value = maxN; maxHourage.value = double.parse((maxH / 60 / 60).toStringAsFixed(2)); averageNumberFiles = (maxN + minN) ~/ 2; + isStatistics = false; } }