From 2e34f31a179b6f3cc51ce14fdb64c8a1ce452880 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Sun, 23 Mar 2025 11:59:03 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=9A=E8=AE=AE?= =?UTF-8?q?=E5=8A=A9=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/db/database_helper.dart | 49 ++++++++ lib/data/services/db/sqflite_api.dart | 111 ++++++++++++++++++ .../controllers/explore_controller.dart | 2 +- .../meeting_record_controller.dart | 23 +++- .../meeting/views/meeting_record_view.dart | 31 ++--- lib/modules/meeting/views/meeting_view.dart | 59 +--------- pubspec.yaml | 3 + 7 files changed, 202 insertions(+), 76 deletions(-) create mode 100644 lib/data/services/db/database_helper.dart create mode 100644 lib/data/services/db/sqflite_api.dart diff --git a/lib/data/services/db/database_helper.dart b/lib/data/services/db/database_helper.dart new file mode 100644 index 000000000..f8914fa37 --- /dev/null +++ b/lib/data/services/db/database_helper.dart @@ -0,0 +1,49 @@ +import 'dart:async'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; + +class DatabaseHelper { + static final DatabaseHelper _instance = DatabaseHelper._internal(); + factory DatabaseHelper() => _instance; + static Database? _database; + + DatabaseHelper._internal(); + + Future get database async { + if (_database != null) return _database!; + _database = await _initDatabase(); + return _database!; + } + + Future _initDatabase() async { + String path = join( + await getDatabasesPath(), + 'database.db', + ); + return await openDatabase(path, version: 1, onCreate: _createDB); + } + + Future _createDB(Database db, int version) async { + final batch = db.batch(); + // 商务会议助手列表 + batch.execute(''' + CREATE TABLE IF NOT EXISTS meeting ( + id INTEGER PRIMARY KEY, + title TEXT, + content TEXT, + seconds INTEGER, + creationtime INTEGER + ) + '''); + // 商务会议助手详情内容 + batch.execute(''' + CREATE TABLE IF NOT EXISTS meetingdetails ( + id INTEGER PRIMARY KEY, + meetingid INTEGER, + audio BLOB, + content TEXT + ) + '''); + await batch.commit(); + } +} diff --git a/lib/data/services/db/sqflite_api.dart b/lib/data/services/db/sqflite_api.dart new file mode 100644 index 000000000..88543775d --- /dev/null +++ b/lib/data/services/db/sqflite_api.dart @@ -0,0 +1,111 @@ +import 'package:get/get.dart'; + +import 'database_helper.dart'; + +class SqfliteApi { + // 获取商务会议助手列表 + static Future getMeetingList(int page) async { + final db = await DatabaseHelper().database; + return await db.query( + 'meeting', + limit: 10, + offset: (page - 1) * 10, + orderBy: 'creationtime DESC', + ); + } + + // 获取商务会议助手详情 + static Future getMeetingDetails(id) async { + final db = await DatabaseHelper().database; + List meetingList = + await db.query('meeting', where: 'id = ?', whereArgs: [id]); + List detailsList = await db.query( + 'meetingdetails', + where: 'meetingid = ?', + whereArgs: [id], + ); + return { + ...meetingList.first, + 'details': detailsList.first + }; + } + + // 插入商务会议助手 + static Future insertMeeting(Map meeting) async { + final db = await DatabaseHelper().database; + try { + int id = 0; + await db.transaction((txn) async { + id = await txn.insert('meeting', { + 'title': meeting['title'], + 'content': meeting['firstContent'], + 'seconds': meeting['seconds'], + 'creationtime': meeting['creationTime'], + }); + await txn.insert('meetingdetails', { + 'meetingid': id, + 'audio': meeting['audio'], + 'content': meeting['content'], + }); + }); + return id; + } catch (e) { + Get.snackbar( + 'error'.tr, // 错误 + 'unknownError'.tr, + snackPosition: SnackPosition.TOP, + ); + return; + } + } + + // 修改商务会议助手 + static Future editMeeting(id, Map meeting) async { + final db = await DatabaseHelper().database; + try { + await db.transaction((txn) async { + await txn.update( + 'meeting', + { + 'title': meeting['title'], + 'content': meeting['firstContent'], + 'seconds': meeting['seconds'], + }, + where: 'id = ?', + whereArgs: [id], + ); + await txn.update( + 'meetingdetails', + {'content': meeting['content']}, + where: 'meetingid = ?', + whereArgs: [id], + ); + }); + return true; + } catch (e) { + Get.snackbar( + 'error'.tr, // 错误 + 'unknownError'.tr, + snackPosition: SnackPosition.TOP, + ); + return; + } + } + + // 修改商务会议助手标题 + static Future editMeetingTitle(id, title) async { + final db = await DatabaseHelper().database; + await db.update('meeting', {'title': title}, + where: 'id = ?', whereArgs: [id]); + } + + // 删除商务会议助手 + static Future deleteMeeting(id) async { + final db = await DatabaseHelper().database; + await db.transaction((txn) async { + await txn.delete('meeting', where: 'id = ?', whereArgs: [id]); + await txn + .delete('meetingdetails', where: 'meetingid = ?', whereArgs: [id]); + }); + } +} diff --git a/lib/modules/explore/controllers/explore_controller.dart b/lib/modules/explore/controllers/explore_controller.dart index 6c19c5f27..d7e78d624 100644 --- a/lib/modules/explore/controllers/explore_controller.dart +++ b/lib/modules/explore/controllers/explore_controller.dart @@ -66,7 +66,7 @@ class ExploreController extends GetxController { ), AIFeature( title: 'simultaneousTranslation'.tr, // 同声翻译 - description: 'simultaneousTranslationInfo.tr', // 实时翻译80+种语言 + description: 'simultaneousTranslationInfo'.tr, // 实时翻译80+种语言 icon: Icons.translate, startColor: const Color(0xFF4CAF50), endColor: const Color(0xFF009688), diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart index d00b823c1..ba1c239ac 100644 --- a/lib/modules/meeting/controllers/meeting_record_controller.dart +++ b/lib/modules/meeting/controllers/meeting_record_controller.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../data/services/asr_service.dart'; +import '../../../data/services/db/sqflite_api.dart'; class MeetingRecordController extends GetxController with GetSingleTickerProviderStateMixin { @@ -15,6 +16,9 @@ class MeetingRecordController extends GetxController RxBool isRecording = false.obs; //是否录音中 RxInt seconds = 0.obs; + String title = ''; + int creationTime = 0; + RxString intermediateContent = ''.obs; RxString finalContent = ''.obs; @@ -25,6 +29,9 @@ class MeetingRecordController extends GetxController super.onInit(); lottieController = AnimationController(vsync: this); _asrService = Get.find(); + final Map args = Get.arguments ?? {}; + title = args['title']; + creationTime = DateTime.now().millisecondsSinceEpoch; } @override @@ -93,6 +100,20 @@ class MeetingRecordController extends GetxController // 完成 void complete() async { - Get.back(); + String firstContent = finalContent.value.length >= 20 + ? finalContent.value.substring(0, 20) + : finalContent.value; + Map data = { + 'title': title, + 'firstContent': firstContent, + 'seconds': seconds.value, + 'creationTime': creationTime, + 'audio': [], + 'content': finalContent.value, + }; + final result = await SqfliteApi.insertMeeting(data); + if (result != null) { + Get.back(); + } } } diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart index d83db0cc9..1079e699d 100644 --- a/lib/modules/meeting/views/meeting_record_view.dart +++ b/lib/modules/meeting/views/meeting_record_view.dart @@ -95,31 +95,20 @@ class MeetingRecordView extends GetView { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Expanded( - child: Text( - '2025-03-03 录音', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 17.sp, - ), - ), - ), - 10.horizontalSpace, - Text( - '中文', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 15.sp, - ), - ), - ], + Text( + controller.title, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 17.sp, + ), ), Padding( padding: EdgeInsets.symmetric(vertical: 5.w), child: Text( - '2025-03-03 16:18', + DateUtil.formatDateMs( + controller.creationTime, + format: DateFormats.y_mo_d_h_m, + ), style: TextStyle( color: isDarkMode ? Colors.grey[400] : Colors.grey[500], fontSize: 14.sp, diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart index beebef661..88bedda58 100644 --- a/lib/modules/meeting/views/meeting_view.dart +++ b/lib/modules/meeting/views/meeting_view.dart @@ -180,56 +180,6 @@ class MeetingView extends GetView { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text( - '录音语言转换', - style: TextStyle( - color: isDarkMode ? Colors.white : Colors.black87, - fontSize: 17.sp, - ), - ), - Container( - height: 48.w, - margin: EdgeInsets.only(top: 10.w, bottom: 20.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: GestureDetector( - onTap: () {}, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 10.w), - child: Row( - children: [ - Expanded( - child: Text( - '中文', - style: TextStyle( - color: - isDarkMode ? Colors.white : Colors.black87, - fontSize: 16.sp, - ), - ), - ), - Icon( - Icons.keyboard_arrow_right, - color: isDarkMode ? Colors.white : Colors.black87, - ), - ], - ), - ), - ), - ), Text( '录音标题(非必填)', style: TextStyle( @@ -308,12 +258,15 @@ class MeetingView extends GetView { GestureDetector( onTap: () { int time = DateTime.now().millisecondsSinceEpoch; - if (controller.titleController.text.isEmpty) { - controller.titleController.text = + String title = controller.titleController.text; + if (title.isEmpty) { + title = '${DateUtil.formatDateMs(time, format: DateFormats.y_mo_d)} 录音'; } Get.back(); - Get.toNamed(Routes.meetingRecord); + Get.toNamed(Routes.meetingRecord, arguments: { + 'title': title, + }); }, child: Container( width: 120.w, diff --git a/pubspec.yaml b/pubspec.yaml index b023e3efe..be5e1da8c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,9 @@ dependencies: # image_cropper: ^9.0.0 dio: ^5.8.0+1 flutter_easyloading: ^3.0.5 + sqflite: ^2.4.2 + path: ^1.9.1 + dev_dependencies: flutter_test: sdk: flutter From 5a488b69a9761ab85523f850b2a7f469e0e9a14d Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Sun, 23 Mar 2025 12:59:50 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E9=9F=B3=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timbre/bindings/timbre_binding.dart | 9 ++++ .../timbre/controllers/timbre_controller.dart | 8 +++ lib/modules/timbre/views/timbre_view.dart | 49 +++++++++++++++++++ lib/routes/app_pages.dart | 7 +++ lib/routes/app_routes.dart | 1 + 5 files changed, 74 insertions(+) create mode 100644 lib/modules/timbre/bindings/timbre_binding.dart create mode 100644 lib/modules/timbre/controllers/timbre_controller.dart create mode 100644 lib/modules/timbre/views/timbre_view.dart diff --git a/lib/modules/timbre/bindings/timbre_binding.dart b/lib/modules/timbre/bindings/timbre_binding.dart new file mode 100644 index 000000000..6883836f7 --- /dev/null +++ b/lib/modules/timbre/bindings/timbre_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import '../controllers/timbre_controller.dart'; + +class TimbreBinding extends Bindings { + @override + void dependencies() { + Get.put(TimbreController()); + } +} diff --git a/lib/modules/timbre/controllers/timbre_controller.dart b/lib/modules/timbre/controllers/timbre_controller.dart new file mode 100644 index 000000000..f25aad7ed --- /dev/null +++ b/lib/modules/timbre/controllers/timbre_controller.dart @@ -0,0 +1,8 @@ +import 'package:get/get.dart'; + +class TimbreController extends GetxController { + @override + void onInit() { + super.onInit(); + } +} diff --git a/lib/modules/timbre/views/timbre_view.dart b/lib/modules/timbre/views/timbre_view.dart new file mode 100644 index 000000000..b76a4b6bc --- /dev/null +++ b/lib/modules/timbre/views/timbre_view.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import '../controllers/timbre_controller.dart'; + +class TimbreView extends GetView { + const TimbreView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Scaffold( + backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], + appBar: _buildAppBar(isDarkMode), + body: _buildBody(context, isDarkMode), + ); + } + + // 构建应用栏 + PreferredSizeWidget _buildAppBar(bool isDarkMode) { + return AppBar( + backgroundColor: isDarkMode ? Colors.black : Colors.white, + elevation: 0.5, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black, + size: 20.sp, + ), + onPressed: () => Get.back(), + ), + title: Text( + '选择音色', + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + centerTitle: true, + ); + } + + // 构建主体内容 + Widget _buildBody(BuildContext context, bool isDarkMode) { + return Container(); + } +} diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 0114183d9..1e9374b4b 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -12,6 +12,8 @@ import '../modules/profile/bindings/profile_binding.dart'; import '../modules/chat/bindings/chat_binding.dart'; import '../modules/chat/views/chat_view.dart'; +import '../modules/timbre/bindings/timbre_binding.dart'; +import '../modules/timbre/views/timbre_view.dart'; import '../modules/translation/views/translation_view.dart'; import '../modules/translation/bindings/translation_binding.dart'; import '../modules/translation/views/translation_history_view.dart'; @@ -93,6 +95,11 @@ abstract class AppPages { page: () => const MeetingRecordView(), binding: MeetingRecordBinding(), ), + GetPage( + name: Routes.timbre, + page: () => const TimbreView(), + binding: TimbreBinding(), + ), GetPage( name: Routes.settings, page: () => const SettingsView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 0504f2c71..2dcd625d8 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -9,6 +9,7 @@ abstract class Routes { static const profile = '/profile'; static const meeting = '/meeting'; static const meetingRecord = '/meetingRecord'; + static const timbre = '/timbre'; static const settings = '/settings'; static const speechDemo = '/speech_demo'; static const TTS_TEST = '/tts_test'; From 62bffe63d8b6320c4cb2a46746946c9e1c59f1e2 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Sun, 23 Mar 2025 13:47:22 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E9=9F=B3=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timbre/controllers/timbre_controller.dart | 6 + lib/modules/timbre/views/timbre_view.dart | 125 +++++++++++++++++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/lib/modules/timbre/controllers/timbre_controller.dart b/lib/modules/timbre/controllers/timbre_controller.dart index f25aad7ed..3fd12cf53 100644 --- a/lib/modules/timbre/controllers/timbre_controller.dart +++ b/lib/modules/timbre/controllers/timbre_controller.dart @@ -1,6 +1,12 @@ import 'package:get/get.dart'; class TimbreController extends GetxController { + RxList dataList = [ + {'type': '1', 'name': '默认音色', 'description': '微软云端音色服务'}, + {'type': '2', 'name': '声音复刻', 'description': '复刻本人音色服务'} + ].obs; + RxString type = '1'.obs; + @override void onInit() { super.onInit(); diff --git a/lib/modules/timbre/views/timbre_view.dart b/lib/modules/timbre/views/timbre_view.dart index b76a4b6bc..37bdd8d45 100644 --- a/lib/modules/timbre/views/timbre_view.dart +++ b/lib/modules/timbre/views/timbre_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import '../../../core/theme/app_colors.dart'; import '../controllers/timbre_controller.dart'; class TimbreView extends GetView { @@ -13,7 +14,7 @@ class TimbreView extends GetView { return Scaffold( backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], appBar: _buildAppBar(isDarkMode), - body: _buildBody(context, isDarkMode), + body: _buildBody(isDarkMode), ); } @@ -43,7 +44,125 @@ class TimbreView extends GetView { } // 构建主体内容 - Widget _buildBody(BuildContext context, bool isDarkMode) { - return Container(); + Widget _buildBody(bool isDarkMode) { + return Column( + children: [ + Expanded( + child: Obx( + () => ListView.builder( + itemCount: controller.dataList.length, + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w), + itemBuilder: (BuildContext context, int index) { + Map data = controller.dataList[index]; + return _item(isDarkMode, data); + }, + ), + ), + ), + GestureDetector( + onTap: () {}, + child: Container( + width: 130.w, + height: 48.w, + margin: EdgeInsets.symmetric(vertical: 20.w), + decoration: BoxDecoration( + color: isDarkMode ? AppColors.primary : Colors.blue, + borderRadius: BorderRadius.circular(12.w), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.keyboard_voice, color: Colors.white), + 5.horizontalSpace, + Text( + '声音复刻', + style: TextStyle(color: Colors.white, fontSize: 16.sp), + ), + ], + ), + ), + ), + ], + ); + } + + Widget _item(bool isDarkMode, Map data) { + return InkWell( + onTap: () { + controller.type.value = data['type']; + }, + child: Obx( + () => Container( + margin: EdgeInsets.symmetric(vertical: 6.h), + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: controller.type.value == data['type'] + ? (isDarkMode + ? Colors.teal[900]!.withOpacity(0.3) + : Colors.teal[50]!) + : (isDarkMode + ? Colors.white.withOpacity(0.05) + : Colors.grey[100]!), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: controller.type.value == data['type'] + ? (isDarkMode ? Colors.teal[300]! : Colors.teal[600]!) + : Colors.transparent, + width: 1, + ), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + data['name'], + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 4.h), + Text( + data['description'], + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ], + ), + ), + Container( + width: 24.w, + height: 24.w, + margin: EdgeInsets.only(left: 12.w), + decoration: BoxDecoration( + color: controller.type.value == data['type'] + ? (isDarkMode ? Colors.teal[300] : Colors.teal[600]) + : (isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[300]), + shape: BoxShape.circle, + ), + child: controller.type.value == data['type'] + ? Icon( + Icons.check, + size: 16.w, + color: Colors.white, + ) + : null, + ), + ], + ), + ), + ), + ); } }