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: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); bool _keywordLottieReady = false; Timer? _loopTimer; Duration loopStart = const Duration(milliseconds: 0); Duration loopEnd = const Duration(milliseconds: 5032); LocationAnalyseController(this.trackRepository); @override void onInit() { super.onInit(); _initArgs(); _getKeyword(); _getErrorAddr(); graduallyController.setGraduallyFinishedListener(() { _isRequestingStream = false; }); locaController = VideoPlayerController.asset( Assets.anim.locationAnalyseRobot, ) ..setVolume(0.0) ..initialize().then((_) { _videoReady.value = true; _startMonitorLoop(); }).catchError((error) { debugPrint('Error initializing video: $error'); }); } void _startMonitorLoop() { locaController.play(); _loopTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) async { final position = await locaController.position; if (position == null) return; 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() { if (_isRequestingStream) { return; } _startMonitorLoop(); _startKeywordLottieAnimation(); _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(); _setAnalyseSuccess(); }); }).catchError((error) { _isRequestedAnalyse.value = false; _isRequestingStream = false; if (error is ServerErrorException) { final msg = error.message; if (msg == null) { _summaryError.value = error.message ?? "服务出错,请稍后再试"; } else { graduallyController.append(msg); graduallyController.appendDone(); } _setAnalyseSuccess(); } else { _summaryError.value = "网络错误,请检查网络连接"; debugPrint("error: $error"); debugPrintStack(); } }); } void locationKeywordLottieLoad(LottieComposition composition) { _keywordLottieReady = true; keywordLottieController.duration = composition.duration; _startKeywordLottieAnimation(); } void _startKeywordLottieAnimation() async { if (!_keywordLottieReady) { return; } await keywordLottieController.animateTo(0.84); keywordLottieController.repeat(min: 0, max: 0.84, reverse: true); } void _setAnalyseSuccess() async { _loopTimer?.cancel(); if (_keywordLottieReady) { await keywordLottieController.animateTo(1); } _isRequestedAnalyse.value = true; } @override void onClose() { _loopTimer?.cancel(); _streamSubscription?.cancel(); keywordLottieController.dispose(); super.onClose(); } }