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