6 changed files with 581 additions and 33 deletions
@ -0,0 +1,497 @@ |
|||
import 'package:common_utils/common_utils.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_easyloading/flutter_easyloading.dart'; |
|||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:get_storage/get_storage.dart'; |
|||
|
|||
import '../../../data/services/db/sqflite_api.dart'; |
|||
import '../controllers/meeting_details_controller.dart'; |
|||
|
|||
class SpeakerAllBottomSheet extends StatefulWidget { |
|||
const SpeakerAllBottomSheet({super.key}); |
|||
|
|||
@override |
|||
State<SpeakerAllBottomSheet> createState() => _SpeakerAllBottomSheetState(); |
|||
} |
|||
|
|||
class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> { |
|||
final _controller = Get.find<MeetingDetailsController>(); |
|||
final GetStorage _storage = GetStorage(); |
|||
|
|||
List _dataList = []; |
|||
|
|||
List _historySpeakerList = []; |
|||
List _speakerList = []; |
|||
|
|||
@override |
|||
void initState() { |
|||
super.initState(); |
|||
_getSpeakerList(); |
|||
} |
|||
|
|||
void _getSpeakerList() { |
|||
_historySpeakerList = _storage.read("speaker_list") ?? []; |
|||
_speakerList = List.from(_historySpeakerList); |
|||
List textList = _controller.textList; |
|||
List dataList = []; |
|||
for (var item in textList) { |
|||
String speaker = item['speaker']; |
|||
Map? foundElement = dataList.firstWhere( |
|||
(element) => element['speaker'] == speaker, |
|||
orElse: () => null, |
|||
); |
|||
if (foundElement != null) { |
|||
int time = item['endtime'] - item['starttime']; |
|||
foundElement['totaltime'] = foundElement['totaltime'] + time; |
|||
if (!foundElement['isOmit']) { |
|||
if (foundElement['list'].length < 2) { |
|||
foundElement['list'].add(Map.from(item)); |
|||
} else { |
|||
foundElement['isOmit'] = true; |
|||
} |
|||
} |
|||
} else { |
|||
dataList.add({ |
|||
'speaker': speaker, |
|||
'speakerText': TextEditingController(), |
|||
'isShow': false, |
|||
'totaltime': item['endtime'] - item['starttime'], |
|||
'isOmit': false, |
|||
'list': [Map.from(item)], |
|||
}); |
|||
} |
|||
if (speaker.isNotEmpty && |
|||
!RegExp(r'^Speaker \d+$').hasMatch(speaker) && |
|||
!_speakerList.contains(speaker)) { |
|||
_speakerList.add(speaker); |
|||
} |
|||
} |
|||
setState(() { |
|||
_dataList = dataList; |
|||
}); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Padding( |
|||
padding: EdgeInsets.only( |
|||
left: 12.w, |
|||
right: 12.w, |
|||
bottom: MediaQuery.of(context).viewInsets.bottom, |
|||
), |
|||
child: Column( |
|||
mainAxisSize: MainAxisSize.min, |
|||
children: [ |
|||
Container( |
|||
padding: EdgeInsets.symmetric(vertical: 10.w), |
|||
child: Row( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
children: [ |
|||
Text( |
|||
'命名发言者', |
|||
style: TextStyle( |
|||
fontSize: 16.sp, |
|||
color: Colors.black87, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
GestureDetector( |
|||
onTap: () => Get.back(), |
|||
child: Container( |
|||
width: 26.w, |
|||
height: 26.w, |
|||
decoration: BoxDecoration( |
|||
color: Colors.white, |
|||
borderRadius: BorderRadius.circular(26.r), |
|||
), |
|||
child: Icon( |
|||
Icons.close, |
|||
size: 18.w, |
|||
color: Colors.grey[400], |
|||
), |
|||
), |
|||
) |
|||
], |
|||
), |
|||
), |
|||
Divider( |
|||
height: 1, |
|||
color: Colors.grey[300], |
|||
), |
|||
Container( |
|||
constraints: BoxConstraints( |
|||
maxHeight: |
|||
1.sh - 240.w - MediaQuery.of(context).viewInsets.bottom, |
|||
), |
|||
child: ListView.builder( |
|||
itemCount: _dataList.length, |
|||
shrinkWrap: true, |
|||
itemBuilder: (BuildContext context, int index) { |
|||
Map item = _dataList[index]; |
|||
return Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Container( |
|||
margin: EdgeInsets.only(top: 15.w), |
|||
alignment: Alignment.center, |
|||
decoration: BoxDecoration( |
|||
color: Colors.white, |
|||
borderRadius: item['isShow'] && _speakerList.isNotEmpty |
|||
? BorderRadius.vertical( |
|||
top: Radius.circular(12.r), |
|||
) |
|||
: BorderRadius.circular(12.r), |
|||
), |
|||
child: Focus( |
|||
onFocusChange: (bool hasFocus) { |
|||
item['isShow'] = hasFocus; |
|||
}, |
|||
child: TextField( |
|||
controller: item['speakerText'], |
|||
style: TextStyle( |
|||
fontSize: 16.sp, |
|||
color: Colors.black87, |
|||
letterSpacing: 0, |
|||
height: 1.4, |
|||
), |
|||
decoration: InputDecoration( |
|||
hintText: item['speaker'], |
|||
hintStyle: TextStyle( |
|||
fontSize: 16.sp, |
|||
color: Colors.grey[400], |
|||
letterSpacing: 0, |
|||
height: 1.4, |
|||
), |
|||
isDense: true, |
|||
fillColor: Colors.transparent, |
|||
border: InputBorder.none, |
|||
contentPadding: EdgeInsets.symmetric( |
|||
horizontal: 10.w, |
|||
vertical: 12.w, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
if (item['isShow'] && _speakerList.isNotEmpty) |
|||
Container( |
|||
padding: EdgeInsets.only(left: 10.w), |
|||
clipBehavior: Clip.hardEdge, |
|||
decoration: BoxDecoration( |
|||
color: Colors.white, |
|||
borderRadius: BorderRadius.vertical( |
|||
bottom: Radius.circular(12.r), |
|||
), |
|||
border: Border( |
|||
top: BorderSide( |
|||
width: 1, |
|||
color: Colors.grey[300]!, |
|||
), |
|||
), |
|||
), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Padding( |
|||
padding: EdgeInsets.only(top: 10.w, bottom: 5.w), |
|||
child: Text( |
|||
'最近使用过的名称', |
|||
style: TextStyle( |
|||
fontSize: 12.sp, |
|||
color: Colors.grey[600]!, |
|||
), |
|||
), |
|||
), |
|||
Container( |
|||
constraints: BoxConstraints( |
|||
maxHeight: 150.h, |
|||
), |
|||
child: ListView.builder( |
|||
itemCount: _speakerList.length, |
|||
shrinkWrap: true, |
|||
itemBuilder: (BuildContext context, int idx) { |
|||
return GestureDetector( |
|||
onTap: () { |
|||
item['speakerText'].text = |
|||
_speakerList[idx]; |
|||
FocusScope.of(context).unfocus(); |
|||
}, |
|||
behavior: HitTestBehavior.translucent, |
|||
child: SizedBox( |
|||
height: 42.w, |
|||
child: Align( |
|||
alignment: Alignment.centerLeft, |
|||
child: Text( |
|||
_speakerList[idx], |
|||
style: TextStyle( |
|||
fontSize: 14.sp, |
|||
color: Colors.black87, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
); |
|||
}, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
Padding( |
|||
padding: EdgeInsets.symmetric( |
|||
horizontal: 10.w, |
|||
vertical: 8.w, |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
Text( |
|||
'总计', |
|||
style: TextStyle( |
|||
fontSize: 12.sp, |
|||
color: Colors.grey, |
|||
), |
|||
), |
|||
5.horizontalSpace, |
|||
Text( |
|||
'${item['totaltime'] / 1000}s', |
|||
style: TextStyle( |
|||
fontSize: 12.sp, |
|||
color: Colors.black87, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
...List.generate( |
|||
item['list'].length, |
|||
(int idx) { |
|||
Map data = item['list'][idx]; |
|||
return Padding( |
|||
padding: EdgeInsets.symmetric(horizontal: 10.w), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Row( |
|||
children: [ |
|||
Icon( |
|||
Icons.play_circle, |
|||
color: Colors.red[400], |
|||
size: 15.w, |
|||
), |
|||
2.horizontalSpace, |
|||
Text( |
|||
DateUtil.formatDateMs( |
|||
data['starttime'], |
|||
format: 'HH:mm:ss', |
|||
isUtc: true, |
|||
), |
|||
style: TextStyle( |
|||
color: Colors.grey, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
Padding( |
|||
padding: |
|||
EdgeInsets.symmetric(horizontal: 2.w), |
|||
child: Text( |
|||
'-', |
|||
style: TextStyle( |
|||
color: Colors.grey, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
), |
|||
Text( |
|||
DateUtil.formatDateMs( |
|||
data['endtime'], |
|||
format: 'HH:mm:ss', |
|||
isUtc: true, |
|||
), |
|||
style: TextStyle( |
|||
color: Colors.grey, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
5.horizontalSpace, |
|||
Text( |
|||
'${(data['endtime'] - data['starttime']) / 1000}s', |
|||
style: TextStyle( |
|||
color: Colors.black54, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
Padding( |
|||
padding: EdgeInsets.symmetric(vertical: 8.w), |
|||
child: Text( |
|||
data['content'], |
|||
overflow: TextOverflow.ellipsis, |
|||
maxLines: 2, |
|||
style: TextStyle( |
|||
color: Colors.grey[700], |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
}, |
|||
), |
|||
if (item['isOmit']) |
|||
Padding( |
|||
padding: |
|||
EdgeInsets.only(left: 10.w, top: 5.w, bottom: 8.w), |
|||
child: Row( |
|||
children: List.generate( |
|||
6, |
|||
(int idx) => Container( |
|||
width: 3.w, |
|||
height: 3.w, |
|||
margin: EdgeInsets.only(right: 3.w), |
|||
decoration: BoxDecoration( |
|||
color: Colors.grey[700], |
|||
borderRadius: BorderRadius.circular(3.r), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
Padding( |
|||
padding: EdgeInsets.only(top: 7.w), |
|||
child: _separator(), |
|||
), |
|||
], |
|||
); |
|||
}, |
|||
), |
|||
), |
|||
Padding( |
|||
padding: EdgeInsets.symmetric(vertical: 5.w), |
|||
child: Text( |
|||
'总结的发言者将自动更新', |
|||
style: TextStyle( |
|||
fontSize: 14.sp, |
|||
color: Colors.grey[600], |
|||
), |
|||
), |
|||
), |
|||
Padding( |
|||
padding: EdgeInsets.only(bottom: 20.w), |
|||
child: Row( |
|||
children: [ |
|||
Expanded( |
|||
child: GestureDetector( |
|||
onTap: () => Get.back(), |
|||
child: Container( |
|||
height: 48.w, |
|||
alignment: Alignment.center, |
|||
decoration: BoxDecoration( |
|||
color: Colors.grey[300], |
|||
borderRadius: BorderRadius.circular(12.r), |
|||
), |
|||
child: Text( |
|||
'取消', |
|||
style: TextStyle( |
|||
fontSize: 16.sp, |
|||
color: Colors.black, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
10.horizontalSpace, |
|||
Expanded( |
|||
child: GestureDetector( |
|||
onTap: _save, |
|||
child: Container( |
|||
height: 48.w, |
|||
alignment: Alignment.center, |
|||
decoration: BoxDecoration( |
|||
color: Colors.black, |
|||
borderRadius: BorderRadius.circular(12.r), |
|||
), |
|||
child: Text( |
|||
'确认', |
|||
style: TextStyle( |
|||
fontSize: 16.sp, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
Widget _separator() { |
|||
return LayoutBuilder( |
|||
builder: (BuildContext context, BoxConstraints constraints) { |
|||
final boxWidth = constraints.constrainWidth(); |
|||
const dashWidth = 2.0; |
|||
const dashHeight = 1.0; |
|||
final dashCount = (boxWidth / (2 * dashWidth)).floor(); |
|||
return Flex( |
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|||
direction: Axis.horizontal, |
|||
children: List.generate(dashCount, (_) { |
|||
return SizedBox( |
|||
width: dashWidth, |
|||
height: dashHeight, |
|||
child: DecoratedBox( |
|||
decoration: BoxDecoration(color: Colors.grey[300]), |
|||
), |
|||
); |
|||
}), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
|
|||
void _save() async { |
|||
EasyLoading.show(); |
|||
List textList = _controller.textList; |
|||
List newTextList = []; |
|||
List editSpeakerList = []; |
|||
String personnel = ''; |
|||
for (var element in textList) { |
|||
final dataMap = Map<String, dynamic>.from(element); |
|||
for (var data in _dataList) { |
|||
String speaker = data['speakerText'].text; |
|||
if (speaker.isNotEmpty && speaker != data['speaker']) { |
|||
if (dataMap['speaker'] == data['speaker']) { |
|||
dataMap['speaker'] = speaker; |
|||
editSpeakerList.add({ |
|||
'id': dataMap['id'], |
|||
'speaker': dataMap['speaker'], |
|||
}); |
|||
} |
|||
if (!RegExp(r'^Speaker \d+$').hasMatch(speaker) && |
|||
!_historySpeakerList.contains(speaker)) { |
|||
_historySpeakerList.insert(0, speaker); |
|||
} |
|||
} |
|||
} |
|||
if (!personnel.contains(dataMap['speaker'])) { |
|||
personnel += '[${dataMap['speaker']}] '; |
|||
} |
|||
newTextList.add(dataMap); |
|||
} |
|||
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList); |
|||
if (result != null) { |
|||
_controller.textList.value = newTextList; |
|||
SqfliteApi.editMeeting(_controller.id, {'personnel': personnel}); |
|||
_controller.meetingDetails.value.personnel = personnel; |
|||
_storage.write("speaker_list", _historySpeakerList); |
|||
Get.back(); |
|||
} |
|||
EasyLoading.dismiss(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue