From 79cae4e417595384950ed13a150622392faa8c16 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Tue, 20 May 2025 15:31:13 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=80=9D=E7=BB=B4?= =?UTF-8?q?=E5=AF=BC=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting_details_controller.dart | 31 +- .../meeting/views/meeting_details_view.dart | 283 +++++++++++------- lib/modules/meeting/views/mind_map_tab.dart | 3 + 3 files changed, 179 insertions(+), 138 deletions(-) diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index 15275b1e7..a80dc30a2 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:flutter/material.dart'; 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:http/http.dart' as http; @@ -18,13 +19,13 @@ import '../model/meeting_model.dart'; class MeetingDetailsController extends GetxController with GetSingleTickerProviderStateMixin { - late ScrollController scrollController; late TabController tabController; final RxInt currentIndex = 0.obs; final PlayerController playerController = PlayerController(); RxBool isTop = false.obs; RxInt durationMax = 0.obs; + RxDouble dragOffset = 200.0.w.obs; int id = 0; @@ -52,8 +53,6 @@ class MeetingDetailsController extends GetxController @override void onInit() { super.onInit(); - scrollController = ScrollController(initialScrollOffset: 0.0); - scrollController.addListener(_onScroll); tabController = TabController(length: 4, vsync: this); tabController.addListener(() { currentIndex.value = tabController.index; @@ -70,38 +69,12 @@ class MeetingDetailsController extends GetxController @override void onClose() { - scrollController.dispose(); tabController.dispose(); playerController.dispose(); _isDisposed = true; super.onClose(); } - void _onScroll() async { - double pixels = scrollController.position.pixels; - double maxScrollExtent = scrollController.position.maxScrollExtent; - if (!isTop.value && pixels > 5) { - await scrollController.animateTo( - maxScrollExtent, - duration: const Duration(milliseconds: 100), - curve: Curves.easeOut, - ); - if (pixels > maxScrollExtent - 2) { - isTop.value = true; - } - } - if (isTop.value && pixels < maxScrollExtent - 5) { - await scrollController.animateTo( - 0, - duration: const Duration(milliseconds: 100), - curve: Curves.easeOut, - ); - if (pixels < 2) { - isTop.value = false; - } - } - } - void _getAgent() async { final GetStorage storage = GetStorage(); Map userInfo = storage.read("user_info"); diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart index d1b2ec7a5..8aaf00315 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -161,21 +161,63 @@ class MeetingDetailsView extends GetView { // 构建主体内容 Widget _buildBody(BuildContext context, bool isDarkMode) { + double initialPosition = 0; return Column( children: [ - _soundWave(isDarkMode), - Container( - padding: EdgeInsets.symmetric(horizontal: 8.w), - margin: EdgeInsets.symmetric(vertical: 10.w), - child: Obx( - () => Row( - children: [ - _iconItem(Icons.summarize, '概述', 0), - _iconItem(Icons.message, '转写', 1), - _iconItem(Icons.article, '总结', 2), - _iconItem(Icons.lan, '思维导图', 3), - ], - ), + GestureDetector( + behavior: HitTestBehavior.translucent, + onVerticalDragStart: (DragStartDetails dragStartDetails) { + initialPosition = dragStartDetails.localPosition.dy; + }, + onVerticalDragUpdate: (DragUpdateDetails dragUpdateDetails) { + controller.dragOffset.value += dragUpdateDetails.delta.dy; + }, + onVerticalDragEnd: (DragEndDetails dragEndDetails) { + double offset = initialPosition - dragEndDetails.localPosition.dy; + if (offset > 0) { + if (controller.isTop.value) { + controller.dragOffset.value = 0; + return; + } + if (offset > 60) { + controller.dragOffset.value = 0; + controller.isTop.value = true; + } else { + controller.dragOffset.value = 200.w; + controller.isTop.value = false; + } + } else { + if (!controller.isTop.value) { + controller.dragOffset.value = 200.w; + return; + } + if (offset < -60) { + controller.dragOffset.value = 200.w; + controller.isTop.value = false; + } else { + controller.dragOffset.value = 0; + controller.isTop.value = true; + } + } + }, + child: Column( + children: [ + _soundWave(isDarkMode), + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w), + margin: EdgeInsets.symmetric(vertical: 10.w), + child: Obx( + () => Row( + children: [ + _iconItem(Icons.summarize, '概述', 0), + _iconItem(Icons.message, '转写', 1), + _iconItem(Icons.article, '总结', 2), + _iconItem(Icons.lan, '思维导图', 3), + ], + ), + ), + ), + ], ), ), Expanded( @@ -198,108 +240,131 @@ class MeetingDetailsView extends GetView { } Widget _soundWave(bool isDarkMode) { - return Container( - height: 200.w, - margin: EdgeInsets.only(bottom: 10.w), - decoration: BoxDecoration( - color: Colors.grey[200]!, - borderRadius: BorderRadius.vertical( - bottom: Radius.circular(20.r), - ), - ), - child: Column( - children: [ - Container( - margin: EdgeInsets.only(bottom: 5.w), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8.r), - ), - child: AudioFileWaveforms( - size: Size(350.w, 120.w), - playerController: controller.playerController, - playerWaveStyle: PlayerWaveStyle( - fixedWaveColor: Colors.grey[300]!, - liveWaveColor: Colors.red, - seekLineColor: Colors.red, - scaleFactor: 600, - ), - ), - ), - SizedBox( - width: 350.w, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - StreamBuilder( - stream: controller.playerController.onCurrentDurationChanged, - builder: (context, snapshot) { - final position = snapshot.data ?? 0; - return Text( - DateUtil.formatDateMs( - position, - format: 'HH:mm:ss', - isUtc: true, + return Obx( + () => SizedBox( + height: controller.dragOffset.value < 0 + ? 0 + : controller.dragOffset.value > 200.w + ? 200.w + : controller.dragOffset.value, + child: Stack( + children: [ + Positioned( + bottom: 0, + child: Container( + width: 1.sw, + height: 200.w, + margin: EdgeInsets.only(bottom: 10.w), + decoration: BoxDecoration( + color: Colors.grey[200]!, + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(20.r), + ), + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 5.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.r), ), - style: TextStyle( - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - fontSize: 12.sp, + child: AudioFileWaveforms( + size: Size(350.w, 120.w), + playerController: controller.playerController, + playerWaveStyle: PlayerWaveStyle( + fixedWaveColor: Colors.grey[300]!, + liveWaveColor: Colors.red, + seekLineColor: Colors.red, + scaleFactor: 600, + ), ), - ); - }, - ), - Obx( - () => Text( - DateUtil.formatDateMs( - controller.durationMax.value, - format: 'HH:mm:ss', - isUtc: true, ), - style: TextStyle( - color: isDarkMode ? Colors.grey[400] : Colors.grey[500], - fontSize: 12.sp, + SizedBox( + width: 350.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StreamBuilder( + stream: controller + .playerController.onCurrentDurationChanged, + builder: (context, snapshot) { + final position = snapshot.data ?? 0; + return Text( + DateUtil.formatDateMs( + position, + format: 'HH:mm:ss', + isUtc: true, + ), + style: TextStyle( + color: isDarkMode + ? Colors.grey[400] + : Colors.grey[500], + fontSize: 12.sp, + ), + ); + }, + ), + Obx( + () => Text( + DateUtil.formatDateMs( + controller.durationMax.value, + format: 'HH:mm:ss', + isUtc: true, + ), + style: TextStyle( + color: isDarkMode + ? Colors.grey[400] + : Colors.grey[500], + fontSize: 12.sp, + ), + ), + ), + ], + ), ), - ), - ), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon( - Icons.replay_10, - color: Colors.black, - 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, + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + Icons.replay_10, + color: Colors.black, + 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, + ), + ); + }, + ), + Icon( + Icons.forward_10, + color: Colors.black, + size: 30.w, + ), + ], ), - ); - }, - ), - Icon( - Icons.forward_10, - color: Colors.black, - size: 30.w, + ], + ), ), - ], - ), - ], + ), + ], + ), ), ); } diff --git a/lib/modules/meeting/views/mind_map_tab.dart b/lib/modules/meeting/views/mind_map_tab.dart index 1f93e04fa..54b97dded 100644 --- a/lib/modules/meeting/views/mind_map_tab.dart +++ b/lib/modules/meeting/views/mind_map_tab.dart @@ -41,12 +41,14 @@ class _MindMapTabState extends State { height: 100%; margin: 0; padding: 0; + overflow: hidden; } .markmap { width: 100%; height: 100%; position: relative; + overflow: hidden; } .markmap>svg { @@ -76,6 +78,7 @@ class _MindMapTabState extends State { markmap: maxWidth: 300 colorFreezeLevel: 2 + initialExpandLevel: 3 --- ${_meetingDetailsController.meetingDetails.value.summary} From b7f833121428b99a37ea5cd15a0dff41beef1a43 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Tue, 20 May 2025 16:36:30 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=80=9D=E7=BB=B4?= =?UTF-8?q?=E5=AF=BC=E5=9B=BE=E7=BC=93=E5=AD=98=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting_details_controller.dart | 6 ++++- .../meeting/views/meeting_details_view.dart | 24 +++++++++++-------- lib/modules/meeting/views/mind_map_tab.dart | 7 +++++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index a80dc30a2..3c367eb2a 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -25,7 +25,7 @@ class MeetingDetailsController extends GetxController final PlayerController playerController = PlayerController(); RxBool isTop = false.obs; RxInt durationMax = 0.obs; - RxDouble dragOffset = 200.0.w.obs; + RxDouble dragOffset = 210.0.w.obs; int id = 0; @@ -86,6 +86,10 @@ class MeetingDetailsController extends GetxController final data = await SqfliteApi.getMeetingDetails(id); meetingData.value = MeetingModel.fromJson(data); meetingDetails.value = MeetingDetailsModel.fromJson(data['details']); + if (meetingDetails.value.summary.isNotEmpty) { + dragOffset.value = 0; + isTop.value = true; + } await playerController.preparePlayer( path: meetingData.value.filepath, noOfSamples: 1200, diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart index 8aaf00315..9af120ebb 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -183,16 +183,16 @@ class MeetingDetailsView extends GetView { controller.dragOffset.value = 0; controller.isTop.value = true; } else { - controller.dragOffset.value = 200.w; + controller.dragOffset.value = 210.w; controller.isTop.value = false; } } else { if (!controller.isTop.value) { - controller.dragOffset.value = 200.w; + controller.dragOffset.value = 210.w; return; } if (offset < -60) { - controller.dragOffset.value = 200.w; + controller.dragOffset.value = 210.w; controller.isTop.value = false; } else { controller.dragOffset.value = 0; @@ -226,11 +226,15 @@ class MeetingDetailsView extends GetView { child: TabBarView( controller: controller.tabController, physics: const NeverScrollableScrollPhysics(), - children: const [ - OverviewTab(), - SpeechTab(), - SummaryTab(), - MindMapTab(), + children: [ + const OverviewTab(), + const SpeechTab(), + const SummaryTab(), + Obx( + () => controller.meetingDetails.value.summary.isNotEmpty + ? const MindMapTab() + : const SizedBox(), + ), ], ), ), @@ -244,8 +248,8 @@ class MeetingDetailsView extends GetView { () => SizedBox( height: controller.dragOffset.value < 0 ? 0 - : controller.dragOffset.value > 200.w - ? 200.w + : controller.dragOffset.value > 210.w + ? 210.w : controller.dragOffset.value, child: Stack( children: [ diff --git a/lib/modules/meeting/views/mind_map_tab.dart b/lib/modules/meeting/views/mind_map_tab.dart index 54b97dded..40528c592 100644 --- a/lib/modules/meeting/views/mind_map_tab.dart +++ b/lib/modules/meeting/views/mind_map_tab.dart @@ -11,7 +11,8 @@ class MindMapTab extends StatefulWidget { State createState() => _MindMapTabState(); } -class _MindMapTabState extends State { +class _MindMapTabState extends State + with AutomaticKeepAliveClientMixin { late final WebViewController _controller; final _meetingDetailsController = Get.find(); @@ -93,8 +94,12 @@ ${_meetingDetailsController.meetingDetails.value.summary} ..loadHtmlString(html); } + @override + bool get wantKeepAlive => true; + @override Widget build(BuildContext context) { + super.build(context); return WebViewWidget( controller: _controller, ); From 995129f371cef3d53f8bf029fc6c3926acc2c55c Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 21 May 2025 14:33:52 +0800 Subject: [PATCH 3/4] =?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 --- .../meeting_details_controller.dart | 3 + .../views/speaker_all_bottom_sheet.dart | 4 +- lib/modules/meeting/views/speech_tab.dart | 14 +- lib/modules/meeting/views/summary_tab.dart | 212 +++++++++++++++--- 4 files changed, 188 insertions(+), 45 deletions(-) diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index 3c367eb2a..4aa2a200a 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -55,6 +55,9 @@ class MeetingDetailsController extends GetxController super.onInit(); tabController = TabController(length: 4, vsync: this); tabController.addListener(() { + if (currentIndex.value == 2 && tabController.index != 2) { + FocusScope.of(Get.context!).unfocus(); + } currentIndex.value = tabController.index; }); final Map args = Get.arguments ?? {}; diff --git a/lib/modules/meeting/views/speaker_all_bottom_sheet.dart b/lib/modules/meeting/views/speaker_all_bottom_sheet.dart index 48b331c8f..bb3271747 100644 --- a/lib/modules/meeting/views/speaker_all_bottom_sheet.dart +++ b/lib/modules/meeting/views/speaker_all_bottom_sheet.dart @@ -145,7 +145,9 @@ class _SpeakerAllBottomSheetState extends State { ), child: Focus( onFocusChange: (bool hasFocus) { - item['isShow'] = hasFocus; + setState(() { + item['isShow'] = hasFocus; + }); }, child: TextField( controller: item['speakerText'], diff --git a/lib/modules/meeting/views/speech_tab.dart b/lib/modules/meeting/views/speech_tab.dart index f7a4bb0f3..94c7debe2 100644 --- a/lib/modules/meeting/views/speech_tab.dart +++ b/lib/modules/meeting/views/speech_tab.dart @@ -21,7 +21,7 @@ class SpeechTab extends GetView { itemBuilder: (BuildContext context, int index) { Map item = controller.textList[index]; TextStyle textStyle = TextStyle( - fontSize: 16.sp, + fontSize: 15.sp, color: Colors.black87, letterSpacing: 0, height: 1.4, @@ -35,11 +35,11 @@ class SpeechTab extends GetView { children: [ if (item['speaker'].isNotEmpty) Container( - width: 12.w, - height: 12.w, - margin: EdgeInsets.only(right: 2.w), + width: 11.w, + height: 11.w, + margin: EdgeInsets.only(right: 3.w), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(28.r), + borderRadius: BorderRadius.circular(11.r), border: Border.all( color: const Color(0xFFFFC107), width: 3.w, @@ -71,7 +71,7 @@ class SpeechTab extends GetView { item['speaker'], style: TextStyle( color: const Color(0xFFFFC107), - fontSize: 14.sp, + fontSize: 13.sp, ), ), ), @@ -84,7 +84,7 @@ class SpeechTab extends GetView { ), style: TextStyle( color: Colors.grey, - fontSize: 14.sp, + fontSize: 13.sp, ), ), ], diff --git a/lib/modules/meeting/views/summary_tab.dart b/lib/modules/meeting/views/summary_tab.dart index d663ee748..b4eb594d2 100644 --- a/lib/modules/meeting/views/summary_tab.dart +++ b/lib/modules/meeting/views/summary_tab.dart @@ -4,15 +4,117 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import '../../../data/services/db/sqflite_api.dart'; import '../controllers/meeting_details_controller.dart'; import 'speaker_all_bottom_sheet.dart'; -class SummaryTab extends GetView { +class SummaryTab extends StatefulWidget { const SummaryTab({super.key}); + @override + State createState() => _SummaryTabState(); +} + +class _SummaryTabState extends State { + final _controller = Get.find(); + final String _addressPrefix = '地点:'; + String _addressText = ''; + final String _personnelPrefix = '与会人员:'; + String _personnelText = ''; + final TextEditingController _addressController = TextEditingController(); + final TextEditingController _personnelController = TextEditingController(); + + @override + void initState() { + super.initState(); + _addressController.text = _addressPrefix + + (_controller.meetingDetails.value.address.isNotEmpty + ? _controller.meetingDetails.value.address + : '[输入地点]'); + _personnelController.text = _personnelPrefix + + (_controller.meetingDetails.value.personnel.isNotEmpty + ? _controller.meetingDetails.value.personnel + : '[输入与会人员]'); + _addressController.addListener(_handleAddressChanged); + _personnelController.addListener(_handlePersonnelChanged); + } + + void _handleAddressChanged() { + String currentText = _addressController.text; + TextSelection currentSelection = _addressController.selection; + if (!currentText.startsWith(_addressPrefix)) { + _addressController.value = TextEditingValue( + text: _addressPrefix + _addressText, + selection: TextSelection.collapsed( + offset: _addressPrefix.length + _addressText.length, + ), + ); + return; + } else { + _addressText = currentText.substring(_addressPrefix.length); + } + + if (currentSelection.start < _addressPrefix.length || + currentSelection.end < _addressPrefix.length) { + int newStart = currentSelection.start < _addressPrefix.length + ? _addressPrefix.length + : currentSelection.start; + int newEnd = currentSelection.end < _addressPrefix.length + ? _addressPrefix.length + : currentSelection.end; + _addressController.selection = currentSelection.copyWith( + baseOffset: newStart, + extentOffset: newEnd, + ); + } + } + + void _handlePersonnelChanged() { + String currentText = _personnelController.text; + TextSelection currentSelection = _personnelController.selection; + if (!currentText.startsWith(_personnelPrefix)) { + _personnelController.value = TextEditingValue( + text: _personnelPrefix + _personnelText, + selection: TextSelection.collapsed( + offset: _personnelPrefix.length + _personnelText.length, + ), + ); + return; + } else { + _personnelText = currentText.substring(_personnelPrefix.length); + } + + if (currentSelection.start < _personnelPrefix.length || + currentSelection.end < _personnelPrefix.length) { + int newStart = currentSelection.start < _personnelPrefix.length + ? _personnelPrefix.length + : currentSelection.start; + int newEnd = currentSelection.end < _personnelPrefix.length + ? _personnelPrefix.length + : currentSelection.end; + _personnelController.selection = currentSelection.copyWith( + baseOffset: newStart, + extentOffset: newEnd, + ); + } + } + + @override + void dispose() { + _addressController.dispose(); + _personnelController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; + final TextStyle textStyle = TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], + fontSize: 16.sp, + letterSpacing: 0, + height: 1.5, + ); return Stack( children: [ @@ -25,7 +127,7 @@ class SummaryTab extends GetView { Padding( padding: EdgeInsets.symmetric(vertical: 10.w), child: Text( - controller.meetingData.value.title, + _controller.meetingData.value.title, style: TextStyle( fontSize: 20.sp, fontWeight: FontWeight.bold, @@ -43,7 +145,7 @@ class SummaryTab extends GetView { borderRadius: BorderRadius.circular(4.w), ), child: Text( - controller.meetingData.value.type, + _controller.meetingData.value.type, style: TextStyle( color: isDarkMode ? Colors.grey[400] : Colors.grey[500], @@ -59,7 +161,7 @@ class SummaryTab extends GetView { 2.horizontalSpace, Text( DateUtil.formatDateMs( - controller.meetingData.value.creationtime, + _controller.meetingData.value.creationtime, format: DateFormats.y_mo_d_h_m, ), style: TextStyle( @@ -75,7 +177,7 @@ class SummaryTab extends GetView { ), 2.horizontalSpace, Text( - '${controller.meetingData.value.seconds}s', + '${_controller.meetingData.value.seconds}s', style: TextStyle( color: isDarkMode ? Colors.grey[400] : Colors.grey[500], fontSize: 12.sp, @@ -103,43 +205,79 @@ class SummaryTab extends GetView { ), ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '日期和时间:${DateUtil.formatDateMs( - controller.meetingData.value.creationtime, - format: DateFormats.y_mo_d_h_m, - )}', - style: TextStyle( - color: - isDarkMode ? Colors.grey[400] : Colors.grey[700], - fontSize: 16.sp, + child: Focus( + onFocusChange: (bool hasFocus) { + if (!hasFocus) { + bool isEdit = false; + if (_addressText.isNotEmpty && + _addressText != + _controller.meetingDetails.value.address) { + _controller.meetingDetails.value.address = + _addressText; + isEdit = true; + } + if (_personnelText.isNotEmpty && + _personnelText != + _controller.meetingDetails.value.personnel) { + _controller.meetingDetails.value.personnel = + _personnelText; + isEdit = true; + } + if (isEdit) { + SqfliteApi.editMeeting( + _controller.id, + { + 'address': + _controller.meetingDetails.value.address, + 'personnel': + _controller.meetingDetails.value.personnel, + }, + ); + } + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '日期和时间:${DateUtil.formatDateMs( + _controller.meetingData.value.creationtime, + format: DateFormats.y_mo_d_h_m, + )}', + style: textStyle, ), - ), - Text( - '地点:${controller.meetingDetails.value.address.isNotEmpty ? controller.meetingDetails.value.address : '[输入地点]'}', - style: TextStyle( - color: - isDarkMode ? Colors.grey[400] : Colors.grey[700], - fontSize: 16.sp, + TextField( + controller: _addressController, + style: textStyle, + minLines: 1, + maxLines: 100, + decoration: const InputDecoration( + isDense: true, + fillColor: Colors.transparent, + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + ), ), - ), - Text( - '与会人员:${controller.meetingDetails.value.personnel.isNotEmpty ? controller.meetingDetails.value.personnel : '[输入与会人员]'}', - style: TextStyle( - color: - isDarkMode ? Colors.grey[400] : Colors.grey[700], - fontSize: 16.sp, + TextField( + controller: _personnelController, + style: textStyle, + minLines: 1, + maxLines: 100, + decoration: const InputDecoration( + isDense: true, + fillColor: Colors.transparent, + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + ), ), - ), - ], + ], + ), ), ), Padding( padding: EdgeInsets.only(top: 10.w), child: MarkdownBody( - data: controller.meetingDetails.value.summary, + data: _controller.meetingDetails.value.summary, ), ), ], @@ -154,7 +292,7 @@ class SummaryTab extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (controller.meetingDetails.value.summary.isNotEmpty) + if (_controller.meetingDetails.value.summary.isNotEmpty) _button( Icons.supervisor_account, '编辑发言者', @@ -174,13 +312,13 @@ class SummaryTab extends GetView { ); }, ), - if (controller.meetingDetails.value.summary.isEmpty) + if (_controller.meetingDetails.value.summary.isEmpty) SizedBox( width: 150.w, child: _button( Icons.auto_awesome, '生成', - controller.summaryCreate, + _controller.summaryCreate, ), ), ], From 051ea80398e4b5cb39f1fc48560651d99286227b Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 21 May 2025 18:24:50 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=96=87=E5=AD=97=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting_details_controller.dart | 7 +- .../meeting/views/meeting_details_view.dart | 123 ++++++++++-------- lib/modules/meeting/views/speech_tab.dart | 52 ++++++-- 3 files changed, 120 insertions(+), 62 deletions(-) diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index 4aa2a200a..8f085f76e 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/lib/modules/meeting/controllers/meeting_details_controller.dart @@ -25,7 +25,9 @@ class MeetingDetailsController extends GetxController final PlayerController playerController = PlayerController(); RxBool isTop = false.obs; RxInt durationMax = 0.obs; - RxDouble dragOffset = 210.0.w.obs; + RxDouble dragOffset = 210.w.obs; + + RxInt currentDuration = 0.obs; int id = 0; @@ -99,6 +101,9 @@ class MeetingDetailsController extends GetxController ); durationMax.value = await playerController.getDuration(DurationType.max); playerController.setFinishMode(finishMode: FinishMode.pause); + playerController.onCurrentDurationChanged.listen((int position) { + currentDuration.value = position; + }); 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 9af120ebb..f6bbd6984 100644 --- a/lib/modules/meeting/views/meeting_details_view.dart +++ b/lib/modules/meeting/views/meeting_details_view.dart @@ -92,14 +92,22 @@ class MeetingDetailsView extends GetView { }, ), 4.horizontalSpace, - AudioFileWaveforms( - size: Size(130.w, 30.w), - playerController: controller.playerController, - playerWaveStyle: PlayerWaveStyle( - fixedWaveColor: Colors.grey[400]!, - liveWaveColor: Colors.red, - seekLineColor: Colors.red, - scaleFactor: 100, + GestureDetector( + onTap: () { + if (controller.isSpeakerText.value) return; + controller.dragOffset.value = 210.w; + controller.isTop.value = false; + }, + child: AudioFileWaveforms( + size: Size(130.w, 30.w), + playerController: controller.playerController, + enableSeekGesture: false, + playerWaveStyle: PlayerWaveStyle( + fixedWaveColor: Colors.grey[400]!, + liveWaveColor: Colors.red, + seekLineColor: Colors.red, + scaleFactor: 100, + ), ), ), ], @@ -203,19 +211,23 @@ class MeetingDetailsView extends GetView { child: Column( children: [ _soundWave(isDarkMode), - Container( - padding: EdgeInsets.symmetric(horizontal: 8.w), - margin: EdgeInsets.symmetric(vertical: 10.w), - child: Obx( - () => Row( - children: [ - _iconItem(Icons.summarize, '概述', 0), - _iconItem(Icons.message, '转写', 1), - _iconItem(Icons.article, '总结', 2), - _iconItem(Icons.lan, '思维导图', 3), - ], - ), - ), + Obx( + () => !controller.isSpeakerText.value + ? Container( + padding: EdgeInsets.symmetric(horizontal: 8.w), + margin: EdgeInsets.symmetric(vertical: 10.w), + child: Obx( + () => Row( + children: [ + _iconItem(Icons.summarize, '概述', 0), + _iconItem(Icons.message, '转写', 1), + _iconItem(Icons.article, '总结', 2), + _iconItem(Icons.lan, '思维导图', 3), + ], + ), + ), + ) + : const SizedBox(), ), ], ), @@ -289,25 +301,20 @@ class MeetingDetailsView extends GetView { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - StreamBuilder( - stream: controller - .playerController.onCurrentDurationChanged, - builder: (context, snapshot) { - final position = snapshot.data ?? 0; - return Text( - DateUtil.formatDateMs( - position, - format: 'HH:mm:ss', - isUtc: true, - ), - style: TextStyle( - color: isDarkMode - ? Colors.grey[400] - : Colors.grey[500], - fontSize: 12.sp, - ), - ); - }, + Obx( + () => Text( + DateUtil.formatDateMs( + controller.currentDuration.value, + format: 'HH:mm:ss', + isUtc: true, + ), + style: TextStyle( + color: isDarkMode + ? Colors.grey[400] + : Colors.grey[500], + fontSize: 12.sp, + ), + ), ), Obx( () => Text( @@ -330,10 +337,20 @@ class MeetingDetailsView extends GetView { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Icon( - Icons.replay_10, - color: Colors.black, - size: 30.w, + GestureDetector( + onTap: () { + int progress = + controller.currentDuration.value - 10000; + if (progress < 0) { + progress = 0; + } + controller.playerController.seekTo(progress); + }, + child: Icon( + Icons.replay_10, + color: Colors.black, + size: 30.w, + ), ), StreamBuilder( stream: @@ -356,10 +373,17 @@ class MeetingDetailsView extends GetView { ); }, ), - Icon( - Icons.forward_10, - color: Colors.black, - size: 30.w, + GestureDetector( + onTap: () { + int progress = + controller.currentDuration.value + 10000; + controller.playerController.seekTo(progress); + }, + child: Icon( + Icons.forward_10, + color: Colors.black, + size: 30.w, + ), ), ], ), @@ -382,9 +406,6 @@ class MeetingDetailsView extends GetView { onTap: () { if (isActive) return; controller.tabController.index = index; - controller.isSpeakerText.value = false; - controller.editSpeakerTextId.value = 0; - controller.editSpeakerTextList = []; }, child: Container( height: 45.w, diff --git a/lib/modules/meeting/views/speech_tab.dart b/lib/modules/meeting/views/speech_tab.dart index 94c7debe2..a61ebae6a 100644 --- a/lib/modules/meeting/views/speech_tab.dart +++ b/lib/modules/meeting/views/speech_tab.dart @@ -151,16 +151,46 @@ class SpeechTab extends GetView { ), ); } else { - return Container( - padding: EdgeInsets.symmetric( - horizontal: 14.w, - vertical: 5.w, - ), - child: Text( - item['content'], - style: textStyle, - ), - ); + return 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 GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + controller.playerController + .seekTo(item['starttime']); + controller.playerController.startPlayer(); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 14.w, + vertical: 5.w, + ), + child: Text( + item['content'], + style: TextStyle( + fontSize: 15.sp, + color: isActive ? Colors.red : Colors.black87, + letterSpacing: 0, + height: 1.4, + ), + ), + ), + ); + }); } }), ], @@ -183,6 +213,8 @@ class SpeechTab extends GetView { '编辑', () { controller.isSpeakerText.value = true; + controller.dragOffset.value = 0; + controller.isTop.value = true; }, ), if (controller.meetingDetails.value.tasktype == 1 &&