import 'package:dsbridge_flutter/dsbridge_flutter.dart'; import 'package:electronic_assistant/base/base_page.dart'; import 'package:electronic_assistant/module/talk/controller.dart'; import 'package:electronic_assistant/resource/colors.gen.dart'; import 'package:electronic_assistant/utils/expand.dart'; import 'package:electronic_assistant/utils/fixed_size_tab_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '../../data/bean/talks.dart'; import '../../data/consts/event_report_id.dart'; import '../../resource/assets.gen.dart'; import '../../resource/string.gen.dart'; import '../../router/app_pages.dart'; import '../../utils/common_style.dart'; class TalkPage extends BasePage { final String? talkId; TalkPage({super.key}) : talkId = Get.arguments[TalkController.argumentTalkId] { Get.lazyPut(() => TalkController(), tag: talkId, fenix: true); } @override get controller => Get.find(tag: talkId); static void start(TalkBean item, {String eventTag = EventId.id_002}) { if (Get.currentRoute == RoutePath.talkDetail && Get.arguments[TalkController.argumentTalkId] == item.id) { return; } Get.toNamed(RoutePath.talkDetail, arguments: { TalkController.argumentItem: item, TalkController.argumentEventTag: eventTag, TalkController.argumentTalkId: item.id, }, preventDuplicates: false); } static void startById(String talkId, {String eventTag = ''}) { if (Get.currentRoute == RoutePath.talkDetail && Get.arguments[TalkController.argumentTalkId] == talkId) { return; } Get.toNamed(RoutePath.talkDetail, arguments: { TalkController.argumentTalkId: talkId, TalkController.argumentEventTag: eventTag, }, preventDuplicates: false); } @override Widget buildBody(BuildContext context) { return WillPopScope( onWillPop: () async { if (controller.isEditModel) { controller.onEditCancel(); return false; } if (controller.isSearchModel.value) { controller.onSearchCancel(); return false; } if (controller.isShowMindFullScreen.value) { controller.onExitMindFullScreen(); return false; } return true; }, child: Stack( children: [ Obx(() { return controller.temporaryController != null ? DWebViewWidget(controller: controller.temporaryController!) : const SizedBox.shrink(); }), Obx(() { return controller.isInitializedView.value ? DefaultTabController( initialIndex: controller.defaultIndex, length: controller.tabBeans.length, child: _buildTalkContentView()) : const SizedBox.shrink(); }), buildBottomView() ], ), ); } Widget _buildTalkContentView() { return Builder(builder: (context) { final statusBarHeight = MediaQuery.of(context).padding.top; return Obx(() { return Column( children: [ AnimatedContainer( decoration: BoxDecoration( gradient: LinearGradient( colors: ['#E1E9FF'.toColor(), '#F9FAFE'.toColor()], begin: Alignment.topCenter, end: Alignment.bottomCenter, stops: const [0, 1.0], ), ), height: controller.getChangeHeadHeight(), duration: controller.mindFullDuration, child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: Column(key: controller.headGlobalKey, children: [ SizedBox(height: statusBarHeight), _buildHeadView(), buildTabBar(context), ]), ), ), buildTalkContentView() ], ); }); }); } Widget _buildHeadView() { return Obx(() { if (controller.isEditModel) { return _buildEditHeadView(); } if (controller.isSearchModel.value) { return _buildSearchHeadView(); } return _buildHeadNormalView(); }); } Widget _buildSearchHeadView() { return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Align( alignment: Alignment.centerLeft, child: IconButton( icon: Assets.images.iconTalkEditCancel .image(width: 24.w, height: 24.w), onPressed: () { controller.onSearchCancel(); }, ), ), // TextField() Expanded( child: Container( height: 38.w, decoration: BoxDecoration( color: ColorName.white, border: Border.all(color: '#E7E9F6'.color, width: 1.w), borderRadius: BorderRadius.circular(6.w), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: TextField( maxLines: 1, style: TextStyle( fontSize: 14.sp, color: ColorName.primaryTextColor), // controller: controller.searchController, decoration: InputDecoration( hintText: StringName.talkSearchHint.tr, hintStyle: TextStyle( fontSize: 14.sp, color: ColorName.tertiaryTextColor, ), contentPadding: EdgeInsets.symmetric(horizontal: 8.w), border: const OutlineInputBorder(borderSide: BorderSide.none), ), onChanged: (value) { controller.setSearchChangeTxt(value); }), ), Text(controller.searchResultDesc.value, style: TextStyle( fontSize: 14.sp, color: ColorName.secondaryTextColor)), SizedBox(width: 8.w) ], ), )), SizedBox(width: 11.w), GestureDetector( onTap: () { controller.onSearchPrevious(); }, child: Assets.images.iconTalkSearchPrevious .image(width: 24.w, height: 24.w)), SizedBox(width: 16.w), GestureDetector( onTap: () { controller.onSearchNext(); }, child: Assets.images.iconTalkSearchNext .image(width: 24.w, height: 24.w)), SizedBox(width: 12.w), ], ); } Widget _buildEditHeadView() { return Row( children: [ Align( alignment: Alignment.centerLeft, child: IconButton( icon: Assets.images.iconTalkEditCancel .image(width: 24.w, height: 24.w), onPressed: () { controller.onEditCancel(); }, ), ), const Spacer(), GestureDetector( onTap: () { controller.onEditDoneClick(); }, child: Padding( padding: EdgeInsets.symmetric(horizontal: 12.w), child: Text(StringName.done.tr, style: TextStyle( fontSize: 17.sp, color: ColorName.primaryTextColor)), ), ) ], ); } Widget _buildHeadNormalView() { return Row( children: [ IconButton( icon: Assets.images.iconTalkBack.image(width: 24.w, height: 24.w), onPressed: () { Get.back(); }, ), SizedBox(width: 6.w), Obx(() { return GestureDetector( onTap: () { controller.onEditTitleClick(); }, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ ConstrainedBox( constraints: BoxConstraints(maxWidth: 0.65.sw), child: Text( maxLines: 1, overflow: TextOverflow.ellipsis, controller.talkBean.value?.title.value ?? '', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 15.sp, color: ColorName.primaryTextColor, ), ), ), SizedBox(width: 2.w), Assets.images.iconTalkEditTitle .image(width: 16.w, height: 16.w) ], ), SizedBox(height: 2.h), Text( controller.talkBean.value?.createTime ?? '', style: TextStyle( fontSize: 11.sp, color: ColorName.secondaryTextColor), ) ], ), ); }), const Spacer(), Row( children: [ IconButton( icon: Assets.images.iconTalkShare.image(width: 20.w, height: 20.w), onPressed: () { controller.onShareClick(); }, ), ], ) ], ); } Widget buildTabBar(BuildContext context) { TabController tabController = DefaultTabController.of(context); tabController.addListener(() { controller.updateTabIndex(tabController.index); }); return Column( children: [ Obx(() { return AbsorbPointer( absorbing: controller.isEditModel || controller.isSearchModel.value, child: TabBar( key: controller.tabBarGlobalKey, labelStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold), unselectedLabelStyle: TextStyle(fontSize: 14.sp), labelColor: ColorName.primaryTextColor, unselectedLabelColor: ColorName.secondaryTextColor, labelPadding: EdgeInsets.only(top: 4.h), dividerHeight: 0, splashFactory: NoSplash.splashFactory, indicator: FixedSizeTabIndicator( width: 16.w, height: 3.w, radius: 3, color: ColorName.colorPrimary), tabs: controller.tabBeans .map((bean) => Tab(text: bean.title)) .toList()), ); }), SizedBox(height: 6.h), Divider(height: 1, color: '#F2F4F9'.color) ], ); } @override bool immersive() { return true; } Widget buildTalkContentView() { return Obx(() { if (controller.talkBean.value?.status.value == TalkStatus.notAnalysis && controller.isUploading.value != true) { return buildNotAnalysisView(); } else { return buildTabContentView(); } }); } Widget buildTabContentView() { return Expanded( child: TabBarView( physics: controller.isEditModel || controller.isSearchModel.value || controller.checkTabBean.value?.isDisallowScroll == true ? const NeverScrollableScrollPhysics() : null, children: controller.pages, ), ); } Widget buildNotAnalysisView() { return SizedBox( width: double.infinity, child: Column( children: [ SizedBox(height: 119.h), SizedBox( width: 100.w, height: 100.w, child: Assets.images.iconTalkSummaryUnanalyzed.image()), SizedBox(height: 4.h), Text(StringName.talkUnAnalyzed.tr, style: TextStyle( fontSize: 15.sp, color: ColorName.primaryTextColor)), SizedBox(height: 2.h), Text(StringName.talkUnAnalyzedTips.tr, style: TextStyle( fontSize: 12.sp, color: ColorName.secondaryTextColor)), SizedBox(height: 24.h), GestureDetector( onTap: () { controller.checkCanAnalyze(); }, child: Container( decoration: getPrimaryBtnDecoration(8), width: 240.w, height: 48.w, child: Center( child: Text( StringName.talkAnalyzedBtnTxt.tr, style: TextStyle(fontSize: 16.sp, color: ColorName.white), ), ), ), ) ], ), ); } Widget buildBottomView() { return Obx(() { return AnimatedPositioned( key: controller.bottomGlobalKey, duration: controller.mindFullDuration, bottom: controller.getChangeBottomHeight(), left: 0, right: 0, child: Align( alignment: Alignment.bottomCenter, child: Container( margin: EdgeInsets.only(bottom: 20.h), child: IntrinsicHeight( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ _buildAIAnalysisView(), SizedBox(height: 8.h), _buildOperationBtnView(), SizedBox(height: 10.h), buildAudioView() ], ), ), ), ), ); }); } Widget _buildOperationBtnView() { return SizedBox( height: 28.w, child: Obx(() { return Visibility( visible: controller.talkBean.value?.status.value == TalkStatus.analysisSuccess, child: Row( children: [ Visibility( visible: controller.checkTabBean.value?.isShowEdit == true, child: _buildCommonOperationView( Assets.images.iconTalkEdit.provider(), StringName.talkUpdateTxt.tr, onClick: () { controller.onEditModelClick(); })), Visibility( visible: controller.checkTabBean.value?.isShowSearch == true, child: _buildCommonOperationView( Assets.images.iconTalkSearch.provider(), StringName.talkDetailSearchTxt.tr, onClick: () { controller.onSearchClick(); })), ], ), ); }), ); } Widget _buildCommonOperationView(ImageProvider provider, String txt, {VoidCallback? onClick}) { return GestureDetector( onTap: () => onClick?.call(), child: Container( margin: EdgeInsets.only(left: 12.w), height: 28.w, padding: EdgeInsets.symmetric(horizontal: 8.w), decoration: BoxDecoration( color: ColorName.white, borderRadius: BorderRadius.circular(100), boxShadow: [ BoxShadow( color: ColorName.black5.withOpacity(0.08), spreadRadius: 2, blurRadius: 12, offset: const Offset(0, 1), ), ]), child: IntrinsicWidth( child: Row( children: [ Image(image: provider, width: 16.w, height: 16.w), SizedBox(width: 2.w), Text(txt, style: TextStyle( height: 1, fontSize: 14.sp, color: ColorName.primaryTextColor)) ], ), ), ), ); } Widget _buildAIAnalysisView() { return Obx(() { return Visibility( visible: controller.talkBean.value?.status.value == TalkStatus.analysisSuccess, child: GestureDetector( onTap: () { controller.clickAIAnalysis(); }, child: Container( margin: EdgeInsets.only(right: 8.w), width: 64.w, height: 64.w, child: Assets.images.iconTalkLogo.image()), ), ); }); } buildAudioView() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(100), boxShadow: [ BoxShadow( color: ColorName.black5.withOpacity(0.1), spreadRadius: 2, blurRadius: 12, offset: const Offset(0, 3), ), ], ), margin: EdgeInsets.symmetric(horizontal: 12.w), padding: EdgeInsets.symmetric(vertical: 6.w, horizontal: 9.w), child: Row( children: [ GestureDetector( onTap: () { controller.clickPlayAudio(); }, child: Obx(() { return SizedBox( width: 36.w, height: 36.w, child: (controller.isAudioPlaying.value) ? Assets.images.iconTalkAudioPlaying.image() : Assets.images.iconTalkAudioPause.image()); }), ), SizedBox(width: 15.w), Builder(builder: (context) { return Flexible( child: Obx(() { return SliderTheme( data: SliderTheme.of(context).copyWith( thumbColor: Colors.white, overlayShape: SliderComponentShape.noOverlay, trackHeight: 8, activeTrackColor: "#8A89E9".toColor(), inactiveTrackColor: "#F6F5F8".toColor(), trackShape: CustomTrackShape(), ), child: Slider( value: controller.audioProgressValue.value, min: 0.0, max: controller.sliderMax, onChanged: (value) { controller.updateProgress(value); }, ), ); }), ); }), SizedBox(width: 11.w), Obx(() { return Text(controller.audioDuration.value.toFormattedString(), style: TextStyle( fontSize: 10.sp, color: ColorName.secondaryTextColor)); }) ], ), ); } } class CustomTrackShape extends RoundedRectSliderTrackShape { @override Rect getPreferredRect({ required RenderBox parentBox, Offset offset = Offset.zero, required SliderThemeData sliderTheme, bool isEnabled = false, bool isDiscrete = false, }) { final trackHeight = sliderTheme.trackHeight; final trackLeft = offset.dx; final trackTop = offset.dy + (parentBox.size.height - trackHeight!) / 2; final trackWidth = parentBox.size.width; return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight); } }