|
|
@@ -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>[];
|
|
|
+}
|