import 'dart:async'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:electronic_assistant/base/base_controller.dart'; import 'package:electronic_assistant/data/consts/event_report_id.dart'; import 'package:electronic_assistant/data/repositories/account_repository.dart'; import 'package:electronic_assistant/data/repositories/task_repository.dart'; import 'package:electronic_assistant/utils/audio_picker_utils.dart'; import 'package:electronic_assistant/handler/event_handler.dart'; import 'package:electronic_assistant/module/chat/view.dart'; import 'package:electronic_assistant/module/login/view.dart'; import 'package:electronic_assistant/module/store/view.dart'; import 'package:electronic_assistant/module/talk/summary/view.dart'; import 'package:electronic_assistant/module/talk/todo/controller.dart'; import 'package:electronic_assistant/module/talk/todo/view.dart'; import 'package:electronic_assistant/resource/assets.gen.dart'; import 'package:electronic_assistant/resource/colors.gen.dart'; import 'package:electronic_assistant/resource/string.gen.dart'; import 'package:electronic_assistant/utils/error_handler.dart'; import 'package:electronic_assistant/utils/expand.dart'; import 'package:electronic_assistant/utils/file_upload_check_helper.dart'; import 'package:electronic_assistant/utils/mmkv_util.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import '../../data/api/request/agenda_update_bean.dart'; import '../../data/bean/agenda.dart'; import '../../data/bean/agenda_list_all_bean.dart'; import '../../data/bean/talks.dart'; import '../../data/repositories/agenda_repository.dart'; import '../../data/repositories/talk_repository.dart'; import '../../dialog/add_agenda_dialog.dart'; import '../../dialog/alert_dialog.dart'; import '../../utils/event_bus.dart'; import '../../utils/toast_util.dart'; import '../record/controller.dart'; import 'original/view.dart'; class TalkController extends BaseController { static const String argumentItem = 'argument_item'; static const String argumentEventTag = 'argument_event_tag'; final String uploadNoPrompts = "UPLOAD_NO_PROMPTS"; final Rxn talkBean = Rxn(); final isShowElectricLow = false.obs; bool isAudioLoading = false; final double sliderMax = 1; bool? audioFileIsExist; bool? isUploadedFile; Rxn isUploading = Rxn(); final isAudioPlaying = false.obs; final audioProgressValue = 0.0.obs; final audioDuration = Duration.zero.obs; final agendaOriginalAllList = []; final agendaAllList = [].obs; final _isEditModel = false.obs; final TextEditingController editTalkNameController = TextEditingController(); final tabIndex = 0.obs; final List tabBeans = [ StringName.talkTabSummary.tr, StringName.talkTabMyTask.tr, StringName.talkTabOriginal.tr ]; bool get isEditModel => _isEditModel.value; RxBool get isEditModelRx => _isEditModel; final _audioPlayer = AudioPlayer(); StreamSubscription? _talkBeanListener; final pages = [const SummaryView(), const TodoView(), const OriginalView()]; TextEditingController? _agendaContentController; TextEditingController? _agendaNameController; TextEditingController get agendaContentController { _agendaContentController ??= TextEditingController(); return _agendaContentController!; } TextEditingController get agendaNameController { _agendaNameController ??= TextEditingController(); return _agendaNameController!; } String? eventTag; bool isLocalFileHas = false; @override void onReady() { super.onReady(); _initAudioPlayer(); _initListener(); _getArguments(); eventReport(EventId.event_101001, params: {EventId.id: eventTag}); } RxDouble getUploadingProgress() { return talkRepository.getUploadProgress(talkBean.value!.id); } void eventReport(String eventId, {Map? params}) { if (talkBean.value == null || talkBean.value?.isExample == true) { return; } EventHandler.report(eventId, params: params); } void _initAudioPlayer() { _audioPlayer.playerStateStream.listen((playerState) { if (playerState.processingState == ProcessingState.loading || playerState.processingState == ProcessingState.buffering) { isAudioLoading = true; debugPrint('音频load = true'); } else { debugPrint('音频load = false'); isAudioLoading = false; if (playerState.processingState == ProcessingState.completed) { _audioPlayer.stop(); _audioPlayer.seek(Duration.zero); isAudioPlaying.value = false; debugPrint('音频 播放结束了'); } } isAudioPlaying.value = playerState.playing; }, onError: (Object e, StackTrace stackTrace) { debugPrint('音频加载异常 == $e'); }); _audioPlayer.durationStream.listen((duration) { if (duration != null) { debugPrint('音频总播放时长 == ${duration.inMilliseconds}'); audioDuration.value = duration; } }); _audioPlayer.positionStream.listen((position) { if (audioDuration.value.inMilliseconds > 0) { audioProgressValue.value = (position.inMilliseconds / audioDuration.value.inMilliseconds) .clamp(0.0, sliderMax); } }); } void _initListener() { _talkBeanListener = talkBean.listen((bean) { _dealTalkUpdate(bean); }); } void _dealTalkUpdate(TalkBean? bean) async { String? id = talkBean.value?.id; if (id == null) { return; } if (talkRepository.isUploadingTalk(id) && talkBean.value?.status.value == TalkStatus.notAnalysis) { isUploading.value = true; } else { isUploading.value = false; } _loadAudioFile(bean); } Future _loadAudioFile(TalkBean? bean, {bool? loadPlay}) async { try { Uri? uri; if (bean?.isExample == true && bean?.audioUrl != null) { uri = Uri.parse(bean!.audioUrl!); } else { File? file = await getFileByTalk(talkBean.value); if (file?.existsSync() == true) { uri = file?.uri; } } if (uri == null) { throw '音频文件不存在'; } await _audioPlayer.setAudioSource(AudioSource.uri(uri)); if (loadPlay == true) { clickPlayAudio(); } audioFileIsExist = true; } catch (e) { audioFileIsExist = false; debugPrint('音频设置异常 == $e'); } } void _getArguments() { TalkBean? bean = parameters?[argumentItem]; if (bean != null) { talkBean.value = bean; } eventTag = parameters?[argumentEventTag]; } void updateProgress(double value) { final newPosition = Duration( milliseconds: (value * audioDuration.value.inMilliseconds).toInt()); _audioPlayer.seek(newPosition); } void clickPlayAudio() async { if (audioFileIsExist != true && isLocalFileHas == false) { ToastUtil.showToast(StringName.talkFileNotFind.tr); return; } if (isLocalFileHas == true && audioFileIsExist == false && !await AudioPickerUtils.hasPermission()) { bool has = await AudioPickerUtils.requestPermissionExtend(); if (has == false) { ToastUtil.showToast(StringName.authorizationFailed.tr); return; } //重新加载 _loadAudioFile(talkBean.value, loadPlay: true); return; } if (isAudioLoading) { ToastUtil.showToast(StringName.talkAudioLoading.tr); return; } if (_audioPlayer.playing) { _audioPlayer.pause(); isAudioPlaying.value = false; } else { _audioPlayer.play(); isAudioPlaying.value = true; } } void _checkFileSizeAndNet() async { String? id = talkBean.value?.id; if (id == null) { return; } File? file = await getFileByTalk(talkBean.value); if (isLocalFileHas == true && audioFileIsExist == false && !await AudioPickerUtils.hasPermission()) { bool has = await AudioPickerUtils.requestPermissionExtend(); if (has == false) { ToastUtil.showToast(StringName.authorizationFailed.tr); return; } //重新上传 _checkFileSizeAndNet(); return; } if (file == null || !file.existsSync()) { ToastUtil.showToast(StringName.talkUploadFileNotExist.tr); return; } bool isCheckRemind = KVUtil.getBool(uploadNoPrompts, false); if (isCheckRemind) { _requestAnalyze(file); return; } //如果文件大小低于250MB 不弹窗提醒 if (file.lengthSync() < 250 * 1024 * 1024) { _requestAnalyze(file); return; } final List connectivityResult = await (Connectivity().checkConnectivity()); if (connectivityResult.contains(ConnectivityResult.wifi)) { _requestAnalyze(file); } else { _showTrafficRemindDialog(file.lengthSync().toReadableSize(), confirmOnTap: (isCheckRemind) { if (isCheckRemind) { KVUtil.putBool(uploadNoPrompts, true); } _requestAnalyze(file); }); } } void _showTrafficRemindDialog(String holderTxt, {void Function(bool isCheckRemind)? confirmOnTap}) { final remindTrafficConsume = false.obs; Widget getSelectIcon() { return Obx(() { return remindTrafficConsume.value ? Assets.images.iconSelectTrue.image() : Assets.images.iconSelectFalse.image(); }); } Assets.images.iconSelectTrue.image(); EAAlertDialog.show( contentWidget: Column( children: [ Text( StringName.talkTrafficRemindTitle.tr .replacePlaceholders([holderTxt]), style: TextStyle(fontSize: 15.sp, color: ColorName.primaryTextColor), ), SizedBox(height: 8.h), GestureDetector( onTap: () { remindTrafficConsume.value = !remindTrafficConsume.value; }, child: IntrinsicWidth( child: Row( children: [ SizedBox(width: 20.w, height: 20.w, child: getSelectIcon()), SizedBox(width: 5.w), Text( StringName.talkTrafficRemindTips.tr, style: TextStyle( fontSize: 15.sp, color: ColorName.tertiaryTextColor), ) ], ), ), ) ], ), cancelText: StringName.cancel.tr, confirmText: StringName.sure.tr, confirmOnTap: () { confirmOnTap?.call(remindTrafficConsume.value); }); } void checkCanAnalyze() { String? id = talkBean.value?.id; double? duration = talkBean.value?.duration; if (id == null || duration == null) { return; } eventReport(EventId.event_101002); talkRepository.checkElectric(duration).then((data) { if (data.enough) { //检查网络以及文件大小 _checkFileSizeAndNet(); } else { ToastUtil.showToast(StringName.talkAnalyseLowToast.tr); isShowElectricLow.value = true; } }).catchError((error) { ToastUtil.showToast(error); }); } void _requestAnalyze(File file) { String? talkId = talkBean.value?.id; double? duration = talkBean.value?.duration; if (talkId == null || duration == null || isUploadedFile == true) { return; } isUploading.value = true; WakelockPlus.enable(); talkRepository.uploadTalkFile(talkId, duration, file).then((taskId) { isUploadedFile = true; talkBean.value?.status.value = TalkStatus.analysing; taskRepository.addTask(taskId); }).catchError((error) { isUploading.value = false; ErrorHandler.toastError(error); }).whenComplete(() => WakelockPlus.disable()); } void refreshAgendaAllData({bool isForceRefresh = false}) { String? id = talkBean.value?.id; if (id == null || (!isForceRefresh && agendaAllList.isNotEmpty)) { return; } agendaRepository.agendaListAll(id).then((agenda) { agendaAllList.clear(); agendaOriginalAllList.clear(); if (agenda.list != null) { agendaOriginalAllList.addAll( agenda.list!.map((item) => AgendaListAllBean.from(item)).toList()); agendaAllList.addAll(agenda.list!); } }); } void clickAIAnalysis() { if (!accountRepository.isLogin.value) { LoginPage.start(fromType: LoginFromType.talkDetail); return; } if (talkBean.value != null) { eventReport(EventId.event_101003); ChatPage.startByTalk( talkBean.value!.isExample == true ? ChatFromType.fromTalkExample : ChatFromType.fromTalkDetail, talkBean.value!); } } void onGoElectricStore() { StorePage.start(fromType: StoreFromType.analyse); Future.delayed(const Duration(milliseconds: 250), () { isShowElectricLow.value = false; }); } void onEditModelClick() { if (!accountRepository.isLogin.value) { LoginPage.start(fromType: LoginFromType.talkDetail); return; } _isEditModel.value = true; if (_audioPlayer.playing) { _audioPlayer.pause(); isAudioPlaying.value = false; } editTalkNameController.text = talkBean.value?.title.value ?? ''; } void updateTabIndex(int index) { tabIndex.value = index; } void onEditCancel() { _isEditModel.value = false; agendaAllList.assignAll(agendaOriginalAllList .map((item) => AgendaListAllBean.from(item)) .toList()); } void onEditDoneClick() { if (talkBean.value == null) { return; } List list = []; for (AgendaListAllBean item in agendaAllList) { if (item.list != null) { for (Agenda agenda in item.list!) { list.add(AgendaUpdateBean(agenda.id, agenda.name, agenda.content)); } } } agendaRepository.agendaUpdate(talkBean.value!.id, list).then((data) { refreshAgendaAllData(isForceRefresh: true); eventBus.emit(TodoController.refreshTalkMineTask); isEditModelRx.value = false; }).catchError((error) { ErrorHandler.toastError(error); }); if (tabIndex.value == 0) { String updateName = editTalkNameController.text; talkRepository.talkRename(talkBean.value!.id, updateName).then((data) { talkBean.value?.title.value = updateName; }).catchError((error) { ErrorHandler.toastError(error); }); } } void removeTalkAgenda(List? list, Agenda agenda) { list?.remove(agenda); agendaAllList.refresh(); } void showSingleAddAgendaDialog(BuildContext context) { showAddAgendaDialog(context, agendaContentController, agendaNameController, list: agendaAllList.map((e) => e.name ?? "").toList(), callback: () { if (agendaContentController.text.isEmpty) { ToastUtil.showToast(StringName.talkAddAgendaContentHint.tr); return; } if (agendaNameController.text.isEmpty) { ToastUtil.showToast(StringName.talkAddAgendaNameHint.tr); return; } Get.back(); _dealAddProcedureList(); }); } void _dealAddProcedureList() { String name = agendaNameController.text; final addItem = Agenda( id: "", talkId: "", name: name, ); addItem.content = agendaContentController.text; for (AgendaListAllBean item in agendaAllList) { if (item.name == name) { List list = item.list ?? []; list.add(addItem); item.list = list; agendaAllList.refresh(); agendaContentController.clear(); agendaNameController.clear(); return; } } agendaAllList.add(AgendaListAllBean(name: name, list: [addItem])); agendaContentController.clear(); agendaNameController.clear(); } Future getFileByTalk(TalkBean? bean) async { isLocalFileHas = false; if (bean == null) { return null; } if (bean.uploadType == TalkUploadType.localUpload) { String? audioId = FileUploadCheckHelper.getLocalAudioId(bean.localAudioUrl); if (audioId != null && audioId.isNotEmpty) { isLocalFileHas = true; return await AudioPickerUtils.getAssetFile(audioId); } else { return await FileUploadCheckHelper.getChoiceUploadFile(bean.id); } } else { return await RecordController.getRecordFile(bean.id); } } @override void onClose() { super.onClose(); _talkBeanListener?.cancel(); _audioPlayer.dispose(); _agendaContentController?.dispose(); _agendaNameController?.dispose(); } }