|
|
@@ -1,19 +1,290 @@
|
|
|
+import 'package:flutter/cupertino.dart';
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+import 'package:flutter_cupertino_datetime_picker/flutter_cupertino_datetime_picker.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/location_info.dart';
|
|
|
+import 'package:location/data/consts/constants.dart';
|
|
|
+import 'package:location/data/repositories/friends_repository.dart';
|
|
|
+import 'package:location/data/repositories/track_repository.dart';
|
|
|
+import 'package:location/dialog/loading_dialog.dart';
|
|
|
+import 'package:location/handler/error_handler.dart';
|
|
|
+import 'package:location/module/track/track_util.dart';
|
|
|
+import 'package:location/resource/string.gen.dart';
|
|
|
+import 'package:location/utils/atmob_log.dart';
|
|
|
+import 'package:location/utils/common_expand.dart';
|
|
|
+import 'package:location/utils/toast_util.dart';
|
|
|
+import 'package:sliding_sheet2/sliding_sheet2.dart';
|
|
|
+import '../../data/bean/atmob_track_point.dart';
|
|
|
+import '../../data/bean/user_info.dart';
|
|
|
+import '../../utils/date_util.dart';
|
|
|
+import '../../utils/location_convert_marker_util.dart';
|
|
|
+import '../../utils/pair.dart';
|
|
|
|
|
|
@injectable
|
|
|
-class TrackController extends BaseController {
|
|
|
+class TrackController extends BaseController
|
|
|
+ with GetSingleTickerProviderStateMixin {
|
|
|
+ final int errorQueryOriginalDataEmpty = 10; //查询原始数据集为空
|
|
|
+ final int errorQueryOriginalTooFew = 11; //查询原始数据集少于2点
|
|
|
+
|
|
|
+ final Rxn<UserInfo> _userInfo = Rxn<UserInfo>();
|
|
|
+
|
|
|
+ UserInfo? get userInfo => _userInfo.value;
|
|
|
+
|
|
|
final MapController mapController = MapController();
|
|
|
|
|
|
+ final Rxn<DateTime> _trackStartTime = Rxn<DateTime>();
|
|
|
+ final Rxn<DateTime> _trackEndTime = Rxn<DateTime>();
|
|
|
+
|
|
|
+ DateTime? get trackStartTime => _trackStartTime.value;
|
|
|
+
|
|
|
+ DateTime? get trackEndTime => _trackEndTime.value;
|
|
|
+ SheetController sheetController = SheetController();
|
|
|
+
|
|
|
+ final Rxn<String> _startAddress = Rxn<String>();
|
|
|
+ final Rxn<String> _endAddress = Rxn<String>();
|
|
|
+
|
|
|
+ String? get startAddress => _startAddress.value;
|
|
|
+
|
|
|
+ String? get endAddress => _endAddress.value;
|
|
|
+
|
|
|
+ final Duration maxDuration = Duration(days: 1);
|
|
|
+ final String timeFormat = "yyyy年-MM月-dd日 HH时:mm分";
|
|
|
+ late TabController tabController;
|
|
|
+ final RxInt _currentIndex = 0.obs;
|
|
|
+
|
|
|
+ int get currentIndex => _currentIndex.value;
|
|
|
+
|
|
|
+ final Rxn<LocationInfo> _currentLocation = Rxn<LocationInfo>();
|
|
|
+
|
|
|
+ LocationInfo? get currentLocation => _currentLocation.value;
|
|
|
+
|
|
|
+ List<LatLng>? points;
|
|
|
+
|
|
|
+ final TrackRepository trackRepository;
|
|
|
+ final FriendsRepository friendsRepository;
|
|
|
+
|
|
|
+ TrackController(this.trackRepository, this.friendsRepository);
|
|
|
+
|
|
|
+ @override
|
|
|
+ void onInit() {
|
|
|
+ final param = Get.arguments;
|
|
|
+ if (param is UserInfo) {
|
|
|
+ _userInfo.value = param;
|
|
|
+ }
|
|
|
+ tabController = TabController(
|
|
|
+ length: 2, vsync: this, initialIndex: _currentIndex.value);
|
|
|
+ tabController.addListener(_handleTabChange);
|
|
|
+ _initTime();
|
|
|
+ _onCurrentLocationQuery(showLoading: false);
|
|
|
+ }
|
|
|
+
|
|
|
@override
|
|
|
void onReady() {
|
|
|
super.onReady();
|
|
|
+ sheetController.expand();
|
|
|
+ }
|
|
|
+
|
|
|
+ void _handleTabChange() {
|
|
|
+ if (tabController.indexIsChanging) return;
|
|
|
+ _currentIndex.value = tabController.index;
|
|
|
+ if (tabController.index == 0) {
|
|
|
+ _showTrack();
|
|
|
+ } else if (tabController.index == 1) {
|
|
|
+ _showCurrentLocation();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _initTime() {
|
|
|
+ //开始时间往前推一天
|
|
|
+ _trackStartTime.value = DateUtil.getNow(subtract: Duration(days: 1));
|
|
|
+ _trackEndTime.value = DateUtil.getNow();
|
|
|
}
|
|
|
|
|
|
void back() {
|
|
|
Get.back();
|
|
|
}
|
|
|
+
|
|
|
+ void onTrackStartTimeClick(BuildContext context) {
|
|
|
+ DatePicker.showDatePicker(context,
|
|
|
+ locale: DateTimePickerLocale.zh_cn,
|
|
|
+ initialDateTime: _trackStartTime.value,
|
|
|
+ dateFormat: timeFormat, onConfirm: (dateTime, selectedIndex) {
|
|
|
+ if (trackEndTime != null &&
|
|
|
+ DateUtil.isTimeRangeExceed(dateTime, trackEndTime!, maxDuration)) {
|
|
|
+ ToastUtil.show(StringName.trackChooseTimeError);
|
|
|
+ _trackEndTime.value = dateTime.add(maxDuration);
|
|
|
+ }
|
|
|
+ _trackStartTime.value = dateTime;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void onTrackEndTimeClick(BuildContext context) {
|
|
|
+ DatePicker.showDatePicker(context,
|
|
|
+ locale: DateTimePickerLocale.zh_cn,
|
|
|
+ initialDateTime: _trackEndTime.value,
|
|
|
+ dateFormat: timeFormat, onConfirm: (dateTime, selectedIndex) {
|
|
|
+ if (trackStartTime != null &&
|
|
|
+ DateUtil.isTimeRangeExceed(dateTime, trackStartTime!, maxDuration)) {
|
|
|
+ ToastUtil.show(StringName.trackChooseTimeError);
|
|
|
+ _trackStartTime.value = dateTime.subtract(maxDuration);
|
|
|
+ }
|
|
|
+ _trackEndTime.value = dateTime;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void onTrackQueryClick() {
|
|
|
+ if (currentIndex == 0) {
|
|
|
+ _onTrackQuery();
|
|
|
+ } else {
|
|
|
+ _onCurrentLocationQuery();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onCurrentLocationQuery({bool showLoading = true}) {
|
|
|
+ if (userInfo == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (showLoading) LoadingDialog.show(StringName.trackLoadingTxt);
|
|
|
+ friendsRepository
|
|
|
+ .getUserInfoFromId(userInfo!.id, isVirtual: userInfo!.virtual)
|
|
|
+ .then((userInfo) {
|
|
|
+ LoadingDialog.hide();
|
|
|
+ _currentLocation.value = userInfo?.lastLocation.value;
|
|
|
+ _showCurrentLocation();
|
|
|
+ }).catchError((error) {
|
|
|
+ debugPrint("error: $error");
|
|
|
+ ErrorHandler.toastError(error);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onTrackQuery() {
|
|
|
+ if (trackStartTime == null || trackEndTime == null || userInfo == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ LoadingDialog.show(StringName.trackLoadingTxt);
|
|
|
+ _startAddress.value = '';
|
|
|
+ _endAddress.value = '';
|
|
|
+ Future.value().then((_) {
|
|
|
+ if (userInfo?.virtual == true) {
|
|
|
+ return trackRepository.queryVirtualTrack();
|
|
|
+ } else {
|
|
|
+ return trackRepository.queryTrack(
|
|
|
+ startTime: trackStartTime?.millisecondsSinceEpoch,
|
|
|
+ endTime: trackEndTime?.millisecondsSinceEpoch,
|
|
|
+ userId: userInfo?.id);
|
|
|
+ }
|
|
|
+ }).map((trackResponse) {
|
|
|
+ final pointsList = trackResponse.trackPoints;
|
|
|
+ if (pointsList == null || pointsList.isEmpty) {
|
|
|
+ throw TrackQueryException(errorQueryOriginalDataEmpty);
|
|
|
+ }
|
|
|
+ if (pointsList.length < 2) {
|
|
|
+ throw TrackQueryException(errorQueryOriginalTooFew);
|
|
|
+ }
|
|
|
+ if (userInfo?.virtual == true) {
|
|
|
+ int nowMill = DateUtil.getNow().millisecondsSinceEpoch;
|
|
|
+ int firstMill = pointsList.first.time;
|
|
|
+ int differ = nowMill - firstMill;
|
|
|
+ pointsList.first.time = nowMill;
|
|
|
+ for (var element in pointsList) {
|
|
|
+ element.time = element.time + differ;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return pointsList;
|
|
|
+ }).then((pointsList) async {
|
|
|
+ final list = TrackUtil.points2TraceLocation(pointsList);
|
|
|
+ List<LatLng>? convertList;
|
|
|
+ try {
|
|
|
+ convertList = await FlutterMap.queryProcessedTrace(
|
|
|
+ lineID: pointsList.hashCode, locations: list);
|
|
|
+ } catch (e) {
|
|
|
+ AtmobLog.e("TrackController", "queryProcessedTrace error: $e");
|
|
|
+ }
|
|
|
+ if (convertList == null || convertList.isEmpty) {
|
|
|
+ //轨迹纠偏失败,使用原始数据
|
|
|
+ convertList = TrackUtil.traceLocation2LatLng(list);
|
|
|
+ }
|
|
|
+ return Pair(pointsList, convertList);
|
|
|
+ }).then((pair) {
|
|
|
+ LoadingDialog.hide();
|
|
|
+ points = pair.second;
|
|
|
+ _showTrack();
|
|
|
+ _setStartAndEndAddress(start: pair.first.first, end: pair.first.last);
|
|
|
+ }).catchError((error) {
|
|
|
+ LoadingDialog.hide();
|
|
|
+ if (error is TrackQueryException) {
|
|
|
+ } else {
|
|
|
+ ErrorHandler.toastError(error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void onClose() {
|
|
|
+ super.onClose();
|
|
|
+ tabController.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ void _setStartAndEndAddress(
|
|
|
+ {required AtmobTrackPoint start, required AtmobTrackPoint end}) {
|
|
|
+ _startAddress.value = start.addr;
|
|
|
+ _endAddress.value = end.addr;
|
|
|
+ }
|
|
|
+
|
|
|
+ void _showCurrentLocation() {
|
|
|
+ mapController.clear();
|
|
|
+ if (currentLocation == null || userInfo == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mapController.updateOrAddMarker(Marker(
|
|
|
+ id: userInfo!.id,
|
|
|
+ markerName: userInfo!.getUserNickName(),
|
|
|
+ longitude: userInfo!.lastLocation.value?.longitude,
|
|
|
+ latitude: userInfo!.lastLocation.value?.latitude,
|
|
|
+ markerType: userInfo!.isMine == true
|
|
|
+ ? MarkerType.traceEndMinePoint
|
|
|
+ : MarkerType.traceEndFriendPoint,
|
|
|
+ ));
|
|
|
+ mapController.animateCamera(CameraPosition(
|
|
|
+ latitude: currentLocation!.latitude,
|
|
|
+ longitude: currentLocation!.longitude,
|
|
|
+ zoom: 18));
|
|
|
+ }
|
|
|
+
|
|
|
+ void _showTrack() {
|
|
|
+ mapController.clear();
|
|
|
+ if (points == null || points!.length < 2) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ mapController.addPolyline(points!,
|
|
|
+ mapPadding:
|
|
|
+ MapPadding(left: 50, top: 100, right: 50, bottom: Get.height / 2));
|
|
|
+ mapController.updateOrAddMarker(Marker(
|
|
|
+ id: Constants.traceStartId,
|
|
|
+ markerName: '',
|
|
|
+ longitude: points!.first.longitude,
|
|
|
+ latitude: points!.first.latitude,
|
|
|
+ markerType: MarkerType.traceStartPoint));
|
|
|
+ mapController.updateOrAddMarker(Marker(
|
|
|
+ id: Constants.traceEndId,
|
|
|
+ markerName: userInfo?.getUserNickName() ?? '',
|
|
|
+ longitude: points!.last.longitude,
|
|
|
+ latitude: points!.last.latitude,
|
|
|
+ markerType: userInfo?.isMine == true
|
|
|
+ ? MarkerType.traceEndMinePoint
|
|
|
+ : MarkerType.traceEndFriendPoint));
|
|
|
+ //显示起点标记
|
|
|
+ // drawMarker();
|
|
|
+ //显示终点标记
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class TrackQueryException implements Exception {
|
|
|
+ final int code;
|
|
|
+
|
|
|
+ TrackQueryException(this.code);
|
|
|
}
|