controller.dart 15 KB

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