controller.dart 16 KB

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