controller.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:connectivity_plus/connectivity_plus.dart';
  4. import 'package:electronic_assistant/base/base_controller.dart';
  5. import 'package:electronic_assistant/data/consts/event_report_id.dart';
  6. import 'package:electronic_assistant/data/repositories/account_repository.dart';
  7. import 'package:electronic_assistant/data/repositories/task_repository.dart';
  8. import 'package:electronic_assistant/utils/audio_picker_utils.dart';
  9. import 'package:electronic_assistant/handler/event_handler.dart';
  10. import 'package:electronic_assistant/module/chat/view.dart';
  11. import 'package:electronic_assistant/module/login/view.dart';
  12. import 'package:electronic_assistant/module/store/view.dart';
  13. import 'package:electronic_assistant/module/talk/summary/view.dart';
  14. import 'package:electronic_assistant/module/talk/todo/controller.dart';
  15. import 'package:electronic_assistant/module/talk/todo/view.dart';
  16. import 'package:electronic_assistant/resource/assets.gen.dart';
  17. import 'package:electronic_assistant/resource/colors.gen.dart';
  18. import 'package:electronic_assistant/resource/string.gen.dart';
  19. import 'package:electronic_assistant/utils/error_handler.dart';
  20. import 'package:electronic_assistant/utils/expand.dart';
  21. import 'package:electronic_assistant/utils/file_upload_check_helper.dart';
  22. import 'package:electronic_assistant/utils/mmkv_util.dart';
  23. import 'package:flutter/cupertino.dart';
  24. import 'package:flutter/material.dart';
  25. import 'package:flutter_screenutil/flutter_screenutil.dart';
  26. import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
  27. import 'package:get/get.dart';
  28. import 'package:just_audio/just_audio.dart';
  29. import 'package:wakelock_plus/wakelock_plus.dart';
  30. import '../../data/api/request/agenda_update_bean.dart';
  31. import '../../data/bean/agenda.dart';
  32. import '../../data/bean/agenda_list_all_bean.dart';
  33. import '../../data/bean/talks.dart';
  34. import '../../data/repositories/agenda_repository.dart';
  35. import '../../data/repositories/talk_repository.dart';
  36. import '../../dialog/add_agenda_dialog.dart';
  37. import '../../dialog/alert_dialog.dart';
  38. import '../../dialog/talk_share_dialog.dart';
  39. import '../../utils/event_bus.dart';
  40. import '../../utils/system_share_util.dart';
  41. import '../../utils/toast_util.dart';
  42. import '../record/controller.dart';
  43. import 'original/view.dart';
  44. import 'package:share_plus/share_plus.dart';
  45. class TalkController extends BaseController {
  46. static const String argumentItem = 'argument_item';
  47. static const String argumentEventTag = 'argument_event_tag';
  48. final String uploadNoPrompts = "UPLOAD_NO_PROMPTS";
  49. final Rxn<TalkBean> talkBean = Rxn();
  50. final isShowElectricLow = false.obs;
  51. bool isAudioLoading = false;
  52. final double sliderMax = 1;
  53. bool? audioFileIsExist;
  54. bool? isUploadedFile;
  55. Rxn<bool> isUploading = Rxn();
  56. final isAudioPlaying = false.obs;
  57. final audioProgressValue = 0.0.obs;
  58. final audioDuration = Duration.zero.obs;
  59. final agendaOriginalAllList = <AgendaListAllBean>[];
  60. final agendaAllList = <AgendaListAllBean>[].obs;
  61. final _isEditModel = false.obs;
  62. final TextEditingController editTalkNameController = TextEditingController();
  63. final tabIndex = 0.obs;
  64. final List<String> tabBeans = [
  65. StringName.talkTabSummary.tr,
  66. StringName.talkTabMyTask.tr,
  67. StringName.talkTabOriginal.tr
  68. ];
  69. bool get isEditModel => _isEditModel.value;
  70. RxBool get isEditModelRx => _isEditModel;
  71. final _audioPlayer = AudioPlayer();
  72. StreamSubscription? _talkBeanListener;
  73. final pages = [const SummaryView(), const TodoView(), const OriginalView()];
  74. TextEditingController? _agendaContentController;
  75. TextEditingController? _agendaNameController;
  76. TextEditingController get agendaContentController {
  77. _agendaContentController ??= TextEditingController();
  78. return _agendaContentController!;
  79. }
  80. TextEditingController get agendaNameController {
  81. _agendaNameController ??= TextEditingController();
  82. return _agendaNameController!;
  83. }
  84. String? eventTag;
  85. bool isLocalFileHas = false;
  86. @override
  87. void onReady() {
  88. super.onReady();
  89. _initAudioPlayer();
  90. _initListener();
  91. _getArguments();
  92. eventReport(EventId.event_101001, params: {EventId.id: eventTag});
  93. }
  94. RxDouble getUploadingProgress() {
  95. return talkRepository.getUploadProgress(talkBean.value!.id);
  96. }
  97. void eventReport(String eventId, {Map<String, dynamic>? params}) {
  98. if (talkBean.value == null || talkBean.value?.isExample == true) {
  99. return;
  100. }
  101. EventHandler.report(eventId, params: params);
  102. }
  103. void _initAudioPlayer() {
  104. _audioPlayer.playerStateStream.listen((playerState) {
  105. if (playerState.processingState == ProcessingState.loading ||
  106. playerState.processingState == ProcessingState.buffering) {
  107. isAudioLoading = true;
  108. debugPrint('音频load = true');
  109. } else {
  110. debugPrint('音频load = false');
  111. isAudioLoading = false;
  112. if (playerState.processingState == ProcessingState.completed) {
  113. _audioPlayer.stop();
  114. _audioPlayer.seek(Duration.zero);
  115. isAudioPlaying.value = false;
  116. debugPrint('音频 播放结束了');
  117. }
  118. }
  119. isAudioPlaying.value = playerState.playing;
  120. }, onError: (Object e, StackTrace stackTrace) {
  121. debugPrint('音频加载异常 == $e');
  122. });
  123. _audioPlayer.durationStream.listen((duration) {
  124. if (duration != null) {
  125. debugPrint('音频总播放时长 == ${duration.inMilliseconds}');
  126. audioDuration.value = duration;
  127. }
  128. });
  129. _audioPlayer.positionStream.listen((position) {
  130. if (audioDuration.value.inMilliseconds > 0) {
  131. audioProgressValue.value =
  132. (position.inMilliseconds / audioDuration.value.inMilliseconds)
  133. .clamp(0.0, sliderMax);
  134. }
  135. });
  136. }
  137. void _initListener() {
  138. _talkBeanListener = talkBean.listen((bean) {
  139. _dealTalkUpdate(bean);
  140. });
  141. }
  142. void _dealTalkUpdate(TalkBean? bean) async {
  143. String? id = talkBean.value?.id;
  144. if (id == null) {
  145. return;
  146. }
  147. if (talkRepository.isUploadingTalk(id) &&
  148. talkBean.value?.status.value == TalkStatus.notAnalysis) {
  149. isUploading.value = true;
  150. } else {
  151. isUploading.value = false;
  152. }
  153. _loadAudioFile(bean);
  154. }
  155. Future<void> _loadAudioFile(TalkBean? bean, {bool? loadPlay}) async {
  156. try {
  157. Uri? uri;
  158. if (bean?.isExample == true && bean?.audioUrl != null) {
  159. uri = Uri.parse(bean!.audioUrl!);
  160. } else {
  161. File? file = await getFileByTalk(talkBean.value);
  162. if (file?.existsSync() == true) {
  163. uri = file?.uri;
  164. }
  165. }
  166. if (uri == null) {
  167. throw '音频文件不存在';
  168. }
  169. await _audioPlayer.setAudioSource(AudioSource.uri(uri));
  170. if (loadPlay == true) {
  171. clickPlayAudio();
  172. }
  173. audioFileIsExist = true;
  174. } catch (e) {
  175. audioFileIsExist = false;
  176. debugPrint('音频设置异常 == $e');
  177. }
  178. }
  179. void _getArguments() {
  180. TalkBean? bean = parameters?[argumentItem];
  181. if (bean != null) {
  182. talkBean.value = bean;
  183. }
  184. eventTag = parameters?[argumentEventTag];
  185. }
  186. void updateProgress(double value) {
  187. final newPosition = Duration(
  188. milliseconds: (value * audioDuration.value.inMilliseconds).toInt());
  189. _audioPlayer.seek(newPosition);
  190. }
  191. void clickPlayAudio() async {
  192. if (audioFileIsExist != true && isLocalFileHas == false) {
  193. ToastUtil.showToast(StringName.talkFileNotFind.tr);
  194. return;
  195. }
  196. if (isLocalFileHas == true &&
  197. audioFileIsExist == false &&
  198. !await AudioPickerUtils.hasPermission()) {
  199. bool has = await AudioPickerUtils.requestPermissionExtend();
  200. if (has == false) {
  201. ToastUtil.showToast(StringName.authorizationFailed.tr);
  202. return;
  203. }
  204. //重新加载
  205. _loadAudioFile(talkBean.value, loadPlay: true);
  206. return;
  207. }
  208. if (isAudioLoading) {
  209. ToastUtil.showToast(StringName.talkAudioLoading.tr);
  210. return;
  211. }
  212. if (_audioPlayer.playing) {
  213. _audioPlayer.pause();
  214. isAudioPlaying.value = false;
  215. } else {
  216. _audioPlayer.play();
  217. isAudioPlaying.value = true;
  218. }
  219. }
  220. void _checkFileSizeAndNet() async {
  221. String? id = talkBean.value?.id;
  222. if (id == null) {
  223. return;
  224. }
  225. File? file = await getFileByTalk(talkBean.value);
  226. if (isLocalFileHas == true &&
  227. audioFileIsExist == false &&
  228. !await AudioPickerUtils.hasPermission()) {
  229. bool has = await AudioPickerUtils.requestPermissionExtend();
  230. if (has == false) {
  231. ToastUtil.showToast(StringName.authorizationFailed.tr);
  232. return;
  233. }
  234. //重新上传
  235. _checkFileSizeAndNet();
  236. return;
  237. }
  238. if (file == null || !file.existsSync()) {
  239. ToastUtil.showToast(StringName.talkUploadFileNotExist.tr);
  240. return;
  241. }
  242. bool isCheckRemind = KVUtil.getBool(uploadNoPrompts, false);
  243. if (isCheckRemind) {
  244. _requestAnalyze(file);
  245. return;
  246. }
  247. //如果文件大小低于250MB 不弹窗提醒
  248. if (file.lengthSync() < 250 * 1024 * 1024) {
  249. _requestAnalyze(file);
  250. return;
  251. }
  252. final List<ConnectivityResult> connectivityResult =
  253. await (Connectivity().checkConnectivity());
  254. if (connectivityResult.contains(ConnectivityResult.wifi)) {
  255. _requestAnalyze(file);
  256. } else {
  257. _showTrafficRemindDialog(file.lengthSync().toReadableSize(),
  258. confirmOnTap: (isCheckRemind) {
  259. if (isCheckRemind) {
  260. KVUtil.putBool(uploadNoPrompts, true);
  261. }
  262. _requestAnalyze(file);
  263. });
  264. }
  265. }
  266. void _showTrafficRemindDialog(String holderTxt,
  267. {void Function(bool isCheckRemind)? confirmOnTap}) {
  268. final remindTrafficConsume = false.obs;
  269. Widget getSelectIcon() {
  270. return Obx(() {
  271. return remindTrafficConsume.value
  272. ? Assets.images.iconSelectTrue.image()
  273. : Assets.images.iconSelectFalse.image();
  274. });
  275. }
  276. Assets.images.iconSelectTrue.image();
  277. EAAlertDialog.show(
  278. contentWidget: Column(
  279. children: [
  280. Text(
  281. StringName.talkTrafficRemindTitle.tr
  282. .replacePlaceholders([holderTxt]),
  283. style:
  284. TextStyle(fontSize: 15.sp, color: ColorName.primaryTextColor),
  285. ),
  286. SizedBox(height: 8.h),
  287. GestureDetector(
  288. onTap: () {
  289. remindTrafficConsume.value = !remindTrafficConsume.value;
  290. },
  291. child: IntrinsicWidth(
  292. child: Row(
  293. children: [
  294. SizedBox(width: 20.w, height: 20.w, child: getSelectIcon()),
  295. SizedBox(width: 5.w),
  296. Text(
  297. StringName.talkTrafficRemindTips.tr,
  298. style: TextStyle(
  299. fontSize: 15.sp, color: ColorName.tertiaryTextColor),
  300. )
  301. ],
  302. ),
  303. ),
  304. )
  305. ],
  306. ),
  307. cancelText: StringName.cancel.tr,
  308. confirmText: StringName.sure.tr,
  309. confirmOnTap: () {
  310. confirmOnTap?.call(remindTrafficConsume.value);
  311. });
  312. }
  313. void checkCanAnalyze() {
  314. String? id = talkBean.value?.id;
  315. double? duration = talkBean.value?.duration;
  316. if (id == null || duration == null) {
  317. return;
  318. }
  319. eventReport(EventId.event_101002);
  320. talkRepository.checkElectric(duration).then((data) {
  321. if (data.enough) {
  322. //检查网络以及文件大小
  323. _checkFileSizeAndNet();
  324. } else {
  325. ToastUtil.showToast(StringName.talkAnalyseLowToast.tr);
  326. isShowElectricLow.value = true;
  327. }
  328. }).catchError((error) {
  329. ToastUtil.showToast(error);
  330. });
  331. }
  332. void _requestAnalyze(File file) {
  333. String? talkId = talkBean.value?.id;
  334. double? duration = talkBean.value?.duration;
  335. if (talkId == null || duration == null || isUploadedFile == true) {
  336. return;
  337. }
  338. isUploading.value = true;
  339. WakelockPlus.enable();
  340. talkRepository.uploadTalkFile(talkId, duration, file).then((taskId) {
  341. isUploadedFile = true;
  342. talkBean.value?.status.value = TalkStatus.analysing;
  343. taskRepository.addTask(taskId);
  344. }).catchError((error) {
  345. isUploading.value = false;
  346. ErrorHandler.toastError(error);
  347. }).whenComplete(() => WakelockPlus.disable());
  348. }
  349. void refreshAgendaAllData({bool isForceRefresh = false}) {
  350. String? id = talkBean.value?.id;
  351. if (id == null || (!isForceRefresh && agendaAllList.isNotEmpty)) {
  352. return;
  353. }
  354. agendaRepository.agendaListAll(id).then((agenda) {
  355. agendaAllList.clear();
  356. agendaOriginalAllList.clear();
  357. if (agenda.list != null) {
  358. agendaOriginalAllList.addAll(
  359. agenda.list!.map((item) => AgendaListAllBean.from(item)).toList());
  360. agendaAllList.addAll(agenda.list!);
  361. }
  362. });
  363. }
  364. void clickAIAnalysis() {
  365. if (!accountRepository.isLogin.value) {
  366. LoginPage.start(fromType: LoginFromType.talkDetail);
  367. return;
  368. }
  369. if (talkBean.value != null) {
  370. eventReport(EventId.event_101003);
  371. ChatPage.startByTalk(
  372. talkBean.value!.isExample == true
  373. ? ChatFromType.fromTalkExample
  374. : ChatFromType.fromTalkDetail,
  375. talkBean.value!);
  376. }
  377. }
  378. void onGoElectricStore() {
  379. StorePage.start(fromType: StoreFromType.analyse);
  380. Future.delayed(const Duration(milliseconds: 250), () {
  381. isShowElectricLow.value = false;
  382. });
  383. }
  384. void onEditModelClick() {
  385. if (!accountRepository.isLogin.value) {
  386. LoginPage.start(fromType: LoginFromType.talkDetail);
  387. return;
  388. }
  389. _isEditModel.value = true;
  390. if (_audioPlayer.playing) {
  391. _audioPlayer.pause();
  392. isAudioPlaying.value = false;
  393. }
  394. editTalkNameController.text = talkBean.value?.title.value ?? '';
  395. }
  396. void updateTabIndex(int index) {
  397. tabIndex.value = index;
  398. }
  399. void onEditCancel() {
  400. _isEditModel.value = false;
  401. agendaAllList.assignAll(agendaOriginalAllList
  402. .map((item) => AgendaListAllBean.from(item))
  403. .toList());
  404. }
  405. void onEditDoneClick() {
  406. if (talkBean.value == null) {
  407. return;
  408. }
  409. List<AgendaUpdateBean> list = [];
  410. for (AgendaListAllBean item in agendaAllList) {
  411. if (item.list != null) {
  412. for (Agenda agenda in item.list!) {
  413. list.add(AgendaUpdateBean(agenda.id, agenda.name, agenda.content));
  414. }
  415. }
  416. }
  417. agendaRepository.agendaUpdate(talkBean.value!.id, list).then((data) {
  418. refreshAgendaAllData(isForceRefresh: true);
  419. eventBus.emit(TodoController.refreshTalkMineTask);
  420. isEditModelRx.value = false;
  421. }).catchError((error) {
  422. ErrorHandler.toastError(error);
  423. });
  424. if (tabIndex.value == 0) {
  425. String updateName = editTalkNameController.text;
  426. talkRepository.talkRename(talkBean.value!.id, updateName).then((data) {
  427. talkBean.value?.title.value = updateName;
  428. }).catchError((error) {
  429. ErrorHandler.toastError(error);
  430. });
  431. }
  432. }
  433. void removeTalkAgenda(List<Agenda>? list, Agenda agenda) {
  434. list?.remove(agenda);
  435. agendaAllList.refresh();
  436. }
  437. void showSingleAddAgendaDialog(BuildContext context) {
  438. showAddAgendaDialog(context, agendaContentController, agendaNameController,
  439. list: agendaAllList.map((e) => e.name ?? "").toList(), callback: () {
  440. if (agendaContentController.text.isEmpty) {
  441. ToastUtil.showToast(StringName.talkAddAgendaContentHint.tr);
  442. return;
  443. }
  444. if (agendaNameController.text.isEmpty) {
  445. ToastUtil.showToast(StringName.talkAddAgendaNameHint.tr);
  446. return;
  447. }
  448. Get.back();
  449. _dealAddProcedureList();
  450. });
  451. }
  452. void _dealAddProcedureList() {
  453. String name = agendaNameController.text;
  454. final addItem = Agenda(
  455. id: "",
  456. talkId: "",
  457. name: name,
  458. );
  459. addItem.content = agendaContentController.text;
  460. for (AgendaListAllBean item in agendaAllList) {
  461. if (item.name == name) {
  462. List<Agenda> list = item.list ?? [];
  463. list.add(addItem);
  464. item.list = list;
  465. agendaAllList.refresh();
  466. agendaContentController.clear();
  467. agendaNameController.clear();
  468. return;
  469. }
  470. }
  471. agendaAllList.add(AgendaListAllBean(name: name, list: [addItem]));
  472. agendaContentController.clear();
  473. agendaNameController.clear();
  474. }
  475. Future<File?> getFileByTalk(TalkBean? bean) async {
  476. isLocalFileHas = false;
  477. if (bean == null) {
  478. return null;
  479. }
  480. if (bean.uploadType == TalkUploadType.localUpload) {
  481. String? audioId =
  482. FileUploadCheckHelper.getLocalAudioId(bean.localAudioUrl);
  483. if (audioId != null && audioId.isNotEmpty) {
  484. isLocalFileHas = true;
  485. return await AudioPickerUtils.getAssetFile(audioId);
  486. } else {
  487. return await FileUploadCheckHelper.getChoiceUploadFile(bean.id);
  488. }
  489. } else {
  490. return await RecordController.getRecordFile(bean.id);
  491. }
  492. }
  493. void onShareClick() {
  494. if (!accountRepository.isLogin.value) {
  495. LoginPage.start(fromType: LoginFromType.talkDetail);
  496. return;
  497. }
  498. if (talkBean.value?.status.value != TalkStatus.analysisSuccess) {
  499. return;
  500. }
  501. eventReport(EventId.event_101004);
  502. showTalkShareDialog(talkBean.value?.title.value,
  503. (type, shareTo, fileName, tag) {
  504. talkRepository
  505. .talkExport(talkBean.value!.id, fileName, type)
  506. .then((file) async {
  507. if (shareTo == ShareTo.ios) {
  508. await Share.shareXFiles([XFile(file.path)], subject: fileName);
  509. } else if (shareTo == ShareTo.wechat) {
  510. await SystemShareUtil.shareWechatFile(file.path);
  511. } else {
  512. await SystemShareUtil.shareQQFile(file.path);
  513. }
  514. SmartDialog.dismiss(tag: tag);
  515. }).catchError((error) {
  516. if (error is SystemShareException) {
  517. ToastUtil.showToast(error.message);
  518. } else {
  519. ErrorHandler.toastError(error);
  520. }
  521. });
  522. });
  523. }
  524. @override
  525. void onClose() {
  526. super.onClose();
  527. _talkBeanListener?.cancel();
  528. _audioPlayer.dispose();
  529. _agendaContentController?.dispose();
  530. _agendaNameController?.dispose();
  531. }
  532. }