controller.dart 25 KB

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