周睿 hace 2 años
padre
commit
195704a543

+ 11 - 1
app_business/lib/view/home/home.dart

@@ -1,9 +1,11 @@
 import 'package:app_business/view/home/event_manage/event_manage_controller.dart';
+import 'package:app_business/view/home/personal_rank.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:get/get.dart';
 import 'package:track_common/view.dart';
 import 'package:track_common/view/home/field_control/field_control_controller.dart';
 import 'package:track_common/view/home/map/map_page.dart';
+import 'package:track_common/view/home/personal_rank.dart';
 
 import 'event_manage/event_manage.dart';
 import 'field_control.dart';
@@ -34,7 +36,15 @@ class HomeControllerImpl extends HomeController {
             () => _TabBuilder<EventManagerController>(
                   init: () => EventManagerController(),
                   builder: (c) {
-                    return EventManage();
+                    return const EventManage();
+                  },
+                )),
+        HomeTab(
+            '排名',
+            () => _TabBuilder<PersonalRankController>(
+                  init: () => PersonalRankControllerImpl(),
+                  builder: (c) {
+                    return const PersonalRankPage();
                   },
                 )),
       ];

+ 46 - 0
app_business/lib/view/home/personal_rank.dart

@@ -0,0 +1,46 @@
+import 'package:app_business/generated/base.pb.dart' as pb;
+import 'package:app_business/service/api.dart';
+import 'package:track_common/model.dart';
+import 'package:track_common/view/home/personal_rank.dart';
+
+class PersonalRankControllerImpl extends PersonalRankController {
+  @override
+  Future<List<RankActiveInfo>> getRankList(int mapId, DateTime startAt) async {
+    final startAtSec = filterStartAt.value.millisecondsSinceEpoch ~/ 1000;
+    final r = await Get.find<ApiService>()
+        .stub
+        .toGameRanking(pb.ToGameRankingRequest()
+          ..mapId = mapId
+          ..startSecond = Int64(startAtSec));
+
+    final out = <RankActiveInfo>[];
+
+    for (final actSrc in r.list) {
+      final act = RankActiveInfo()
+        ..id = actSrc.actId
+        ..name = actSrc.actName
+        ..userList = actSrc.rankList.map((e) => e.toModel()).toList();
+
+      out.add(act);
+    }
+    return out;
+  }
+}
+
+extension UserRankInfoExt on pb.ToOrienteerRankInfo {
+  RankUserInfo toModel() {
+    return RankUserInfo()
+      ..id = oId
+      ..name = oName
+      ..routeName = courseName
+      ..state = switch (state) {
+        1 => GameState.finish,
+        2 => GameState.processing,
+        _ => GameState.unFinish
+      }
+      ..phone = phone
+      ..startAt = startAt.toModel()
+      ..duration = duration.toModel()
+      ..distance = distance.meter;
+  }
+}

+ 4 - 4
libs/track_common/lib/view/home/app_bar.dart

@@ -32,8 +32,8 @@ class HomeAppBar extends GetView<HomeController>
                   ? const SizedBox()
                   : Row(children: [
                       Container(
-                        width: 10,
-                        height: 34,
+                        width: 7.11,
+                        height: 24.18,
                         margin: const EdgeInsets.only(right: 12),
                         decoration: BoxDecoration(
                             color: Colors.blue,
@@ -41,7 +41,7 @@ class HomeAppBar extends GetView<HomeController>
                       ),
                       Text(controller.selectMapName.value,
                           style: const TextStyle(
-                              color: Colors.white, fontSize: 22))
+                              color: Colors.white, fontSize: 15.64))
                     ]))),
           TextButton(
               onPressed: () {},
@@ -50,7 +50,7 @@ class HomeAppBar extends GetView<HomeController>
                 children: [
                   Icon(Icons.radio, color: Colors.white),
                   Text(' 广播',
-                      style: TextStyle(color: Colors.white, fontSize: 20))
+                      style: TextStyle(color: Colors.white, fontSize: 15.64))
                 ],
               ))
         ],

+ 421 - 0
libs/track_common/lib/view/home/personal_rank.dart

@@ -0,0 +1,421 @@
+import 'package:common_pub/logger.dart';
+import 'package:common_pub/model/distance.dart';
+import 'package:common_pub/model/pace.dart';
+import 'package:common_pub/utils.dart';
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../../../widget/title_point.dart';
+import '../../service/map_watch.dart';
+
+abstract class PersonalRankController extends GetxController {
+  @override
+  void onInit() {
+    super.onInit();
+    final now = DateTime.now();
+    filterStartAt.value = DateTime(now.year, now.month, now.day);
+
+    workFlushData();
+  }
+
+  Future<void> workFlushData() async {
+    while (!isClosed) {
+      final map = mapWatch;
+      if (map == null) {
+        activeList.clear();
+        await 3.seconds.delay();
+        continue;
+      }
+      try {
+        final out = await getRankList(map.id, filterStartAt.value);
+
+        for (final one in out) {
+          final oldEvent = map.getEventById(one.id);
+          if (oldEvent != null) {
+            for (final user in one.userList) {
+              final oldUser = oldEvent.getUserById(user.id);
+              if (oldUser != null) {
+                user.flag = oldUser.flag.value;
+              }
+            }
+          }
+        }
+
+        activeList.value = out;
+      } catch (_) {}
+
+      await 3.seconds.delay();
+    }
+  }
+
+  final filterStartAt = DateTime.now().obs;
+  final activeList = <RankActiveInfo>[].obs;
+  final Rx<RankActiveInfo?> selectActive = Rx(null);
+  MapWatch? get mapWatch => Get.find<MapWatchService>().instance;
+
+  Future<List<RankActiveInfo>> getRankList(int mapId, DateTime startAt);
+}
+
+class PersonalRankPage extends GetView<PersonalRankController> {
+  const PersonalRankPage({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: double.infinity,
+      height: double.infinity,
+      margin: const EdgeInsets.all(20),
+      padding: const EdgeInsets.fromLTRB(12, 17, 12, 17),
+      decoration: BoxDecoration(
+          color: Colors.transparent, borderRadius: BorderRadius.circular(16)),
+      child: DefaultTextStyle(
+          style: const TextStyle(color: Colors.white),
+          child: Row(
+            children: [
+              SizedBox(
+                  width: 260,
+                  height: double.infinity,
+                  child: Obx(() => eActiveList(context, controller))),
+              const SizedBox(width: 20),
+              Expanded(child: Obx(() => eUserList(context, controller)))
+            ],
+          )),
+    );
+  }
+
+  Future<void> _pickDate(BuildContext context, PersonalRankController c) async {
+    final now = c.filterStartAt.value;
+    final time = await showTimePicker(
+        context: context, initialTime: TimeOfDay.fromDateTime(now));
+
+    if (time != null) {
+      c.filterStartAt.value =
+          DateTime(now.year, now.month, now.day, time.hour, time.minute);
+      info('time: ${c.filterStartAt.value}');
+      c.selectActive.value = null;
+      c.activeList.clear();
+    }
+    // final date = await showDatePicker(
+    //     context: context,
+    //     initialDate: c.filterStartAt.value,
+    //     firstDate: DateTime(now.year - 1),
+    //     lastDate: DateTime(now.year, now.month, now.day + 1),
+    // );
+    //
+    // if (date != null) {
+    //   c.filterStartAt.value = DateTime(date.year, date.month, date.day);
+    // }
+  }
+
+  Widget titlePoint() {
+    return const TitlePoint(color: Color(0xff98d8ff));
+  }
+
+  Widget eActiveList(BuildContext context, PersonalRankController c) {
+    return Column(
+      children: [
+        Row(
+          children: [
+            Padding(padding: const EdgeInsets.all(8), child: titlePoint()),
+            Text('活动列表',
+                style: context.textTheme.titleLarge
+                    ?.copyWith(color: Colors.white)),
+            const Spacer(),
+            Container(
+                decoration: BoxDecoration(
+                    border: Border.all(color: const Color(0xffe3e3e3))),
+                child: TextButton(
+                    onPressed: () => _pickDate(context, c),
+                    child: Text(TimeOfDay.fromDateTime(c.filterStartAt.value)
+                        .format(context))))
+          ],
+        ),
+        const SizedBox(height: 20),
+        Expanded(
+            child: Container(
+                padding: const EdgeInsets.all(12),
+                decoration: BoxDecoration(
+                    color: const Color(0xff003656),
+                    borderRadius: BorderRadius.circular(9)),
+                child: ListView(
+                  children: c.activeList
+                      .map((e) => wActiveCard(
+                              context, e, c.selectActive.value?.id == e.id, () {
+                            c.selectActive.value = e;
+                          }))
+                      .toList(),
+                ))),
+      ],
+    );
+  }
+
+  Widget wActiveCard(BuildContext context, RankActiveInfo active, bool selected,
+      VoidCallback onTap) {
+    return GestureDetector(
+        onTap: onTap,
+        child: Container(
+          decoration: const BoxDecoration(color: Color(0xff00a0ff), boxShadow: [
+            BoxShadow(color: Color(0x4d000000), blurRadius: 3.5)
+          ]),
+          height: _cardHeight,
+          width: double.infinity,
+          margin: const EdgeInsets.only(top: 7),
+          padding: const EdgeInsets.only(right: 11),
+          child: Row(
+            children: [
+              Container(
+                margin: const EdgeInsets.only(right: 10),
+                height: double.infinity,
+                width: 6.4,
+                color: selected ? const Color(0xffff870d) : Colors.transparent,
+              ),
+              Expanded(child: Text(active.name)),
+              const SizedBox(width: 8),
+              Text(active.userList.length.toString())
+            ],
+          ),
+        ));
+  }
+
+  Widget eUserList(BuildContext context, PersonalRankController c) {
+    final active = c.selectActive.value;
+
+    if (active == null) {
+      return const SizedBox();
+    }
+
+    final userList = c.selectActive.value?.userList ?? <RankUserInfo>[];
+
+    return Column(
+      children: [
+        Row(
+          children: [
+            const Padding(padding: EdgeInsets.all(8), child: TitlePoint()),
+            Text('个人排名',
+                style: context.textTheme.titleLarge
+                    ?.copyWith(color: Colors.white)),
+            Text(' (${active.name})',
+                style: context.textTheme.titleLarge
+                    ?.copyWith(color: const Color(0xffffcb00))),
+          ],
+        ),
+        Expanded(
+            child: Padding(
+                padding: const EdgeInsets.all(18),
+                child: Column(
+                  children: [
+                    eUserListTitle(context),
+                    Expanded(
+                        child: ListView(
+                      children: userList.indexed.map<Widget>((t) {
+                        return eUserCard(context, c, t.$1 + 1, t.$2);
+                      }).toList(),
+                    ))
+                  ],
+                )))
+      ],
+    );
+  }
+
+  Widget eUserListTitle(BuildContext context) {
+    return DefaultTextStyle(
+        style: const TextStyle(
+            color: Color(0xff98d8ff),
+            fontSize: 14.22,
+            fontWeight: FontWeight.w700),
+        child: Row(
+          children: [
+            const SizedBox(
+                width: _userIndexWidth,
+                child: Text('排名', textAlign: TextAlign.center)),
+            const SizedBox(width: 4),
+            const SizedBox(
+                width: _userNameWidth,
+                child: Text('用户名', textAlign: TextAlign.center)),
+            verticalDivider(show: false),
+            const SizedBox(
+              width: _userPhoneWidth,
+              child: Text(
+                '手机号',
+                textAlign: TextAlign.center,
+              ),
+            ),
+            verticalDivider(show: false),
+            const Expanded(
+                flex: 5, child: Text('路线ID', textAlign: TextAlign.center)),
+            verticalDivider(show: false),
+            const SizedBox(
+                width: _userTimeWidth,
+                child: Text('总时间', textAlign: TextAlign.center)),
+            verticalDivider(show: false),
+            const Expanded(
+                flex: 3, child: Text('总里程', textAlign: TextAlign.center)),
+            verticalDivider(show: false),
+            const Expanded(
+                flex: 4, child: Text('配速', textAlign: TextAlign.center)),
+            verticalDivider(show: false),
+            const SizedBox(
+                width: _userResultWidth,
+                child: Text('状态', textAlign: TextAlign.center)),
+            // verticalDivider(show: false),
+            // const SizedBox(
+            //     width: _userFlagWidth,
+            //     child: Text('分组', textAlign: TextAlign.center)),
+            // verticalDivider(show: false),
+          ],
+        ));
+  }
+
+  Widget eUserCard(BuildContext context, PersonalRankController c, int index,
+      RankUserInfo data) {
+    return DefaultTextStyle(
+        style: context.textTheme.bodyMedium!.copyWith(color: Colors.white),
+        child: Container(
+            height: _cardHeight,
+            width: double.infinity,
+            margin: const EdgeInsets.only(top: 3),
+            child: Row(
+              children: [
+                Container(
+                    width: _userIndexWidth,
+                    height: double.infinity,
+                    decoration: const BoxDecoration(
+                        color: Color(0xffff870d),
+                        borderRadius: BorderRadius.only(
+                            topLeft: Radius.circular(6),
+                            bottomLeft: Radius.circular(6))),
+                    alignment: Alignment.center,
+                    child: Text(
+                      index.toString(),
+                      textAlign: TextAlign.center,
+                      style: const TextStyle(
+                          fontSize: 34,
+                          fontWeight: FontWeight.w700,
+                          fontStyle: FontStyle.italic),
+                    )),
+                const SizedBox(width: 4),
+                Expanded(
+                    child: Container(
+                  height: double.infinity,
+                  decoration: const BoxDecoration(
+                      color: Color(0xff003656),
+                      borderRadius: BorderRadius.only(
+                          topRight: Radius.circular(6),
+                          bottomRight: Radius.circular(6))),
+                  child: Row(
+                    children: [
+                      SizedBox(
+                          width: _userNameWidth,
+                          child: Text(
+                            data.name,
+                            maxLines: 1,
+                            textAlign: TextAlign.center,
+                          )),
+                      verticalDivider(),
+                      SizedBox(
+                          width: _userPhoneWidth,
+                          child: Text(
+                            data.phone,
+                            textAlign: TextAlign.center,
+                          )),
+                      verticalDivider(),
+                      Expanded(
+                          flex: 5,
+                          child: Text(
+                            data.routeName,
+                            textAlign: TextAlign.center,
+                          )),
+                      verticalDivider(),
+                      SizedBox(
+                          width: _userTimeWidth,
+                          child: Text(
+                            data.duration.toMinSecondString(),
+                            textAlign: TextAlign.center,
+                          )),
+
+                      verticalDivider(),
+                      Expanded(
+                          flex: 3,
+                          child: Text(
+                            data.distance.toString(),
+                            textAlign: TextAlign.center,
+                          )),
+                      verticalDivider(),
+                      Expanded(
+                          flex: 4,
+                          child: Container(
+                              margin: const EdgeInsets.only(left: 8, right: 8),
+                              alignment: Alignment.center,
+                              height: 18,
+                              decoration: BoxDecoration(
+                                  color: data.pace.color,
+                                  borderRadius: BorderRadius.circular(9)),
+                              child: Text(
+                                data.pace.toString(),
+                                textAlign: TextAlign.center,
+                              ))),
+                      // verticalDivider(),
+                      verticalDivider(),
+                      SizedBox(
+                          width: _userResultWidth,
+                          child: Text(
+                            switch (data.state) {
+                              GameState.processing => '进行中',
+                              GameState.finish => '完赛',
+                              GameState.unFinish => '退赛'
+                            },
+                            textAlign: TextAlign.center,
+                          )),
+                      // Container(
+                      //     alignment: Alignment.center,
+                      //     width: _userFlagWidth,
+                      //     child: Icon(Icons.flag, color: data.flag.color)),
+                    ],
+                  ),
+                ))
+              ],
+            )));
+  }
+
+  Widget verticalDivider({bool show = true}) {
+    return VerticalDivider(
+      width: 3,
+      indent: 14,
+      endIndent: 14,
+      color: show ? Colors.white : Colors.transparent,
+    );
+  }
+
+  static const _userIndexWidth = 56.0;
+  static const _userNameWidth = 70.0;
+  static const _userPhoneWidth = 94.0;
+  static const _userResultWidth = 62.0;
+  static const _userTimeWidth = 62.0;
+  static const _cardHeight = 45.0;
+  // static const _userFlagWidth = 52.0;
+}
+
+enum GameState {
+  processing,
+  finish,
+  unFinish,
+}
+
+class RankUserInfo {
+  var id = 0;
+  var name = '';
+  var routeName = '';
+  var state = GameState.processing;
+  var duration = 0.seconds;
+  var distance = 0.meter;
+  var startAt = DateTime(2000);
+  var phone = '';
+  Pace get pace => Pace(distance, duration);
+  var flag = Flag.red;
+}
+
+class RankActiveInfo {
+  var id = 0;
+  var name = '';
+  var userList = <RankUserInfo>[];
+}