From 06611a9215dcf163c3465b73b1ee47c530395218 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 23 May 2025 14:37:46 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/lottie/sound_effect.json | 1 + .../meeting_details_controller.dart | 17 ++++- .../meeting/views/meeting_details_view.dart | 69 +++++++++---------- .../views/speaker_all_bottom_sheet.dart | 16 +++++ lib/modules/meeting/views/speech_tab.dart | 52 +++++++++++++- 5 files changed, 114 insertions(+), 41 deletions(-) create mode 100644 assets/lottie/sound_effect.json diff --git a/assets/lottie/sound_effect.json b/assets/lottie/sound_effect.json new file mode 100644 index 000000000..44c2522c1 --- /dev/null +++ b/assets/lottie/sound_effect.json @@ -0,0 +1 @@ +{"nm":"3line_soundwave","ddd":0,"h":200,"w":80,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"3 Outlines","sr":1,"st":0,"op":24,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[16.036,58.68,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[64.215,101.592,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":8},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":12},{"s":[1],"t":16}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.527,-0.046],[1.003,-1.094],[-0.16,-1.436],[-1.184,-1.231],[-3.258,-8.386],[0,-9.388],[3.259,-8.385],[6.107,-6.426],[0.159,-1.435],[-0.957,-1.094],[-1.527,-0.022],[-1.184,1.276],[-3.896,9.684],[0,10.892],[3.897,9.684],[7.11,7.428]],"o":[[-1.527,0.046],[-0.957,1.094],[0.159,1.436],[6.107,6.426],[3.259,8.385],[0,9.388],[-3.258,8.386],[-1.184,1.231],[-0.16,1.436],[1.003,1.14],[1.527,0.023],[7.11,-7.429],[3.897,-9.685],[0,-10.892],[-3.896,-9.685],[-1.184,-1.276]],"v":[[-10.635,-58.385],[-14.429,-56.675],[-15.625,-52.882],[-13.609,-48.882],[0.439,-26.666],[5.327,-0.006],[0.439,26.654],[-13.609,48.871],[-15.625,52.87],[-14.429,56.665],[-10.635,58.408],[-6.568,56.529],[9.941,30.859],[15.786,-0.006],[9.941,-30.87],[-6.568,-56.538]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9569,0.2627,0.2118],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[16.035,58.68],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"2 Outlines","sr":1,"st":0,"op":24,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[12.743,42.89,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[44.128,101.592,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0],"t":4},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":8},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":16},{"s":[0],"t":20}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.572,0.046],[1.002,-1.23],[-0.205,-1.413],[-1.185,-1.276],[-2.119,-5.628],[0,-6.289],[2.119,-5.651],[3.965,-4.421],[0.205,-1.39],[-1.049,-1.139],[-1.572,0.023],[-1.23,1.368],[-2.666,6.973],[0,7.748],[2.666,6.973],[4.967,5.377]],"o":[[-1.572,-0.046],[-1.049,1.094],[0.205,1.413],[3.965,4.466],[2.119,5.628],[0,6.289],[-2.119,5.652],[-1.185,1.322],[-0.205,1.39],[1.002,1.185],[1.572,-0.022],[4.967,-5.332],[2.666,-6.972],[0,-7.702],[-2.666,-6.972],[-1.23,-1.322]],"v":[[-7.161,-42.594],[-11.023,-40.816],[-12.288,-37.057],[-10.202,-33.023],[-1.077,-17.882],[2.102,-0.006],[-1.077,17.904],[-10.202,33.012],[-12.288,37.079],[-11.023,40.874],[-7.161,42.617],[-2.956,40.531],[8.493,22.074],[12.493,-0.006],[8.493,-22.018],[-2.956,-40.542]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9569,0.2627,0.2118],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[12.742,42.89],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"1 Outlines","sr":1,"st":0,"op":24,"ip":-5,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[9.137,25.999,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[24.355,101.586,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[1.641,0.046],[1.048,-1.23],[-0.274,-1.344],[-1.003,-1.413],[0,-6.243],[3.463,-4.466],[0.273,-1.345],[-1.048,-1.139],[-1.641,0.046],[-1.139,1.458],[-1.413,4.011],[0,4.375],[1.412,4.01],[2.598,3.236]],"o":[[-1.641,-0.046],[-1.048,1.14],[0.273,1.344],[3.463,4.466],[0,6.244],[-1.003,1.413],[-0.274,1.344],[1.048,1.231],[1.641,-0.045],[2.598,-3.236],[1.412,-4.01],[0,-4.375],[-1.413,-4.01],[-1.139,-1.458]],"v":[[-3.418,-25.703],[-7.451,-23.926],[-8.613,-20.2],[-6.699,-16.064],[-1.504,0],[-6.699,16.065],[-8.613,20.201],[-7.451,23.926],[-3.418,25.703],[0.752,23.448],[6.768,12.578],[8.887,0],[6.768,-12.578],[0.752,-23.447]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.9569,0.2627,0.2118],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.137,25.999],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3}],"v":"5.7.4","fr":12,"op":24,"ip":0,"assets":[]} \ No newline at end of file diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index 6a65612e2..dbf05cccf 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -18,15 +18,18 @@ import '../model/meeting_details_model.dart'; import '../model/meeting_model.dart'; class MeetingDetailsController extends GetxController - with GetSingleTickerProviderStateMixin { + with GetTickerProviderStateMixin { late TabController tabController; final RxInt currentIndex = 0.obs; + late AnimationController lottieController; + final PlayerController playerController = PlayerController(); RxBool isTop = false.obs; RxInt durationMax = 0.obs; RxDouble dragOffset = 210.w.obs; + RxBool isPlay = false.obs; RxInt currentDuration = 0.obs; RxInt speakerAllStarttime = (-1).obs; @@ -65,6 +68,8 @@ class MeetingDetailsController extends GetxController } currentIndex.value = tabController.index; }); + lottieController = AnimationController(vsync: this) + ..duration = const Duration(seconds: 2); final Map args = Get.arguments ?? {}; id = args['id']; _headers = { @@ -80,6 +85,7 @@ class MeetingDetailsController extends GetxController tabController.dispose(); playerController.dispose(); _isDisposed = true; + lottieController.dispose(); super.onClose(); } @@ -114,6 +120,15 @@ class MeetingDetailsController extends GetxController } } }); + playerController.onPlayerStateChanged.listen((PlayerState playerState) { + isPlay.value = playerState.isPlaying; + if (_isDisposed) return; + if (playerState.isPlaying) { + lottieController.repeat(); + } else { + lottieController.stop(); + } + }); if (meetingDetails.value.tasktype == 1) { List speakerList = await SqfliteApi.getMeetingSpeaker(id); textList.value = List.from(speakerList); diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart index f6bbd6984..fa8bb5621 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -71,25 +71,23 @@ class MeetingDetailsView extends GetView { ), child: Row( children: [ - StreamBuilder( - stream: controller.playerController.onPlayerStateChanged, - builder: (context, snapshot) { - bool isPlay = snapshot.data?.isPlaying ?? false; - return GestureDetector( - onTap: () { - if (isPlay) { - controller.playerController.pausePlayer(); - } else { - controller.playerController.startPlayer(); - } - }, - child: Icon( - isPlay ? Icons.pause_circle : Icons.play_circle, - color: Colors.black, - size: 30.w, - ), - ); + GestureDetector( + onTap: () { + if (controller.isPlay.value) { + controller.playerController.pausePlayer(); + } else { + controller.playerController.startPlayer(); + } }, + child: Obx( + () => Icon( + controller.isPlay.value + ? Icons.pause_circle + : Icons.play_circle, + color: Colors.black, + size: 30.w, + ), + ), ), 4.horizontalSpace, GestureDetector( @@ -352,26 +350,23 @@ class MeetingDetailsView extends GetView { size: 30.w, ), ), - StreamBuilder( - stream: - controller.playerController.onPlayerStateChanged, - builder: (context, snapshot) { - bool isPlay = snapshot.data?.isPlaying ?? false; - return GestureDetector( - onTap: () { - if (isPlay) { - controller.playerController.pausePlayer(); - } else { - controller.playerController.startPlayer(); - } - }, - child: Icon( - isPlay ? Icons.pause_circle : Icons.play_circle, - color: Colors.black, - size: 48.w, - ), - ); + GestureDetector( + onTap: () { + if (controller.isPlay.value) { + controller.playerController.pausePlayer(); + } else { + controller.playerController.startPlayer(); + } }, + child: Obx( + () => Icon( + controller.isPlay.value + ? Icons.pause_circle + : Icons.play_circle, + color: Colors.black, + size: 48.w, + ), + ), ), GestureDetector( onTap: () { diff --git a/lib/modules/meeting/views/speaker_all_bottom_sheet.dart b/lib/modules/meeting/views/speaker_all_bottom_sheet.dart index ecf12291e..c73e1575d 100644 --- a/lib/modules/meeting/views/speaker_all_bottom_sheet.dart +++ b/lib/modules/meeting/views/speaker_all_bottom_sheet.dart @@ -4,6 +4,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:lottie/lottie.dart'; import '../../../data/services/db/sqflite_api.dart'; import '../controllers/meeting_details_controller.dart'; @@ -361,6 +362,21 @@ class _SpeakerAllBottomSheetState extends State { fontSize: 12.sp, ), ), + if (isActive) + Container( + width: 16.w, + height: 16.w, + margin: EdgeInsets.only(left: 5.w), + alignment: Alignment.center, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: Transform.scale( + scale: 1.6, + child: Lottie.asset( + 'assets/lottie/sound_effect.json', + ), + ), + ), ], ), Padding( diff --git a/lib/modules/meeting/views/speech_tab.dart b/lib/modules/meeting/views/speech_tab.dart index 1d742a493..1d14f57ef 100644 --- a/lib/modules/meeting/views/speech_tab.dart +++ b/lib/modules/meeting/views/speech_tab.dart @@ -2,6 +2,7 @@ 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 '../controllers/meeting_details_controller.dart'; import 'speaker_all_bottom_sheet.dart'; @@ -87,6 +88,43 @@ class SpeechTab extends GetView { fontSize: 13.sp, ), ), + Obx(() { + bool isActive = false; + int starttime = item['starttime']; + int endtime = 0; + if (index == controller.textList.length - 1) { + endtime = controller.durationMax.value; + } else { + endtime = + controller.textList[index + 1]['starttime']; + } + if (controller.currentDuration.value != 0) { + if (controller.currentDuration.value >= starttime && + controller.currentDuration.value < endtime) { + isActive = true; + } + } + return isActive + ? Container( + width: 16.w, + height: 16.w, + margin: EdgeInsets.only(left: 5.w), + alignment: Alignment.center, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: BorderRadius.circular(4.r), + ), + child: Transform.scale( + scale: 1.6, + child: Lottie.asset( + 'assets/lottie/sound_effect.json', + controller: controller.lottieController, + ), + ), + ) + : const SizedBox(); + }), ], ), ), @@ -169,9 +207,17 @@ class SpeechTab extends GetView { return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { - controller.playerController - .seekTo(item['starttime']); - controller.playerController.startPlayer(); + if (isActive) { + if (controller.isPlay.value) { + controller.playerController.pausePlayer(); + } else { + controller.playerController.startPlayer(); + } + } else { + controller.playerController + .seekTo(item['starttime']); + controller.playerController.startPlayer(); + } }, child: Container( width: double.infinity, From 06807023ef0afe2b94a74dc279c876bca0996b8b Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 23 May 2025 21:09:52 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting_details_controller.dart | 6 - .../meeting/views/generate_bottom_sheet.dart | 392 ++++++++++++++++++ .../views/record_language_bottom_sheet.dart | 137 ++++++ lib/modules/meeting/views/speech_tab.dart | 17 +- lib/modules/meeting/views/summary_tab.dart | 17 +- 5 files changed, 561 insertions(+), 8 deletions(-) create mode 100644 lib/modules/meeting/views/generate_bottom_sheet.dart create mode 100644 lib/modules/meeting/views/record_language_bottom_sheet.dart diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index dbf05cccf..8200c349c 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -176,12 +176,6 @@ class MeetingDetailsController extends GetxController } } - void speakerCreate() async { - EasyLoading.show(); - await _audioSpeaker(); - EasyLoading.dismiss(); - } - void summaryCreate() async { EasyLoading.show(); if (meetingDetails.value.tasktype != 1) { diff --git a/lib/modules/meeting/views/generate_bottom_sheet.dart b/lib/modules/meeting/views/generate_bottom_sheet.dart new file mode 100644 index 000000000..c23066b17 --- /dev/null +++ b/lib/modules/meeting/views/generate_bottom_sheet.dart @@ -0,0 +1,392 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +import '../controllers/meeting_details_controller.dart'; +import 'record_language_bottom_sheet.dart'; + +class GenerateBottomSheet extends StatefulWidget { + const GenerateBottomSheet({super.key}); + + @override + State createState() => _GenerateBottomSheetState(); +} + +class _GenerateBottomSheetState extends State { + final _controller = Get.find(); + + bool _isSpeaker = true; + String _languageText = '中文普通话(简体)'; + String _language = 'zh-CN'; + + int _templateId = 0; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), + child: Row( + children: [ + Icon( + Icons.audio_file_outlined, + color: Colors.grey, + size: 32.w, + ), + 5.horizontalSpace, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _controller.meetingData.value.title, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + Text( + '${_controller.meetingData.value.seconds}s', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + ], + ), + ), + GestureDetector( + onTap: () => Get.back(), + child: Container( + width: 26.w, + height: 26.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(26.r), + ), + child: Icon( + Icons.close, + size: 18.w, + color: Colors.grey[400], + ), + ), + ) + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w), + margin: EdgeInsets.only(bottom: 10.w), + child: Divider( + height: 1, + color: Colors.grey[300], + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w), + child: Row( + children: [ + Expanded( + child: Text( + '选择总结模版', + style: TextStyle( + fontSize: 14.sp, + color: Colors.black87, + ), + ), + ), + Row( + children: [ + Text( + '查看全部', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + const Icon( + Icons.keyboard_arrow_right, + color: Colors.grey, + ) + ], + ), + ], + ), + ), + Container( + height: 130.w, + margin: EdgeInsets.only(bottom: 16.w), + child: ListView.builder( + itemCount: 10, + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 4.w), + itemBuilder: (BuildContext context, int index) { + return _item(); + }, + ), + ), + Container( + margin: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.r), + ), + child: Column( + children: [ + _row( + '区分说话人', + widget: Transform.scale( + scale: 0.8, + alignment: Alignment.centerRight, + child: Switch( + value: _isSpeaker, + onChanged: (bool value) { + setState(() { + _isSpeaker = value; + }); + }, + ), + ), + ), + _row( + '录音语言', + value: _languageText, + isBorder: false, + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (BuildContext context) { + return RecordLanguageBottomSheet(_language); + }, + ).then((value) { + if (value != null) { + setState(() { + _languageText = value['name']; + _language = value['value']; + }); + } + }); + }, + ), + ], + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w), + child: GestureDetector( + onTap: () { + Get.back(); + // _controller.summaryCreate(); + }, + child: Container( + height: 48.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(12.r), + ), + child: Text( + '立即生成', + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ); + } + + Widget _item() { + return Container( + width: 200.w, + margin: EdgeInsets.symmetric(horizontal: 8.w), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14.w), + ), + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(5.w), + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Colors.blue, Colors.pinkAccent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + ), + child: Container( + width: double.infinity, + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.w), + color: Colors.white, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 5.w), + child: Icon( + Icons.perm_phone_msg, + color: Colors.orange, + size: 30.w, + ), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Text( + '会议纪要', + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + Wrap( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 5.w, vertical: 2.w), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(4.r), + ), + child: Text( + '主题', + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey[600], + ), + ), + ), + ], + ), + // Text( + // '主题主题主题主题主题主题主题主题主题主题', + // overflow: TextOverflow.ellipsis, + // style: TextStyle( + // fontSize: 12.sp, + // color: Colors.grey[600], + // ), + // ), + ], + ), + ), + ), + Positioned( + top: 0, + left: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.w), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + bottomRight: Radius.circular(4.w), + ), + ), + child: Text( + '上次使用', + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Positioned( + right: -18.w, + bottom: -18.w, + child: Container( + width: 40.w, + height: 40.w, + padding: EdgeInsets.all(2.w), + alignment: Alignment.topLeft, + decoration: BoxDecoration( + color: Colors.pinkAccent, + borderRadius: BorderRadius.circular(40.r), + ), + child: Icon( + Icons.check, + color: Colors.white, + size: 18.w, + ), + ), + ), + ], + ), + ); + } + + Widget _row( + String title, { + String? value, + Widget? widget, + bool isBorder = true, + Function()? onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: EdgeInsets.only(left: 15.w), + child: Container( + height: 46.w, + padding: EdgeInsets.only(right: 10.w), + decoration: BoxDecoration( + border: isBorder + ? Border( + bottom: BorderSide( + color: Colors.grey[300]!, + ), + ) + : null, + ), + child: Row( + children: [ + Expanded( + child: Text( + title, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black87, + ), + ), + ), + widget ?? + Row( + children: [ + Text( + value ?? '', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + const Icon( + Icons.keyboard_arrow_right, + color: Colors.grey, + ) + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/modules/meeting/views/record_language_bottom_sheet.dart b/lib/modules/meeting/views/record_language_bottom_sheet.dart new file mode 100644 index 000000000..217613373 --- /dev/null +++ b/lib/modules/meeting/views/record_language_bottom_sheet.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class RecordLanguageBottomSheet extends StatefulWidget { + final String language; + + const RecordLanguageBottomSheet(this.language, {super.key}); + + @override + State createState() => + _RecordLanguageBottomSheetState(); +} + +class _RecordLanguageBottomSheetState extends State { + final List _languages = [ + {'name': '中文普通话(简体)', 'value': 'zh-CN'}, + {'name': '粤语', 'value': 'cant'}, + {'name': '四川话', 'value': 'sc'}, + {'name': '上海话', 'value': 'zh_shanghai'}, + {'name': '英文', 'value': 'en-US'}, + {'name': '日语', 'value': 'ja-JP'}, + {'name': '韩语', 'value': 'ko-KR'}, + {'name': '法语', 'value': 'fr-FR'}, + {'name': '西班牙语', 'value': 'es-MX'}, + {'name': '葡萄牙语', 'value': 'pt-BR'}, + {'name': '印尼语', 'value': 'id-ID'}, + ]; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '录音语言', + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + GestureDetector( + onTap: () => Get.back(), + child: Container( + width: 26.w, + height: 26.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(26.r), + ), + child: Icon( + Icons.close, + size: 18.w, + color: Colors.grey[400], + ), + ), + ) + ], + ), + ), + Divider( + height: 1, + color: Colors.grey[300], + ), + Container( + margin: EdgeInsets.symmetric(vertical: 20.w), + constraints: BoxConstraints( + maxHeight: 1.sh - 180.w, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.r), + ), + child: ListView.builder( + itemCount: _languages.length, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + Map data = _languages[index]; + return _item(index, data); + }, + ), + ), + ], + ), + ); + } + + Widget _item(int index, Map data) { + return GestureDetector( + onTap: () { + Get.back(result: data); + }, + child: Padding( + padding: EdgeInsets.only(left: 15.w), + child: Container( + height: 46.w, + padding: EdgeInsets.only(right: 10.w), + decoration: BoxDecoration( + border: index != 0 + ? Border( + top: BorderSide( + color: Colors.grey[300]!, + ), + ) + : null, + ), + child: Row( + children: [ + Expanded( + child: Text( + data['name'], + style: TextStyle( + fontSize: 14.sp, + color: Colors.black87, + ), + ), + ), + if (widget.language == data['value']) + const Icon( + Icons.check, + color: Colors.black87, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/modules/meeting/views/speech_tab.dart b/lib/modules/meeting/views/speech_tab.dart index 1d14f57ef..0e65a49c8 100644 --- a/lib/modules/meeting/views/speech_tab.dart +++ b/lib/modules/meeting/views/speech_tab.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:lottie/lottie.dart'; import '../controllers/meeting_details_controller.dart'; +import 'generate_bottom_sheet.dart'; import 'speaker_all_bottom_sheet.dart'; import 'speaker_bottom_sheet.dart'; @@ -290,7 +291,21 @@ class SpeechTab extends GetView { child: _button( Icons.auto_awesome, '生成', - controller.speakerCreate, + () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (BuildContext context) { + return const GenerateBottomSheet(); + }, + ); + }, ), ), ], diff --git a/lib/modules/meeting/views/summary_tab.dart b/lib/modules/meeting/views/summary_tab.dart index b4eb594d2..4da336ef9 100644 --- a/lib/modules/meeting/views/summary_tab.dart +++ b/lib/modules/meeting/views/summary_tab.dart @@ -6,6 +6,7 @@ import 'package:get/get.dart'; import '../../../data/services/db/sqflite_api.dart'; import '../controllers/meeting_details_controller.dart'; +import 'generate_bottom_sheet.dart'; import 'speaker_all_bottom_sheet.dart'; class SummaryTab extends StatefulWidget { @@ -318,7 +319,21 @@ class _SummaryTabState extends State { child: _button( Icons.auto_awesome, '生成', - _controller.summaryCreate, + () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (BuildContext context) { + return const GenerateBottomSheet(); + }, + ); + }, ), ), ], From eacd135277760c1140f9f402f473714d542bf8f5 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Sat, 24 May 2025 15:47:06 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=A8=A1=E7=89=88=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/bindings/template_binding.dart | 10 + .../controllers/template_controller.dart | 3 + .../meeting/views/generate_bottom_sheet.dart | 301 +++++++++++------- lib/modules/meeting/views/template_view.dart | 50 +++ lib/routes/app_pages.dart | 9 + lib/routes/app_routes.dart | 1 + 6 files changed, 258 insertions(+), 116 deletions(-) create mode 100644 lib/modules/meeting/bindings/template_binding.dart create mode 100644 lib/modules/meeting/controllers/template_controller.dart create mode 100644 lib/modules/meeting/views/template_view.dart diff --git a/lib/modules/meeting/bindings/template_binding.dart b/lib/modules/meeting/bindings/template_binding.dart new file mode 100644 index 000000000..414f2f659 --- /dev/null +++ b/lib/modules/meeting/bindings/template_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; +import '../controllers/template_controller.dart'; + +class TemplateBinding extends Bindings { + @override + void dependencies() { + // 注册控制器 + Get.lazyPut(() => TemplateController()); + } +} diff --git a/lib/modules/meeting/controllers/template_controller.dart b/lib/modules/meeting/controllers/template_controller.dart new file mode 100644 index 000000000..e61cec694 --- /dev/null +++ b/lib/modules/meeting/controllers/template_controller.dart @@ -0,0 +1,3 @@ +import 'package:get/get.dart'; + +class TemplateController extends GetxController {} diff --git a/lib/modules/meeting/views/generate_bottom_sheet.dart b/lib/modules/meeting/views/generate_bottom_sheet.dart index c23066b17..e3a14eb60 100644 --- a/lib/modules/meeting/views/generate_bottom_sheet.dart +++ b/lib/modules/meeting/views/generate_bottom_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import '../../../routes/app_routes.dart'; import '../controllers/meeting_details_controller.dart'; import 'record_language_bottom_sheet.dart'; @@ -20,6 +21,44 @@ class _GenerateBottomSheetState extends State { String _language = 'zh-CN'; int _templateId = 0; + int _lastTemplateId = 0; + final List _templateList = [ + { + 'id': 1, + 'name': '会议纪要', + 'icon': Icons.auto_stories, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + { + 'id': 2, + 'name': 'Autopilot', + 'icon': Icons.auto_graph, + 'tag': ['自适应结构', '全场景适配'], + 'desc': '', + }, + { + 'id': 3, + 'name': '推理Autopilot', + 'icon': Icons.add_to_drive_outlined, + 'tag': ['推理模型', '自动结构化'], + 'desc': '', + }, + { + 'id': 4, + 'name': '通话', + 'icon': Icons.perm_phone_msg, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + { + 'id': 5, + 'name': '自定义', + 'icon': Icons.auto_fix_high, + 'tag': [], + 'desc': '自定义模版', + }, + ]; @override Widget build(BuildContext context) { @@ -97,20 +136,32 @@ class _GenerateBottomSheetState extends State { ), ), ), - Row( - children: [ - Text( - '查看全部', - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey, + GestureDetector( + onTap: () async { + var result = await Get.toNamed(Routes.template, arguments: { + 'id': _templateId, + }); + if (result != null) { + setState(() { + _templateId = result; + }); + } + }, + child: Row( + children: [ + Text( + '查看全部', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), ), - ), - const Icon( - Icons.keyboard_arrow_right, - color: Colors.grey, - ) - ], + const Icon( + Icons.keyboard_arrow_right, + color: Colors.grey, + ) + ], + ), ), ], ), @@ -119,11 +170,12 @@ class _GenerateBottomSheetState extends State { height: 130.w, margin: EdgeInsets.only(bottom: 16.w), child: ListView.builder( - itemCount: 10, + itemCount: _templateList.length, scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: 4.w), itemBuilder: (BuildContext context, int index) { - return _item(); + Map data = _templateList[index]; + return _item(data); }, ), ), @@ -208,7 +260,7 @@ class _GenerateBottomSheetState extends State { ); } - Widget _item() { + Widget _item(Map data) { return Container( width: 200.w, margin: EdgeInsets.symmetric(horizontal: 8.w), @@ -216,119 +268,136 @@ class _GenerateBottomSheetState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(14.w), ), - child: Stack( - children: [ - Container( - padding: EdgeInsets.all(5.w), - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue, Colors.pinkAccent], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - ), - child: Container( - width: double.infinity, - padding: EdgeInsets.all(10.w), + child: GestureDetector( + onTap: () { + setState(() { + _templateId = data['id']; + }); + }, + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(5.w), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.w), color: Colors.white, + gradient: _templateId == data['id'] + ? const LinearGradient( + colors: [Colors.blue, Colors.pinkAccent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ) + : null, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 5.w), - child: Icon( - Icons.perm_phone_msg, - color: Colors.orange, - size: 30.w, - ), - ), - Padding( - padding: EdgeInsets.symmetric(vertical: 10.w), - child: Text( - '会议纪要', - style: TextStyle( - fontSize: 16.sp, - color: Colors.black87, - fontWeight: FontWeight.bold, + child: Container( + width: double.infinity, + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.w), + color: Colors.white, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 5.w), + child: Icon( + data['icon'], + color: Colors.orange, + size: 30.w, ), ), - ), - Wrap( - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: 5.w, vertical: 2.w), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(4.r), - ), - child: Text( - '主题', - style: TextStyle( - fontSize: 10.sp, - color: Colors.grey[600], - ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Text( + data['name'], + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + fontWeight: FontWeight.bold, ), ), - ], - ), - // Text( - // '主题主题主题主题主题主题主题主题主题主题', - // overflow: TextOverflow.ellipsis, - // style: TextStyle( - // fontSize: 12.sp, - // color: Colors.grey[600], - // ), - // ), - ], + ), + data['tag'].isNotEmpty + ? Wrap( + spacing: 5.w, + children: List.generate( + data['tag'].length, + (int idx) { + return Container( + padding: EdgeInsets.symmetric( + horizontal: 5.w, vertical: 2.w), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(4.r), + ), + child: Text( + data['tag'][idx], + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey[600], + ), + ), + ); + }, + ), + ) + : Text( + data['desc'], + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey[600], + ), + ), + ], + ), ), ), - ), - Positioned( - top: 0, - left: 0, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.w), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(12.w), - bottomRight: Radius.circular(4.w), + if (_lastTemplateId == data['id']) + Positioned( + top: 0, + left: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.w), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + bottomRight: Radius.circular(4.w), + ), + ), + child: Text( + '上次使用', + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), ), ), - child: Text( - '上次使用', - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - fontWeight: FontWeight.bold, + if (_templateId == data['id']) + Positioned( + right: -18.w, + bottom: -18.w, + child: Container( + width: 40.w, + height: 40.w, + padding: EdgeInsets.all(2.w), + alignment: Alignment.topLeft, + decoration: BoxDecoration( + color: Colors.pinkAccent, + borderRadius: BorderRadius.circular(40.r), + ), + child: Icon( + Icons.check, + color: Colors.white, + size: 18.w, + ), ), ), - ), - ), - Positioned( - right: -18.w, - bottom: -18.w, - child: Container( - width: 40.w, - height: 40.w, - padding: EdgeInsets.all(2.w), - alignment: Alignment.topLeft, - decoration: BoxDecoration( - color: Colors.pinkAccent, - borderRadius: BorderRadius.circular(40.r), - ), - child: Icon( - Icons.check, - color: Colors.white, - size: 18.w, - ), - ), - ), - ], + ], + ), ), ); } diff --git a/lib/modules/meeting/views/template_view.dart b/lib/modules/meeting/views/template_view.dart new file mode 100644 index 000000000..0e8c963cf --- /dev/null +++ b/lib/modules/meeting/views/template_view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +import '../controllers/template_controller.dart'; + +class TemplateView extends GetView { + const TemplateView({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: _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 0cf57c7b1..0918b9a72 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -7,8 +7,10 @@ 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/template_binding.dart'; import '../modules/meeting/views/meeting_details_view.dart'; import '../modules/meeting/views/meeting_view.dart'; +import '../modules/meeting/views/template_view.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; import '../modules/chat/bindings/chat_binding.dart'; @@ -140,6 +142,13 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), + GetPage( + name: Routes.template, + page: () => const TemplateView(), + binding: TemplateBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), + ), GetPage( name: Routes.timbre, page: () => const TimbreView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index ca88986e8..d1bb1c277 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -22,6 +22,7 @@ abstract class Routes { // 会议相关 static const meeting = '/meeting'; static const meetingDetails = '/meetingDetails'; + static const template = '/template'; // 语言功能 static const translation = '/translation'; // 实时翻译 From 304b8a81e0bfb225e3db58363a5a85c26a2c4973 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Sat, 24 May 2025 19:06:10 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=A8=A1=E7=89=88=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/bindings/template_binding.dart | 10 - .../controllers/template_controller.dart | 3 - .../meeting/views/generate_bottom_sheet.dart | 58 ++- lib/modules/meeting/views/template_view.dart | 365 +++++++++++++++++- lib/routes/app_pages.dart | 9 - lib/routes/app_routes.dart | 1 - pubspec.yaml | 2 +- 7 files changed, 404 insertions(+), 44 deletions(-) delete mode 100644 lib/modules/meeting/bindings/template_binding.dart delete mode 100644 lib/modules/meeting/controllers/template_controller.dart diff --git a/lib/modules/meeting/bindings/template_binding.dart b/lib/modules/meeting/bindings/template_binding.dart deleted file mode 100644 index 414f2f659..000000000 --- a/lib/modules/meeting/bindings/template_binding.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/template_controller.dart'; - -class TemplateBinding extends Bindings { - @override - void dependencies() { - // 注册控制器 - Get.lazyPut(() => TemplateController()); - } -} diff --git a/lib/modules/meeting/controllers/template_controller.dart b/lib/modules/meeting/controllers/template_controller.dart deleted file mode 100644 index e61cec694..000000000 --- a/lib/modules/meeting/controllers/template_controller.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:get/get.dart'; - -class TemplateController extends GetxController {} diff --git a/lib/modules/meeting/views/generate_bottom_sheet.dart b/lib/modules/meeting/views/generate_bottom_sheet.dart index e3a14eb60..413e81bc2 100644 --- a/lib/modules/meeting/views/generate_bottom_sheet.dart +++ b/lib/modules/meeting/views/generate_bottom_sheet.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import '../../../routes/app_routes.dart'; import '../controllers/meeting_details_controller.dart'; import 'record_language_bottom_sheet.dart'; +import 'template_view.dart'; class GenerateBottomSheet extends StatefulWidget { const GenerateBottomSheet({super.key}); @@ -15,14 +15,13 @@ class GenerateBottomSheet extends StatefulWidget { class _GenerateBottomSheetState extends State { final _controller = Get.find(); + final ScrollController _scrollController = ScrollController(); bool _isSpeaker = true; String _languageText = '中文普通话(简体)'; String _language = 'zh-CN'; - int _templateId = 0; - int _lastTemplateId = 0; - final List _templateList = [ + final List _originalTemplate = [ { 'id': 1, 'name': '会议纪要', @@ -51,15 +50,29 @@ class _GenerateBottomSheetState extends State { 'tag': ['主题', '代办事项'], 'desc': '', }, - { - 'id': 5, - 'name': '自定义', - 'icon': Icons.auto_fix_high, - 'tag': [], - 'desc': '自定义模版', - }, ]; + int _templateId = 0; + int _lastTemplateId = 0; + List _templateList = []; + + @override + void initState() { + super.initState(); + if (_lastTemplateId != 0) { + _templateId = _lastTemplateId; + } else { + _templateId = _originalTemplate[0]['id']; + } + _templateList = List.from(_originalTemplate); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( @@ -138,12 +151,24 @@ class _GenerateBottomSheetState extends State { ), GestureDetector( onTap: () async { - var result = await Get.toNamed(Routes.template, arguments: { - 'id': _templateId, - }); + var result = await Get.to(TemplateView( + _templateId, + _lastTemplateId, + )); if (result != null) { + _scrollController.jumpTo(0); + List list = List.from(_originalTemplate); + int index = list + .indexWhere((element) => element['id'] == result['id']); + if (index != -1) { + Map itemToMove = list.removeAt(index); + list.insert(0, itemToMove); + } else { + list.insert(0, result); + } setState(() { - _templateId = result; + _templateId = result['id']; + _templateList = list; }); } }, @@ -170,6 +195,7 @@ class _GenerateBottomSheetState extends State { height: 130.w, margin: EdgeInsets.only(bottom: 16.w), child: ListView.builder( + controller: _scrollController, itemCount: _templateList.length, scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: 4.w), @@ -237,7 +263,7 @@ class _GenerateBottomSheetState extends State { child: GestureDetector( onTap: () { Get.back(); - // _controller.summaryCreate(); + _controller.summaryCreate(); }, child: Container( height: 48.w, diff --git a/lib/modules/meeting/views/template_view.dart b/lib/modules/meeting/views/template_view.dart index 0e8c963cf..28e381f5a 100644 --- a/lib/modules/meeting/views/template_view.dart +++ b/lib/modules/meeting/views/template_view.dart @@ -2,10 +2,151 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import '../controllers/template_controller.dart'; +class TemplateView extends StatefulWidget { + final int templateId; + final int lastTemplateId; -class TemplateView extends GetView { - const TemplateView({super.key}); + const TemplateView(this.templateId, this.lastTemplateId, {super.key}); + + @override + State createState() => _TemplateViewState(); +} + +class _TemplateViewState extends State { + final List _categoryList = [ + { + 'id': 1, + 'name': '通用', + 'template': [ + { + 'id': 1, + 'name': '会议纪要', + 'icon': Icons.auto_stories, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + { + 'id': 2, + 'name': 'Autopilot', + 'icon': Icons.auto_graph, + 'tag': ['自适应结构', '全场景适配'], + 'desc': '', + }, + { + 'id': 3, + 'name': '推理Autopilot', + 'icon': Icons.add_to_drive_outlined, + 'tag': ['推理模型', '自动结构化'], + 'desc': '', + }, + { + 'id': 4, + 'name': '通话', + 'icon': Icons.perm_phone_msg, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + ], + }, + { + 'id': 2, + 'name': '会议', + 'template': [ + { + 'id': 5, + 'name': '咨询', + 'icon': Icons.auto_stories, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + { + 'id': 6, + 'name': '讨论', + 'icon': Icons.auto_graph, + 'tag': ['自适应结构', '全场景适配'], + 'desc': '', + }, + { + 'id': 7, + 'name': '汇报', + 'icon': Icons.add_to_drive_outlined, + 'tag': ['推理模型', '自动结构化'], + 'desc': '', + }, + { + 'id': 8, + 'name': '任务分配', + 'icon': Icons.perm_phone_msg, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + ], + }, + { + 'id': 3, + 'name': '演讲', + 'template': [ + { + 'id': 9, + 'name': '课堂笔记', + 'icon': Icons.auto_stories, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + { + 'id': 10, + 'name': '讲座', + 'icon': Icons.auto_graph, + 'tag': ['自适应结构', '全场景适配'], + 'desc': '', + }, + { + 'id': 11, + 'name': '培训', + 'icon': Icons.add_to_drive_outlined, + 'tag': ['推理模型', '自动结构化'], + 'desc': '', + }, + { + 'id': 12, + 'name': 'Sermon', + 'icon': Icons.perm_phone_msg, + 'tag': ['主题', '代办事项'], + 'desc': '', + }, + ], + }, + { + 'id': 4, + 'name': '自定义', + 'template': [ + { + 'id': 13, + 'name': '自定义', + 'icon': Icons.auto_fix_high, + 'tag': [], + 'desc': '自定义模版', + }, + ], + }, + ]; + + Map _templateData = {}; + + @override + void initState() { + super.initState(); + outerLoop: + for (var element in _categoryList) { + List template = element['template']; + for (var item in template) { + if (item['id'] == widget.templateId) { + _templateData = item; + break outerLoop; + } + } + } + } @override Widget build(BuildContext context) { @@ -45,6 +186,222 @@ class TemplateView extends GetView { // 构建主体内容 Widget _buildBody(BuildContext context, bool isDarkMode) { - return Container(); + return Column( + children: [ + Expanded( + child: ListView.builder( + itemCount: _categoryList.length, + itemBuilder: (BuildContext context, int index) { + Map data = _categoryList[index]; + return _item(data); + }, + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w), + child: GestureDetector( + onTap: () { + Get.back(result: _templateData); + }, + child: Container( + height: 48.w, + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(12.r), + ), + child: Text( + '就用这个模版', + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + ), + ), + ), + ), + ), + ], + ); + } + + Widget _item(Map data) { + return Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.w), + child: Row( + children: [ + Text( + data['name'], + style: TextStyle( + fontSize: 18.sp, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + const Icon( + Icons.keyboard_arrow_right, + color: Colors.grey, + ) + ], + ), + ), + Container( + height: 150.w, + margin: EdgeInsets.only(bottom: 15.w), + child: ListView.builder( + itemCount: data['template'].length, + scrollDirection: Axis.horizontal, + padding: EdgeInsets.symmetric(horizontal: 8.w), + itemBuilder: (BuildContext context, int index) { + Map template = data['template'][index]; + return _template(template); + }, + ), + ), + ], + ); + } + + Widget _template(Map template) { + return Container( + width: 200.w, + margin: EdgeInsets.symmetric(horizontal: 4.w), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14.w), + ), + child: GestureDetector( + onTap: () { + setState(() { + _templateData = template; + }); + }, + child: Stack( + children: [ + Container( + padding: EdgeInsets.all(5.w), + decoration: BoxDecoration( + color: Colors.white, + gradient: _templateData['id'] == template['id'] + ? const LinearGradient( + colors: [Colors.blue, Colors.pinkAccent], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ) + : null, + ), + child: Container( + width: double.infinity, + padding: EdgeInsets.all(10.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.w), + color: Colors.white, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 5.w), + child: Icon( + template['icon'], + color: Colors.orange, + size: 30.w, + ), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 10.w), + child: Text( + template['name'], + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ), + template['tag'].isNotEmpty + ? Wrap( + spacing: 5.w, + children: List.generate( + template['tag'].length, + (int idx) { + return Container( + padding: EdgeInsets.symmetric( + horizontal: 5.w, vertical: 2.w), + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(4.r), + ), + child: Text( + template['tag'][idx], + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey[600], + ), + ), + ); + }, + ), + ) + : Text( + template['desc'], + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ), + if (widget.lastTemplateId == template['id']) + Positioned( + top: 0, + left: 0, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.w), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12.w), + bottomRight: Radius.circular(4.w), + ), + ), + child: Text( + '上次使用', + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + if (_templateData['id'] == template['id']) + Positioned( + right: -18.w, + bottom: -18.w, + child: Container( + width: 40.w, + height: 40.w, + padding: EdgeInsets.all(2.w), + alignment: Alignment.topLeft, + decoration: BoxDecoration( + color: Colors.pinkAccent, + borderRadius: BorderRadius.circular(40.r), + ), + child: Icon( + Icons.check, + color: Colors.white, + size: 18.w, + ), + ), + ), + ], + ), + ), + ); } } diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 0918b9a72..0cf57c7b1 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -7,10 +7,8 @@ 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/template_binding.dart'; import '../modules/meeting/views/meeting_details_view.dart'; import '../modules/meeting/views/meeting_view.dart'; -import '../modules/meeting/views/template_view.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; import '../modules/chat/bindings/chat_binding.dart'; @@ -142,13 +140,6 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), - GetPage( - name: Routes.template, - page: () => const TemplateView(), - binding: TemplateBinding(), - transition: Transition.rightToLeft, - transitionDuration: Duration(milliseconds: 250), - ), GetPage( name: Routes.timbre, page: () => const TimbreView(), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index d1bb1c277..ca88986e8 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -22,7 +22,6 @@ abstract class Routes { // 会议相关 static const meeting = '/meeting'; static const meetingDetails = '/meetingDetails'; - static const template = '/template'; // 语言功能 static const translation = '/translation'; // 实时翻译 diff --git a/pubspec.yaml b/pubspec.yaml index 7a8bbb5c2..4cc804bcb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: web_socket_channel: ^3.0.2 flutter_dotenv: ^5.1.0 synchronized: ^3.1.0 - intl: ^0.19.0 + intl: ^0.20.2 crypto: ^3.0.3 flutter_tts: ^4.2.2 logger: ^2.0.2+1