Browse Source

Merge branch 'ble' of https://github.com/deepcloud2048/deep_voice into lxm

newdev_shunjiawei
lxm 1 year ago
parent
commit
67699b5f51
  1. 14
      lib/modules/meeting/controllers/meeting_details_controller.dart
  2. 123
      lib/modules/meeting/views/meeting_details_view.dart
  3. 47
      lib/modules/meeting/views/speaker_all_bottom_sheet.dart
  4. 14
      lib/modules/meeting/views/speaker_bottom_sheet.dart
  5. 66
      lib/modules/meeting/views/speech_tab.dart
  6. 212
      lib/modules/meeting/views/summary_tab.dart

14
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -25,7 +25,9 @@ class MeetingDetailsController extends GetxController
final PlayerController playerController = PlayerController();
RxBool isTop = false.obs;
RxInt durationMax = 0.obs;
RxDouble dragOffset = 210.0.w.obs;
RxDouble dragOffset = 210.w.obs;
RxInt currentDuration = 0.obs;
int id = 0;
@ -55,6 +57,9 @@ class MeetingDetailsController extends GetxController
super.onInit();
tabController = TabController(length: 4, vsync: this);
tabController.addListener(() {
if (currentIndex.value == 2 && tabController.index != 2) {
FocusScope.of(Get.context!).unfocus();
}
currentIndex.value = tabController.index;
});
final Map<String, dynamic> args = Get.arguments ?? {};
@ -96,6 +101,9 @@ class MeetingDetailsController extends GetxController
);
durationMax.value = await playerController.getDuration(DurationType.max);
playerController.setFinishMode(finishMode: FinishMode.pause);
playerController.onCurrentDurationChanged.listen((int position) {
currentDuration.value = position;
});
if (meetingDetails.value.tasktype == 1) {
List speakerList = await SqfliteApi.getMeetingSpeaker(id);
textList.value = List.from(speakerList);
@ -158,7 +166,7 @@ class MeetingDetailsController extends GetxController
String text = '';
for (var element in textList) {
if (element['speaker'].isNotEmpty) {
text += '${element['speaker']}:';
text += '[${element['speaker']}]:';
}
text += '${element['content']}\n';
}
@ -311,7 +319,7 @@ class MeetingDetailsController extends GetxController
String speaker = '';
if (element['additions'].isNotEmpty) {
speaker = 'Speaker ${element['additions']['speaker']}';
text += '$speaker:';
text += '[$speaker]:';
if (!personnel.contains(speaker)) {
personnel += '[$speaker] ';
}

123
lib/modules/meeting/views/meeting_details_view.dart

@ -92,14 +92,22 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
},
),
4.horizontalSpace,
AudioFileWaveforms(
size: Size(130.w, 30.w),
playerController: controller.playerController,
playerWaveStyle: PlayerWaveStyle(
fixedWaveColor: Colors.grey[400]!,
liveWaveColor: Colors.red,
seekLineColor: Colors.red,
scaleFactor: 100,
GestureDetector(
onTap: () {
if (controller.isSpeakerText.value) return;
controller.dragOffset.value = 210.w;
controller.isTop.value = false;
},
child: AudioFileWaveforms(
size: Size(130.w, 30.w),
playerController: controller.playerController,
enableSeekGesture: false,
playerWaveStyle: PlayerWaveStyle(
fixedWaveColor: Colors.grey[400]!,
liveWaveColor: Colors.red,
seekLineColor: Colors.red,
scaleFactor: 100,
),
),
),
],
@ -203,19 +211,23 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
child: Column(
children: [
_soundWave(isDarkMode),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w),
margin: EdgeInsets.symmetric(vertical: 10.w),
child: Obx(
() => Row(
children: [
_iconItem(Icons.summarize, '概述', 0),
_iconItem(Icons.message, '转写', 1),
_iconItem(Icons.article, '总结', 2),
_iconItem(Icons.lan, '思维导图', 3),
],
),
),
Obx(
() => !controller.isSpeakerText.value
? Container(
padding: EdgeInsets.symmetric(horizontal: 8.w),
margin: EdgeInsets.symmetric(vertical: 10.w),
child: Obx(
() => Row(
children: [
_iconItem(Icons.summarize, '概述', 0),
_iconItem(Icons.message, '转写', 1),
_iconItem(Icons.article, '总结', 2),
_iconItem(Icons.lan, '思维导图', 3),
],
),
),
)
: const SizedBox(),
),
],
),
@ -289,25 +301,20 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
StreamBuilder<int>(
stream: controller
.playerController.onCurrentDurationChanged,
builder: (context, snapshot) {
final position = snapshot.data ?? 0;
return Text(
DateUtil.formatDateMs(
position,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: isDarkMode
? Colors.grey[400]
: Colors.grey[500],
fontSize: 12.sp,
),
);
},
Obx(
() => Text(
DateUtil.formatDateMs(
controller.currentDuration.value,
format: 'HH:mm:ss',
isUtc: true,
),
style: TextStyle(
color: isDarkMode
? Colors.grey[400]
: Colors.grey[500],
fontSize: 12.sp,
),
),
),
Obx(
() => Text(
@ -330,10 +337,20 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(
Icons.replay_10,
color: Colors.black,
size: 30.w,
GestureDetector(
onTap: () {
int progress =
controller.currentDuration.value - 10000;
if (progress < 0) {
progress = 0;
}
controller.playerController.seekTo(progress);
},
child: Icon(
Icons.replay_10,
color: Colors.black,
size: 30.w,
),
),
StreamBuilder<PlayerState>(
stream:
@ -356,10 +373,17 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
);
},
),
Icon(
Icons.forward_10,
color: Colors.black,
size: 30.w,
GestureDetector(
onTap: () {
int progress =
controller.currentDuration.value + 10000;
controller.playerController.seekTo(progress);
},
child: Icon(
Icons.forward_10,
color: Colors.black,
size: 30.w,
),
),
],
),
@ -382,9 +406,6 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
onTap: () {
if (isActive) return;
controller.tabController.index = index;
controller.isSpeakerText.value = false;
controller.editSpeakerTextId.value = 0;
controller.editSpeakerTextList = [];
},
child: Container(
height: 45.w,

47
lib/modules/meeting/views/speaker_all_bottom_sheet.dart

@ -145,7 +145,9 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
),
child: Focus(
onFocusChange: (bool hasFocus) {
item['isShow'] = hasFocus;
setState(() {
item['isShow'] = hasFocus;
});
},
child: TextField(
controller: item['speakerText'],
@ -460,12 +462,20 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
List textList = _controller.textList;
List newTextList = [];
List editSpeakerList = [];
List personnelList = [];
String personnel = '';
bool isLoopEnd = false;
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 (!isLoopEnd) {
personnelList.add({
'original': data['speaker'],
'speaker': speaker,
});
}
if (dataMap['speaker'] == data['speaker']) {
dataMap['speaker'] = speaker;
editSpeakerList.add({
@ -483,13 +493,36 @@ class _SpeakerAllBottomSheetState extends State<SpeakerAllBottomSheet> {
personnel += '[${dataMap['speaker']}] ';
}
newTextList.add(dataMap);
isLoopEnd = true;
}
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);
if (editSpeakerList.isNotEmpty) {
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList);
if (result != null) {
bool isEditSummary = false;
String summaryText = _controller.meetingDetails.value.summary;
_controller.textList.value = newTextList;
for (var element in personnelList) {
if (summaryText.contains('[${element['original']}]')) {
summaryText = summaryText.replaceAll(
'[${element['original']}]', '[${element['speaker']}]');
isEditSummary = true;
}
}
if (isEditSummary) {
SqfliteApi.editMeeting(_controller.id, {
'summary': summaryText,
'personnel': personnel,
});
_controller.meetingDetails.value.summary = summaryText;
} else {
SqfliteApi.editMeeting(_controller.id, {'personnel': personnel});
}
_controller.meetingDetails.value.personnel = personnel;
_controller.meetingDetails.refresh();
_storage.write("speaker_list", _historySpeakerList);
Get.back();
}
} else {
Get.back();
}
EasyLoading.dismiss();

14
lib/modules/meeting/views/speaker_bottom_sheet.dart

@ -365,7 +365,19 @@ class _SpeakerBottomSheetState extends State<SpeakerBottomSheet> {
var result = await SqfliteApi.editMeetingSpeaker(editSpeakerList);
if (result != null) {
_controller.textList.value = newTextList;
SqfliteApi.editMeeting(_controller.id, {'personnel': personnel});
if (_controller.meetingDetails.value.summary
.contains('[${widget.speakerName}]')) {
String summaryText = _controller.meetingDetails.value.summary
.replaceAll(
'[${widget.speakerName}]', '[${_speakerController.text}]');
SqfliteApi.editMeeting(_controller.id, {
'summary': summaryText,
'personnel': personnel,
});
_controller.meetingDetails.value.summary = summaryText;
} else {
SqfliteApi.editMeeting(_controller.id, {'personnel': personnel});
}
_controller.meetingDetails.value.personnel = personnel;
_addHistorySpeaker();
Get.back();

66
lib/modules/meeting/views/speech_tab.dart

@ -21,7 +21,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
itemBuilder: (BuildContext context, int index) {
Map item = controller.textList[index];
TextStyle textStyle = TextStyle(
fontSize: 16.sp,
fontSize: 15.sp,
color: Colors.black87,
letterSpacing: 0,
height: 1.4,
@ -35,11 +35,11 @@ class SpeechTab extends GetView<MeetingDetailsController> {
children: [
if (item['speaker'].isNotEmpty)
Container(
width: 12.w,
height: 12.w,
margin: EdgeInsets.only(right: 2.w),
width: 11.w,
height: 11.w,
margin: EdgeInsets.only(right: 3.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28.r),
borderRadius: BorderRadius.circular(11.r),
border: Border.all(
color: const Color(0xFFFFC107),
width: 3.w,
@ -71,7 +71,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
item['speaker'],
style: TextStyle(
color: const Color(0xFFFFC107),
fontSize: 14.sp,
fontSize: 13.sp,
),
),
),
@ -84,7 +84,7 @@ class SpeechTab extends GetView<MeetingDetailsController> {
),
style: TextStyle(
color: Colors.grey,
fontSize: 14.sp,
fontSize: 13.sp,
),
),
],
@ -151,16 +151,46 @@ class SpeechTab extends GetView<MeetingDetailsController> {
),
);
} else {
return Container(
padding: EdgeInsets.symmetric(
horizontal: 14.w,
vertical: 5.w,
),
child: Text(
item['content'],
style: textStyle,
),
);
return Obx(() {
bool isActive = false;
int starttime = item['starttime'];
int endtime = 0;
if (index == controller.textList.length - 1) {
endtime = controller.durationMax.value;
} else {
endtime = controller.textList[index + 1]['starttime'];
}
if (controller.currentDuration.value != 0) {
if (controller.currentDuration.value >= starttime &&
controller.currentDuration.value < endtime) {
isActive = true;
}
}
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
controller.playerController
.seekTo(item['starttime']);
controller.playerController.startPlayer();
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 14.w,
vertical: 5.w,
),
child: Text(
item['content'],
style: TextStyle(
fontSize: 15.sp,
color: isActive ? Colors.red : Colors.black87,
letterSpacing: 0,
height: 1.4,
),
),
),
);
});
}
}),
],
@ -183,6 +213,8 @@ class SpeechTab extends GetView<MeetingDetailsController> {
'编辑',
() {
controller.isSpeakerText.value = true;
controller.dragOffset.value = 0;
controller.isTop.value = true;
},
),
if (controller.meetingDetails.value.tasktype == 1 &&

212
lib/modules/meeting/views/summary_tab.dart

@ -4,15 +4,117 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../controllers/meeting_details_controller.dart';
import 'speaker_all_bottom_sheet.dart';
class SummaryTab extends GetView<MeetingDetailsController> {
class SummaryTab extends StatefulWidget {
const SummaryTab({super.key});
@override
State<SummaryTab> createState() => _SummaryTabState();
}
class _SummaryTabState extends State<SummaryTab> {
final _controller = Get.find<MeetingDetailsController>();
final String _addressPrefix = '地点:';
String _addressText = '';
final String _personnelPrefix = '与会人员:';
String _personnelText = '';
final TextEditingController _addressController = TextEditingController();
final TextEditingController _personnelController = TextEditingController();
@override
void initState() {
super.initState();
_addressController.text = _addressPrefix +
(_controller.meetingDetails.value.address.isNotEmpty
? _controller.meetingDetails.value.address
: '[输入地点]');
_personnelController.text = _personnelPrefix +
(_controller.meetingDetails.value.personnel.isNotEmpty
? _controller.meetingDetails.value.personnel
: '[输入与会人员]');
_addressController.addListener(_handleAddressChanged);
_personnelController.addListener(_handlePersonnelChanged);
}
void _handleAddressChanged() {
String currentText = _addressController.text;
TextSelection currentSelection = _addressController.selection;
if (!currentText.startsWith(_addressPrefix)) {
_addressController.value = TextEditingValue(
text: _addressPrefix + _addressText,
selection: TextSelection.collapsed(
offset: _addressPrefix.length + _addressText.length,
),
);
return;
} else {
_addressText = currentText.substring(_addressPrefix.length);
}
if (currentSelection.start < _addressPrefix.length ||
currentSelection.end < _addressPrefix.length) {
int newStart = currentSelection.start < _addressPrefix.length
? _addressPrefix.length
: currentSelection.start;
int newEnd = currentSelection.end < _addressPrefix.length
? _addressPrefix.length
: currentSelection.end;
_addressController.selection = currentSelection.copyWith(
baseOffset: newStart,
extentOffset: newEnd,
);
}
}
void _handlePersonnelChanged() {
String currentText = _personnelController.text;
TextSelection currentSelection = _personnelController.selection;
if (!currentText.startsWith(_personnelPrefix)) {
_personnelController.value = TextEditingValue(
text: _personnelPrefix + _personnelText,
selection: TextSelection.collapsed(
offset: _personnelPrefix.length + _personnelText.length,
),
);
return;
} else {
_personnelText = currentText.substring(_personnelPrefix.length);
}
if (currentSelection.start < _personnelPrefix.length ||
currentSelection.end < _personnelPrefix.length) {
int newStart = currentSelection.start < _personnelPrefix.length
? _personnelPrefix.length
: currentSelection.start;
int newEnd = currentSelection.end < _personnelPrefix.length
? _personnelPrefix.length
: currentSelection.end;
_personnelController.selection = currentSelection.copyWith(
baseOffset: newStart,
extentOffset: newEnd,
);
}
}
@override
void dispose() {
_addressController.dispose();
_personnelController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final TextStyle textStyle = TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontSize: 16.sp,
letterSpacing: 0,
height: 1.5,
);
return Stack(
children: [
@ -25,7 +127,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
Padding(
padding: EdgeInsets.symmetric(vertical: 10.w),
child: Text(
controller.meetingData.value.title,
_controller.meetingData.value.title,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
@ -43,7 +145,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
borderRadius: BorderRadius.circular(4.w),
),
child: Text(
controller.meetingData.value.type,
_controller.meetingData.value.type,
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[500],
@ -59,7 +161,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
2.horizontalSpace,
Text(
DateUtil.formatDateMs(
controller.meetingData.value.creationtime,
_controller.meetingData.value.creationtime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
@ -75,7 +177,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
),
2.horizontalSpace,
Text(
'${controller.meetingData.value.seconds}s',
'${_controller.meetingData.value.seconds}s',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
@ -103,43 +205,79 @@ class SummaryTab extends GetView<MeetingDetailsController> {
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'日期和时间:${DateUtil.formatDateMs(
controller.meetingData.value.creationtime,
format: DateFormats.y_mo_d_h_m,
)}',
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontSize: 16.sp,
child: Focus(
onFocusChange: (bool hasFocus) {
if (!hasFocus) {
bool isEdit = false;
if (_addressText.isNotEmpty &&
_addressText !=
_controller.meetingDetails.value.address) {
_controller.meetingDetails.value.address =
_addressText;
isEdit = true;
}
if (_personnelText.isNotEmpty &&
_personnelText !=
_controller.meetingDetails.value.personnel) {
_controller.meetingDetails.value.personnel =
_personnelText;
isEdit = true;
}
if (isEdit) {
SqfliteApi.editMeeting(
_controller.id,
{
'address':
_controller.meetingDetails.value.address,
'personnel':
_controller.meetingDetails.value.personnel,
},
);
}
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'日期和时间:${DateUtil.formatDateMs(
_controller.meetingData.value.creationtime,
format: DateFormats.y_mo_d_h_m,
)}',
style: textStyle,
),
),
Text(
'地点:${controller.meetingDetails.value.address.isNotEmpty ? controller.meetingDetails.value.address : '[输入地点]'}',
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontSize: 16.sp,
TextField(
controller: _addressController,
style: textStyle,
minLines: 1,
maxLines: 100,
decoration: const InputDecoration(
isDense: true,
fillColor: Colors.transparent,
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
),
),
Text(
'与会人员:${controller.meetingDetails.value.personnel.isNotEmpty ? controller.meetingDetails.value.personnel : '[输入与会人员]'}',
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontSize: 16.sp,
TextField(
controller: _personnelController,
style: textStyle,
minLines: 1,
maxLines: 100,
decoration: const InputDecoration(
isDense: true,
fillColor: Colors.transparent,
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
),
),
],
],
),
),
),
Padding(
padding: EdgeInsets.only(top: 10.w),
child: MarkdownBody(
data: controller.meetingDetails.value.summary,
data: _controller.meetingDetails.value.summary,
),
),
],
@ -154,7 +292,7 @@ class SummaryTab extends GetView<MeetingDetailsController> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (controller.meetingDetails.value.summary.isNotEmpty)
if (_controller.meetingDetails.value.summary.isNotEmpty)
_button(
Icons.supervisor_account,
'编辑发言者',
@ -174,13 +312,13 @@ class SummaryTab extends GetView<MeetingDetailsController> {
);
},
),
if (controller.meetingDetails.value.summary.isEmpty)
if (_controller.meetingDetails.value.summary.isEmpty)
SizedBox(
width: 150.w,
child: _button(
Icons.auto_awesome,
'生成',
controller.summaryCreate,
_controller.summaryCreate,
),
),
],

Loading…
Cancel
Save