周睿 2 년 전
부모
커밋
2b8e7660a3

+ 21 - 0
app_business/lib/service/all_init.dart

@@ -1,9 +1,12 @@
 import 'package:app_business/service/api.dart';
 import 'package:app_business/service/app.dart';
 import 'package:app_business/service/map_watch.dart';
+import 'package:flutter/material.dart';
+import 'package:package_info_plus/package_info_plus.dart';
 import 'package:track_common/service/map_watch.dart';
 import 'package:track_common/track_common.dart';
 
+import '../generated/base.pb.dart' as pb;
 import 'abase.dart';
 
 Future<void> allInit() async {
@@ -21,4 +24,22 @@ Future<void> allInit() async {
 
   Get.put<MapWatchService>(MapWatchServiceImpl(), permanent: true);
   info('初始化完成');
+
+  final packageInfo = await PackageInfo.fromPlatform();
+  final appVersion = packageInfo.version;
+  final updateInfo = await api.stub
+      .toGetUpdateVersion(pb.ToGetUpdateVersionRequest(vCode: appVersion));
+
+  if (updateInfo.needUpdate && GetPlatform.isAndroid) {
+    final pStream = CommonPub().updateApp(updateInfo.vUrl, updateInfo.vCode);
+    final value = 0.0.obs;
+    value.bindStream(pStream);
+
+    await Get.dialog(
+        AlertDialog(
+          title: const Text('软件更新'),
+          content: Obx(() => LinearProgressIndicator(value: value.value)),
+        ),
+        barrierDismissible: false);
+  }
 }

+ 1 - 2
app_business/lib/service/map_watch.dart

@@ -1,7 +1,6 @@
 import 'package:app_business/service/api.dart';
 import 'package:fixnum/fixnum.dart';
 import 'package:get/get.dart';
-import 'package:track_common/model/event.dart';
 import 'package:track_common/model/map_info.dart';
 import 'package:track_common/service/map_watch.dart';
 
@@ -13,7 +12,7 @@ class MapWatchImpl extends MapWatch {
 
   ApiService get _api => Get.find();
   @override
-  Future<List<EventInfo>> getEventList(int mapId) async {
+  Future<List<EventOnMap>> getEventList(int mapId) async {
     return [];
   }
 }

+ 1 - 1
app_business/pubspec.yaml

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # In Windows, build-name is used as the major, minor, and patch parts
 # of the product and file versions while build-number is used as the build suffix.
-version: 1.0.0+1
+version: 2.0.0+1
 
 environment:
   sdk: '>=3.1.5 <4.0.0'

+ 1 - 1
libs/common_pub

@@ -1 +1 @@
-Subproject commit 1ea47cad737263a7a7633002e8ce819c50ed9e56
+Subproject commit d61c191d21f797cc6fe25e09356dce2e12963297

+ 56 - 8
libs/track_common/lib/service/map_watch.dart

@@ -1,9 +1,8 @@
 import 'package:common_pub/logger.dart';
 import 'package:common_pub/service/controller.dart';
 import 'package:common_pub/ui/map_view/map_view.dart';
-import 'package:track_common/model/event.dart';
-
-import '../model/map_info.dart';
+import 'package:common_pub/ui/map_view/view_map_trace.dart';
+import 'package:track_common/model.dart';
 
 class Flag {
   Flag(this.value);
@@ -30,6 +29,57 @@ class Flag {
   static List<Flag> get values => [red, yellow, blue];
 }
 
+class UserOnMap {
+  var info = UserInfo();
+
+  int get id => info.id;
+
+  String get name => info.name;
+  var startAt = DateTime.now();
+  var cpList = <ControlPoint>[];
+  final isHide = false.obs;
+  var trace = <TracePoint>[].obs;
+  var flag = Flag.red.obs;
+  String routeName = '';
+  int heartRatePercent = 0;
+  Pace pace = Pace.perKm(99.hours);
+  var distance = 0.km;
+  List<HeartRate> hrInfo = [];
+  List<Position> positionList = [];
+
+  Duration get duration => DateTime.now().difference(startAt);
+
+  ControlPoint? nextWant;
+
+  Distance get nextDistance {
+    final one = nextWant;
+    if (one != null) {
+      final p1 = one.position;
+      final p22 = positionList.lastOrNull;
+      if (p22 != null) {
+        return p1.distance(p22);
+      }
+    }
+    return const Distance(m: 1000);
+  }
+
+  String get nextCPSN {
+    return nextWant?.snString ?? '';
+  }
+}
+
+class EventOnMap {
+  var info = EventInfo();
+  var userList = <UserOnMap>[];
+
+  int get id => info.id;
+
+  String get name => info.name;
+
+  int get cpAllCount => info.cpAllCount;
+  final isHide = false.obs;
+}
+
 abstract class MapWatchService extends GetxService {
   final Rx<MapWatch?> _instance = Rx(null);
 
@@ -40,9 +90,7 @@ abstract class MapWatchService extends GetxService {
 
   Future<void> setMap(MapInfo mapInfo) async {
     final thisInstance = await newInstanceByMap(mapInfo);
-
     thisInstance.addPlugs([thisInstance.plugMap]);
-
     _instance.value = thisInstance;
     thisInstance.init();
     thisInstance.workFlushData();
@@ -61,7 +109,7 @@ abstract class MapWatch extends PlugController {
     }
   }
 
-  EventInfo? getEventById(int id) {
+  EventOnMap? getEventById(int id) {
     for (final one in eventList) {
       if (one.id == id) {
         return one;
@@ -75,8 +123,8 @@ abstract class MapWatch extends PlugController {
   final int id;
   String name = '';
   final plugMap = PlugMap();
-  final eventList = <EventInfo>[].obs;
+  final eventList = <EventOnMap>[].obs;
 
   @protected
-  Future<List<EventInfo>> getEventList(int mapId);
+  Future<List<EventOnMap>> getEventList(int mapId);
 }

+ 380 - 0
libs/track_common/lib/view/home/field_control/field_control.dart

@@ -0,0 +1,380 @@
+import 'package:common_pub/common_pub.dart';
+import 'package:common_pub/ui/map_view/map_view.dart';
+import 'package:common_pub/ui/map_view/view_map_cp.dart';
+import 'package:common_pub/ui/map_view/view_map_image.dart';
+import 'package:common_pub/ui/map_view/view_map_touch.dart';
+import 'package:common_pub/ui/map_view/view_map_trace_tail.dart';
+import 'package:common_pub/ui/map_view/view_map_user_point.dart';
+import 'package:common_pub/ui/map_view/view_plug_loading.dart';
+import 'package:track_common/widget/prelude.dart';
+
+import '../../../service/map_watch.dart';
+import 'field_control_controller.dart';
+
+class FieldControlPage extends GetView<FieldControlController> {
+  const FieldControlPage({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+        height: double.infinity,
+        width: double.infinity,
+        color: const Color(0xffc9c0c0),
+        alignment: Alignment.center,
+        child: Obx(() {
+          final mapWatch = controller.mapWatch;
+          return mapWatch != null ? content(context, mapWatch) : noData();
+        }));
+  }
+
+  Widget noData() {
+    return Center(
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          Image.asset(Assets.imagesIcNoData, package: package, height: 64),
+          const SizedBox(height: 25),
+          const Text('没有数据, 请选择地图',
+              style: TextStyle(color: Color(0xff707070), fontSize: 18.5)),
+        ],
+      ),
+    );
+  }
+
+  Widget content(BuildContext context, MapWatch map) {
+    final children = <Widget>[
+      ViewPlugLoading(map.plugMap),
+      ViewMapImage(map.plugMap),
+    ];
+
+    final focusUser = controller.focusUser;
+    if (focusUser != null) {
+      children.add(ViewMapCP(
+        // key:UniqueKey(),
+        map.plugMap,
+        cpWantAndHistoryList: focusUser.cpList,
+        isHideRouteBeforeStart: false,
+        controller: controller.viewCpController,
+      ));
+    }
+
+    children.addAll([
+      _ViewTrace(map: map, traceDuration: 30.seconds),
+      ViewMapTouch(map.plugMap)
+    ]);
+
+    return Row(
+      children: [
+        Expanded(
+            child: Column(
+          children: [
+            _MapView(),
+            // Expanded(
+            //     child: ViewMapStack(plug: map.plugMap, children: children)),
+            _MsgView(),
+          ],
+        )),
+        _ActiveInfoView()
+      ],
+    );
+  }
+}
+
+class _MapView extends GetView<FieldControlController> {
+  @override
+  Widget build(BuildContext context) {
+    return Expanded(
+        child: ViewMapStack(
+      plug: controller.mapWatch!.plugMap,
+      children: [
+        Obx(() {
+          final map = controller.mapWatch!;
+          final children = <Widget>[
+            ViewPlugLoading(map.plugMap),
+            ViewMapImage(map.plugMap),
+          ];
+
+          final focusUser = controller.focusUser;
+          if (focusUser != null) {
+            children.add(ViewMapCP(
+              // key:UniqueKey(),
+              map.plugMap,
+              cpWantAndHistoryList: focusUser.cpList,
+              isHideRouteBeforeStart: false,
+              controller: controller.viewCpController,
+            ));
+          }
+
+          children.addAll([
+            _ViewTrace(map: map, traceDuration: 30.seconds),
+            ViewMapTouch(map.plugMap)
+          ]);
+          return SizedBox.expand(
+            child: Stack(
+              children: children,
+            ),
+          );
+        })
+      ],
+    ));
+  }
+}
+
+class _ViewTrace extends GetView<FieldControlController> {
+  const _ViewTrace({required this.map, required this.traceDuration});
+
+  final MapWatch map;
+  final Duration traceDuration;
+
+  @override
+  Widget build(BuildContext context) {
+    return Obx(() {
+      final children = <Widget>[];
+
+      for (final act in map.eventList) {
+        for (final user in act.userList) {
+          if (user.isHide.value) {
+            continue;
+          }
+          final trace = user.trace.lastOrNull;
+          final traceTailOnMap = <Offset>[];
+          final st = user.startAt;
+          for (final one in user.trace) {
+            if (DateTime.now().difference(st.add(one.ts)) < traceDuration) {
+              traceTailOnMap.add(one.onMap);
+            }
+          }
+
+          if (trace != null) {
+            children.add(ViewMapTraceTail(
+              plug: map.plugMap,
+              onMapTrace: traceTailOnMap,
+              color: user.flag.value.color,
+            ));
+            children.add(ViewMapUserPoint(
+                key: UniqueKey(),
+                map.plugMap,
+                trace,
+                info: user.name,
+                color: user.flag.value.color));
+          }
+        }
+      }
+
+      return Stack(alignment: Alignment.topLeft, children: children);
+    });
+  }
+}
+
+class _ActiveInfoView extends GetView<FieldControlController> {
+  @override
+  Widget build(BuildContext context) {
+    return Obx(() => Container(
+          width: 370,
+          height: double.infinity,
+          color: Colors.white,
+          child: ListView(
+            children: controller.activeList
+                .map((element) => activeView(element))
+                .toList(),
+          ),
+        ));
+  }
+
+  Widget activeView(EventOnMap info) {
+    final children = <Widget>[
+      Row(children: [
+        Expanded(
+            child: Text(
+          '${info.name} (${info.userList.length}人)',
+          maxLines: 1,
+        )),
+        const SizedBox(
+          width: 8,
+        ),
+        IconButton(
+            onPressed: () {
+              info.isHide.value = !info.isHide.value;
+            },
+            icon: info.isHide.value
+                ? const Icon(Icons.arrow_drop_down)
+                : const Icon(Icons.arrow_drop_up))
+      ]),
+    ];
+
+    if (!info.isHide.value) {
+      children.addAll([
+        Container(
+          decoration: BoxDecoration(
+              color: Colors.white, borderRadius: BorderRadius.circular(5)),
+          padding: const EdgeInsets.fromLTRB(26, 11, 26, 11),
+          child: Row(
+            children: [
+              const Text('广播'),
+              const Spacer(),
+              Image.asset(Assets.imagesIcCp, height: 20, width: 20),
+              Text(' ${info.cpAllCount}'),
+              const Spacer(),
+              const Text('全部隐藏'),
+            ],
+          ),
+        )
+      ]);
+      children
+          .addAll(info.userList.map((e) => _UserInfoView(data: e)).toList());
+    }
+
+    return Container(
+      decoration: BoxDecoration(
+          color: const Color(0xffe0e0e0),
+          borderRadius: BorderRadius.circular(5)),
+      margin: const EdgeInsets.fromLTRB(9, 12, 9, 12),
+      padding: const EdgeInsets.all(9),
+      child: Column(
+        children: children,
+      ),
+    );
+  }
+}
+
+class _UserInfoView extends GetView<FieldControlController> {
+  const _UserInfoView({required this.data});
+
+  final UserOnMap data;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      decoration: BoxDecoration(
+          color: Colors.white, borderRadius: BorderRadius.circular(5)),
+      padding: const EdgeInsets.fromLTRB(7, 11, 7, 11),
+      margin: const EdgeInsets.only(top: 5),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          Row(
+            children: [
+              Obx(() => Container(
+                  margin: const EdgeInsets.only(top: 2),
+                  decoration: BoxDecoration(
+                      color: data.flag.value.color,
+                      borderRadius: BorderRadius.circular(4)),
+                  width: 7,
+                  height: 16)),
+              const SizedBox(
+                width: 8,
+              ),
+              Expanded(
+                child: Text.rich(TextSpan(
+                    text: data.name,
+                    children: [TextSpan(text: ' [${data.routeName}]')])),
+              ),
+              GestureDetector(
+                onTap: () {
+                  final oldId = controller.focusUserId.value;
+                  if (oldId != null) {
+                    if (oldId == data.id) {
+                      controller.focusUserId.value = null;
+                      return;
+                    }
+                  }
+                  controller.focusUserId.value = data.id;
+                },
+                child: Obx(() => Icon(
+                      Icons.route,
+                      color: data.id != controller.focusUser?.id
+                          ? Colors.grey
+                          : const Color(0xffffbb77),
+                    )),
+              ),
+              const SizedBox(width: 8),
+              GestureDetector(
+                onTap: () {
+                  data.isHide.value = !data.isHide.value;
+                },
+                child: Obx(() => Icon(
+                      data.isHide.value
+                          ? Icons.visibility_off
+                          : Icons.visibility,
+                      color: data.isHide.value
+                          ? Colors.grey
+                          : const Color(0xffffbb77),
+                    )),
+              )
+            ],
+          ),
+          Container(
+            margin: const EdgeInsets.only(left: 14),
+            child: Row(
+              children: [
+                container(null, cpInfo, Colors.blue),
+                container(
+                    const Icon(
+                      Icons.favorite,
+                      size: 13,
+                      color: Colors.white,
+                    ),
+                    ' ${hr == 0 ? '--' : hr}',
+                    data.heartRatePercent.toHRPColor()),
+                container(null, paceInfo, data.pace.color)
+              ],
+            ),
+          ),
+          const SizedBox(height: 5),
+          Container(
+              margin: const EdgeInsets.only(left: 14),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: [
+                  Text('距离 ${data.nextDistance.toString()}'),
+                  Text('时间 ${data.duration.toAppString()}'),
+                  Text('里程 ${data.distance.toString()}'),
+                ],
+              ))
+        ],
+      ),
+    );
+  }
+
+  int get hr {
+    return data.hrInfo.lastOrNull?.hr ?? 0;
+  }
+
+  String get cpInfo {
+    final next = data.nextWant;
+    return next != null ? '${data.nextCPSN}点(${next.areaId})' : '--';
+  }
+
+  String get paceInfo {
+    Duration;
+    return data.pace.toString();
+  }
+
+  Widget container(Widget? icon, String text, Color color) {
+    final children = <Widget>[];
+    if (icon != null) {
+      children.add(icon);
+    }
+    children.add(
+        Text(text, style: const TextStyle(color: Colors.white, fontSize: 14)));
+
+    return Container(
+      height: 20,
+      padding: const EdgeInsets.fromLTRB(10, 0, 12, 0),
+      margin: const EdgeInsets.only(right: 6),
+      alignment: Alignment.center,
+      decoration:
+          BoxDecoration(color: color, borderRadius: BorderRadius.circular(9)),
+      child: Row(
+        children: children,
+      ),
+    );
+  }
+}
+
+class _MsgView extends GetView<FieldControlController> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 56 - 0
libs/track_common/lib/view/home/field_control/field_control_controller.dart

@@ -0,0 +1,56 @@
+import 'dart:async';
+
+import 'package:common_pub/ui/map_view/view_map_cp.dart';
+import 'package:track_common/model.dart';
+import 'package:track_common/service/map_watch.dart';
+
+class FieldControlController extends GetxController {
+  final _mapWatch = Get.find<MapWatchService>();
+
+  @override
+  void onInit() {
+    super.onInit();
+    final map = _mapWatch.instance;
+    if (map != null) {
+      activeList.bindStream(map.eventList.stream);
+    }
+
+    _subActive = activeList.listen((p0) {
+      final user = focusUser;
+      if (user != null) {
+        viewCpController.setCPList(user.cpList);
+      }
+    });
+  }
+
+  @override
+  void onClose() {
+    super.onClose();
+    _subActive?.cancel();
+  }
+
+  final viewCpController = ViewMapCPController();
+  StreamSubscription<List<EventOnMap>>? _subActive;
+  MapWatch? get mapWatch => _mapWatch.instance;
+  final activeList = <EventOnMap>[].obs;
+  final Rx<int?> focusUserId = Rx(null);
+
+  UserOnMap? findFocusUser(List<EventOnMap> list) {
+    if (focusUserId.value == null) {
+      return null;
+    }
+
+    for (final act in list) {
+      for (final user in act.userList) {
+        if (user.id == focusUserId.value) {
+          return user;
+        }
+      }
+    }
+    return null;
+  }
+
+  UserOnMap? get focusUser {
+    return findFocusUser(activeList);
+  }
+}