controller.dart 11 KB

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