controller.dart 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:connectivity_plus/connectivity_plus.dart';
  4. import 'package:dsbridge_flutter/dsbridge_flutter.dart';
  5. import 'package:electronic_assistant/base/base_controller.dart';
  6. import 'package:electronic_assistant/data/consts/event_report_id.dart';
  7. import 'package:electronic_assistant/data/repositories/account_repository.dart';
  8. import 'package:electronic_assistant/data/repositories/task_repository.dart';
  9. import 'package:electronic_assistant/handler/event_handler.dart';
  10. import 'package:electronic_assistant/module/chat/view.dart';
  11. import 'package:electronic_assistant/module/login/view.dart';
  12. import 'package:electronic_assistant/module/record/record_handler.dart';
  13. import 'package:electronic_assistant/module/store/view.dart';
  14. import 'package:electronic_assistant/module/talk/mindmap/mind_util.dart';
  15. import 'package:electronic_assistant/module/talk/summary/view.dart';
  16. import 'package:electronic_assistant/module/talk/todo/controller.dart';
  17. import 'package:electronic_assistant/module/talk/todo/view.dart';
  18. import 'package:electronic_assistant/resource/assets.gen.dart';
  19. import 'package:electronic_assistant/resource/colors.gen.dart';
  20. import 'package:electronic_assistant/resource/string.gen.dart';
  21. import 'package:electronic_assistant/router/app_pages.dart';
  22. import 'package:electronic_assistant/utils/audio_picker_utils.dart';
  23. import 'package:electronic_assistant/utils/error_handler.dart';
  24. import 'package:electronic_assistant/utils/expand.dart';
  25. import 'package:electronic_assistant/utils/file_upload_check_helper.dart';
  26. import 'package:electronic_assistant/utils/mmkv_util.dart';
  27. import 'package:flutter/cupertino.dart';
  28. import 'package:flutter/material.dart';
  29. import 'package:flutter/scheduler.dart';
  30. import 'package:flutter/services.dart';
  31. import 'package:flutter_screenutil/flutter_screenutil.dart';
  32. import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
  33. import 'package:get/get.dart';
  34. import 'package:just_audio/just_audio.dart';
  35. import 'package:share_plus/share_plus.dart';
  36. import 'package:wakelock_plus/wakelock_plus.dart';
  37. import '../../data/api/request/agenda_update_bean.dart';
  38. import '../../data/bean/agenda.dart';
  39. import '../../data/bean/agenda_list_all_bean.dart';
  40. import '../../data/bean/talks.dart';
  41. import '../../data/bean/template_bean.dart';
  42. import '../../data/repositories/agenda_repository.dart';
  43. import '../../data/repositories/talk_repository.dart';
  44. import '../../dialog/add_agenda_dialog.dart';
  45. import '../../dialog/alert_dialog.dart';
  46. import '../../dialog/loading_dialog.dart';
  47. import '../../dialog/rename_dialog.dart';
  48. import '../../dialog/talk_share_dialog.dart';
  49. import '../../utils/common_utils.dart';
  50. import '../../utils/event_bus.dart';
  51. import '../../utils/system_share_util.dart';
  52. import '../../utils/toast_util.dart';
  53. import 'package:webview_flutter/webview_flutter.dart';
  54. import 'mindmap/view.dart';
  55. import 'original/view.dart';
  56. class TalkController extends BaseController {
  57. static const String argumentItem = 'argument_item';
  58. static const String argumentTalkId = 'argument_talk_id';
  59. static const String argumentEventTag = 'argument_event_tag';
  60. final String uploadNoPrompts = "UPLOAD_NO_PROMPTS";
  61. final Rxn<TalkBean> talkBean = Rxn();
  62. StreamSubscription? _talkUploadListener;
  63. final RxDouble uploadProgress = RxDouble(0);
  64. final isShowElectricLow = false.obs;
  65. bool isAudioLoading = false;
  66. final double sliderMax = 1;
  67. bool? audioFileIsExist;
  68. bool? isUploadedFile;
  69. Rxn<bool> isUploading = Rxn();
  70. final isAudioPlaying = false.obs;
  71. final audioProgressValue = 0.0.obs;
  72. final audioDuration = Duration.zero.obs;
  73. final agendaOriginalAllList = <AgendaListAllBean>[];
  74. final agendaAllList = <AgendaListAllBean>[].obs;
  75. final _isEditModel = false.obs;
  76. final defaultIndex = 0;
  77. final Rxn<TalkBarBean> checkTabBean = Rxn();
  78. final List<TalkBarBean> tabBeans = [];
  79. final List<Widget> pages = [];
  80. final isInitializedView = false.obs;
  81. bool get isEditModel => _isEditModel.value;
  82. RxBool get isEditModelRx => _isEditModel;
  83. final _audioPlayer = AudioPlayer();
  84. StreamSubscription? _talkBeanListener;
  85. TextEditingController? _agendaContentController;
  86. TextEditingController? _agendaNameController;
  87. TextEditingController get agendaContentController {
  88. _agendaContentController ??= TextEditingController();
  89. return _agendaContentController!;
  90. }
  91. TextEditingController get agendaNameController {
  92. _agendaNameController ??= TextEditingController();
  93. return _agendaNameController!;
  94. }
  95. String? paramId;
  96. String? eventTag;
  97. bool isLocalFileHas = false;
  98. final Rxn<Duration> playingDuration = Rxn();
  99. bool isFirstRequestTask = true;
  100. //模板
  101. Rxn<List<TemplateBean>?> templateList = Rxn();
  102. Rxn<int> templateSelectId = Rxn();
  103. final RxBool isShowMindFullScreen = false.obs;
  104. //模板最大数量
  105. int? maxTemplateCount;
  106. final mindFullDuration = const Duration(milliseconds: 250);
  107. GlobalKey headGlobalKey = GlobalKey();
  108. GlobalKey bottomGlobalKey = GlobalKey();
  109. double? _bottomViewHeight;
  110. final RxnDouble _headViewHeight = RxnDouble();
  111. double? get headViewHeight => _headViewHeight.value;
  112. final DWebViewController webViewController =
  113. MindUtil.createMindWebViewController();
  114. final Rxn<DWebViewController> _temporaryController = Rxn();
  115. DWebViewController? get temporaryController => _temporaryController.value;
  116. RxBool isShowMindMap = false.obs;
  117. @override
  118. void onInit() {
  119. super.onInit();
  120. _getArguments();
  121. _analyticWebVersion();
  122. }
  123. Future<void> _analyticWebVersion() async {
  124. if (Platform.isAndroid) {
  125. String? userAgent = await webViewController.getUserAgent();
  126. bool shouldShowMindMap = true;
  127. String? version;
  128. if (userAgent != null) {
  129. RegExp regExp = RegExp(r'Chrome/(\d+)\.');
  130. Match? match = regExp.firstMatch(userAgent);
  131. if (match != null) {
  132. version = match.group(1) ?? '';
  133. if (version.isNotEmpty && isNumeric(version)) {
  134. int versionNum = int.parse(version);
  135. shouldShowMindMap = versionNum > 92;
  136. } else {
  137. shouldShowMindMap = false;
  138. }
  139. }
  140. } else {
  141. shouldShowMindMap = false;
  142. }
  143. _setMindMap(shouldShowMindMap, version: version);
  144. } else {
  145. _setMindMap(true);
  146. }
  147. }
  148. void _setMindMap(bool isShow, {String? version}) {
  149. isShowMindMap.value = isShow;
  150. String? talkId = talkBean.value != null ? talkBean.value!.id : paramId;
  151. if (isShow) {
  152. pages.assignAll([
  153. SummaryView(talkId),
  154. MindMapView(talkId),
  155. TodoView(talkId),
  156. OriginalView(talkId),
  157. ]);
  158. tabBeans.assignAll([
  159. TalkBarBean(TalkBarType.summary, StringName.talkTabSummary.tr, true),
  160. TalkBarBean(TalkBarType.mindMap, StringName.talkMindMap.tr, false,
  161. isDisallowScroll: true),
  162. TalkBarBean(TalkBarType.myTask, StringName.talkTabMyTask.tr, true),
  163. TalkBarBean(TalkBarType.original, StringName.talkTabOriginal.tr, false)
  164. ]);
  165. EventHandler.report(EventId.event_101401, params: {EventId.id: version});
  166. } else {
  167. pages.assignAll([
  168. SummaryView(talkId),
  169. TodoView(talkId),
  170. OriginalView(talkId),
  171. ]);
  172. tabBeans.assignAll([
  173. TalkBarBean(TalkBarType.summary, StringName.talkTabSummary.tr, true),
  174. TalkBarBean(TalkBarType.myTask, StringName.talkTabMyTask.tr, true),
  175. TalkBarBean(TalkBarType.original, StringName.talkTabOriginal.tr, false)
  176. ]);
  177. EventHandler.report(EventId.event_101402, params: {EventId.id: version});
  178. }
  179. checkTabBean.value = tabBeans[defaultIndex];
  180. isInitializedView.value = true;
  181. }
  182. @override
  183. void onReady() {
  184. super.onReady();
  185. _initAudioPlayer();
  186. eventReport(EventId.event_101001, params: {EventId.id: eventTag});
  187. WidgetsBinding.instance.addPostFrameCallback((_) {
  188. _bottomViewHeight = bottomGlobalKey.currentContext?.size?.height;
  189. _headViewHeight.value = headGlobalKey.currentContext?.size?.height;
  190. });
  191. }
  192. double getBottomViewHeight() {
  193. if (_bottomViewHeight == null) {
  194. return -250.h;
  195. }
  196. return -_bottomViewHeight!;
  197. }
  198. void eventReport(String eventId, {Map<String, dynamic>? params}) {
  199. if (talkBean.value == null || talkBean.value?.isExample == true) {
  200. return;
  201. }
  202. EventHandler.report(eventId, params: params);
  203. }
  204. void _initAudioPlayer() {
  205. _audioPlayer.playerStateStream.listen((playerState) {
  206. if (playerState.processingState == ProcessingState.loading ||
  207. playerState.processingState == ProcessingState.buffering) {
  208. isAudioLoading = true;
  209. debugPrint('音频load = true');
  210. } else {
  211. debugPrint('音频load = false');
  212. isAudioLoading = false;
  213. if (playerState.processingState == ProcessingState.completed) {
  214. _audioPlayer.stop();
  215. _audioPlayer.seek(Duration.zero);
  216. isAudioPlaying.value = false;
  217. debugPrint('音频 播放结束了');
  218. }
  219. }
  220. isAudioPlaying.value = playerState.playing;
  221. }, onError: (Object e, StackTrace stackTrace) {
  222. debugPrint('音频加载异常 == $e');
  223. });
  224. _audioPlayer.durationStream.listen((duration) {
  225. if (duration != null) {
  226. debugPrint('音频总播放时长 == ${duration.inMilliseconds}');
  227. audioDuration.value = duration;
  228. }
  229. });
  230. _audioPlayer.positionStream.listen((duration) {
  231. debugPrint('音频播放时长 == ${duration.inMilliseconds}');
  232. playingDuration.value = duration;
  233. if (audioDuration.value.inMilliseconds > 0) {
  234. audioProgressValue.value =
  235. (duration.inMilliseconds / audioDuration.value.inMilliseconds)
  236. .clamp(0.0, sliderMax);
  237. }
  238. });
  239. }
  240. void _dealTalk(TalkBean? bean) async {
  241. debugPrint('talkBean == $bean');
  242. String? id = bean?.id;
  243. if (id == null) {
  244. return;
  245. }
  246. _loadAudioFile(bean);
  247. if (bean?.status.value == TalkStatus.notAnalysis) {
  248. setUploadingProgress(id);
  249. }
  250. if (bean?.status.value == TalkStatus.notAnalysis &&
  251. talkRepository.isUploadingTalk(id)) {
  252. isUploading.value = true;
  253. } else {
  254. isUploading.value = false;
  255. }
  256. }
  257. void setUploadingProgress(String id) {
  258. talkRepository.getUploadProgress(id).listen((progress) {
  259. uploadProgress.value = (progress * 20).toFormattedDouble(1);
  260. });
  261. }
  262. Future<void> _loadAudioFile(TalkBean? bean, {bool? loadPlay}) async {
  263. try {
  264. Uri? uri;
  265. if (bean?.isExample == true && bean?.audioUrl != null) {
  266. uri = Uri.parse(bean!.audioUrl!);
  267. } else {
  268. File? file = await getFileByTalk(talkBean.value);
  269. if (file?.existsSync() == true) {
  270. uri = file?.uri;
  271. }
  272. }
  273. if (uri == null) {
  274. throw '音频文件不存在';
  275. }
  276. await _audioPlayer.setAudioSource(AudioSource.uri(uri));
  277. if (loadPlay == true) {
  278. clickPlayAudio();
  279. }
  280. audioFileIsExist = true;
  281. } catch (e) {
  282. audioFileIsExist = false;
  283. debugPrint('音频设置异常 == $e');
  284. }
  285. }
  286. void _getArguments() {
  287. TalkBean? bean = parameters?[argumentItem];
  288. if (bean != null) {
  289. talkBean.value = bean;
  290. debugPrint('talkBean == ${bean.summary}');
  291. _dealTalk(bean);
  292. } else {
  293. paramId = parameters?[argumentTalkId];
  294. if (paramId != null) {
  295. talkRepository.talkInfo(paramId!).then((data) {
  296. talkBean.value = data.talkInfo;
  297. _dealTalk(data.talkInfo);
  298. }).catchError((error) {
  299. ErrorHandler.toastError(error);
  300. });
  301. }
  302. }
  303. eventTag = parameters?[argumentEventTag];
  304. }
  305. void updateProgress(double value) {
  306. final newPosition = Duration(
  307. milliseconds: (value * audioDuration.value.inMilliseconds).toInt());
  308. _audioPlayer.seek(newPosition);
  309. }
  310. void clickPlayAudio() async {
  311. if (audioFileIsExist != true && isLocalFileHas == false) {
  312. ToastUtil.showToast(StringName.talkFileNotFind.tr);
  313. return;
  314. }
  315. if (isLocalFileHas == true &&
  316. audioFileIsExist == false &&
  317. !await AudioPickerUtils.hasPermission()) {
  318. bool has = await AudioPickerUtils.requestPermissionExtend();
  319. if (has == false) {
  320. ToastUtil.showToast(StringName.authorizationFailed.tr);
  321. return;
  322. }
  323. //重新加载
  324. _loadAudioFile(talkBean.value, loadPlay: true);
  325. return;
  326. }
  327. if (isAudioLoading) {
  328. ToastUtil.showToast(StringName.talkAudioLoading.tr);
  329. return;
  330. }
  331. if (_audioPlayer.playing) {
  332. _audioPlayer.pause();
  333. isAudioPlaying.value = false;
  334. } else {
  335. _audioPlayer.play();
  336. isAudioPlaying.value = true;
  337. }
  338. }
  339. void _checkFileSizeAndNet() async {
  340. String? id = talkBean.value?.id;
  341. if (id == null) {
  342. return;
  343. }
  344. File? file = await getFileByTalk(talkBean.value);
  345. if (isLocalFileHas == true &&
  346. audioFileIsExist == false &&
  347. !await AudioPickerUtils.hasPermission()) {
  348. bool has = await AudioPickerUtils.requestPermissionExtend();
  349. if (has == false) {
  350. ToastUtil.showToast(StringName.authorizationFailed.tr);
  351. return;
  352. }
  353. //重新上传
  354. _checkFileSizeAndNet();
  355. return;
  356. }
  357. if (file == null || !file.existsSync()) {
  358. ToastUtil.showToast(StringName.talkUploadFileNotExist.tr);
  359. return;
  360. }
  361. bool isCheckRemind = KVUtil.getBool(uploadNoPrompts, false);
  362. if (isCheckRemind) {
  363. _requestAnalyze(file);
  364. return;
  365. }
  366. //如果文件大小低于250MB 不弹窗提醒
  367. if (file.lengthSync() < 250 * 1024 * 1024) {
  368. _requestAnalyze(file);
  369. return;
  370. }
  371. final List<ConnectivityResult> connectivityResult =
  372. await (Connectivity().checkConnectivity());
  373. if (connectivityResult.contains(ConnectivityResult.wifi)) {
  374. _requestAnalyze(file);
  375. } else {
  376. _showTrafficRemindDialog(file.lengthSync().toReadableSize(),
  377. confirmOnTap: (isCheckRemind) {
  378. if (isCheckRemind) {
  379. KVUtil.putBool(uploadNoPrompts, true);
  380. }
  381. _requestAnalyze(file);
  382. });
  383. }
  384. }
  385. void _showTrafficRemindDialog(String holderTxt,
  386. {void Function(bool isCheckRemind)? confirmOnTap}) {
  387. final remindTrafficConsume = false.obs;
  388. Widget getSelectIcon() {
  389. return Obx(() {
  390. return remindTrafficConsume.value
  391. ? Assets.images.iconSelectTrue.image()
  392. : Assets.images.iconSelectFalse.image();
  393. });
  394. }
  395. Assets.images.iconSelectTrue.image();
  396. EAAlertDialog.show(
  397. contentWidget: Column(
  398. children: [
  399. Text(
  400. StringName.talkTrafficRemindTitle.tr
  401. .replacePlaceholders([holderTxt]),
  402. style:
  403. TextStyle(fontSize: 15.sp, color: ColorName.primaryTextColor),
  404. ),
  405. SizedBox(height: 8.h),
  406. GestureDetector(
  407. onTap: () {
  408. remindTrafficConsume.value = !remindTrafficConsume.value;
  409. },
  410. child: IntrinsicWidth(
  411. child: Row(
  412. children: [
  413. SizedBox(width: 20.w, height: 20.w, child: getSelectIcon()),
  414. SizedBox(width: 5.w),
  415. Text(
  416. StringName.talkTrafficRemindTips.tr,
  417. style: TextStyle(
  418. fontSize: 15.sp, color: ColorName.tertiaryTextColor),
  419. )
  420. ],
  421. ),
  422. ),
  423. )
  424. ],
  425. ),
  426. cancelText: StringName.cancel.tr,
  427. confirmText: StringName.sure.tr,
  428. confirmOnTap: () {
  429. confirmOnTap?.call(remindTrafficConsume.value);
  430. });
  431. }
  432. void checkCanAnalyze() {
  433. String? id = talkBean.value?.id;
  434. double? duration = talkBean.value?.duration;
  435. if (id == null || duration == null) {
  436. return;
  437. }
  438. eventReport(EventId.event_101002);
  439. talkRepository.checkElectric(duration).then((data) {
  440. if (data.enough) {
  441. //检查网络以及文件大小
  442. _checkFileSizeAndNet();
  443. } else {
  444. ToastUtil.showToast(StringName.talkAnalyseLowToast.tr);
  445. isShowElectricLow.value = true;
  446. }
  447. }).catchError((error) {
  448. ErrorHandler.toastError(error);
  449. });
  450. }
  451. void _requestAnalyze(File file) {
  452. String? talkId = talkBean.value?.id;
  453. double? duration = talkBean.value?.duration;
  454. if (talkId == null || duration == null || isUploadedFile == true) {
  455. return;
  456. }
  457. isUploading.value = true;
  458. WakelockPlus.enable();
  459. talkRepository.uploadTalkFile(talkId, duration, file).then((taskId) {
  460. isUploadedFile = true;
  461. isUploading.value = false;
  462. talkBean.value?.progressContent.value =
  463. StringName.talkUploadingFileTip.tr;
  464. talkBean.value?.progress.value = 20;
  465. talkBean.value?.status.value = TalkStatus.analysing;
  466. taskRepository.addTask(taskId);
  467. }).catchError((error) {
  468. isUploading.value = false;
  469. ErrorHandler.toastError(error);
  470. }).whenComplete(() => WakelockPlus.disable());
  471. }
  472. void refreshAgendaAllData({bool isForceRefresh = false}) {
  473. String? id = talkBean.value?.id;
  474. if (id == null || (!isForceRefresh && agendaAllList.isNotEmpty)) {
  475. return;
  476. }
  477. agendaRepository.agendaListAll(id).then((agenda) {
  478. isFirstRequestTask = false;
  479. agendaAllList.clear();
  480. agendaOriginalAllList.clear();
  481. if (agenda.list != null) {
  482. agendaOriginalAllList.addAll(
  483. agenda.list!.map((item) => AgendaListAllBean.from(item)).toList());
  484. agendaAllList.addAll(agenda.list!);
  485. }
  486. });
  487. }
  488. void clickAIAnalysis() async {
  489. if (!await checkLogin()) {
  490. return;
  491. }
  492. if (talkBean.value != null) {
  493. eventReport(EventId.event_101003);
  494. ChatPage.startByTalk(
  495. talkBean.value!.isExample == true
  496. ? ChatFromType.fromTalkExample
  497. : ChatFromType.fromTalkDetail,
  498. talkBean.value!);
  499. }
  500. }
  501. void onGoElectricStore() {
  502. StorePage.start(fromType: StoreFromType.analyse);
  503. Future.delayed(const Duration(milliseconds: 250), () {
  504. isShowElectricLow.value = false;
  505. });
  506. }
  507. void onEditModelClick() async {
  508. if (!await checkLogin()) {
  509. return;
  510. }
  511. _isEditModel.value = true;
  512. if (_audioPlayer.playing) {
  513. _audioPlayer.pause();
  514. isAudioPlaying.value = false;
  515. }
  516. }
  517. void updateTabIndex(int index) {
  518. checkTabBean.value = tabBeans[index];
  519. }
  520. void onEditCancel() {
  521. _isEditModel.value = false;
  522. agendaAllList.assignAll(agendaOriginalAllList
  523. .map((item) => AgendaListAllBean.from(item))
  524. .toList());
  525. }
  526. void onEditDoneClick() {
  527. if (talkBean.value == null) {
  528. return;
  529. }
  530. List<AgendaUpdateBean> list = [];
  531. for (AgendaListAllBean item in agendaAllList) {
  532. if (item.list != null) {
  533. for (Agenda agenda in item.list!) {
  534. list.add(AgendaUpdateBean(agenda.id, agenda.name, agenda.content));
  535. }
  536. }
  537. }
  538. agendaRepository.agendaUpdate(talkBean.value!.id, list).then((data) {
  539. refreshAgendaAllData(isForceRefresh: true);
  540. eventBus.emit(TodoController.refreshTalkMineTask);
  541. isEditModelRx.value = false;
  542. }).catchError((error) {
  543. ErrorHandler.toastError(error);
  544. });
  545. }
  546. void removeTalkAgenda(List<Agenda>? list, Agenda agenda) {
  547. list?.remove(agenda);
  548. agendaAllList.refresh();
  549. }
  550. void showSingleAddAgendaDialog(BuildContext context) {
  551. showAddAgendaDialog(context, agendaContentController, agendaNameController,
  552. list: agendaAllList.map((e) => e.name ?? "").toList(), callback: () {
  553. if (agendaContentController.text.isEmpty) {
  554. ToastUtil.showToast(StringName.talkAddAgendaContentHint.tr);
  555. return;
  556. }
  557. if (agendaNameController.text.isEmpty) {
  558. ToastUtil.showToast(StringName.talkAddAgendaNameHint.tr);
  559. return;
  560. }
  561. Get.back();
  562. _dealAddProcedureList();
  563. });
  564. }
  565. void _dealAddProcedureList() {
  566. String name = agendaNameController.text;
  567. final addItem = Agenda(
  568. id: "",
  569. talkId: "",
  570. name: name,
  571. );
  572. addItem.content = agendaContentController.text;
  573. for (AgendaListAllBean item in agendaAllList) {
  574. if (item.name == name) {
  575. List<Agenda> list = item.list ?? [];
  576. list.add(addItem);
  577. item.list = list;
  578. agendaAllList.refresh();
  579. agendaContentController.clear();
  580. agendaNameController.clear();
  581. return;
  582. }
  583. }
  584. agendaAllList.add(AgendaListAllBean(name: name, list: [addItem]));
  585. agendaContentController.clear();
  586. agendaNameController.clear();
  587. }
  588. Future<File?> getFileByTalk(TalkBean? bean) async {
  589. isLocalFileHas = false;
  590. if (bean == null) {
  591. return null;
  592. }
  593. if (bean.uploadType == TalkUploadType.localUpload) {
  594. String? audioId =
  595. FileUploadCheckHelper.getLocalAudioId(bean.localAudioUrl);
  596. if (audioId != null && audioId.isNotEmpty) {
  597. isLocalFileHas = true;
  598. return await AudioPickerUtils.getAssetFile(audioId);
  599. } else {
  600. return await FileUploadCheckHelper.getChoiceUploadFile(bean.id);
  601. }
  602. } else {
  603. return await RecordHandler.getRecordFile(bean.id);
  604. }
  605. }
  606. Future<bool> checkLogin() async {
  607. if (!accountRepository.isLogin.value) {
  608. bool isLogin = await LoginPage.start(fromType: LoginFromType.talkDetail);
  609. if (isLogin) {
  610. backToSpecificPage(RoutePath.mainTab);
  611. }
  612. return false;
  613. }
  614. return true;
  615. }
  616. void onShareClick() async {
  617. if (!await checkLogin()) {
  618. return;
  619. }
  620. if (talkBean.value?.status.value != TalkStatus.analysisSuccess) {
  621. return;
  622. }
  623. eventReport(EventId.event_101004);
  624. showTalkShareDialog(talkBean.value?.title.value,
  625. (type, shareTo, fileName, tag) async {
  626. try {
  627. await isInstalled(shareTo);
  628. if (type == ShareTalkType.summary || type == ShareTalkType.original) {
  629. _shareSummaryOrOriginal(
  630. talkBean.value!.id, fileName, type, shareTo, tag);
  631. } else if (type == ShareTalkType.mindMap) {
  632. _shareMindMap(fileName, talkBean.value?.summary.value, shareTo, tag);
  633. }
  634. } catch (error) {
  635. if (error is SystemShareException) {
  636. ToastUtil.showToast(error.message);
  637. } else {
  638. ErrorHandler.toastError(error);
  639. }
  640. }
  641. });
  642. }
  643. Future<bool> isInstalled(ShareTo shareTo) async {
  644. if (shareTo == ShareTo.wechat) {
  645. bool isInstalled =
  646. await SystemShareUtil.isInstalled(SystemShareUtil.wechatPageName);
  647. if (!isInstalled) {
  648. throw SystemShareException('未安装微信');
  649. }
  650. } else if (shareTo == ShareTo.qq) {
  651. bool isInstalled =
  652. await SystemShareUtil.isInstalled(SystemShareUtil.qqPageName);
  653. if (!isInstalled) {
  654. throw SystemShareException('未安装QQ');
  655. }
  656. }
  657. // else if(shareTo == ShareTo.ios){
  658. // //ios不用判断
  659. // }
  660. return true;
  661. }
  662. void _shareMindMap(
  663. String title, String? summary, ShareTo shareTo, String tag) {
  664. LoadingDialog.show(StringName.mindMapExport.tr, backDismiss: true);
  665. if (_temporaryController.value != null) {
  666. _temporaryController.value?.dispose();
  667. _temporaryController.value = null;
  668. }
  669. final temporaryController = MindUtil.createMindWebViewController();
  670. VoidCallback? errorCallback = () {
  671. LoadingDialog.hide();
  672. temporaryController.dispose();
  673. ToastUtil.showToast('思维导图导出失败');
  674. };
  675. try {
  676. Future.delayed(const Duration(seconds: 10), () {
  677. throw Exception('导出超时');
  678. });
  679. _temporaryController.value = temporaryController;
  680. temporaryController.loadFlutterAsset(Assets.html.indexExport);
  681. temporaryController.setNavigationDelegate(
  682. NavigationDelegate(
  683. onHttpError: (error) {
  684. errorCallback?.call();
  685. errorCallback = null;
  686. },
  687. // onWebResourceError: (error) {
  688. // errorCallback?.call();
  689. // errorCallback = null;
  690. // },
  691. onPageFinished: (String url) {
  692. temporaryController.callHandler(MindUtil.functionToJsExport,
  693. args: [title, summary], handler: (value) async {
  694. if (value == null) {
  695. throw Exception('data is null');
  696. }
  697. File file = await MindUtil.saveToFile(value, title);
  698. temporaryController.dispose();
  699. if (shareTo == ShareTo.wechat) {
  700. await SystemShareUtil.shareSystemFile(
  701. SystemShareUtil.wechatPageName, file.path,
  702. shareTitle: '分享到微信', shareFileType: 'image/*');
  703. } else if (shareTo == ShareTo.qq) {
  704. await SystemShareUtil.shareSystemFile(
  705. SystemShareUtil.qqPageName, file.path,
  706. shareTitle: '分享到QQ', shareFileType: 'image/*');
  707. } else if (shareTo == ShareTo.ios) {
  708. await Share.shareXFiles([XFile(file.path)], subject: title);
  709. } else {
  710. throw Exception('不支持该分享方式');
  711. }
  712. SmartDialog.dismiss(tag: tag);
  713. LoadingDialog.hide();
  714. });
  715. },
  716. ),
  717. );
  718. } catch (e) {
  719. errorCallback?.call();
  720. errorCallback = null;
  721. }
  722. }
  723. void _shareSummaryOrOriginal(String id, String fileName, ShareTalkType type,
  724. ShareTo shareTo, String tag) async {
  725. talkRepository.talkExport(id, fileName, type).then((file) async {
  726. if (shareTo == ShareTo.ios) {
  727. await Share.shareXFiles([XFile(file.path)], subject: fileName);
  728. } else if (shareTo == ShareTo.wechat) {
  729. await SystemShareUtil.shareSystemFile(
  730. SystemShareUtil.wechatPageName, file.path,
  731. shareTitle: '分享到微信', shareFileType: 'text/*');
  732. } else if (shareTo == ShareTo.qq) {
  733. await SystemShareUtil.shareSystemFile(
  734. SystemShareUtil.qqPageName, file.path,
  735. shareTitle: '分享到QQ', shareFileType: 'text/*');
  736. }
  737. SmartDialog.dismiss(tag: tag);
  738. }).catchError((error) {
  739. throw error;
  740. });
  741. }
  742. void seekTo(int? startMs) {
  743. if (startMs == null) {
  744. return;
  745. }
  746. _audioPlayer.seek(Duration(milliseconds: startMs));
  747. }
  748. Future<TalkBean?> refreshTalkDetail() async {
  749. String? id = talkBean.value?.id;
  750. if (id == null) {
  751. return null;
  752. }
  753. return talkRepository.talkInfo(id).then((data) {
  754. var bean = data.talkInfo;
  755. if (bean != null) {
  756. if (talkBean.value == null) {
  757. talkBean.value = data.talkInfo;
  758. } else {
  759. talkBean.value?.updateBean(bean);
  760. }
  761. maxTemplateCount = data.maxTemplateCount;
  762. templateSelectId.value = bean.templateId;
  763. }
  764. templateList.value = data.templateList;
  765. return bean;
  766. });
  767. }
  768. void onEditTitleClick() {
  769. reNameDialog(StringName.talkRenameTitle.tr, talkBean.value?.title.value,
  770. hintTxt: StringName.talkRenameTitleHint.tr,
  771. maxLength: 15, returnBuilder: (newName) {
  772. talkRepository.talkRename(talkBean.value!.id, newName).then((data) {
  773. talkBean.value?.title.value = newName;
  774. }).catchError((error) {
  775. ErrorHandler.toastError(error);
  776. });
  777. });
  778. }
  779. void onExitMindFullScreen() {
  780. isShowMindFullScreen.value = false;
  781. }
  782. void selectTemplate(TemplateBean bean) {
  783. String? id = talkBean.value?.id;
  784. int? templateId = bean.id;
  785. if (talkBean.value?.templateId == templateId) {
  786. return;
  787. }
  788. if (id == null || templateId == null) {
  789. ToastUtil.showToast('谈话信息获取异常,请退出页面重试!');
  790. return;
  791. }
  792. LoadingDialog.show('生成总结中...');
  793. talkRepository
  794. .talkSummaryGenerate(id, templateId)
  795. .timeout(const Duration(seconds: 30))
  796. .then((data) {
  797. LoadingDialog.hide();
  798. talkBean.value = data;
  799. ToastUtil.showToast('生成成功', displayType: SmartToastType.last);
  800. }).catchError((error) {
  801. LoadingDialog.hide();
  802. ToastUtil.showToast('生成失败');
  803. });
  804. }
  805. @override
  806. void onClose() {
  807. super.onClose();
  808. _talkUploadListener?.cancel();
  809. _talkBeanListener?.cancel();
  810. _audioPlayer.dispose();
  811. _agendaContentController?.dispose();
  812. _agendaNameController?.dispose();
  813. }
  814. }
  815. enum TalkBarType { summary, mindMap, myTask, original }
  816. class TalkBarBean {
  817. final TalkBarType type;
  818. final String title;
  819. final bool isShowEdit;
  820. final bool? isDisallowScroll;
  821. TalkBarBean(this.type, this.title, this.isShowEdit, {this.isDisallowScroll});
  822. }