controller.dart 30 KB

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