controller.dart 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  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 ||
  223. _isEditModel.value ||
  224. isSearchModel.value
  225. ? getBottomViewHeight()
  226. : 0.h;
  227. }
  228. double getBottomViewHeight() {
  229. return bottomViewHeight.value != null ? -bottomViewHeight.value! : -250.h;
  230. }
  231. void eventReport(String eventId, {Map<String, dynamic>? params}) {
  232. if (talkBean.value == null || talkBean.value?.isExample == true) {
  233. return;
  234. }
  235. EventHandler.report(eventId, params: params);
  236. }
  237. void _initAudioPlayer() {
  238. _audioPlayer.playerStateStream.listen((playerState) {
  239. if (playerState.processingState == ProcessingState.loading ||
  240. playerState.processingState == ProcessingState.buffering) {
  241. isAudioLoading = true;
  242. debugPrint('音频load = true');
  243. } else {
  244. debugPrint('音频load = false');
  245. isAudioLoading = false;
  246. if (playerState.processingState == ProcessingState.completed) {
  247. _audioPlayer.stop();
  248. _audioPlayer.seek(Duration.zero);
  249. isAudioPlaying.value = false;
  250. debugPrint('音频 播放结束了');
  251. }
  252. }
  253. isAudioPlaying.value = playerState.playing;
  254. }, onError: (Object e, StackTrace stackTrace) {
  255. debugPrint('音频加载异常 == $e');
  256. });
  257. _audioPlayer.durationStream.listen((duration) {
  258. if (duration != null) {
  259. debugPrint('音频总播放时长 == ${duration.inMilliseconds}');
  260. audioDuration.value = duration;
  261. }
  262. });
  263. _audioPlayer.positionStream.listen((duration) {
  264. debugPrint('音频播放时长 == ${duration.inMilliseconds}');
  265. playingDuration.value = duration;
  266. if (audioDuration.value.inMilliseconds > 0) {
  267. audioProgressValue.value =
  268. (duration.inMilliseconds / audioDuration.value.inMilliseconds)
  269. .clamp(0.0, sliderMax);
  270. }
  271. });
  272. }
  273. void _dealTalk(TalkBean? bean) async {
  274. debugPrint('talkBean == $bean');
  275. String? id = bean?.id;
  276. if (id == null) {
  277. return;
  278. }
  279. _loadAudioFile(bean);
  280. if (bean?.status.value == TalkStatus.notAnalysis) {
  281. setUploadingProgress(id);
  282. }
  283. if (bean?.status.value == TalkStatus.notAnalysis &&
  284. talkRepository.isUploadingTalk(id)) {
  285. isUploading.value = true;
  286. } else {
  287. isUploading.value = false;
  288. }
  289. }
  290. void setUploadingProgress(String id) {
  291. talkRepository.getUploadProgress(id).listen((progress) {
  292. uploadProgress.value = (progress * 20).toFormattedDouble(1);
  293. });
  294. }
  295. Future<void> _loadAudioFile(TalkBean? bean, {bool? loadPlay}) async {
  296. try {
  297. Uri? uri;
  298. if (bean?.isExample == true && bean?.audioUrl != null) {
  299. uri = Uri.parse(bean!.audioUrl!);
  300. } else {
  301. File? file = await getFileByTalk(talkBean.value);
  302. if (file?.existsSync() == true) {
  303. uri = file?.uri;
  304. }
  305. }
  306. if (uri == null) {
  307. throw '音频文件不存在';
  308. }
  309. await _audioPlayer.setAudioSource(AudioSource.uri(uri));
  310. if (loadPlay == true) {
  311. clickPlayAudio();
  312. }
  313. audioFileIsExist = true;
  314. } catch (e) {
  315. audioFileIsExist = false;
  316. debugPrint('音频设置异常 == $e');
  317. }
  318. }
  319. void _getArguments() {
  320. TalkBean? bean = parameters?[argumentItem];
  321. if (bean != null) {
  322. talkBean.value = bean;
  323. debugPrint('talkBean == ${bean.summary}');
  324. _dealTalk(bean);
  325. } else {
  326. paramId = parameters?[argumentTalkId];
  327. if (paramId != null) {
  328. talkRepository.talkInfo(paramId!).then((data) {
  329. talkBean.value = data.talkInfo;
  330. _dealTalk(data.talkInfo);
  331. }).catchError((error) {
  332. ErrorHandler.toastError(error);
  333. });
  334. }
  335. }
  336. eventTag = parameters?[argumentEventTag];
  337. }
  338. void updateProgress(double value) {
  339. final newPosition = Duration(
  340. milliseconds: (value * audioDuration.value.inMilliseconds).toInt());
  341. _audioPlayer.seek(newPosition);
  342. }
  343. void clickPlayAudio() async {
  344. if (audioFileIsExist != true && isLocalFileHas == false) {
  345. ToastUtil.showToast(StringName.talkFileNotFind.tr);
  346. return;
  347. }
  348. if (isLocalFileHas == true &&
  349. audioFileIsExist == false &&
  350. !await AudioPickerUtils.hasPermission()) {
  351. bool has = await AudioPickerUtils.requestPermissionExtend();
  352. if (has == false) {
  353. ToastUtil.showToast(StringName.authorizationFailed.tr);
  354. return;
  355. }
  356. //重新加载
  357. _loadAudioFile(talkBean.value, loadPlay: true);
  358. return;
  359. }
  360. if (isAudioLoading) {
  361. ToastUtil.showToast(StringName.talkAudioLoading.tr);
  362. return;
  363. }
  364. if (_audioPlayer.playing) {
  365. _audioPlayer.pause();
  366. isAudioPlaying.value = false;
  367. } else {
  368. _audioPlayer.play();
  369. isAudioPlaying.value = true;
  370. }
  371. }
  372. void _checkFileSizeAndNet() async {
  373. String? id = talkBean.value?.id;
  374. if (id == null) {
  375. return;
  376. }
  377. File? file = await getFileByTalk(talkBean.value);
  378. if (isLocalFileHas == true &&
  379. audioFileIsExist == false &&
  380. !await AudioPickerUtils.hasPermission()) {
  381. bool has = await AudioPickerUtils.requestPermissionExtend();
  382. if (has == false) {
  383. ToastUtil.showToast(StringName.authorizationFailed.tr);
  384. return;
  385. }
  386. //重新上传
  387. _checkFileSizeAndNet();
  388. return;
  389. }
  390. if (file == null || !file.existsSync()) {
  391. ToastUtil.showToast(StringName.talkUploadFileNotExist.tr);
  392. return;
  393. }
  394. bool isCheckRemind = KVUtil.getBool(uploadNoPrompts, false);
  395. if (isCheckRemind) {
  396. _requestAnalyze(file);
  397. return;
  398. }
  399. //如果文件大小低于250MB 不弹窗提醒
  400. if (file.lengthSync() < 250 * 1024 * 1024) {
  401. _requestAnalyze(file);
  402. return;
  403. }
  404. final List<ConnectivityResult> connectivityResult =
  405. await (Connectivity().checkConnectivity());
  406. if (connectivityResult.contains(ConnectivityResult.wifi)) {
  407. _requestAnalyze(file);
  408. } else {
  409. _showTrafficRemindDialog(file.lengthSync().toReadableSize(),
  410. confirmOnTap: (isCheckRemind) {
  411. if (isCheckRemind) {
  412. KVUtil.putBool(uploadNoPrompts, true);
  413. }
  414. _requestAnalyze(file);
  415. });
  416. }
  417. }
  418. void _showTrafficRemindDialog(String holderTxt,
  419. {void Function(bool isCheckRemind)? confirmOnTap}) {
  420. final remindTrafficConsume = false.obs;
  421. Widget getSelectIcon() {
  422. return Obx(() {
  423. return remindTrafficConsume.value
  424. ? Assets.images.iconSelectTrue.image()
  425. : Assets.images.iconSelectFalse.image();
  426. });
  427. }
  428. Assets.images.iconSelectTrue.image();
  429. EAAlertDialog.show(
  430. contentWidget: Column(
  431. children: [
  432. Text(
  433. StringName.talkTrafficRemindTitle.tr
  434. .replacePlaceholders([holderTxt]),
  435. style:
  436. TextStyle(fontSize: 15.sp, color: ColorName.primaryTextColor),
  437. ),
  438. SizedBox(height: 8.h),
  439. GestureDetector(
  440. onTap: () {
  441. remindTrafficConsume.value = !remindTrafficConsume.value;
  442. },
  443. child: IntrinsicWidth(
  444. child: Row(
  445. children: [
  446. SizedBox(width: 20.w, height: 20.w, child: getSelectIcon()),
  447. SizedBox(width: 5.w),
  448. Text(
  449. StringName.talkTrafficRemindTips.tr,
  450. style: TextStyle(
  451. fontSize: 15.sp, color: ColorName.tertiaryTextColor),
  452. )
  453. ],
  454. ),
  455. ),
  456. )
  457. ],
  458. ),
  459. cancelText: StringName.cancel.tr,
  460. confirmText: StringName.sure.tr,
  461. confirmOnTap: () {
  462. confirmOnTap?.call(remindTrafficConsume.value);
  463. });
  464. }
  465. void checkCanAnalyze() {
  466. String? id = talkBean.value?.id;
  467. double? duration = talkBean.value?.duration;
  468. if (id == null || duration == null) {
  469. return;
  470. }
  471. eventReport(EventId.event_101002);
  472. talkRepository.checkElectric(duration).then((data) {
  473. if (data.enough) {
  474. //检查网络以及文件大小
  475. _checkFileSizeAndNet();
  476. } else {
  477. StorePage.start(fromType: StoreFromType.analyse);
  478. ToastUtil.showToast(StringName.talkAnalyseLowToast.tr);
  479. }
  480. }).catchError((error) {
  481. ErrorHandler.toastError(error);
  482. });
  483. }
  484. void _requestAnalyze(File file) {
  485. String? talkId = talkBean.value?.id;
  486. double? duration = talkBean.value?.duration;
  487. if (talkId == null || duration == null || isUploadedFile == true) {
  488. return;
  489. }
  490. isUploading.value = true;
  491. WakelockPlus.enable();
  492. talkRepository.uploadTalkFile(talkId, duration, file).then((taskId) {
  493. isUploadedFile = true;
  494. isUploading.value = false;
  495. talkBean.value?.progressContent.value =
  496. StringName.talkUploadingFileTip.tr;
  497. talkBean.value?.progress.value = 20;
  498. talkBean.value?.status.value = TalkStatus.analysing;
  499. taskRepository.addTask(taskId);
  500. }).catchError((error) {
  501. isUploading.value = false;
  502. ErrorHandler.toastError(error);
  503. }).whenComplete(() => WakelockPlus.disable());
  504. }
  505. void refreshAgendaAllData({bool isForceRefresh = false}) {
  506. String? id = talkBean.value?.id;
  507. if (id == null || (!isForceRefresh && agendaAllList.isNotEmpty)) {
  508. return;
  509. }
  510. agendaRepository.agendaListAll(id).then((agenda) {
  511. isFirstRequestTask = false;
  512. agendaAllList.clear();
  513. agendaOriginalAllList.clear();
  514. if (agenda.list != null) {
  515. agendaOriginalAllList.addAll(
  516. agenda.list!.map((item) => AgendaListAllBean.from(item)).toList());
  517. agendaAllList.addAll(agenda.list!);
  518. }
  519. });
  520. }
  521. void clickAIAnalysis() async {
  522. if (!await checkLogin()) {
  523. return;
  524. }
  525. if (talkBean.value != null) {
  526. eventReport(EventId.event_101003);
  527. ChatPage.startByTalk(
  528. talkBean.value!.isExample == true
  529. ? ChatFromType.fromTalkExample
  530. : ChatFromType.fromTalkDetail,
  531. talkBean.value!);
  532. }
  533. }
  534. void onEditModelClick() async {
  535. if (!await checkLogin()) {
  536. return;
  537. }
  538. _isEditModel.value = true;
  539. if (_audioPlayer.playing) {
  540. _audioPlayer.pause();
  541. isAudioPlaying.value = false;
  542. }
  543. }
  544. void onSearchClick() {
  545. isSearchModel.value = true;
  546. EventHandler.report(EventId.event_101302);
  547. }
  548. void updateTabIndex(int index) {
  549. checkTabBean.value = tabBeans[index];
  550. }
  551. void onEditCancel() {
  552. _isEditModel.value = false;
  553. agendaAllList.assignAll(agendaOriginalAllList
  554. .map((item) => AgendaListAllBean.from(item))
  555. .toList());
  556. }
  557. void onSearchCancel() {
  558. isSearchModel.value = false;
  559. searchPrint.value = '';
  560. searchResultDesc.value = '0/0';
  561. }
  562. void onEditDoneClick() {
  563. if (talkBean.value == null) {
  564. return;
  565. }
  566. List<AgendaUpdateBean> list = [];
  567. for (AgendaListAllBean item in agendaAllList) {
  568. if (item.list != null) {
  569. for (Agenda agenda in item.list!) {
  570. list.add(AgendaUpdateBean(agenda.id, agenda.name, agenda.content));
  571. }
  572. }
  573. }
  574. agendaRepository.agendaUpdate(talkBean.value!.id, list).then((data) {
  575. refreshAgendaAllData(isForceRefresh: true);
  576. eventBus.emit(TodoController.refreshTalkMineTask);
  577. isEditModelRx.value = false;
  578. }).catchError((error) {
  579. ErrorHandler.toastError(error);
  580. });
  581. }
  582. void removeTalkAgenda(List<Agenda>? list, Agenda agenda) {
  583. list?.remove(agenda);
  584. agendaAllList.refresh();
  585. }
  586. void showSingleAddAgendaDialog(BuildContext context) {
  587. showAddAgendaDialog(context, agendaContentController, agendaNameController,
  588. list: agendaAllList.map((e) => e.name ?? "").toList(), callback: () {
  589. if (agendaContentController.text.isEmpty) {
  590. ToastUtil.showToast(StringName.talkAddAgendaContentHint.tr);
  591. return;
  592. }
  593. if (agendaNameController.text.isEmpty) {
  594. ToastUtil.showToast(StringName.talkAddAgendaNameHint.tr);
  595. return;
  596. }
  597. Get.back();
  598. _dealAddProcedureList();
  599. });
  600. }
  601. void _dealAddProcedureList() {
  602. String name = agendaNameController.text;
  603. final addItem = Agenda(
  604. id: "",
  605. talkId: "",
  606. name: name,
  607. );
  608. addItem.content = agendaContentController.text;
  609. for (AgendaListAllBean item in agendaAllList) {
  610. if (item.name == name) {
  611. List<Agenda> list = item.list ?? [];
  612. list.add(addItem);
  613. item.list = list;
  614. agendaAllList.refresh();
  615. agendaContentController.clear();
  616. agendaNameController.clear();
  617. return;
  618. }
  619. }
  620. agendaAllList.add(AgendaListAllBean(name: name, list: [addItem]));
  621. agendaContentController.clear();
  622. agendaNameController.clear();
  623. }
  624. Future<File?> getFileByTalk(TalkBean? bean) async {
  625. isLocalFileHas = false;
  626. if (bean == null) {
  627. return null;
  628. }
  629. if (bean.uploadType == TalkUploadType.localUpload) {
  630. String? audioId =
  631. FileUploadCheckHelper.getLocalAudioId(bean.localAudioUrl);
  632. if (audioId != null && audioId.isNotEmpty) {
  633. isLocalFileHas = true;
  634. return await AudioPickerUtils.getAssetFile(audioId);
  635. } else {
  636. return await FileUploadCheckHelper.getChoiceUploadFile(bean.id);
  637. }
  638. } else {
  639. return await RecordHandler.getRecordFile(bean.id);
  640. }
  641. }
  642. Future<bool> checkLogin() async {
  643. if (!accountRepository.isLogin.value) {
  644. bool isLogin = await LoginPage.start(fromType: LoginFromType.talkDetail);
  645. if (isLogin) {
  646. backToSpecificPage(RoutePath.mainTab);
  647. }
  648. return false;
  649. }
  650. return true;
  651. }
  652. List<ShareTalkType> _getShareToType(bool isShowMindMap) {
  653. List<ShareTalkType> shareToType = [ShareTalkType.summary];
  654. if (isShowMindMap) {
  655. shareToType.add(ShareTalkType.mindMap);
  656. }
  657. shareToType.add(ShareTalkType.original);
  658. return shareToType;
  659. }
  660. void onShareClick() async {
  661. if (!await checkLogin()) {
  662. return;
  663. }
  664. if (talkBean.value?.status.value != TalkStatus.analysisSuccess) {
  665. return;
  666. }
  667. eventReport(EventId.event_101004);
  668. showTalkShareDialog(talkBean.value?.title.value,
  669. shareToType: _getShareToType(isShowMindMap.value),
  670. callback: (type, shareTo, fileName, tag) async {
  671. try {
  672. await isInstalled(shareTo);
  673. if (type == ShareTalkType.summary || type == ShareTalkType.original) {
  674. _shareSummaryOrOriginal(
  675. talkBean.value!.id, fileName, type, shareTo, tag);
  676. } else if (type == ShareTalkType.mindMap) {
  677. _shareMindMap(fileName, talkBean.value?.summary.value, shareTo, tag);
  678. }
  679. } catch (error) {
  680. if (error is SystemShareException) {
  681. ToastUtil.showToast(error.message);
  682. } else {
  683. ErrorHandler.toastError(error);
  684. }
  685. }
  686. });
  687. }
  688. Future<bool> isInstalled(ShareTo shareTo) async {
  689. if (shareTo == ShareTo.wechat) {
  690. bool isInstalled =
  691. await SystemShareUtil.isInstalled(SystemShareUtil.wechatPageName);
  692. if (!isInstalled) {
  693. throw SystemShareException('未安装微信');
  694. }
  695. } else if (shareTo == ShareTo.qq) {
  696. bool isInstalled =
  697. await SystemShareUtil.isInstalled(SystemShareUtil.qqPageName);
  698. if (!isInstalled) {
  699. throw SystemShareException('未安装QQ');
  700. }
  701. }
  702. // else if(shareTo == ShareTo.ios){
  703. // //ios不用判断
  704. // }
  705. return true;
  706. }
  707. void _shareMindMap(
  708. String title, String? summary, ShareTo shareTo, String tag) {
  709. LoadingDialog.show(StringName.mindMapExport.tr, backDismiss: true);
  710. if (_temporaryController.value != null) {
  711. _temporaryController.value?.dispose();
  712. _temporaryController.value = null;
  713. }
  714. final temporaryController = MindUtil.createMindWebViewController();
  715. VoidCallback? errorCallback = () {
  716. LoadingDialog.hide();
  717. temporaryController.dispose();
  718. ToastUtil.showToast('思维导图导出失败');
  719. };
  720. try {
  721. Future.delayed(const Duration(seconds: 10), () {
  722. throw Exception('导出超时');
  723. });
  724. _temporaryController.value = temporaryController;
  725. temporaryController.loadFlutterAsset(Assets.html.indexExport);
  726. temporaryController.setNavigationDelegate(
  727. NavigationDelegate(
  728. onHttpError: (error) {
  729. errorCallback?.call();
  730. errorCallback = null;
  731. },
  732. // onWebResourceError: (error) {
  733. // errorCallback?.call();
  734. // errorCallback = null;
  735. // },
  736. onPageFinished: (String url) {
  737. temporaryController.callHandler(MindUtil.functionToJsExport,
  738. args: [title, summary], handler: (value) async {
  739. if (value == null) {
  740. throw Exception('data is null');
  741. }
  742. File file = await MindUtil.saveToFile(value, title);
  743. temporaryController.dispose();
  744. if (shareTo == ShareTo.wechat) {
  745. await SystemShareUtil.shareSystemFile(
  746. SystemShareUtil.wechatPageName, file.path,
  747. shareTitle: '分享到微信', shareFileType: 'image/*');
  748. } else if (shareTo == ShareTo.qq) {
  749. await SystemShareUtil.shareSystemFile(
  750. SystemShareUtil.qqPageName, file.path,
  751. shareTitle: '分享到QQ', shareFileType: 'image/*');
  752. } else if (shareTo == ShareTo.ios) {
  753. await Share.shareXFiles([XFile(file.path)], subject: title);
  754. } else {
  755. throw Exception('不支持该分享方式');
  756. }
  757. SmartDialog.dismiss(tag: tag);
  758. LoadingDialog.hide();
  759. });
  760. },
  761. ),
  762. );
  763. } catch (e) {
  764. errorCallback?.call();
  765. errorCallback = null;
  766. }
  767. }
  768. void _shareSummaryOrOriginal(String id, String fileName, ShareTalkType type,
  769. ShareTo shareTo, String tag) async {
  770. talkRepository.talkExport(id, fileName, type).then((file) async {
  771. if (shareTo == ShareTo.ios) {
  772. await Share.shareXFiles([XFile(file.path)], subject: fileName);
  773. } else if (shareTo == ShareTo.wechat) {
  774. await SystemShareUtil.shareSystemFile(
  775. SystemShareUtil.wechatPageName, file.path,
  776. shareTitle: '分享到微信', shareFileType: 'text/*');
  777. } else if (shareTo == ShareTo.qq) {
  778. await SystemShareUtil.shareSystemFile(
  779. SystemShareUtil.qqPageName, file.path,
  780. shareTitle: '分享到QQ', shareFileType: 'text/*');
  781. }
  782. SmartDialog.dismiss(tag: tag);
  783. }).catchError((error) {
  784. throw error;
  785. });
  786. }
  787. void seekTo(int? startMs) {
  788. if (startMs == null) {
  789. return;
  790. }
  791. _audioPlayer.seek(Duration(milliseconds: startMs));
  792. }
  793. Future<TalkBean?> refreshTalkDetail() async {
  794. String? id = talkBean.value?.id;
  795. if (id == null) {
  796. return null;
  797. }
  798. return talkRepository.talkInfo(id).then((data) {
  799. var bean = data.talkInfo;
  800. if (bean != null) {
  801. if (talkBean.value == null) {
  802. talkBean.value = data.talkInfo;
  803. } else {
  804. talkBean.value?.updateBean(bean);
  805. }
  806. maxTemplateCount = data.maxTemplateCount;
  807. templateSelectId.value = bean.templateId;
  808. }
  809. templateList.value = data.templateList;
  810. return bean;
  811. });
  812. }
  813. void onEditTitleClick() {
  814. reNameDialog(StringName.talkRenameTitle.tr, talkBean.value?.title.value,
  815. hintTxt: StringName.talkRenameTitleHint.tr,
  816. maxLength: 15, returnBuilder: (newName) {
  817. talkRepository.talkRename(talkBean.value!.id, newName).then((data) {
  818. talkBean.value?.title.value = newName;
  819. }).catchError((error) {
  820. ErrorHandler.toastError(error);
  821. });
  822. });
  823. }
  824. void onExitMindFullScreen() {
  825. isShowMindFullScreen.value = false;
  826. }
  827. void selectTemplate(TemplateBean bean) {
  828. String? id = talkBean.value?.id;
  829. int? templateId = bean.id;
  830. if (talkBean.value?.templateId == templateId) {
  831. return;
  832. }
  833. if (id == null || templateId == null) {
  834. ToastUtil.showToast('谈话信息获取异常,请退出页面重试!');
  835. return;
  836. }
  837. LoadingDialog.show('生成总结中...');
  838. talkRepository
  839. .talkSummaryGenerate(id, templateId)
  840. .timeout(const Duration(seconds: 60))
  841. .then((data) {
  842. LoadingDialog.hide();
  843. talkBean.value?.updateBean(data);
  844. templateSelectId.value = templateId;
  845. ToastUtil.showToast('生成成功', displayType: SmartToastType.last);
  846. }).catchError((error) {
  847. LoadingDialog.hide();
  848. ToastUtil.showToast('生成失败');
  849. });
  850. }
  851. void setSearchChangeTxt(String value) {
  852. searchPrint.value = value;
  853. }
  854. void updateSearchPositionDesc(int now, int total) {
  855. searchResultDesc.value = '$now/$total';
  856. }
  857. void onSearchPrevious() {
  858. searchOperationCallback.value =
  859. Pair(checkTabBean.value?.type, SearchOperationType.previous);
  860. }
  861. void onSearchNext() {
  862. searchOperationCallback.value =
  863. Pair(checkTabBean.value?.type, SearchOperationType.next);
  864. }
  865. @override
  866. void onClose() {
  867. super.onClose();
  868. _talkUploadListener?.cancel();
  869. _talkBeanListener?.cancel();
  870. _audioPlayer.dispose();
  871. _agendaContentController?.dispose();
  872. _agendaNameController?.dispose();
  873. }
  874. }
  875. enum SearchOperationType { previous, next }
  876. enum TalkBarType { summary, mindMap, myTask, original }
  877. class TalkBarBean {
  878. final TalkBarType type;
  879. final String title;
  880. final bool? isShowEdit;
  881. final bool? isDisallowScroll;
  882. final bool? isShowSearch;
  883. TalkBarBean(this.type, this.title,
  884. {this.isShowEdit, this.isShowSearch, this.isDisallowScroll});
  885. }