import 'dart:async'; import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:get/get.dart'; import 'package:get/get_core/src/get_main.dart'; import 'package:injectable/injectable.dart'; import 'package:location/base/base_controller.dart'; import 'package:location/data/bean/user_info.dart'; import 'package:location/data/repositories/track_repository.dart'; import 'package:location/sdk/map/map_helper.dart'; import 'package:location/utils/atmob_log.dart'; import 'package:lottie/lottie.dart'; import 'package:video_player/video_player.dart'; import '../../data/bean/stream_chat_origin_data.dart'; import '../../data/bean/track_daily_bean.dart'; import '../../resource/assets.gen.dart'; import '../../resource/string.gen.dart'; import '../../utils/http_handler.dart'; import '../../widget/gradually_print_text.dart'; import 'location_analyse_util.dart'; @injectable class LocationAnalyseController extends BaseController with GetTickerProviderStateMixin { late final VideoPlayerController locaController; final RxBool _videoReady = RxBool(false); UserInfo? userInfo; TrackDailyBean? errorInfo; RxList errorAddr = RxList(); final RxBool _isShowAnalyseAddr = RxBool(false); bool get isShowAnalyseAddr => _isShowAnalyseAddr.value; final RxBool _showAnalyseRemainContent = RxBool(false); bool get showAnalyseRemainContent => _showAnalyseRemainContent.value; bool get videoReady => _videoReady.value; StreamSubscription? _streamSubscription; final GraduallyController graduallyController = GraduallyController(); final RxBool _isShowAnalyseResult = RxBool(false); bool get isShowAnalyseResult => _isShowAnalyseResult.value; final RxBool _isRequestedAnalyse = RxBool(false); bool get isRequestedAnalyse => _isRequestedAnalyse.value; bool _isRequestingStream = false; final RxnString _summaryError = RxnString(); String? get summaryError => _summaryError.value; final Rxn _keywordDelegates = Rxn(); LottieDelegates? get keywordDelegates => _keywordDelegates.value; final TrackRepository trackRepository; late AnimationController keywordLottieController = AnimationController(vsync: this); Timer? _loopTimer; bool _triggerFinalLoop = false; //视频播放总长度 8175毫秒 Duration videoTotalDuration = const Duration(milliseconds: 8175); Duration loopStart = const Duration(milliseconds: 5670); Duration loopEnd = const Duration(milliseconds: 6570); LocationAnalyseController(this.trackRepository); @override void onInit() { super.onInit(); _initArgs(); _getKeyword(); _getErrorAddr(); graduallyController.setGraduallyFinishedListener(() { _isRequestedAnalyse.value = true; _isRequestingStream = false; }); locaController = VideoPlayerController.asset( Assets.anim.locationAnalyseRobot, ) ..setVolume(0.0) ..initialize().then((_) { _videoReady.value = true; locaController.play(); AtmobLog.d('zk', "duration:${locaController.value.duration}"); _startMonitorLoop(); }).catchError((error) { debugPrint('Error initializing video: $error'); }); } void _startMonitorLoop() { _loopTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) async { final position = await locaController.position; if (position == null) return; // 第一阶段:0~6s,播放完成后进入循环 if (!_triggerFinalLoop) { if (position >= loopEnd) { // 循环 4~6s locaController.seekTo(loopStart); } } else { // 第二阶段:触发后进入 6~8s 循环 if (loopStart != const Duration(seconds: 6)) { loopStart = const Duration(seconds: 6); loopEnd = videoTotalDuration; } if (position >= loopEnd) { locaController.seekTo(loopStart); } } }); } void _getErrorAddr() { trackRepository .trackDailyInterpret( startTime: errorInfo?.start, endTime: errorInfo?.end, userId: userInfo?.id) .then((list) { errorAddr.assignAll(list ?? []); }); } void _getKeyword() { trackRepository .dailyKeyword( startTime: errorInfo?.start, endTime: errorInfo?.end, userId: userInfo?.id) .then((list) { //填充分析异常点到lottie占位中,有6个占位点,list如果小于则循环显示,如果超过6个,则随机6个选择,但不能重复出现 if (list != null && list.isNotEmpty) { _keywordDelegates.value = LocationAnalyseUtil.convertKeywordDelegates(list); } }); } void _initArgs() { final info = parameters?['userInfo']; if (info is UserInfo) { userInfo = info; } final errorInfo = parameters?['errorData']; if (errorInfo is TrackDailyBean) { this.errorInfo = errorInfo; } } void back() { Get.back(); } void onTrackRefreshClick() { _analyseErrorAddr(); } String getErrorDistance(TrackDailyBean errorAddr) { final lastLocation = MapHelper.getLastLocation(); if (lastLocation == null || errorAddr.lat == null || errorAddr.lng == null) { return StringName.unopenedPositioning; } final distance = MapUtil.calculateLineDistance(lastLocation.longitude, lastLocation.latitude, errorAddr.lng!, errorAddr.lat!); return MapUtil.format(distance); } void onAnalyseTextComplete() { _isShowAnalyseAddr.value = true; Future.delayed(Duration(milliseconds: 700), () { _showAnalyseRemainContent.value = true; }); } void onAnalyseFinishComplete() { _isShowAnalyseResult.value = true; _analyseErrorAddr(); } void _analyseErrorAddr() { //准备调用分析总结 if (_isRequestingStream) { return; } _isRequestedAnalyse.value = false; _isRequestingStream = true; graduallyController.clear(); _summaryError.value = null; trackRepository .streamDailyExceptionAnalyse( startTime: errorInfo?.start, endTime: errorInfo?.end, userId: userInfo?.id) .then((stream) { _streamSubscription?.cancel(); _streamSubscription = stream.listen((event) { try { Map json = jsonDecode(event.data); if (json.isEmpty) { return; } StreamChatOriginData data = StreamChatOriginData.fromJson(json); if (data.choices == null || data.choices!.isEmpty) { return; } Delta? delta = data.choices![0].delta; if (delta == null) { return; } graduallyController.append(delta.content ?? ""); } catch (ignore) {} }, onDone: () { graduallyController.appendDone(); _setAnalyseSuccess(); }, onError: (error) { _summaryError.value = "网络错误,请检查网络连接"; debugPrint("error: $error"); _isRequestedAnalyse.value = false; _isRequestingStream = false; debugPrintStack(); }); }).catchError((error) { _isRequestedAnalyse.value = false; _isRequestingStream = false; if (error is ServerErrorException) { _summaryError.value = error.message ?? "服务出错,请稍后再试"; } else { _summaryError.value = "网络错误,请检查网络连接"; debugPrint("error: $error"); debugPrintStack(); } }); } void locationKeywordLottieLoad(LottieComposition composition) async { keywordLottieController.duration = composition.duration; await keywordLottieController.animateTo(0.84); keywordLottieController.repeat(min: 0, max: 0.84, reverse: true); } void _setAnalyseSuccess() { keywordLottieController.animateTo(1); } @override void onClose() { _streamSubscription?.cancel(); keywordLottieController.dispose(); super.onClose(); } }