Browse Source

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

weicu
liwei1dao 1 year ago
parent
commit
f16c40fa89
  1. 4
      lib/core/bindings/initial_binding.dart
  2. 144
      lib/data/services/account_service.dart
  3. 249
      lib/modules/agent/views/agent_view.dart
  4. 55
      lib/modules/login/controllers/login_controller.dart
  5. 54
      lib/modules/login/views/login_view.dart
  6. 88
      lib/modules/meeting/controllers/meeting_controller.dart
  7. 1
      lib/modules/meeting/controllers/meeting_details_controller.dart
  8. 3
      lib/modules/meeting/controllers/meeting_record_controller.dart
  9. 8
      lib/modules/meeting/views/meeting_details_view.dart
  10. 2
      lib/modules/meeting/views/meeting_record_view.dart
  11. 106
      lib/modules/profile/controllers/profile_controller.dart
  12. 133
      lib/modules/translation/controllers/translation_list_controller.dart
  13. 274
      lib/modules/translation/views/translation_list_view.dart

4
lib/core/bindings/initial_binding.dart

@ -9,6 +9,7 @@ import '../../data/services/language_manager.dart';
import '../../core/utils/logger.dart';
import '../../data/services/bluetooth_manager.dart';
import '../../data/services/ble_manager.dart';
import '../../data/services/account_service.dart';
/// 初始绑定,用于管理全局依赖
class InitialBinding extends Bindings {
@ -42,6 +43,9 @@ class InitialBinding extends Bindings {
// 火山AI服务
// Get.lazyPut<VolcanoAIService>(() => VolcanoAIService(), fenix: true);
// 账号管理服务
Get.lazyPut<AccountService>(() => AccountService(), fenix: true);
// 杰理蓝牙服务
// // 语言管理器(需要最先初始化)

144
lib/data/services/account_service.dart

@ -0,0 +1,144 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:logger/logger.dart';
import '../../modules/login/controllers/googleLogin.dart';
import '../../modules/login/controllers/facebookLogin.dart';
import '../services/network/api.dart';
/// 账号管理服务
///
/// 负责处理所有与用户账号相关的操作,包括登出和账号删除
class AccountService extends GetxService {
final GetStorage _storage = GetStorage();
final logger = Logger();
/// 初始化服务
@override
void onInit() {
super.onInit();
logger.d('AccountService 已初始化');
}
/// 清理本地用户数据
///
/// 返回操作是否成功
Future<bool> clearUserData() async {
try {
// 清理所有存储
await _storage.erase();
// 或者逐个清理关键存储项
await _storage.write("logintoken", null);
await _storage.write("user_info", null);
await _storage.write("local_avatar", null);
logger.d('用户数据清理成功');
return true;
} catch (e) {
logger.e('用户数据清理失败: $e');
return false;
}
}
/// 退出第三方登录
///
/// 返回操作是否成功
Future<bool> logoutThirdParty() async {
try {
final google = Google();
final facebook = Facebook();
await Future.wait([
google.signOutGoogle(),
facebook.signOutFacebook(),
]);
logger.d('第三方登录退出成功');
return true;
} catch (e) {
logger.e('第三方登录退出失败: $e');
// 返回true,因为这不应阻止整体登出流程
return true;
}
}
/// 用户登出
///
/// 清理本地数据并退出第三方登录
/// 返回操作结果,包含成功状态和错误信息
Future<AccountOperationResult> signOut() async {
try {
// 清理本地数据
final dataCleaned = await clearUserData();
if (!dataCleaned) {
return AccountOperationResult(
success: false,
errorMessage: '清理用户数据失败',
);
}
// 退出第三方登录
await logoutThirdParty();
return AccountOperationResult(
success: true,
message: '已退出登录',
);
} catch (e) {
logger.e('登出过程发生错误: $e');
return AccountOperationResult(
success: false,
errorMessage: '退出登录失败,请重试',
);
}
}
/// 删除用户账号
///
/// 调用API删除账号,然后清理本地数据
/// 返回操作结果,包含成功状态和错误信息
Future<AccountOperationResult> deleteAccount() async {
try {
// 调用API删除账号
final response = await Api.deleteUser();
if (response == null) {
return AccountOperationResult(
success: false,
errorMessage: '删除用户数据失败',
);
}
// 清理本地数据
final dataCleaned = await clearUserData();
if (!dataCleaned) {
return AccountOperationResult(
success: false,
errorMessage: '清理用户数据失败',
);
}
// 退出第三方登录
await logoutThirdParty();
return AccountOperationResult(
success: true,
message: '账号已被永久删除',
);
} catch (e) {
logger.e('删除账号过程发生错误: $e');
return AccountOperationResult(
success: false,
errorMessage: '删除账号失败,请重试',
);
}
}
}
/// 账号操作结果
class AccountOperationResult {
final bool success;
final String? message;
final String? errorMessage;
AccountOperationResult({
required this.success,
this.message,
this.errorMessage,
});
}

249
lib/modules/agent/views/agent_view.dart

@ -168,34 +168,22 @@ class AgentView extends StatelessWidget {
final message = controller
.messages[controller.messages.length - 1 - index];
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: !message.isUser
? GestureDetector(
onLongPressStart:
(LongPressStartDetails details) {
_onLongPressMessageContent(
context, isDarkMode, details, message);
},
child: MessageBubble(
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
),
)
: MessageBubble(
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
),
);
padding: const EdgeInsets.only(bottom: 8),
child: GestureDetector(
onLongPressStart: (LongPressStartDetails details) {
_onLongPressMessageContent(
context, isDarkMode, details, message);
},
child: MessageBubble(
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
),
));
},
),
),
@ -602,164 +590,36 @@ class AgentView extends StatelessWidget {
));
},
),
// 自定义分隔线样式
PopupMenuItem(
height: 1, // 控制间距
enabled: false,
child: Divider(
height: 1,
thickness: 0.8,
indent: 0,
endIndent: 0,
color: isDarkMode ? Colors.grey[700] : Colors.grey[300],
if (!message.isUser) ...[
// 自定义分隔线样式
PopupMenuItem(
height: 1, // 控制间距
enabled: false,
child: Divider(
height: 1,
thickness: 0.8,
indent: 0,
endIndent: 0,
color: isDarkMode ? Colors.grey[700] : Colors.grey[300],
),
),
),
_popupMenuEntry(
Icon(
Icons.report,
size: 20.w,
color: isDarkMode ? Colors.red[700] : Colors.red,
_popupMenuEntry(
Icon(
Icons.report,
size: 20.w,
color: isDarkMode ? Colors.red[700] : Colors.red,
),
'举报'.tr, // 举报
isDarkMode ? Colors.red[700]! : Colors.red,
() {
// 实现举报功能
final reportController = Get.find<ReportController>();
reportController.initReport(message);
_showReportForm(context, isDarkMode);
},
),
'举报'.tr, // 举报
isDarkMode ? Colors.red[700]! : Colors.red,
() {
// 实现举报功能
final reportController = Get.find<ReportController>();
reportController.initReport(message);
_showReportForm(context, isDarkMode);
// showModalBottomSheet(
// context: context,
// isScrollControlled: true, // 关键:允许弹窗随键盘移动
// backgroundColor:
// isDarkMode ? AppColors.cardBackground : Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
// ),
// builder: (BuildContext context) {
// return Padding(
// // 添加底部内边距以容纳键盘
// padding: EdgeInsets.only(
// bottom: MediaQuery.of(context).viewInsets.bottom,
// ),
// child: Container(
// padding: EdgeInsets.all(16.w),
// child: SingleChildScrollView(
// // 添加滚动视图
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.stretch,
// children: [
// Stack(
// alignment: Alignment.center,
// children: [
// Center(
// child: Text(
// '举报',
// style: TextStyle(
// fontSize: 18.sp,
// fontWeight: FontWeight.bold,
// color: isDarkMode
// ? Colors.white
// : Colors.black,
// ),
// ),
// ),
// SizedBox(
// height: 20.w,
// ),
// Positioned(
// right: 0,
// child: IconButton(
// icon: Icon(
// Icons.close,
// color: isDarkMode
// ? Colors.grey[400]
// : Colors.black54,
// ),
// onPressed: () => Navigator.pop(context),
// ),
// ),
// ],
// ),
// Container(
// padding: EdgeInsets.symmetric(horizontal: 12.w),
// margin: EdgeInsets.only(bottom: 10.w),
// child: Divider(
// height: 1,
// color: Colors.grey[300],
// ),
// ),
// Wrap(
// spacing: 8.w,
// runSpacing: 8.h,
// children: [
// _buildReportChip('有害/不安全', isDarkMode),
// _buildReportChip('色情低俗', isDarkMode),
// _buildReportChip('误导攻击', isDarkMode),
// _buildReportChip('违法犯罪', isDarkMode),
// _buildReportChip('隐私相关', isDarkMode),
// _buildReportChip('侵犯我的著作权、名誉权等', isDarkMode),
// _buildReportChip('其他', isDarkMode),
// ],
// ),
// SizedBox(height: 16.h),
// Container(
// padding: EdgeInsets.all(12.w),
// decoration: BoxDecoration(
// color: isDarkMode
// ? Colors.grey[800]
// : Colors.grey[200],
// borderRadius: BorderRadius.circular(8.r),
// ),
// child: TextField(
// maxLines: 3,
// style: TextStyle(
// color: isDarkMode ? Colors.white : Colors.black,
// ),
// decoration: InputDecoration(
// hintText: '我们想知道你举报的原因,你可以描述你遇到的问题。',
// hintStyle: TextStyle(
// color: isDarkMode
// ? Colors.white70
// : Colors.black54,
// fontSize: 14.sp,
// ),
// border: InputBorder.none,
// ),
// ),
// ),
// SizedBox(height: 14.h),
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.blue,
// foregroundColor: Colors.white,
// padding: EdgeInsets.symmetric(vertical: 12.h),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8.r),
// ),
// ),
// child: Text('提交'),
// onPressed: () {
// Navigator.pop(context);
// Get.snackbar(
// '提示',
// '举报已提交,我们会尽快处理',
// backgroundColor: Colors.green.withOpacity(0.8),
// colorText: Colors.white,
// );
// },
// ),
// SizedBox(height: 8.h),
// ],
// ),
// ),
// ),
// );
// },
// );
},
),
]
],
);
}
@ -784,27 +644,6 @@ class AgentView extends StatelessWidget {
);
}
// // 添加这个辅助方法来创建举报选项
// Widget _buildReportChip(String label, bool isDarkMode) {
// return ChoiceChip(
// label: Text(
// label,
// style: TextStyle(
// color: isDarkMode ? Colors.white : Colors.black87,
// fontSize: 14.sp,
// ),
// ),
// selected: false,
// backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(16.r),
// ),
// onSelected: (bool selected) {
// // 可以在这里处理选择逻辑
// },
// );
// }
PopupMenuEntry _popupMenuEntry(
Icon icon,
String title,

55
lib/modules/login/controllers/login_controller.dart

@ -201,6 +201,9 @@ class LoginController extends GetxController {
// 显示隐私政策弹窗
void _showPrivacyDialog() {
// 获取当前主题模式
final bool isDarkMode = Get.theme.brightness == Brightness.dark;
Get.dialog(
WillPopScope(
onWillPop: () async => false, // 禁止通过返回键关闭弹窗
@ -208,16 +211,20 @@ class LoginController extends GetxController {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.white, // 暗黑模式使用深灰色背景
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
Text(
'DeapSound 声明',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color:
isDarkMode ? Colors.white : Colors.black, // 暗黑模式使用白色文字
),
),
const SizedBox(height: 16),
@ -226,9 +233,14 @@ class LoginController extends GetxController {
child: Text.rich(
TextSpan(
children: [
const TextSpan(
TextSpan(
text: '欢迎使用DeapSound,请充分阅读',
style: TextStyle(fontSize: 14),
style: TextStyle(
fontSize: 14,
color: isDarkMode
? Colors.white70
: Colors.black87, // 暗黑模式使用浅白色
),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
@ -244,18 +256,21 @@ class LoginController extends GetxController {
title: 'privacyPolicy'.tr,
));
},
child: Text(
child: const Text(
'《隐私政策》',
style: TextStyle(
fontSize: 14,
color: const Color(0xFF3B82F6),
color: Color(0xFF3B82F6),
),
),
),
),
const TextSpan(
TextSpan(
text: '和',
style: TextStyle(fontSize: 14),
style: TextStyle(
fontSize: 14,
color: isDarkMode ? Colors.white70 : Colors.black87,
),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
@ -271,18 +286,21 @@ class LoginController extends GetxController {
title: 'userAgreement'.tr,
));
},
child: Text(
child: const Text(
'《用户协议》',
style: TextStyle(
fontSize: 14,
color: const Color(0xFF3B82F6),
color: Color(0xFF3B82F6),
),
),
),
),
const TextSpan(
TextSpan(
text: ',点击“同意并进入”表示您已充分阅读并同意上述协议。点击“不同意”将退出应用。',
style: TextStyle(fontSize: 14),
style: TextStyle(
fontSize: 14,
color: isDarkMode ? Colors.white70 : Colors.black87,
),
),
],
),
@ -299,9 +317,17 @@ class LoginController extends GetxController {
exit(0);
},
style: TextButton.styleFrom(
foregroundColor: Colors.black54,
foregroundColor: isDarkMode
? Colors.grey[400]
: Colors.black54, // 暗黑模式使用浅灰色
),
child: Text(
'不同意',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.black54,
),
),
child: const Text('不同意'),
//child: const Text('不同意'),
),
ElevatedButton(
onPressed: () {
@ -317,6 +343,9 @@ class LoginController extends GetxController {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: isDarkMode ? 4 : 2, // 暗黑模式下增加阴影效果提高辨识度
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 10), // 统一的内边距
),
child: const Text('同意并进入'),
),

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

@ -177,9 +177,23 @@ class LoginView extends GetView<LoginController> {
return Container(
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[900]!.withOpacity(0.4)
? Colors.grey[850]!.withOpacity(0.6) // 更深的背景色,增加不透明度
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(16.r),
border: Border.all(
color:
isDarkMode ? Colors.grey[700]! : Colors.transparent, // 暗黑模式添加边框
width: 1,
),
boxShadow: isDarkMode
? [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: [], // 暗黑模式添加阴影
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
@ -213,32 +227,27 @@ class LoginView extends GetView<LoginController> {
Container(
height: 20.h,
width: 1,
color: isDarkMode ? Colors.grey[800] : Colors.grey[300],
color:
isDarkMode ? Colors.grey[600] : Colors.grey[300], // 调整分隔线颜色
margin: EdgeInsets.symmetric(horizontal: 8.w),
),
// Icon(
// Icons.person_outline,
// color: isDarkMode ? Colors.grey[400] : Colors.black54,
// size: 20.sp,
// ),
//SizedBox(width: 12.w),
Expanded(
child: TextField(
controller: controller.contactController,
// keyboardType: controller.selectedCountryFlag == '✉️'
// ? TextInputType.emailAddress
// : TextInputType.phone,
decoration: InputDecoration(
hintText: controller.selectedCountryFlag == '✉️'
? 'email'.tr // 需要在翻译文件中添加
: 'phoneNumber'.tr, // 需要在翻译文件中添加
hintStyle: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[600] : Colors.black38,
color: isDarkMode
? Colors.grey[500]
: Colors.black38, // 调整提示文字颜色
),
border: InputBorder.none,
fillColor: Colors.transparent,
filled: true,
focusedBorder: InputBorder.none,
),
style: TextStyle(
fontSize: 14.sp,
@ -322,9 +331,23 @@ class LoginView extends GetView<LoginController> {
return Container(
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[900]!.withOpacity(0.4)
? Colors.grey[850]!.withOpacity(0.6) // 更深的背景色,增加不透明度
: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(16.r),
border: Border.all(
color:
isDarkMode ? Colors.grey[700]! : Colors.transparent, // 暗黑模式添加边框
width: 1,
),
boxShadow: isDarkMode
? [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: [], // 暗黑模式添加阴影
),
child: Padding(
padding: EdgeInsets.only(left: 16.w, right: 4.w, top: 4.h, bottom: 4.h),
@ -345,12 +368,15 @@ class LoginView extends GetView<LoginController> {
hintText: 'verificationCode'.tr, // 验证码
hintStyle: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[600] : Colors.black38,
color: isDarkMode
? Colors.grey[500]
: Colors.black38, // 调整提示文字颜色
),
border: InputBorder.none,
counterText: '',
fillColor: Colors.transparent,
filled: true,
focusedBorder: InputBorder.none,
),
style: TextStyle(
fontSize: 14.sp,

88
lib/modules/meeting/controllers/meeting_controller.dart

@ -7,6 +7,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
@ -17,6 +18,8 @@ import '../../../core/utils/permission_util.dart';
import 'package:share_plus/share_plus.dart';
import '../../../routes/app_routes.dart';
class MeetingController extends GetxController {
final ScrollController listController = ScrollController();
final AudioPlayer _audioPlayer = AudioPlayer();
@ -38,6 +41,7 @@ class MeetingController extends GetxController {
List dataListTask = [];
bool isDisposed = false;
String filePath = '';
final String _appid = '2316081933';
final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En';
@ -57,6 +61,15 @@ class MeetingController extends GetxController {
};
_getDataList();
addMeetingAudio();
final Map<String, dynamic> args = Get.arguments ?? {};
// 检查参数是否存在且包含有效路径
if (args != null &&
args.containsKey('filePath') &&
args['filePath'] is String &&
args['filePath'].isNotEmpty) {
final String path = args['filePath'];
addAudio(path);
}
}
@override
@ -251,6 +264,7 @@ class MeetingController extends GetxController {
// 移动文件
await file.copy(newPath);
await file.delete();
addQuantity++;
}
} catch (e) {
@ -304,39 +318,53 @@ class MeetingController extends GetxController {
EasyLoading.dismiss();
}
void addAudio() async {
void addAudio([String? filePath]) async {
EasyLoading.show(status: '正在导入...');
FilePickerResult? result = await FilePicker.platform.pickFiles(
withData: true,
);
if (result != null) {
PlatformFile file = result.files.first;
String filePath = await _saveFile(file);
String fileName = file.name;
await _audioPlayer.setAudioSource(AudioSource.file(filePath));
var audiourl =
await UploadOss.upload(filepath: filePath, rootDir: 'ExternalAudio');
int seconds = _audioPlayer.duration?.inSeconds ?? 0;
int creationTime = DateTime.now().millisecondsSinceEpoch;
Map<String, Object?> data = {
'title': fileName,
'type': 'IMPORT',
'seconds': seconds,
'filepath': filePath,
'audiourl': audiourl,
'tasktype': 0,
'creationtime': creationTime,
};
final meetingId = await SqfliteApi.insertMeeting(data);
if (meetingId != null) {
dataList.insert(0, {
'id': meetingId,
'taskid': '',
...data,
});
addQuantity++;
String fileName = "";
bool isParamProvided = filePath != null; // 更清晰的变量名
if (filePath != null) {
// 正确截取文件名(跨平台安全方式)
fileName = path.basename(filePath);
} else {
FilePickerResult? result = await FilePicker.platform.pickFiles(
withData: true,
);
if (result != null) {
PlatformFile file = result.files.first;
filePath = await _saveFile(file);
fileName = file.name;
}
}
await _audioPlayer.setAudioSource(AudioSource.file(filePath!));
var audiourl =
await UploadOss.upload(filepath: filePath, rootDir: 'ExternalAudio');
int seconds = _audioPlayer.duration?.inSeconds ?? 0;
int creationTime = DateTime.now().millisecondsSinceEpoch;
Map<String, Object?> data = {
'title': fileName,
'type': 'IMPORT',
'seconds': seconds,
'filepath': filePath,
'audiourl': audiourl,
'tasktype': 0,
'creationtime': creationTime,
};
final meetingId = await SqfliteApi.insertMeeting(data);
if (meetingId != null) {
dataList.insert(0, {
'id': meetingId,
'taskid': '',
...data,
});
addQuantity++;
}
if (isParamProvided) {
Get.toNamed(Routes.meetingDetails, arguments: {
'id': meetingId,
});
}
EasyLoading.dismiss();
}

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

@ -145,6 +145,7 @@ class MeetingDetailsController extends GetxController
}
await playerController.preparePlayer(
path: meetingData.value.filepath,
shouldExtractWaveform: false,
noOfSamples: 88,
);
durationMax.value = await playerController.getDuration(DurationType.max);

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

@ -153,7 +153,8 @@ class MeetingRecordController extends GetxController
}
/// Saves new title and exits editing mode
void saveTitleChange() {
Future<void> saveTitleChange() async {
isEditingTitle.value = true;
if (isEditingTitle.value) {
newName.value = titleEditingController.text;
isEditingTitle.value = false;

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

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
@ -108,6 +110,9 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
child: AudioFileWaveforms(
size: Size(350.w, 30.w),
playerController: controller.playerController,
waveformData: List.generate(88, (int index) {
return 0.01 + Random().nextDouble() * 0.09;
}),
waveformType: WaveformType.fitWidth,
enableSeekGesture: false,
playerWaveStyle: PlayerWaveStyle(
@ -350,6 +355,9 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
child: AudioFileWaveforms(
size: Size(350.w, 120.w),
playerController: controller.playerController,
waveformData: List.generate(88, (int index) {
return 0.01 + Random().nextDouble() * 0.09;
}),
waveformType: WaveformType.fitWidth,
playerWaveStyle: PlayerWaveStyle(
spacing: 4.w,

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

@ -348,7 +348,7 @@ class MeetingRecordView extends GetView<MeetingRecordController> {
Expanded(
child: TextButton(
onPressed: () async {
controller.saveTitleChange();
await controller.saveTitleChange();
await controller.saveRecording();
Get.back();
},

106
lib/modules/profile/controllers/profile_controller.dart

@ -7,14 +7,15 @@ import 'package:logger/logger.dart';
import '../../../data/models/user_Info.dart';
import '../../settings/controllers/settings_controller.dart';
import 'dart:io';
import 'dart:convert';
//import 'dart:convert';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import '../../../data/services/network/api.dart';
import 'package:dio/dio.dart';
import '../../../routes/app_routes.dart';
import '../../../modules/login/controllers/googleLogin.dart';
import '../../../modules/login/controllers/facebookLogin.dart';
//import '../../../modules/login/controllers/googleLogin.dart';
//import '../../../modules/login/controllers/facebookLogin.dart';
import '../../../data/services/account_service.dart';
//import 'package:http_parser/http_parser.dart'; // 新增此导入
class ProfileController extends GetxController {
@ -31,9 +32,14 @@ class ProfileController extends GetxController {
// 使用响应式变量
final currentLanguage = 'zh'.obs;
// 账号服务
late final AccountService _accountService;
@override
void onInit() {
super.onInit();
// 获取或注册账号服务
_accountService = Get.find<AccountService>();
loadUserInfo();
loadLanguagePreference();
}
@ -358,49 +364,41 @@ class ProfileController extends GetxController {
}
}
// 退出登录
Future<void> signOut() async {
try {
//显示加载提示
// Get.dialog(
// const Center(child: CircularProgressIndicator()),
// barrierDismissible: false,
// );
// 清理所有存储
await _storage.erase();
// 或者逐个清理关键存储项
await _storage.write("logintoken", null);
await _storage.write("user_info", null);
await _storage.write("local_avatar", null);
print('清理存储成功');
// 第三方登录退出
try {
final google = Google();
final facebook = Facebook();
await Future.wait([
google.signOutGoogle(),
facebook.signOutFacebook(),
]);
} catch (e) {
print('第三方登录退出错误: $e');
// 继续执行,不中断退出流程
}
// 显示加载提示
Get.dialog(
const Center(child: CircularProgressIndicator()),
barrierDismissible: false,
);
// 调用账号服务执行登出
final result = await _accountService.signOut();
// 关闭加载提示
//Get.back();
Get.back();
// 确保导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
if (result.success) {
// 先显示成功提示
Get.snackbar('成功', result.message ?? '已退出登录');
Logger().d('退出登录成功');
Get.snackbar('成功', '已退出登录');
print('退出登录成功');
// 然后导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
} else {
Get.snackbar('错误', result.errorMessage ?? '退出登录失败,请重试');
Logger().e('退出登录失败: ${result.errorMessage}');
}
} catch (e) {
//Get.back(); // 关闭加载提示
// 确保加载提示被关闭
if (Get.isDialogOpen ?? false) {
Get.back();
}
Get.snackbar('错误', '退出登录失败,请重试');
print('退出登录错误: $e');
Logger().e('退出登录错误: $e');
}
}
@ -413,24 +411,32 @@ class ProfileController extends GetxController {
barrierDismissible: false,
);
// 删除后台用户数据
final responce = await Api.deleteUser();
if (responce == null) {
Get.back(); // 关闭加载提示
Get.snackbar('错误', '删除用户数据失败');
return;
}
// 调用账号服务执行账号删除
final result = await _accountService.deleteAccount();
// 关闭加载提示
Get.back();
signOut(); // 调用 signOut 方法
if (result.success) {
// 先显示成功提示
Get.snackbar('成功', result.message ?? '账号已被永久删除');
Logger().d('账号删除成功');
Get.snackbar('Success', 'Account has been permanently deleted');
Logger().d('账号删除成功');
// 然后导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
} else {
Get.snackbar('错误', result.errorMessage ?? '删除账号失败,请重试');
Logger().e('删除账号失败: ${result.errorMessage}');
}
} catch (e) {
Get.back(); // 关闭加载提示
// 确保加载提示被关闭
if (Get.isDialogOpen ?? false) {
Get.back();
}
Get.snackbar('错误', '删除账号失败,请重试');
Logger().d('账号删除失败: $e');
Logger().e('删除账号失败: $e');
}
}
}

133
lib/modules/translation/controllers/translation_list_controller.dart

@ -1,18 +1,11 @@
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/upload_oss.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../core/utils/permission_util.dart';
import 'package:share_plus/share_plus.dart';
@ -28,14 +21,21 @@ class TranslationListController extends GetxController {
final appDocDir = getApplicationDocumentsDirectory();
// final directory = Directory(
// '/storage/emulated/0/Android/data/com.yunqiinnovation.deepsound/files');
List<FileSystemEntity> files = [];
TextEditingController titleController = TextEditingController(); // 录音标题
final RxList<FileSystemEntity> files = <FileSystemEntity>[].obs;
RxList dataList = [].obs;
List dataListTask = [];
bool isDisposed = false;
// 添加音频播放器
// 添加音频时长缓存
final Map<String, Duration> audioDurations = {};
// 播放状态
final RxBool isPlaying = false.obs;
// 当前播放的文件路径
final RxString currentPlayingPath = ''.obs;
final AudioPlayer audioPlayer = AudioPlayer();
final PlayerController playerController = PlayerController();
@override
void onInit() {
super.onInit();
@ -49,16 +49,47 @@ class TranslationListController extends GetxController {
// _getDataList();
// addMeetingAudio();
loadTranslatAudio();
// 监听播放状态变化
audioPlayer.playerStateStream.listen((playerState) {
isPlaying.value = playerState.playing;
if (playerState.processingState == ProcessingState.completed) {
// 播放完成后重置状态
currentPlayingPath.value = '';
}
});
}
@override
void onClose() {
titleController.dispose();
isDisposed = true;
audioPlayer.dispose(); // 释放播放器资源
super.onClose();
}
// 播放/暂停音频
void togglePlay(FileSystemEntity file) async {
final filePath = file.path;
if (currentPlayingPath.value == filePath) {
// 同一个文件:切换播放/暂停
if (isPlaying.value) {
await audioPlayer.pause();
} else {
await audioPlayer.play();
}
} else {
// 新文件:停止当前播放,开始新文件
currentPlayingPath.value = filePath;
try {
await audioPlayer.stop();
await audioPlayer.setFilePath(filePath);
await audioPlayer.play();
} catch (e) {
print("播放失败: $e");
currentPlayingPath.value = '';
}
}
}
// 请求存储权限
void _requestStoragePermission() async {
// final BuildContext context = Get.overlayContext!;
@ -81,16 +112,22 @@ class TranslationListController extends GetxController {
});
}
void loadTranslatopus() async {
final appDir = await appDocDir; // 先 await 获取目录
final dir = Directory("${appDir.path}/OpusAudio");
print("appDocDir=${dir?.path}");
// 添加方法:获取音频时长
Future<Duration> getAudioDuration(String filePath) async {
if (audioDurations.containsKey(filePath)) {
return audioDurations[filePath]!;
}
files = dir
.listSync()
.where((f) => f is File && (f.path.endsWith('.opus')))
.toList();
try {
final audioPlayer = AudioPlayer();
await audioPlayer.setFilePath(filePath);
final duration = await audioPlayer.duration ?? Duration.zero;
audioDurations[filePath] = duration;
return duration;
} catch (e) {
print("获取音频时长失败: $e");
return Duration.zero;
}
}
void loadTranslatAudio() async {
@ -102,12 +139,12 @@ class TranslationListController extends GetxController {
// Create directory if it doesn't exist
if (!await dir.exists()) {
await dir.create(recursive: true); // Create directory tree if needed
files = []; // No files exist in new directory
files.value = []; // No files exist in new directory
return;
}
// Directory exis
files = dir
files.value = dir
.listSync()
.where((f) =>
f is File &&
@ -120,7 +157,7 @@ class TranslationListController extends GetxController {
.toList();
} catch (e) {
print("Error loading files: $e");
files = []; // Return empty list on error
files.value = []; // Return empty list on error
}
}
// 导入音频文件
@ -144,48 +181,4 @@ class TranslationListController extends GetxController {
EasyLoading.showToast('分享失败: ${e.toString()}');
}
}
Future<String> _saveFile(PlatformFile file) async {
final directory = await getApplicationDocumentsDirectory();
final directoryDir = Directory('${directory.path}/ExternalAudio');
if (!await directoryDir.exists()) {
await directoryDir.create(recursive: true);
}
final String filePath = '${directory.path}/ExternalAudio/${file.name}';
final File localFile = File(filePath);
await localFile.writeAsBytes(file.bytes!);
return filePath;
}
// 修改标题
void editMeetingTitle(int id, String title) async {
if (title.isNotEmpty) {
SqfliteApi.editMeetingTitle(id, {'title': title});
int index = dataList.indexWhere((item) => item['id'] == id);
if (index != -1) {
final dataMap = Map<String, dynamic>.from(dataList[index]);
dataMap['title'] = title;
dataList[index] = dataMap;
}
}
Get.back();
}
// 删除数据
void deleteMeeting(int id, String filepath) async {
dataList.removeWhere((item) => item['id'] == id);
// 删除数据库中的记录
SqfliteApi.deleteMeeting(id);
_deleteFile(filepath);
addQuantity--;
}
// 删除文件
Future<void> _deleteFile(String filepath) async {
File file = File(filepath);
bool fileExists = await file.exists();
if (fileExists) {
await file.delete();
}
}
}

274
lib/modules/translation/views/translation_list_view.dart

@ -1,12 +1,10 @@
import 'dart:io';
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:haptic_feedback/haptic_feedback.dart';
import 'package:intl/intl.dart';
import '../../../core/theme/app_colors.dart';
import '../../../routes/app_routes.dart';
import '../controllers/translation_list_controller.dart';
@ -39,7 +37,7 @@ class TranslationListView extends GetView<TranslationListController> {
onPressed: () => Get.back(),
),
title: Text(
'conferenceAssistant'.tr, // 商务会议助手
"翻译录音文件", //
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
@ -52,7 +50,8 @@ class TranslationListView extends GetView<TranslationListController> {
Widget _buildAudioList(BuildContext context, bool isDarkMode) {
return Obx(() {
if (controller.files.isEmpty) {
final fileList = controller.files; // 不要 toList()
if (fileList.isEmpty) {
return Center(child: Text('暂无音频文件'));
}
@ -72,115 +71,168 @@ class TranslationListView extends GetView<TranslationListController> {
final fileName = file.path.split('/').last;
final fileStat = file.statSync(); // 获取文件信息
final modifiedTime = fileStat.modified;
return GestureDetector(
onTap: () {
// TODO: 播放音频
print("播放音频: $fileName");
},
onLongPressStart: (details) {
_onLongPressAudioItem(context, isDarkMode, details, file);
print("长按操作: $fileName");
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// 文件名
Text(
fileName,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 18.sp,
),
overflow: TextOverflow.ellipsis,
),
/// 修改时间 & 大小
Padding(
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Row(
children: [
Icon(
Icons.access_time,
size: 14.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
4.horizontalSpace,
Text(
DateFormat('yyyy-MM-dd HH:mm').format(modifiedTime),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
20.horizontalSpace,
Icon(
Icons.sd_storage,
size: 14.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
4.horizontalSpace,
Text(
'${(fileStat.size / 1024).toStringAsFixed(1)} KB',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
],
),
// 添加 FutureBuilder 来异步获取音频时长
return FutureBuilder<Duration>(
future: controller.getAudioDuration(file.path),
builder: (context, snapshot) {
String durationText = "计算中...";
if (snapshot.hasData) {
final duration = snapshot.data!;
if (duration.inSeconds > 0) {
durationText =
"${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}";
} else {
durationText = "未知";
}
} else if (snapshot.hasError) {
durationText = "错误";
}
return GestureDetector(
onLongPressStart: (details) {
_onLongPressAudioItem(context, isDarkMode, details, file);
print("长按操作: $fileName");
},
onTap: () {
// 添加点击播放功能
controller.togglePlay(file);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.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),
),
],
),
/// 文件类型标签
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.w),
margin: EdgeInsets.only(top: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(4.w),
),
child: Text(
file.path.split('.').last.toUpperCase(),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontSize: 10.sp,
),
/// 文件名
Text(
fileName,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 18.sp,
),
overflow: TextOverflow.ellipsis,
),
/// 修改时间 & 大小
Padding(
padding: EdgeInsets.only(top: 5.w),
child: Text(
'点击播放',
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,
),
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Row(
children: [
Icon(
Icons.access_time,
size: 14.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
4.horizontalSpace,
Text(
DateFormat('yyyy-MM-dd HH:mm').format(modifiedTime),
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
20.horizontalSpace,
Icon(
Icons.sd_storage,
size: 14.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
4.horizontalSpace,
Text(
'${(fileStat.size / 1024).toStringAsFixed(1)} KB',
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
20.horizontalSpace,
Icon(Icons.music_note,
size: 14.w,
color:
isDarkMode ? Colors.grey[400] : Colors.grey[500]),
4.horizontalSpace,
Text(
durationText, // 使用计算出的时长文本
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
],
),
),
/// 文件类型标签
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding:
EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.w),
margin: EdgeInsets.only(top: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(4.w),
),
child: Text(
file.path.split('.').last.toUpperCase(),
style: TextStyle(
color:
isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontSize: 10.sp,
),
),
),
Obx(() {
final isCurrentFile =
controller.currentPlayingPath.value == file.path;
final isPlaying = controller.isPlaying.value;
return Row(
children: [
// 播放/暂停图标
if (isCurrentFile)
Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
size: 20.w,
color: Colors.blue,
),
// 播放状态文本
Text(
isCurrentFile
? (isPlaying ? '播放中...' : '已暂停')
: '点击播放',
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,
),
),
],
);
}),
],
),
],
),
],
),
),
),
);
},
);
}
@ -237,6 +289,20 @@ class TranslationListView extends GetView<TranslationListController> {
controller.loadTranslatAudio(); // 刷新列表
},
),
_popupMenuEntry(
Icon(
Icons.delete_outline,
size: 20.w,
color: isDarkMode ? Colors.red[700] : Colors.red,
),
'导入',
isDarkMode ? Colors.red[700]! : Colors.red,
() {
Get.toNamed(Routes.meeting, arguments: {
'filePath': file.path,
});
},
),
],
);
}

Loading…
Cancel
Save