controller.dart 30 KB

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