From 2478b97aab51efc22112e0346972451fe3c7909c Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 26 Mar 2025 18:17:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=9A=E8=AE=AE=E5=8A=A9?= =?UTF-8?q?=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bindings/meeting_details_binding.dart | 10 + .../bindings/meeting_edit_binding.dart | 11 + .../meeting_details_controller.dart | 51 ++++ .../controllers/meeting_edit_controller.dart | 62 +++++ .../meeting/model/meeting_details_model.dart | 6 +- .../meeting/views/meeting_details_view.dart | 253 +++++++++++++++++ .../meeting/views/meeting_edit_view.dart | 262 ++++++++++++++++++ lib/modules/meeting/views/meeting_view.dart | 6 +- lib/routes/app_pages.dart | 14 + lib/routes/app_routes.dart | 2 + 10 files changed, 673 insertions(+), 4 deletions(-) create mode 100644 lib/modules/meeting/bindings/meeting_details_binding.dart create mode 100644 lib/modules/meeting/bindings/meeting_edit_binding.dart create mode 100644 lib/modules/meeting/controllers/meeting_details_controller.dart create mode 100644 lib/modules/meeting/controllers/meeting_edit_controller.dart create mode 100644 lib/modules/meeting/views/meeting_details_view.dart create mode 100644 lib/modules/meeting/views/meeting_edit_view.dart diff --git a/lib/modules/meeting/bindings/meeting_details_binding.dart b/lib/modules/meeting/bindings/meeting_details_binding.dart new file mode 100644 index 000000000..6676625bd --- /dev/null +++ b/lib/modules/meeting/bindings/meeting_details_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; +import '../controllers/meeting_details_controller.dart'; + +class MeetingDetailsBinding extends Bindings { + @override + void dependencies() { + // 注册控制器 + Get.lazyPut(() => MeetingDetailsController()); + } +} diff --git a/lib/modules/meeting/bindings/meeting_edit_binding.dart b/lib/modules/meeting/bindings/meeting_edit_binding.dart new file mode 100644 index 000000000..31d1443ee --- /dev/null +++ b/lib/modules/meeting/bindings/meeting_edit_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; + +import '../controllers/meeting_edit_controller.dart'; + +class MeetingEditBinding extends Bindings { + @override + void dependencies() { + // 注册控制器 + Get.lazyPut(() => MeetingEditController()); + } +} diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart new file mode 100644 index 000000000..ee4668196 --- /dev/null +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../data/services/db/sqflite_api.dart'; +import '../model/meeting_details_model.dart'; +import '../model/meeting_model.dart'; + +class MeetingDetailsController extends GetxController + with GetSingleTickerProviderStateMixin { + late AnimationController lottieController; + + int id = 0; + + RxBool isLoading = true.obs; + Rx meetingData = MeetingModel().obs; + Rx dataDetails = MeetingDetailsModel().obs; + + RxBool isPlay = false.obs; + + @override + void onInit() { + super.onInit(); + lottieController = AnimationController(vsync: this); + final Map args = Get.arguments ?? {}; + id = args['id']; + _getDataDetails(); + } + + @override + void onClose() { + lottieController.dispose(); + super.onClose(); + } + + void _getDataDetails() async { + final data = await SqfliteApi.getMeetingDetails(id); + meetingData.value = MeetingModel.fromJson(data); + dataDetails.value = MeetingDetailsModel.fromJson(data['details']); + isLoading.value = false; + } + + void start() { + if (isPlay.value) { + lottieController.stop(); + isPlay.value = false; + } else { + lottieController.repeat(); + isPlay.value = true; + } + } +} diff --git a/lib/modules/meeting/controllers/meeting_edit_controller.dart b/lib/modules/meeting/controllers/meeting_edit_controller.dart new file mode 100644 index 000000000..9dc2fe89a --- /dev/null +++ b/lib/modules/meeting/controllers/meeting_edit_controller.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../data/services/db/sqflite_api.dart'; +import '../model/meeting_details_model.dart'; +import '../model/meeting_model.dart'; + +class MeetingEditController extends GetxController + with GetSingleTickerProviderStateMixin { + late AnimationController lottieController; + + late TextEditingController titleController; + late TextEditingController contentController; + + int id = 0; + + RxBool isLoading = true.obs; + Rx meetingData = MeetingModel().obs; + Rx dataDetails = MeetingDetailsModel().obs; + + RxBool isPlay = false.obs; + + @override + void onInit() { + super.onInit(); + lottieController = AnimationController(vsync: this); + final Map args = Get.arguments ?? {}; + id = args['id']; + _getDataDetails(); + } + + @override + void onClose() { + lottieController.dispose(); + titleController.dispose(); + contentController.dispose(); + super.onClose(); + } + + void _getDataDetails() async { + final data = await SqfliteApi.getMeetingDetails(id); + meetingData.value = MeetingModel.fromJson(data); + dataDetails.value = MeetingDetailsModel.fromJson(data['details']); + titleController = TextEditingController( + text: meetingData.value.title, + ); + contentController = TextEditingController( + text: dataDetails.value.content, + ); + isLoading.value = false; + } + + void start() { + if (isPlay.value) { + lottieController.stop(); + isPlay.value = false; + } else { + lottieController.repeat(); + isPlay.value = true; + } + } +} diff --git a/lib/modules/meeting/model/meeting_details_model.dart b/lib/modules/meeting/model/meeting_details_model.dart index e314222a7..0e8798640 100644 --- a/lib/modules/meeting/model/meeting_details_model.dart +++ b/lib/modules/meeting/model/meeting_details_model.dart @@ -4,21 +4,21 @@ class MeetingDetailsModel { MeetingDetailsModel(); late int id; - late int noteid; + late int meetingid; late Uint8List audio; late String content; factory MeetingDetailsModel.fromJson(Map map) => MeetingDetailsModel() ..id = map["id"] - ..noteid = map["noteid"] + ..meetingid = map["meetingid"] ..audio = map["audio"] ..content = map["content"]; Map toJson() { final data = {}; data['id'] = id; - data['noteid'] = noteid; + data['meetingid'] = meetingid; data['audio'] = audio; data['content'] = content; return data; diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart new file mode 100644 index 000000000..7423791ef --- /dev/null +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -0,0 +1,253 @@ +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:lottie/lottie.dart'; + +import '../../../core/theme/app_colors.dart'; +import '../../../routes/app_routes.dart'; +import '../controllers/meeting_details_controller.dart'; + +class MeetingDetailsView extends GetView { + const MeetingDetailsView({super.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: Obx(() => !controller.isLoading.value + ? _buildBody(isDarkMode) + : const SizedBox()), + ); + } + + // 构建应用栏 + 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(bool isDarkMode) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + controller.meetingData.value.title, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 17.sp, + ), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Text( + DateUtil.formatDateMs( + controller.meetingData.value.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( + padding: EdgeInsets.only(bottom: 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( + children: [ + SizedBox( + height: 70.w, + 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, + ), + ), + Expanded( + child: Lottie.asset( + 'assets/lottie/audio_wave.json', + controller: controller.lottieController, + fit: BoxFit.fill, + ), + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + '00:00:00', + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + GestureDetector( + onTap: controller.start, + child: Container( + width: 30.w, + height: 30.w, + decoration: BoxDecoration( + color: isDarkMode ? AppColors.primary : Colors.blue, + borderRadius: BorderRadius.circular( + 12.w, + ), + ), + child: Icon( + controller.isPlay.value ? Icons.pause : Icons.play_arrow, + color: Colors.white, + size: 25.w, + ), + ), + ), + Text( + '00:00:00', + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + ], + ), + ], + ), + ); + } + + // 记录内容 + Widget _recordContent(bool isDarkMode) { + return Expanded( + child: Container( + width: double.infinity, + padding: EdgeInsets.all(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: Align( + alignment: Alignment.topLeft, + child: SingleChildScrollView( + reverse: true, + child: Obx( + () => Text( + controller.dataDetails.value.content, + ), + ), + ), + ), + ), + ); + } + + // 底部按钮 + Widget _bottomButton(bool isDarkMode) { + return Container( + padding: EdgeInsets.only(top: 40.w, bottom: 30.w), + alignment: Alignment.center, + child: GestureDetector( + onTap: () { + Get.toNamed(Routes.meetingEdit, arguments: { + 'id': controller.id, + }); + }, + child: Container( + width: 130.w, + height: 48.w, + decoration: BoxDecoration( + color: isDarkMode ? AppColors.primary : Colors.blue, + borderRadius: BorderRadius.circular(12.w), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.edit_calendar, + color: Colors.white, + size: 20.w, + ), + 5.horizontalSpace, + Text( + '编辑', + style: TextStyle(color: Colors.white, fontSize: 16.sp), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/modules/meeting/views/meeting_edit_view.dart b/lib/modules/meeting/views/meeting_edit_view.dart new file mode 100644 index 000000000..3e6f6852c --- /dev/null +++ b/lib/modules/meeting/views/meeting_edit_view.dart @@ -0,0 +1,262 @@ +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:lottie/lottie.dart'; + +import '../../../core/theme/app_colors.dart'; +import '../controllers/meeting_edit_controller.dart'; + +class MeetingEditView extends GetView { + const MeetingEditView({super.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: Obx(() => !controller.isLoading.value + ? _buildBody(isDarkMode) + : const SizedBox()), + ); + } + + // 构建应用栏 + 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(), + ), + actions: [ + GestureDetector( + onTap: () {}, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 6.w, + ), + child: Text( + '保存', + style: TextStyle( + color: isDarkMode ? AppColors.primary : Colors.blue, + fontSize: 18.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); + } + + // 构建主体内容 + Widget _buildBody(bool isDarkMode) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 48.w, + alignment: Alignment.center, + 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: TextField( + controller: controller.titleController, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + decoration: InputDecoration( + hintText: '最多50个字符', + hintStyle: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 16.sp, + ), + fillColor: Colors.transparent, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10.w, + ), + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 5.w), + child: Text( + DateUtil.formatDateMs( + controller.meetingData.value.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), + ], + ), + ); + } + + // 音波 + Widget _audioWave(bool isDarkMode) { + return Container( + padding: EdgeInsets.only(bottom: 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( + children: [ + SizedBox( + height: 70.w, + 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, + ), + ), + Expanded( + child: Lottie.asset( + 'assets/lottie/audio_wave.json', + controller: controller.lottieController, + fit: BoxFit.fill, + ), + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + '00:00:00', + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + GestureDetector( + onTap: controller.start, + child: Container( + width: 30.w, + height: 30.w, + decoration: BoxDecoration( + color: isDarkMode ? AppColors.primary : Colors.blue, + borderRadius: BorderRadius.circular( + 12.w, + ), + ), + child: Icon( + controller.isPlay.value ? Icons.pause : Icons.play_arrow, + color: Colors.white, + size: 25.w, + ), + ), + ), + Text( + '00:00:00', + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[500], + fontSize: 12.sp, + ), + ), + ], + ), + ], + ), + ); + } + + // 记录内容 + Widget _recordContent(bool isDarkMode) { + return Expanded( + child: Container( + width: double.infinity, + padding: EdgeInsets.all(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: TextField( + controller: controller.contentController, + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.black87, + fontSize: 16.sp, + ), + maxLines: 50, + decoration: InputDecoration( + hintText: '编辑记录内容', + hintStyle: TextStyle( + color: isDarkMode ? Colors.white70 : Colors.black54, + fontSize: 16.sp, + ), + fillColor: Colors.transparent, + border: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10.w, + ), + ), + ), + ), + ); + } +} diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart index d51e63a86..10678dc88 100644 --- a/lib/modules/meeting/views/meeting_view.dart +++ b/lib/modules/meeting/views/meeting_view.dart @@ -96,7 +96,11 @@ class MeetingView extends GetView { Widget _item(BuildContext context, bool isDarkMode, MeetingModel data) { return GestureDetector( - onTap: () {}, + onTap: () { + Get.toNamed(Routes.meetingDetails, arguments: { + 'id': data.id, + }); + }, onLongPressStart: (details) { RenderBox? renderBox = Overlay.of(context).context.findRenderObject() as RenderBox; diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 1e9374b4b..8a2466fda 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -4,7 +4,11 @@ import '../modules/home/bindings/home_binding.dart'; import '../modules/explore/views/explore_view.dart'; import '../modules/explore/bindings/explore_binding.dart'; import '../modules/meeting/bindings/meeting_binding.dart'; +import '../modules/meeting/bindings/meeting_details_binding.dart'; +import '../modules/meeting/bindings/meeting_edit_binding.dart'; import '../modules/meeting/bindings/meeting_record_binding.dart'; +import '../modules/meeting/views/meeting_details_view.dart'; +import '../modules/meeting/views/meeting_edit_view.dart'; import '../modules/meeting/views/meeting_record_view.dart'; import '../modules/meeting/views/meeting_view.dart'; import '../modules/profile/views/profile_view.dart'; @@ -95,6 +99,16 @@ abstract class AppPages { page: () => const MeetingRecordView(), binding: MeetingRecordBinding(), ), + GetPage( + name: Routes.meetingDetails, + page: () => const MeetingDetailsView(), + binding: MeetingDetailsBinding(), + ), + GetPage( + name: Routes.meetingEdit, + page: () => const MeetingEditView(), + binding: MeetingEditBinding(), + ), GetPage( name: Routes.timbre, page: () => const TimbreView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 2dcd625d8..4488f63db 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -9,6 +9,8 @@ abstract class Routes { static const profile = '/profile'; static const meeting = '/meeting'; static const meetingRecord = '/meetingRecord'; + static const meetingDetails = '/meetingDetails'; + static const meetingEdit = '/meetingEdit'; static const timbre = '/timbre'; static const settings = '/settings'; static const speechDemo = '/speech_demo';