|
|
@ -1,8 +1,15 @@ |
|
|
import 'dart:io'; |
|
|
import 'dart:io'; |
|
|
|
|
|
import 'dart:typed_data'; |
|
|
|
|
|
import 'dart:ui' as ui; |
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
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:flutter_screenutil/flutter_screenutil.dart'; |
|
|
import 'package:get/get.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 '../../../profile/controllers/profile_controller.dart'; |
|
|
import '../../controllers/meeting_mine_controller.dart'; |
|
|
import '../../controllers/meeting_mine_controller.dart'; |
|
|
@ -18,6 +25,8 @@ class _StatisticsBottomSheetState extends State<StatisticsBottomSheet> { |
|
|
final _mineController = Get.find<MeetingMineController>(); |
|
|
final _mineController = Get.find<MeetingMineController>(); |
|
|
final _profileCtrl = Get.find<ProfileController>(); |
|
|
final _profileCtrl = Get.find<ProfileController>(); |
|
|
|
|
|
|
|
|
|
|
|
final GlobalKey _posterKey = GlobalKey(); |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
Widget build(BuildContext context) { |
|
|
Widget build(BuildContext context) { |
|
|
return Stack( |
|
|
return Stack( |
|
|
@ -30,225 +39,263 @@ class _StatisticsBottomSheetState extends State<StatisticsBottomSheet> { |
|
|
height: 1.sh, |
|
|
height: 1.sh, |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
Column( |
|
|
Padding( |
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w), |
|
|
children: [ |
|
|
child: Column( |
|
|
Container( |
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 20.w), |
|
|
children: [ |
|
|
margin: EdgeInsets.symmetric(horizontal: 10.w), |
|
|
RepaintBoundary( |
|
|
decoration: BoxDecoration( |
|
|
key: _posterKey, |
|
|
color: Colors.white, |
|
|
child: Container( |
|
|
borderRadius: BorderRadius.circular(16.w), |
|
|
padding: |
|
|
image: const DecorationImage( |
|
|
EdgeInsets.symmetric(horizontal: 15.w, vertical: 20.w), |
|
|
alignment: Alignment.topCenter, |
|
|
decoration: BoxDecoration( |
|
|
image: AssetImage('assets/images/mine-background.png'), |
|
|
color: Colors.white, |
|
|
fit: BoxFit.fitWidth, |
|
|
borderRadius: BorderRadius.circular(16.w), |
|
|
), |
|
|
image: const DecorationImage( |
|
|
), |
|
|
alignment: Alignment.topCenter, |
|
|
child: Column( |
|
|
image: AssetImage('assets/images/mine-background.png'), |
|
|
children: [ |
|
|
fit: BoxFit.fitWidth, |
|
|
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<Object>?, |
|
|
|
|
|
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( |
|
|
child: Column( |
|
|
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( |
|
|
|
|
|
children: [ |
|
|
children: [ |
|
|
Text( |
|
|
Container( |
|
|
'过去12个月的热力图', |
|
|
width: 50.w, |
|
|
style: TextStyle( |
|
|
height: 50.w, |
|
|
color: Colors.grey, |
|
|
margin: EdgeInsets.only(bottom: 10.w), |
|
|
fontSize: 12.sp, |
|
|
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<Object>?, |
|
|
|
|
|
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( |
|
|
Text( |
|
|
'较冷', |
|
|
_profileCtrl.userInfo.value.name, |
|
|
style: TextStyle( |
|
|
style: TextStyle( |
|
|
color: Colors.grey, |
|
|
color: Colors.black87, |
|
|
fontSize: 12.sp, |
|
|
fontSize: 20.sp, |
|
|
|
|
|
fontWeight: FontWeight.bold, |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
5.horizontalSpace, |
|
|
Padding( |
|
|
Container( |
|
|
padding: EdgeInsets.symmetric(vertical: 20.w), |
|
|
width: 10.w, |
|
|
child: Wrap( |
|
|
height: 10.w, |
|
|
spacing: 5.w, |
|
|
margin: EdgeInsets.symmetric(horizontal: 1.w), |
|
|
runSpacing: 5.w, |
|
|
decoration: BoxDecoration( |
|
|
children: [ |
|
|
color: Colors.grey[200], |
|
|
_item( |
|
|
borderRadius: BorderRadius.circular(2.w), |
|
|
'天', |
|
|
|
|
|
'${_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( |
|
|
Row( |
|
|
width: 10.w, |
|
|
children: [ |
|
|
height: 10.w, |
|
|
Text( |
|
|
margin: EdgeInsets.symmetric(horizontal: 1.w), |
|
|
'过去12个月的热力图', |
|
|
decoration: BoxDecoration( |
|
|
style: TextStyle( |
|
|
color: Colors.red[100], |
|
|
color: Colors.grey, |
|
|
borderRadius: BorderRadius.circular(2.w), |
|
|
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( |
|
|
Container( |
|
|
width: 10.w, |
|
|
height: 50.w, |
|
|
height: 10.w, |
|
|
alignment: Alignment.center, |
|
|
margin: EdgeInsets.symmetric(horizontal: 1.w), |
|
|
child: GridView.builder( |
|
|
decoration: BoxDecoration( |
|
|
shrinkWrap: true, |
|
|
color: Colors.red, |
|
|
physics: const NeverScrollableScrollPhysics(), |
|
|
borderRadius: BorderRadius.circular(2.w), |
|
|
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, |
|
|
DefaultTextStyle( |
|
|
Text( |
|
|
|
|
|
'较热', |
|
|
|
|
|
style: TextStyle( |
|
|
style: TextStyle( |
|
|
color: Colors.grey, |
|
|
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), |
|
|
SizedBox(height: 10.w), |
|
|
Row( |
|
|
Padding( |
|
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w), |
|
|
|
|
|
child: Row( |
|
|
|
|
|
children: [ |
|
|
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, |
|
|
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<StatisticsBottomSheet> { |
|
|
), |
|
|
), |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<Uint8List> _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; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|