view.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. import 'package:dsbridge_flutter/dsbridge_flutter.dart';
  2. import 'package:electronic_assistant/base/base_page.dart';
  3. import 'package:electronic_assistant/module/talk/controller.dart';
  4. import 'package:electronic_assistant/resource/colors.gen.dart';
  5. import 'package:electronic_assistant/utils/expand.dart';
  6. import 'package:electronic_assistant/utils/fixed_size_tab_indicator.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter_screenutil/flutter_screenutil.dart';
  10. import 'package:get/get.dart';
  11. import '../../data/bean/talks.dart';
  12. import '../../data/consts/event_report_id.dart';
  13. import '../../resource/assets.gen.dart';
  14. import '../../resource/string.gen.dart';
  15. import '../../router/app_pages.dart';
  16. import '../../utils/common_style.dart';
  17. class TalkPage extends BasePage<TalkController> {
  18. final String? talkId;
  19. TalkPage({super.key})
  20. : talkId = Get.arguments[TalkController.argumentTalkId] {
  21. Get.lazyPut<TalkController>(() => TalkController(),
  22. tag: talkId, fenix: true);
  23. }
  24. @override
  25. get controller => Get.find<TalkController>(tag: talkId);
  26. static void start(TalkBean item, {String eventTag = EventId.id_002}) {
  27. if (Get.currentRoute == RoutePath.talkDetail &&
  28. Get.arguments[TalkController.argumentTalkId] == item.id) {
  29. return;
  30. }
  31. Get.toNamed(RoutePath.talkDetail,
  32. arguments: {
  33. TalkController.argumentItem: item,
  34. TalkController.argumentEventTag: eventTag,
  35. TalkController.argumentTalkId: item.id,
  36. },
  37. preventDuplicates: false);
  38. }
  39. static void startById(String talkId, {String eventTag = ''}) {
  40. if (Get.currentRoute == RoutePath.talkDetail &&
  41. Get.arguments[TalkController.argumentTalkId] == talkId) {
  42. return;
  43. }
  44. Get.toNamed(RoutePath.talkDetail,
  45. arguments: {
  46. TalkController.argumentTalkId: talkId,
  47. TalkController.argumentEventTag: eventTag,
  48. },
  49. preventDuplicates: false);
  50. }
  51. @override
  52. Widget buildBody(BuildContext context) {
  53. return WillPopScope(
  54. onWillPop: () async {
  55. if (controller.isEditModel) {
  56. controller.onEditCancel();
  57. return false;
  58. }
  59. if (controller.isShowMindFullScreen.value) {
  60. controller.onExitMindFullScreen();
  61. return false;
  62. }
  63. return true;
  64. },
  65. child: Stack(
  66. children: [
  67. Obx(() {
  68. return controller.temporaryController != null
  69. ? DWebViewWidget(controller: controller.temporaryController!)
  70. : const SizedBox.shrink();
  71. }),
  72. Obx(() {
  73. return controller.isInitializedView.value
  74. ? DefaultTabController(
  75. initialIndex: controller.defaultIndex,
  76. length: controller.tabBeans.length,
  77. child: _buildTalkContentView())
  78. : const SizedBox.shrink();
  79. }),
  80. buildBottomView()
  81. ],
  82. ),
  83. );
  84. }
  85. Widget _buildTalkContentView() {
  86. return Builder(builder: (context) {
  87. final statusBarHeight = MediaQuery.of(context).padding.top;
  88. return Obx(() {
  89. return Column(
  90. children: [
  91. AnimatedContainer(
  92. key: controller.headGlobalKey,
  93. decoration: BoxDecoration(
  94. gradient: LinearGradient(
  95. colors: ['#E1E9FF'.toColor(), '#F9FAFE'.toColor()],
  96. begin: Alignment.topCenter,
  97. end: Alignment.bottomCenter,
  98. stops: const [0, 1.0],
  99. ),
  100. ),
  101. height: controller.isShowMindFullScreen.value
  102. ? 0
  103. : controller.headViewHeight,
  104. duration: controller.mindFullDuration,
  105. child: SingleChildScrollView(
  106. physics: const NeverScrollableScrollPhysics(),
  107. child: Column(children: [
  108. SizedBox(height: statusBarHeight),
  109. Row(
  110. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  111. children: [
  112. _buildToolbarLeftView(),
  113. _buildToolbarRightView(),
  114. ],
  115. ),
  116. buildTabBar(context),
  117. ]),
  118. ),
  119. ),
  120. buildTalkContentView()
  121. ],
  122. );
  123. });
  124. });
  125. }
  126. Widget buildTabBar(BuildContext context) {
  127. TabController tabController = DefaultTabController.of(context);
  128. tabController.addListener(() {
  129. controller.updateTabIndex(tabController.index);
  130. });
  131. return Obx(() {
  132. if (!controller.isEditModel) {
  133. return Column(
  134. children: [
  135. TabBar(
  136. labelStyle:
  137. TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
  138. unselectedLabelStyle: TextStyle(fontSize: 14.sp),
  139. labelColor: ColorName.primaryTextColor,
  140. unselectedLabelColor: ColorName.secondaryTextColor,
  141. labelPadding: EdgeInsets.only(top: 4.h),
  142. dividerHeight: 0,
  143. splashFactory: NoSplash.splashFactory,
  144. indicator: FixedSizeTabIndicator(
  145. width: 16.w,
  146. height: 3.w,
  147. radius: 3,
  148. color: ColorName.colorPrimary),
  149. tabs: controller.tabBeans
  150. .map((bean) => Tab(text: bean.title))
  151. .toList()),
  152. SizedBox(height: 6.h),
  153. Divider(height: 1, color: '#F2F4F9'.color)
  154. ],
  155. );
  156. } else {
  157. return SizedBox(height: 8.h, width: double.infinity);
  158. }
  159. });
  160. }
  161. @override
  162. bool immersive() {
  163. return true;
  164. }
  165. Widget buildTalkContentView() {
  166. return Obx(() {
  167. if (controller.talkBean.value?.status.value == TalkStatus.notAnalysis &&
  168. controller.isUploading.value != true) {
  169. if (controller.isShowElectricLow.value) {
  170. return buildElectricLowView();
  171. } else {
  172. return buildNotAnalysisView();
  173. }
  174. } else {
  175. return buildTabContentView();
  176. }
  177. });
  178. }
  179. Widget buildTabContentView() {
  180. return Expanded(
  181. child: TabBarView(
  182. physics: controller.isEditModel ||
  183. controller.checkTabBean.value?.isDisallowScroll == true
  184. ? const NeverScrollableScrollPhysics()
  185. : null,
  186. children: controller.pages,
  187. ),
  188. );
  189. }
  190. Widget buildNotAnalysisView() {
  191. return SizedBox(
  192. width: double.infinity,
  193. child: Column(
  194. children: [
  195. SizedBox(height: 119.h),
  196. SizedBox(
  197. width: 100.w,
  198. height: 100.w,
  199. child: Assets.images.iconTalkSummaryUnanalyzed.image()),
  200. SizedBox(height: 4.h),
  201. Text(StringName.talkUnAnalyzed.tr,
  202. style: TextStyle(
  203. fontSize: 15.sp, color: ColorName.primaryTextColor)),
  204. SizedBox(height: 2.h),
  205. Text(StringName.talkUnAnalyzedTips.tr,
  206. style: TextStyle(
  207. fontSize: 12.sp, color: ColorName.secondaryTextColor)),
  208. SizedBox(height: 24.h),
  209. GestureDetector(
  210. onTap: () {
  211. controller.checkCanAnalyze();
  212. },
  213. child: Container(
  214. decoration: getPrimaryBtnDecoration(8),
  215. width: 240.w,
  216. height: 48.w,
  217. child: Center(
  218. child: Text(
  219. StringName.talkAnalyzedBtnTxt.tr,
  220. style: TextStyle(fontSize: 16.sp, color: ColorName.white),
  221. ),
  222. ),
  223. ),
  224. )
  225. ],
  226. ),
  227. );
  228. }
  229. Widget buildElectricLowView() {
  230. return GestureDetector(
  231. onTap: () {
  232. controller.onGoElectricStore();
  233. },
  234. child: Container(
  235. color: const Color(0xFFDFE4FC),
  236. padding:
  237. EdgeInsets.only(left: 16.w, right: 12.w, top: 8.h, bottom: 8.h),
  238. child: Row(
  239. children: [
  240. SizedBox(
  241. width: 46.w,
  242. height: 56.w,
  243. child: Assets.images.iconTalkElectricLow.image()),
  244. SizedBox(width: 10.w),
  245. IntrinsicHeight(
  246. child: Column(
  247. crossAxisAlignment: CrossAxisAlignment.start,
  248. children: [
  249. SizedBox(
  250. width: 90.w,
  251. height: 21.w,
  252. child: Assets.images.iconTalkElectricLowTxt.image()),
  253. SizedBox(width: 1.w),
  254. Text(StringName.talkElectricLow.tr,
  255. style: TextStyle(
  256. fontSize: 12.sp,
  257. color: ColorName.secondaryTextColor)),
  258. ],
  259. ),
  260. ),
  261. const Spacer(),
  262. Container(
  263. decoration: getPrimaryBtnDecoration(8),
  264. width: 100.w,
  265. height: 36.w,
  266. child: Center(
  267. child: Text(StringName.talkGoStore.tr,
  268. style:
  269. TextStyle(fontSize: 16.sp, color: ColorName.white)),
  270. ))
  271. ],
  272. ),
  273. ),
  274. );
  275. }
  276. Widget buildBottomView() {
  277. return Obx(() {
  278. return Visibility(
  279. key: controller.bottomGlobalKey,
  280. visible: controller.isEditModel == false,
  281. child: AnimatedPositioned(
  282. duration: controller.mindFullDuration,
  283. bottom: controller.isShowMindFullScreen.value
  284. ? controller.getBottomViewHeight()
  285. : 0.h,
  286. left: 0,
  287. right: 0,
  288. child: Align(
  289. alignment: Alignment.bottomCenter,
  290. child: Container(
  291. margin: EdgeInsets.only(bottom: 20.h),
  292. child: IntrinsicHeight(
  293. child: Column(
  294. crossAxisAlignment: CrossAxisAlignment.end,
  295. children: [
  296. _buildAIAnalysisView(),
  297. SizedBox(height: 8.h),
  298. _buildTalkEditView(),
  299. SizedBox(height: 10.h),
  300. buildAudioView()
  301. ],
  302. ),
  303. ),
  304. ),
  305. ),
  306. ),
  307. );
  308. });
  309. }
  310. Widget _buildTalkEditView() {
  311. return Obx(() {
  312. return Visibility(
  313. visible: controller.talkBean.value?.status.value ==
  314. TalkStatus.analysisSuccess &&
  315. controller.checkTabBean.value?.isShowEdit == true,
  316. maintainState: true,
  317. maintainAnimation: true,
  318. maintainSize: true,
  319. child: GestureDetector(
  320. onTap: () => controller.onEditModelClick(),
  321. child: Align(
  322. alignment: Alignment.centerLeft,
  323. child: Container(
  324. margin: EdgeInsets.only(left: 12.w),
  325. padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.w),
  326. decoration: BoxDecoration(
  327. color: ColorName.white,
  328. borderRadius: BorderRadius.circular(100),
  329. boxShadow: [
  330. BoxShadow(
  331. color: ColorName.black5.withOpacity(0.08),
  332. spreadRadius: 2,
  333. blurRadius: 12,
  334. offset: const Offset(0, 1),
  335. ),
  336. ]),
  337. child: IntrinsicWidth(
  338. child: Row(
  339. children: [
  340. Assets.images.iconTalkEdit.image(width: 16.w, height: 16.w),
  341. SizedBox(width: 2.w),
  342. Text(StringName.talkUpdateTxt.tr,
  343. style: TextStyle(
  344. height: 1,
  345. fontSize: 14.sp,
  346. color: ColorName.primaryTextColor))
  347. ],
  348. ),
  349. ),
  350. ),
  351. ),
  352. ),
  353. );
  354. });
  355. }
  356. Widget _buildAIAnalysisView() {
  357. return Obx(() {
  358. return Visibility(
  359. visible: controller.talkBean.value?.status.value ==
  360. TalkStatus.analysisSuccess,
  361. child: GestureDetector(
  362. onTap: () {
  363. controller.clickAIAnalysis();
  364. },
  365. child: Container(
  366. margin: EdgeInsets.only(right: 8.w),
  367. width: 64.w,
  368. height: 64.w,
  369. child: Assets.images.iconTalkLogo.image()),
  370. ),
  371. );
  372. });
  373. }
  374. buildAudioView() {
  375. return Container(
  376. decoration: BoxDecoration(
  377. color: Colors.white,
  378. borderRadius: BorderRadius.circular(100),
  379. boxShadow: [
  380. BoxShadow(
  381. color: ColorName.black5.withOpacity(0.1),
  382. spreadRadius: 2,
  383. blurRadius: 12,
  384. offset: const Offset(0, 3),
  385. ),
  386. ],
  387. ),
  388. margin: EdgeInsets.symmetric(horizontal: 12.w),
  389. padding: EdgeInsets.symmetric(vertical: 6.w, horizontal: 9.w),
  390. child: Row(
  391. children: [
  392. GestureDetector(
  393. onTap: () {
  394. controller.clickPlayAudio();
  395. },
  396. child: Obx(() {
  397. return SizedBox(
  398. width: 36.w,
  399. height: 36.w,
  400. child: (controller.isAudioPlaying.value)
  401. ? Assets.images.iconTalkAudioPlaying.image()
  402. : Assets.images.iconTalkAudioPause.image());
  403. }),
  404. ),
  405. SizedBox(width: 15.w),
  406. Builder(builder: (context) {
  407. return Flexible(
  408. child: Obx(() {
  409. return SliderTheme(
  410. data: SliderTheme.of(context).copyWith(
  411. thumbColor: Colors.white,
  412. overlayShape: SliderComponentShape.noOverlay,
  413. trackHeight: 8,
  414. activeTrackColor: "#8A89E9".toColor(),
  415. inactiveTrackColor: "#F6F5F8".toColor(),
  416. trackShape: CustomTrackShape(),
  417. ),
  418. child: Slider(
  419. value: controller.audioProgressValue.value,
  420. min: 0.0,
  421. max: controller.sliderMax,
  422. onChanged: (value) {
  423. controller.updateProgress(value);
  424. },
  425. ),
  426. );
  427. }),
  428. );
  429. }),
  430. SizedBox(width: 11.w),
  431. Obx(() {
  432. return Text(controller.audioDuration.value.toFormattedString(),
  433. style: TextStyle(
  434. fontSize: 10.sp, color: ColorName.secondaryTextColor));
  435. })
  436. ],
  437. ),
  438. );
  439. }
  440. Widget _buildToolbarLeftView() {
  441. return Obx(() {
  442. if (controller.isEditModel) {
  443. return Align(
  444. alignment: Alignment.centerLeft,
  445. child: IconButton(
  446. icon: Assets.images.iconTalkEditCancel
  447. .image(width: 24.w, height: 24.w),
  448. onPressed: () {
  449. controller.onEditCancel();
  450. },
  451. ),
  452. );
  453. } else {
  454. return Row(
  455. children: [
  456. IconButton(
  457. icon: Assets.images.iconTalkBack.image(width: 24.w, height: 24.w),
  458. onPressed: () {
  459. Get.back();
  460. },
  461. ),
  462. SizedBox(width: 6.w),
  463. Obx(() {
  464. return GestureDetector(
  465. onTap: () {
  466. controller.onEditTitleClick();
  467. },
  468. child: Column(
  469. mainAxisAlignment: MainAxisAlignment.center,
  470. crossAxisAlignment: CrossAxisAlignment.start,
  471. children: [
  472. Row(
  473. children: [
  474. ConstrainedBox(
  475. constraints: BoxConstraints(maxWidth: 0.65.sw),
  476. child: Text(
  477. maxLines: 1,
  478. overflow: TextOverflow.ellipsis,
  479. controller.talkBean.value?.title.value ?? '',
  480. style: TextStyle(
  481. fontWeight: FontWeight.bold,
  482. fontSize: 15.sp,
  483. color: ColorName.primaryTextColor,
  484. ),
  485. ),
  486. ),
  487. SizedBox(width: 2.w),
  488. Assets.images.iconTalkEditTitle
  489. .image(width: 16.w, height: 16.w)
  490. ],
  491. ),
  492. SizedBox(height: 2.h),
  493. Text(
  494. controller.talkBean.value?.createTime ?? '',
  495. style: TextStyle(
  496. fontSize: 11.sp, color: ColorName.secondaryTextColor),
  497. )
  498. ],
  499. ),
  500. );
  501. })
  502. ],
  503. );
  504. }
  505. });
  506. }
  507. Widget _buildToolbarRightView() {
  508. return Obx(() {
  509. return Visibility(
  510. visible: controller.talkBean.value?.status.value ==
  511. TalkStatus.analysisSuccess,
  512. child: controller.isEditModel
  513. ? GestureDetector(
  514. onTap: () {
  515. controller.onEditDoneClick();
  516. },
  517. child: Padding(
  518. padding: EdgeInsets.symmetric(horizontal: 12.w),
  519. child: Text(StringName.done.tr,
  520. style: TextStyle(
  521. fontSize: 17.sp, color: ColorName.primaryTextColor)),
  522. ),
  523. )
  524. : Row(
  525. children: [
  526. IconButton(
  527. icon: Assets.images.iconTalkShare
  528. .image(width: 20.w, height: 20.w),
  529. onPressed: () {
  530. controller.onShareClick();
  531. },
  532. ),
  533. ],
  534. ),
  535. );
  536. });
  537. }
  538. }
  539. class CustomTrackShape extends RoundedRectSliderTrackShape {
  540. @override
  541. Rect getPreferredRect({
  542. required RenderBox parentBox,
  543. Offset offset = Offset.zero,
  544. required SliderThemeData sliderTheme,
  545. bool isEnabled = false,
  546. bool isDiscrete = false,
  547. }) {
  548. final trackHeight = sliderTheme.trackHeight;
  549. final trackLeft = offset.dx;
  550. final trackTop = offset.dy + (parentBox.size.height - trackHeight!) / 2;
  551. final trackWidth = parentBox.size.width;
  552. return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
  553. }
  554. }