controller.dart 32 KB

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