Browse Source

修改会议记录

weicu
tanlongsheng 2 years ago
parent
commit
854118c83d
  1. 2
      lib/data/services/db/database_helper.dart
  2. 3
      lib/modules/meeting/controllers/meeting_details_controller.dart
  3. 4
      lib/modules/meeting/controllers/meeting_edit_controller.dart
  4. 38
      lib/modules/meeting/controllers/meeting_record_controller.dart
  5. 4
      lib/modules/meeting/model/meeting_details_model.dart
  6. 246
      lib/modules/meeting/views/meeting_record_view.dart
  7. 6
      lib/modules/meeting/views/meeting_view.dart

2
lib/data/services/db/database_helper.dart

@ -40,7 +40,7 @@ class DatabaseHelper {
CREATE TABLE IF NOT EXISTS meetingdetails ( CREATE TABLE IF NOT EXISTS meetingdetails (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
meetingid INTEGER, meetingid INTEGER,
audio BLOB, audio TEXT,
content TEXT content TEXT
) )
'''); ''');

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

@ -3,7 +3,6 @@ import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/volcano_tts_api_service.dart';
import '../model/meeting_details_model.dart'; import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart'; import '../model/meeting_model.dart';
@ -42,7 +41,7 @@ class MeetingDetailsController extends GetxController
final data = await SqfliteApi.getMeetingDetails(id); final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data); meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']); dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
audioPlayer.setAudioSource(BytesAudioSource(dataDetails.value.audio)); audioPlayer.setAudioSource(AudioSource.file(dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async { audioPlayer.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) { if (state.processingState == ProcessingState.completed) {
await audioPlayer.seek(Duration.zero); await audioPlayer.seek(Duration.zero);

4
lib/modules/meeting/controllers/meeting_edit_controller.dart

@ -3,7 +3,6 @@ import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/volcano_tts_api_service.dart';
import 'meeting_controller.dart'; import 'meeting_controller.dart';
import 'meeting_details_controller.dart'; import 'meeting_details_controller.dart';
@ -53,7 +52,7 @@ class MeetingEditController extends GetxController
); );
creationTime = meetingDetailsController.meetingData.value.creationtime; creationTime = meetingDetailsController.meetingData.value.creationtime;
audioPlayer.setAudioSource( audioPlayer.setAudioSource(
BytesAudioSource(meetingDetailsController.dataDetails.value.audio)); AudioSource.file(meetingDetailsController.dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async { audioPlayer.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) { if (state.processingState == ProcessingState.completed) {
await audioPlayer.seek(Duration.zero); await audioPlayer.seek(Duration.zero);
@ -71,7 +70,6 @@ class MeetingEditController extends GetxController
isPlay.value = false; isPlay.value = false;
} else { } else {
lottieController.repeat(); lottieController.repeat();
await audioPlayer.seek(Duration.zero);
audioPlayer.play(); audioPlayer.play();
isPlay.value = true; isPlay.value = true;
} }

38
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -1,9 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/asr_service.dart'; import '../../../data/services/asr_service.dart';
import '../../../data/services/db/sqflite_api.dart'; import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/xunfei_asr_service.dart'; import '../../../data/services/speech_impl/xunfei_asr_service.dart';
@ -34,6 +38,7 @@ class MeetingRecordController extends GetxController
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
Permission.storage.request();
lottieController = AnimationController(vsync: this); lottieController = AnimationController(vsync: this);
Get.put(XunfeiAsrService()); Get.put(XunfeiAsrService());
_asrService = Get.find<XunfeiAsrService>(); _asrService = Get.find<XunfeiAsrService>();
@ -144,6 +149,20 @@ class MeetingRecordController extends GetxController
// 完成 // 完成
void complete() async { void complete() async {
if (await Permission.storage.request().isGranted) {
_save();
} else {
Get.snackbar(
'error'.tr, // 错误
'请开启文件存储权限',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red.withOpacity(0.1),
colorText: Colors.red,
);
}
}
void _save() async {
String firstContent = finalContent.value.length >= 30 String firstContent = finalContent.value.length >= 30
? finalContent.value.substring(0, 30) ? finalContent.value.substring(0, 30)
: finalContent.value; : finalContent.value;
@ -155,12 +174,15 @@ class MeetingRecordController extends GetxController
dataLength: audio.length, dataLength: audio.length,
); );
Uint8List wavData = Uint8List.fromList([...wavHeader, ...audio]); Uint8List wavData = Uint8List.fromList([...wavHeader, ...audio]);
String audiopPath =
await _saveAudioToFile(wavData, 'meeting_$creationTime.wav');
if (audiopPath.isEmpty) return;
Map<String, Object?> data = { Map<String, Object?> data = {
'title': title, 'title': title,
'firstContent': firstContent.replaceAll("\n", ""), 'firstContent': firstContent.replaceAll("\n", ""),
'seconds': seconds.value, 'seconds': seconds.value,
'creationTime': creationTime, 'creationTime': creationTime,
'audio': wavData, 'audio': audiopPath,
'content': finalContent.value, 'content': finalContent.value,
}; };
final result = await SqfliteApi.insertMeeting(data); final result = await SqfliteApi.insertMeeting(data);
@ -176,6 +198,20 @@ class MeetingRecordController extends GetxController
} }
} }
Future<String> _saveAudioToFile(Uint8List audioData, String fileName) async {
try {
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/$fileName');
// 写入文件
await file.writeAsBytes(audioData);
return file.path;
} catch (e) {
Logger.error('保存失败: $e');
return '';
}
}
Uint8List generateWavHeader({ Uint8List generateWavHeader({
required int sampleRate, required int sampleRate,
required int numberOfChannels, required int numberOfChannels,

4
lib/modules/meeting/model/meeting_details_model.dart

@ -1,11 +1,9 @@
import 'dart:typed_data';
class MeetingDetailsModel { class MeetingDetailsModel {
MeetingDetailsModel(); MeetingDetailsModel();
late int id; late int id;
late int meetingid; late int meetingid;
late Uint8List audio; late String audio;
late String content; late String content;
factory MeetingDetailsModel.fromJson(Map<String, dynamic> map) => factory MeetingDetailsModel.fromJson(Map<String, dynamic> map) =>

246
lib/modules/meeting/views/meeting_record_view.dart

@ -90,86 +90,36 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
// 构建主体内容 // 构建主体内容
Widget _buildBody(bool isDarkMode) { Widget _buildBody(bool isDarkMode) {
return Padding( return Column(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, 10.verticalSpace,
children: [ Padding(
Text( padding: EdgeInsets.symmetric(horizontal: 12.w),
child: Text(
controller.title, controller.title,
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 17.sp, fontSize: 17.sp,
), ),
), ),
Padding( ),
padding: EdgeInsets.symmetric(vertical: 5.w), Padding(
child: Text( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
DateUtil.formatDateMs( child: Text(
controller.creationTime, DateUtil.formatDateMs(
format: DateFormats.y_mo_d_h_m, controller.creationTime,
), format: DateFormats.y_mo_d_h_m,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
),
),
_audioWave(isDarkMode),
_recordContent(isDarkMode),
_bottomButton(isDarkMode),
],
),
);
}
// 音波
Widget _audioWave(bool isDarkMode) {
return Container(
width: double.infinity,
height: 70.w,
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
onLoaded: (composition) {
controller.lottieController.duration = composition.duration;
},
),
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
), ),
), style: TextStyle(
Expanded( color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
child: Lottie.asset( fontSize: 14.sp,
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
), ),
), ),
], ),
), _recordContent(isDarkMode),
_bottomButton(isDarkMode),
],
); );
} }
@ -179,7 +129,7 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
margin: EdgeInsets.symmetric(vertical: 5.w), margin: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w), borderRadius: BorderRadius.circular(12.w),
@ -210,51 +160,121 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
// 底部按钮 // 底部按钮
Widget _bottomButton(bool isDarkMode) { Widget _bottomButton(bool isDarkMode) {
return Padding( return Container(
padding: EdgeInsets.only(top: 40.w, bottom: 30.w), margin: EdgeInsets.only(top: 10.h),
child: Row( decoration: BoxDecoration(
mainAxisAlignment: MainAxisAlignment.spaceBetween, color: isDarkMode ? AppColors.cardBackground : Colors.white,
children: [ boxShadow: [
GestureDetector( BoxShadow(
onTap: controller.recorderHandle, color: Colors.black.withOpacity(0.05),
child: Container( blurRadius: 3,
width: 130.w, offset: const Offset(0, -1),
height: 60.w, ),
decoration: BoxDecoration( ],
borderRadius: BorderRadius.circular(60.w), ),
border: Border.all( child: SafeArea(
color: isDarkMode ? AppColors.primary : Colors.blue, child: SizedBox(
width: 2.w, height: 80.h,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 语音录制按钮
Container(
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
spreadRadius: 1,
),
],
),
child: Center(
child: Obx(
() => IconButton(
icon: Icon(
controller.isRecording.value
? Icons.mic
: Icons.mic_none,
),
color: controller.isRecording.value
? AppColors.primary
: Colors.grey.shade400,
iconSize: 28.r,
padding: EdgeInsets.zero,
onPressed: controller.recorderHandle,
),
),
),
), ),
), 10.horizontalSpace,
child: Obx( Expanded(
() => Icon( child: Lottie.asset(
controller.isRecording.value ? Icons.pause : Icons.play_arrow, 'assets/lottie/audio_wave.json',
color: isDarkMode ? AppColors.primary : Colors.blue, controller: controller.lottieController,
size: 40.w, fit: BoxFit.fill,
onLoaded: (composition) {
controller.lottieController.duration =
composition.duration;
},
),
), ),
), Expanded(
), child: Lottie.asset(
), 'assets/lottie/audio_wave.json',
GestureDetector( controller: controller.lottieController,
onTap: () { fit: BoxFit.fill,
_showAlertDialog(isDarkMode); onLoaded: (composition) {
}, controller.lottieController.duration =
child: Container( composition.duration;
width: 130.w, },
height: 60.w, ),
decoration: BoxDecoration( ),
color: isDarkMode ? AppColors.primary : Colors.blue, 10.horizontalSpace,
borderRadius: BorderRadius.circular(60.w), // 关闭按钮
), Container(
child: Icon( width: 60.r,
Icons.fiber_manual_record_outlined, height: 60.r,
color: Colors.white, decoration: BoxDecoration(
size: 40.w, color: isDarkMode
), ? Colors.white.withOpacity(0.1)
: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
spreadRadius: 1,
),
],
),
child: Center(
child: IconButton(
icon: Icon(
Icons.close,
color: isDarkMode ? Colors.white : Colors.grey.shade600,
size: 28.r,
),
padding: EdgeInsets.zero,
onPressed: () {
_showAlertDialog(isDarkMode);
},
),
),
),
],
), ),
), ),
], ),
), ),
); );
} }

6
lib/modules/meeting/views/meeting_view.dart

@ -82,7 +82,7 @@ class MeetingView extends GetView<MeetingController> {
const Icon(Icons.keyboard_voice, color: Colors.white), const Icon(Icons.keyboard_voice, color: Colors.white),
5.horizontalSpace, 5.horizontalSpace,
Text( Text(
'录音', '开始会议',
style: TextStyle(color: Colors.white, fontSize: 16.sp), style: TextStyle(color: Colors.white, fontSize: 16.sp),
), ),
], ],
@ -264,7 +264,7 @@ class MeetingView extends GetView<MeetingController> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
id != null ? '修改录音标题(非必填)' : '录音标题(非必填)', id != null ? '修改会议标题(非必填)' : '会议标题(非必填)',
style: TextStyle( style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp, fontSize: 16.sp,
@ -347,7 +347,7 @@ class MeetingView extends GetView<MeetingController> {
} else { } else {
if (title.isEmpty) { if (title.isEmpty) {
title = title =
'${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 录音'; '${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 会议';
} }
Get.back(); Get.back();
Get.toNamed(Routes.meetingRecord, arguments: { Get.toNamed(Routes.meetingRecord, arguments: {

Loading…
Cancel
Save