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 createState() => _SpeakerAllBottomSheetState(); } class _SpeakerAllBottomSheetState extends State { final _controller = Get.find(); 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.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(); } }