Browse Source

Merge commit '7b36c171ddf45ae4067f62abe6724ef3b4c39a0b' into lxm

weicu
lxm 2 years ago
parent
commit
869016df72
  1. 2
      lib/data/services/db/database_helper.dart
  2. 10
      lib/modules/login/views/login_view.dart
  3. 3
      lib/modules/meeting/controllers/meeting_details_controller.dart
  4. 4
      lib/modules/meeting/controllers/meeting_edit_controller.dart
  5. 38
      lib/modules/meeting/controllers/meeting_record_controller.dart
  6. 4
      lib/modules/meeting/model/meeting_details_model.dart
  7. 246
      lib/modules/meeting/views/meeting_record_view.dart
  8. 6
      lib/modules/meeting/views/meeting_view.dart
  9. 136
      lib/modules/settings/views/settings_view.dart

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

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

10
lib/modules/login/views/login_view.dart

@ -171,7 +171,7 @@ class LoginView extends GetView<LoginController> {
);
}
// 输入框
// 输入框
Widget _buildInputField(bool isDarkMode) {
return Container(
decoration: BoxDecoration(
@ -224,13 +224,13 @@ class LoginView extends GetView<LoginController> {
Expanded(
child: TextField(
controller: controller.contactController,
// keyboardType: controller.selectedCountryFlag == '✉️'
// keyboardType: controller.selectedCountryFlag == '✉️'
// ? TextInputType.emailAddress
// : TextInputType.phone,
decoration: InputDecoration(
hintText: controller.selectedCountryFlag == '✉️'
? 'email'.tr // 需要在翻译文件中添加
: 'phoneNumber'.tr, // 需要在翻译文件中添加
hintText: controller.selectedCountryFlag == '✉️'
? 'email'.tr // 需要在翻译文件中添加
: 'phoneNumber'.tr, // 需要在翻译文件中添加
hintStyle: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[600] : Colors.black38,

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 '../../../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_model.dart';
@ -42,7 +41,7 @@ class MeetingDetailsController extends GetxController
final data = await SqfliteApi.getMeetingDetails(id);
meetingData.value = MeetingModel.fromJson(data);
dataDetails.value = MeetingDetailsModel.fromJson(data['details']);
audioPlayer.setAudioSource(BytesAudioSource(dataDetails.value.audio));
audioPlayer.setAudioSource(AudioSource.file(dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
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 '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/speech_impl/volcano_tts_api_service.dart';
import 'meeting_controller.dart';
import 'meeting_details_controller.dart';
@ -53,7 +52,7 @@ class MeetingEditController extends GetxController
);
creationTime = meetingDetailsController.meetingData.value.creationtime;
audioPlayer.setAudioSource(
BytesAudioSource(meetingDetailsController.dataDetails.value.audio));
AudioSource.file(meetingDetailsController.dataDetails.value.audio));
audioPlayer.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
await audioPlayer.seek(Duration.zero);
@ -71,7 +70,6 @@ class MeetingEditController extends GetxController
isPlay.value = false;
} else {
lottieController.repeat();
await audioPlayer.seek(Duration.zero);
audioPlayer.play();
isPlay.value = true;
}

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

@ -1,9 +1,13 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.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/db/sqflite_api.dart';
import '../../../data/services/speech_impl/xunfei_asr_service.dart';
@ -34,6 +38,7 @@ class MeetingRecordController extends GetxController
@override
void onInit() {
super.onInit();
Permission.storage.request();
lottieController = AnimationController(vsync: this);
Get.put(XunfeiAsrService());
_asrService = Get.find<XunfeiAsrService>();
@ -144,6 +149,20 @@ class MeetingRecordController extends GetxController
// 完成
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
? finalContent.value.substring(0, 30)
: finalContent.value;
@ -155,12 +174,15 @@ class MeetingRecordController extends GetxController
dataLength: audio.length,
);
Uint8List wavData = Uint8List.fromList([...wavHeader, ...audio]);
String audiopPath =
await _saveAudioToFile(wavData, 'meeting_$creationTime.wav');
if (audiopPath.isEmpty) return;
Map<String, Object?> data = {
'title': title,
'firstContent': firstContent.replaceAll("\n", ""),
'seconds': seconds.value,
'creationTime': creationTime,
'audio': wavData,
'audio': audiopPath,
'content': finalContent.value,
};
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({
required int sampleRate,
required int numberOfChannels,

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

@ -1,11 +1,9 @@
import 'dart:typed_data';
class MeetingDetailsModel {
MeetingDetailsModel();
late int id;
late int meetingid;
late Uint8List audio;
late String audio;
late String content;
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) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
10.verticalSpace,
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w),
child: Text(
controller.title,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 17.sp,
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Text(
DateUtil.formatDateMs(
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,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
child: Text(
DateUtil.formatDateMs(
controller.creationTime,
format: DateFormats.y_mo_d_h_m,
),
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 14.sp,
),
),
],
),
),
_recordContent(isDarkMode),
_bottomButton(isDarkMode),
],
);
}
@ -179,7 +129,7 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
child: Container(
width: double.infinity,
padding: EdgeInsets.all(10.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
margin: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
@ -210,51 +160,121 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
// 底部按钮
Widget _bottomButton(bool isDarkMode) {
return Padding(
padding: EdgeInsets.only(top: 40.w, bottom: 30.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: controller.recorderHandle,
child: Container(
width: 130.w,
height: 60.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(60.w),
border: Border.all(
color: isDarkMode ? AppColors.primary : Colors.blue,
width: 2.w,
return Container(
margin: EdgeInsets.only(top: 10.h),
decoration: BoxDecoration(
color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
offset: const Offset(0, -1),
),
],
),
child: SafeArea(
child: SizedBox(
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,
),
),
),
),
),
child: Obx(
() => Icon(
controller.isRecording.value ? Icons.pause : Icons.play_arrow,
color: isDarkMode ? AppColors.primary : Colors.blue,
size: 40.w,
10.horizontalSpace,
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
onLoaded: (composition) {
controller.lottieController.duration =
composition.duration;
},
),
),
),
),
),
GestureDetector(
onTap: () {
_showAlertDialog(isDarkMode);
},
child: Container(
width: 130.w,
height: 60.w,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(60.w),
),
child: Icon(
Icons.fiber_manual_record_outlined,
color: Colors.white,
size: 40.w,
),
Expanded(
child: Lottie.asset(
'assets/lottie/audio_wave.json',
controller: controller.lottieController,
fit: BoxFit.fill,
onLoaded: (composition) {
controller.lottieController.duration =
composition.duration;
},
),
),
10.horizontalSpace,
// 关闭按钮
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: 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),
5.horizontalSpace,
Text(
'录音',
'开始会议',
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
],
@ -264,7 +264,7 @@ class MeetingView extends GetView<MeetingController> {
mainAxisSize: MainAxisSize.min,
children: [
Text(
id != null ? '修改录音标题(非必填)' : '录音标题(非必填)',
id != null ? '修改会议标题(非必填)' : '会议标题(非必填)',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
@ -347,7 +347,7 @@ class MeetingView extends GetView<MeetingController> {
} else {
if (title.isEmpty) {
title =
'${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 录音';
'${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 会议';
}
Get.back();
Get.toNamed(Routes.meetingRecord, arguments: {

136
lib/modules/settings/views/settings_view.dart

@ -45,6 +45,8 @@ class SettingsView extends GetView<SettingsController> {
children: [
_buildUserInfoCard(isDarkMode),
SizedBox(height: 20.h),
_buildAISettingsSection(isDarkMode),
SizedBox(height: 20.h),
_buildSystemSettingsSection(isDarkMode),
SizedBox(height: 20.h),
_buildAboutSection(isDarkMode),
@ -110,12 +112,12 @@ class SettingsView extends GetView<SettingsController> {
// 裁剪圆形头像
borderRadius: BorderRadius.circular(30.r),
child: controller.userInfo.value.avatar.isEmpty
?
// Image.network(
// 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80',
// fit: BoxFit.cover, // 图片填充方式
// )
Container(
?
// Image.network(
// 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80',
// fit: BoxFit.cover, // 图片填充方式
// )
Container(
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(30.r),
@ -142,12 +144,15 @@ class SettingsView extends GetView<SettingsController> {
return Container(
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(30.r),
borderRadius:
BorderRadius.circular(30.r),
),
child: Center(
child: Text(
controller.userInfo.value.name.isNotEmpty
? controller.userInfo.value.name[0]
controller.userInfo.value.name
.isNotEmpty
? controller
.userInfo.value.name[0]
.toUpperCase()
: '?',
style: TextStyle(
@ -266,6 +271,82 @@ class SettingsView extends GetView<SettingsController> {
);
}
// AI设置
Widget _buildAISettingsSection(bool isDarkMode) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 4.w, bottom: 8.h),
child: Text(
'AI设置',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.blue[400] : Colors.blue[600],
letterSpacing: 1,
),
),
),
Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: BackdropFilter(
filter: isDarkMode
? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column(
children: [
// 语音服务选择
_buildSpeechServiceSetting(isDarkMode),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
Obx(
() => _buildNavigationSetting(
title: 'timbreService'.tr, // 音色服务
subtitle: 'timbreServiceInfo'.tr, // 选择音色服务
icon: Icons.photo_camera_front_outlined,
iconBgColor: isDarkMode
? Colors.cyan[900]!.withOpacity(0.3)
: Colors.cyan[100]!,
iconColor:
isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!,
value: controller.timbreName.value.tr,
valueStyle: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
onTap: () {
Get.toNamed(Routes.timbre);
},
isDarkMode: isDarkMode,
),
),
],
),
),
),
),
],
);
}
// 系统设置部分
Widget _buildSystemSettingsSection(bool isDarkMode) {
return Column(
@ -370,46 +451,13 @@ class SettingsView extends GetView<SettingsController> {
),
onTap: () {
// 打开备份设置页面
print('---------用户头像-${controller.userInfo.value.avatar}');
print(
'---------用户头像-${controller.userInfo.value.avatar}');
print('---------用户名-${controller.userInfo.value.name}');
//print('user${User.instance.value}');
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// 语音服务选择
_buildSpeechServiceSetting(isDarkMode),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
Obx(
() => _buildNavigationSetting(
title: 'timbreService'.tr, // 音色服务
subtitle: 'timbreServiceInfo'.tr, // 选择音色服务
icon: Icons.photo_camera_front_outlined,
iconBgColor: isDarkMode
? Colors.cyan[900]!.withOpacity(0.3)
: Colors.cyan[100]!,
iconColor:
isDarkMode ? Colors.cyan[300]! : Colors.cyan[600]!,
value: controller.timbreName.value.tr,
valueStyle: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
onTap: () {
Get.toNamed(Routes.timbre);
},
isDarkMode: isDarkMode,
),
),
Divider(
height: 1,
color: isDarkMode

Loading…
Cancel
Save