controller.dart 14 KB

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