controller.dart 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  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, params: {EventId.id: version});
  162. } else {
  163. EventHandler.report(EventId.event_101402, params: {EventId.id: version});
  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. }
  547. void updateTabIndex(int index) {
  548. checkTabBean.value = tabBeans[index];
  549. }
  550. void onEditCancel() {
  551. _isEditModel.value = false;
  552. agendaAllList.assignAll(agendaOriginalAllList
  553. .map((item) => AgendaListAllBean.from(item))
  554. .toList());
  555. }
  556. void onSearchCancel() {
  557. isSearchModel.value = false;
  558. searchPrint.value = '';
  559. searchResultDesc.value = '0/0';
  560. }
  561. void onEditDoneClick() {
  562. if (talkBean.value == null) {
  563. return;
  564. }
  565. List<AgendaUpdateBean> list = [];
  566. for (AgendaListAllBean item in agendaAllList) {
  567. if (item.list != null) {
  568. for (Agenda agenda in item.list!) {
  569. list.add(AgendaUpdateBean(agenda.id, agenda.name, agenda.content));
  570. }
  571. }
  572. }
  573. agendaRepository.agendaUpdate(talkBean.value!.id, list).then((data) {
  574. refreshAgendaAllData(isForceRefresh: true);
  575. eventBus.emit(TodoController.refreshTalkMineTask);
  576. isEditModelRx.value = false;
  577. }).catchError((error) {
  578. ErrorHandler.toastError(error);
  579. });
  580. }
  581. void removeTalkAgenda(List<Agenda>? list, Agenda agenda) {
  582. list?.remove(agenda);
  583. agendaAllList.refresh();
  584. }
  585. void showSingleAddAgendaDialog(BuildContext context) {
  586. showAddAgendaDialog(context, agendaContentController, agendaNameController,
  587. list: agendaAllList.map((e) => e.name ?? "").toList(), callback: () {
  588. if (agendaContentController.text.isEmpty) {
  589. ToastUtil.showToast(StringName.talkAddAgendaContentHint.tr);
  590. return;
  591. }
  592. if (agendaNameController.text.isEmpty) {
  593. ToastUtil.showToast(StringName.talkAddAgendaNameHint.tr);
  594. return;
  595. }
  596. Get.back();
  597. _dealAddProcedureList();
  598. });
  599. }
  600. void _dealAddProcedureList() {
  601. String name = agendaNameController.text;
  602. final addItem = Agenda(
  603. id: "",
  604. talkId: "",
  605. name: name,
  606. );
  607. addItem.content = agendaContentController.text;
  608. for (AgendaListAllBean item in agendaAllList) {
  609. if (item.name == name) {
  610. List<Agenda> list = item.list ?? [];
  611. list.add(addItem);
  612. item.list = list;
  613. agendaAllList.refresh();
  614. agendaContentController.clear();
  615. agendaNameController.clear();
  616. return;
  617. }
  618. }
  619. agendaAllList.add(AgendaListAllBean(name: name, list: [addItem]));
  620. agendaContentController.clear();
  621. agendaNameController.clear();
  622. }
  623. Future<File?> getFileByTalk(TalkBean? bean) async {
  624. isLocalFileHas = false;
  625. if (bean == null) {
  626. return null;
  627. }
  628. if (bean.uploadType == TalkUploadType.localUpload) {
  629. String? audioId =
  630. FileUploadCheckHelper.getLocalAudioId(bean.localAudioUrl);
  631. if (audioId != null && audioId.isNotEmpty) {
  632. isLocalFileHas = true;
  633. return await AudioPickerUtils.getAssetFile(audioId);
  634. } else {
  635. return await FileUploadCheckHelper.getChoiceUploadFile(bean.id);
  636. }
  637. } else {
  638. return await RecordHandler.getRecordFile(bean.id);
  639. }
  640. }
  641. Future<bool> checkLogin() async {
  642. if (!accountRepository.isLogin.value) {
  643. bool isLogin = await LoginPage.start(fromType: LoginFromType.talkDetail);
  644. if (isLogin) {
  645. backToSpecificPage(RoutePath.mainTab);
  646. }
  647. return false;
  648. }
  649. return true;
  650. }
  651. List<ShareTalkType> _getShareToType(bool isShowMindMap) {
  652. List<ShareTalkType> shareToType = [ShareTalkType.summary];
  653. if (isShowMindMap) {
  654. shareToType.add(ShareTalkType.mindMap);
  655. }
  656. shareToType.add(ShareTalkType.original);
  657. return shareToType;
  658. }
  659. void onShareClick() async {
  660. if (!await checkLogin()) {
  661. return;
  662. }
  663. if (talkBean.value?.status.value != TalkStatus.analysisSuccess) {
  664. return;
  665. }
  666. eventReport(EventId.event_101004);
  667. showTalkShareDialog(talkBean.value?.title.value,
  668. shareToType: _getShareToType(isShowMindMap.value),
  669. callback: (type, shareTo, fileName, tag) async {
  670. try {
  671. await isInstalled(shareTo);
  672. if (type == ShareTalkType.summary || type == ShareTalkType.original) {
  673. _shareSummaryOrOriginal(
  674. talkBean.value!.id, fileName, type, shareTo, tag);
  675. } else if (type == ShareTalkType.mindMap) {
  676. _shareMindMap(fileName, talkBean.value?.summary.value, shareTo, tag);
  677. }
  678. } catch (error) {
  679. if (error is SystemShareException) {
  680. ToastUtil.showToast(error.message);
  681. } else {
  682. ErrorHandler.toastError(error);
  683. }
  684. }
  685. });
  686. }
  687. Future<bool> isInstalled(ShareTo shareTo) async {
  688. if (shareTo == ShareTo.wechat) {
  689. bool isInstalled =
  690. await SystemShareUtil.isInstalled(SystemShareUtil.wechatPageName);
  691. if (!isInstalled) {
  692. throw SystemShareException('未安装微信');
  693. }
  694. } else if (shareTo == ShareTo.qq) {
  695. bool isInstalled =
  696. await SystemShareUtil.isInstalled(SystemShareUtil.qqPageName);
  697. if (!isInstalled) {
  698. throw SystemShareException('未安装QQ');
  699. }
  700. }
  701. // else if(shareTo == ShareTo.ios){
  702. // //ios不用判断
  703. // }
  704. return true;
  705. }
  706. void _shareMindMap(
  707. String title, String? summary, ShareTo shareTo, String tag) {
  708. LoadingDialog.show(StringName.mindMapExport.tr, backDismiss: true);
  709. if (_temporaryController.value != null) {
  710. _temporaryController.value?.dispose();
  711. _temporaryController.value = null;
  712. }
  713. final temporaryController = MindUtil.createMindWebViewController();
  714. VoidCallback? errorCallback = () {
  715. LoadingDialog.hide();
  716. temporaryController.dispose();
  717. ToastUtil.showToast('思维导图导出失败');
  718. };
  719. try {
  720. Future.delayed(const Duration(seconds: 10), () {
  721. throw Exception('导出超时');
  722. });
  723. _temporaryController.value = temporaryController;
  724. temporaryController.loadFlutterAsset(Assets.html.indexExport);
  725. temporaryController.setNavigationDelegate(
  726. NavigationDelegate(
  727. onHttpError: (error) {
  728. errorCallback?.call();
  729. errorCallback = null;
  730. },
  731. // onWebResourceError: (error) {
  732. // errorCallback?.call();
  733. // errorCallback = null;
  734. // },
  735. onPageFinished: (String url) {
  736. temporaryController.callHandler(MindUtil.functionToJsExport,
  737. args: [title, summary], handler: (value) async {
  738. if (value == null) {
  739. throw Exception('data is null');
  740. }
  741. File file = await MindUtil.saveToFile(value, title);
  742. temporaryController.dispose();
  743. if (shareTo == ShareTo.wechat) {
  744. await SystemShareUtil.shareSystemFile(
  745. SystemShareUtil.wechatPageName, file.path,
  746. shareTitle: '分享到微信', shareFileType: 'image/*');
  747. } else if (shareTo == ShareTo.qq) {
  748. await SystemShareUtil.shareSystemFile(
  749. SystemShareUtil.qqPageName, file.path,
  750. shareTitle: '分享到QQ', shareFileType: 'image/*');
  751. } else if (shareTo == ShareTo.ios) {
  752. await Share.shareXFiles([XFile(file.path)], subject: title);
  753. } else {
  754. throw Exception('不支持该分享方式');
  755. }
  756. SmartDialog.dismiss(tag: tag);
  757. LoadingDialog.hide();
  758. });
  759. },
  760. ),
  761. );
  762. } catch (e) {
  763. errorCallback?.call();
  764. errorCallback = null;
  765. }
  766. }
  767. void _shareSummaryOrOriginal(String id, String fileName, ShareTalkType type,
  768. ShareTo shareTo, String tag) async {
  769. talkRepository.talkExport(id, fileName, type).then((file) async {
  770. if (shareTo == ShareTo.ios) {
  771. await Share.shareXFiles([XFile(file.path)], subject: fileName);
  772. } else if (shareTo == ShareTo.wechat) {
  773. await SystemShareUtil.shareSystemFile(
  774. SystemShareUtil.wechatPageName, file.path,
  775. shareTitle: '分享到微信', shareFileType: 'text/*');
  776. } else if (shareTo == ShareTo.qq) {
  777. await SystemShareUtil.shareSystemFile(
  778. SystemShareUtil.qqPageName, file.path,
  779. shareTitle: '分享到QQ', shareFileType: 'text/*');
  780. }
  781. SmartDialog.dismiss(tag: tag);
  782. }).catchError((error) {
  783. throw error;
  784. });
  785. }
  786. void seekTo(int? startMs) {
  787. if (startMs == null) {
  788. return;
  789. }
  790. _audioPlayer.seek(Duration(milliseconds: startMs));
  791. }
  792. Future<TalkBean?> refreshTalkDetail() async {
  793. String? id = talkBean.value?.id;
  794. if (id == null) {
  795. return null;
  796. }
  797. return talkRepository.talkInfo(id).then((data) {
  798. var bean = data.talkInfo;
  799. if (bean != null) {
  800. if (talkBean.value == null) {
  801. talkBean.value = data.talkInfo;
  802. } else {
  803. talkBean.value?.updateBean(bean);
  804. }
  805. maxTemplateCount = data.maxTemplateCount;
  806. templateSelectId.value = bean.templateId;
  807. }
  808. templateList.value = data.templateList;
  809. return bean;
  810. });
  811. }
  812. void onEditTitleClick() {
  813. reNameDialog(StringName.talkRenameTitle.tr, talkBean.value?.title.value,
  814. hintTxt: StringName.talkRenameTitleHint.tr,
  815. maxLength: 15, returnBuilder: (newName) {
  816. talkRepository.talkRename(talkBean.value!.id, newName).then((data) {
  817. talkBean.value?.title.value = newName;
  818. }).catchError((error) {
  819. ErrorHandler.toastError(error);
  820. });
  821. });
  822. }
  823. void onExitMindFullScreen() {
  824. isShowMindFullScreen.value = false;
  825. }
  826. void selectTemplate(TemplateBean bean) {
  827. String? id = talkBean.value?.id;
  828. int? templateId = bean.id;
  829. if (talkBean.value?.templateId == templateId) {
  830. return;
  831. }
  832. if (id == null || templateId == null) {
  833. ToastUtil.showToast('谈话信息获取异常,请退出页面重试!');
  834. return;
  835. }
  836. LoadingDialog.show('生成总结中...');
  837. talkRepository
  838. .talkSummaryGenerate(id, templateId)
  839. .timeout(const Duration(seconds: 60))
  840. .then((data) {
  841. LoadingDialog.hide();
  842. talkBean.value?.updateBean(data);
  843. templateSelectId.value = templateId;
  844. ToastUtil.showToast('生成成功', displayType: SmartToastType.last);
  845. }).catchError((error) {
  846. LoadingDialog.hide();
  847. ToastUtil.showToast('生成失败');
  848. });
  849. }
  850. void setSearchChangeTxt(String value) {
  851. searchPrint.value = value;
  852. }
  853. void updateSearchPositionDesc(int now, int total) {
  854. searchResultDesc.value = '$now/$total';
  855. }
  856. void onSearchPrevious() {
  857. searchOperationCallback.value =
  858. Pair(checkTabBean.value?.type, SearchOperationType.previous);
  859. }
  860. void onSearchNext() {
  861. searchOperationCallback.value =
  862. Pair(checkTabBean.value?.type, SearchOperationType.next);
  863. }
  864. @override
  865. void onClose() {
  866. super.onClose();
  867. _talkUploadListener?.cancel();
  868. _talkBeanListener?.cancel();
  869. _audioPlayer.dispose();
  870. _agendaContentController?.dispose();
  871. _agendaNameController?.dispose();
  872. }
  873. }
  874. enum SearchOperationType { previous, next }
  875. enum TalkBarType { summary, mindMap, myTask, original }
  876. class TalkBarBean {
  877. final TalkBarType type;
  878. final String title;
  879. final bool? isShowEdit;
  880. final bool? isDisallowScroll;
  881. final bool? isShowSearch;
  882. TalkBarBean(this.type, this.title,
  883. {this.isShowEdit, this.isShowSearch, this.isDisallowScroll});
  884. }