|
|
@@ -1,25 +1,77 @@
|
|
|
+import 'dart:io';
|
|
|
+
|
|
|
+import 'package:application/logger.dart';
|
|
|
+import 'package:application/service/api.dart';
|
|
|
import 'package:application/service/map_watch.dart';
|
|
|
+import 'package:common_pub/model/distance.dart';
|
|
|
+import 'package:common_pub/model/pace.dart';
|
|
|
import 'package:common_pub/utils.dart';
|
|
|
+import 'package:fixnum/fixnum.dart';
|
|
|
import 'package:get/get.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
|
+import 'package:intl/intl.dart';
|
|
|
import '../../../widget/title_point.dart';
|
|
|
|
|
|
class PersonalRankController extends GetxController {
|
|
|
@override
|
|
|
void onInit() {
|
|
|
super.onInit();
|
|
|
- final map = MapWatchService.instance;
|
|
|
- if (map != null) {
|
|
|
- activeList.bindStream(map.activeList.stream);
|
|
|
+ final now = DateTime.now();
|
|
|
+ filterStartAt.value = DateTime(now.year, now.month, now.day);
|
|
|
+
|
|
|
+ workFlushData();
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> workFlushData() async {
|
|
|
+ while (!isClosed) {
|
|
|
+ final map = MapWatchService.instance;
|
|
|
+ if (map == null) {
|
|
|
+ activeList.clear();
|
|
|
+ await 3.seconds.delay();
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ final startAt = filterStartAt.value.millisecondsSinceEpoch ~/ 1000;
|
|
|
+ try {
|
|
|
+ final r = await ApiService.to.stub.toGameRanking(ToGameRankingRequest(
|
|
|
+ mapId: map.id.toInt(), startSecond: Int64(startAt)));
|
|
|
+ final out = <RankActiveInfo>[];
|
|
|
+
|
|
|
+ for (final actSrc in r.list) {
|
|
|
+ final act = RankActiveInfo()
|
|
|
+ ..id = actSrc.actId
|
|
|
+ ..name = actSrc.actName
|
|
|
+ ..userList = actSrc.rankList.map((e) {
|
|
|
+ final one = e.toModel();
|
|
|
+ final memAct = map.getActiveById(actSrc.actId);
|
|
|
+ if (memAct != null) {
|
|
|
+ final memUser = memAct.getUserById(one.id);
|
|
|
+ if (memUser != null) {
|
|
|
+ one.flag = memUser.flag.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return one;
|
|
|
+ }).toList();
|
|
|
+
|
|
|
+ out.add(act);
|
|
|
+ }
|
|
|
+
|
|
|
+ activeList.value = out;
|
|
|
+ } catch (_) {}
|
|
|
+
|
|
|
+ await 3.seconds.delay();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- final activeList = <ActiveInfo>[].obs;
|
|
|
- final Rx<ActiveInfo?> selectActive = Rx(null);
|
|
|
+ final filterStartAt = DateTime.now().obs;
|
|
|
+ final activeList = <RankActiveInfo>[].obs;
|
|
|
+ final Rx<RankActiveInfo?> selectActive = Rx(null);
|
|
|
}
|
|
|
|
|
|
class PersonalRankPage extends StatelessWidget {
|
|
|
- const PersonalRankPage({super.key});
|
|
|
+ PersonalRankPage({super.key});
|
|
|
+
|
|
|
+ final dateFmt = DateFormat('yyyy-MM-dd');
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
@@ -50,6 +102,21 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ Future<void> _pickDate(BuildContext context, PersonalRankController c) async {
|
|
|
+ final now = DateTime.now();
|
|
|
+
|
|
|
+ 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));
|
|
|
}
|
|
|
@@ -67,7 +134,9 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
Container(
|
|
|
decoration: BoxDecoration(
|
|
|
border: Border.all(color: const Color(0xffe3e3e3))),
|
|
|
- child: const Text('2023-06-26'),
|
|
|
+ child: TextButton(
|
|
|
+ onPressed: () => _pickDate(context, c),
|
|
|
+ child: Text(dateFmt.format(c.filterStartAt.value))),
|
|
|
)
|
|
|
],
|
|
|
),
|
|
|
@@ -90,7 +159,7 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget wActiveCard(BuildContext context, ActiveInfo active, bool selected,
|
|
|
+ Widget wActiveCard(BuildContext context, RankActiveInfo active, bool selected,
|
|
|
VoidCallback onTap) {
|
|
|
return GestureDetector(
|
|
|
onTap: onTap,
|
|
|
@@ -110,8 +179,8 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
width: 6.4,
|
|
|
color: selected ? const Color(0xffff870d) : Colors.transparent,
|
|
|
),
|
|
|
- Text(active.name),
|
|
|
- const Spacer(),
|
|
|
+ Expanded(child: Text(active.name)),
|
|
|
+ const SizedBox(width: 8),
|
|
|
Text(active.userList.length.toString())
|
|
|
],
|
|
|
),
|
|
|
@@ -125,7 +194,7 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
return const SizedBox();
|
|
|
}
|
|
|
|
|
|
- final userList = c.selectActive.value?.userList ?? <UserInfo>[];
|
|
|
+ final userList = c.selectActive.value?.userList ?? <RankUserInfo>[];
|
|
|
|
|
|
return Column(
|
|
|
children: [
|
|
|
@@ -176,18 +245,20 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
verticalDivider(show: false),
|
|
|
const Expanded(child: Text('路线ID', textAlign: TextAlign.center)),
|
|
|
verticalDivider(show: false),
|
|
|
+ const SizedBox(
|
|
|
+ width: _userResultWidth,
|
|
|
+ child: Text('成绩', textAlign: TextAlign.center)),
|
|
|
+ verticalDivider(show: false),
|
|
|
const SizedBox(
|
|
|
width: _userTimeWidth,
|
|
|
child: Text('总时间', textAlign: TextAlign.center)),
|
|
|
verticalDivider(show: false),
|
|
|
- const Expanded(
|
|
|
- child: Text('总里程', textAlign: TextAlign.center)),
|
|
|
+ const Expanded(child: Text('总里程', textAlign: TextAlign.center)),
|
|
|
verticalDivider(show: false),
|
|
|
- const Expanded(
|
|
|
- child: Text('配速', textAlign: TextAlign.center)),
|
|
|
+ const Expanded(child: Text('配速', textAlign: TextAlign.center)),
|
|
|
verticalDivider(show: false),
|
|
|
const SizedBox(
|
|
|
- width: _userResultWidth,
|
|
|
+ width: _userFlagWidth,
|
|
|
child: Text('分组', textAlign: TextAlign.center)),
|
|
|
verticalDivider(show: false),
|
|
|
],
|
|
|
@@ -195,104 +266,159 @@ class PersonalRankPage extends StatelessWidget {
|
|
|
}
|
|
|
|
|
|
Widget eUserCard(BuildContext context, PersonalRankController c, int index,
|
|
|
- UserInfo data) {
|
|
|
- var startAt = '--';
|
|
|
- if (data.startAt != null) {
|
|
|
- startAt = data.startAt!.toIso8601String();
|
|
|
- }
|
|
|
-
|
|
|
+ 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,
|
|
|
+ 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(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,
|
|
|
- textAlign: TextAlign.center,
|
|
|
- )),
|
|
|
- verticalDivider(),
|
|
|
- Expanded(
|
|
|
- child: Text(
|
|
|
- data.routeName,
|
|
|
- textAlign: TextAlign.center,
|
|
|
- )),
|
|
|
- verticalDivider(),
|
|
|
- SizedBox(
|
|
|
- width: _userTimeWidth,
|
|
|
- child: Text(
|
|
|
- data.duration.toMinSecondString(),
|
|
|
- textAlign: TextAlign.center,
|
|
|
- )),
|
|
|
- verticalDivider(),
|
|
|
- Expanded(
|
|
|
-
|
|
|
- child: Text(
|
|
|
- data.distance.toString(),
|
|
|
- textAlign: TextAlign.center,
|
|
|
- )),
|
|
|
- verticalDivider(),
|
|
|
- Expanded(
|
|
|
- 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)),
|
|
|
+ color: Color(0xff003656),
|
|
|
+ borderRadius: BorderRadius.only(
|
|
|
+ topRight: Radius.circular(6),
|
|
|
+ bottomRight: Radius.circular(6))),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ SizedBox(
|
|
|
+ width: _userNameWidth,
|
|
|
child: Text(
|
|
|
- data.pace.toString(),
|
|
|
+ data.name,
|
|
|
textAlign: TextAlign.center,
|
|
|
- )
|
|
|
- ) ),
|
|
|
- verticalDivider(),
|
|
|
- Container(
|
|
|
- alignment: Alignment.center,
|
|
|
- width: _userResultWidth,
|
|
|
- child: Icon(Icons.flag, color: data.flag.value.color)),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ))
|
|
|
- ],
|
|
|
- )
|
|
|
- ));
|
|
|
+ )),
|
|
|
+ verticalDivider(),
|
|
|
+ Expanded(
|
|
|
+ child: Text(
|
|
|
+ data.routeName,
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ )),
|
|
|
+ verticalDivider(),
|
|
|
+ SizedBox(
|
|
|
+ width: _userResultWidth,
|
|
|
+ child: Text(
|
|
|
+ switch(data.state){
|
|
|
+ GameState.processing=>'进行中',
|
|
|
+ GameState.finish=>'完赛',
|
|
|
+ GameState.unFinish=>'退赛'
|
|
|
+ },
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ )),
|
|
|
+ verticalDivider(),
|
|
|
+ SizedBox(
|
|
|
+ width: _userTimeWidth,
|
|
|
+ child: Text(
|
|
|
+ data.duration.toMinSecondString(),
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ )),
|
|
|
+ verticalDivider(),
|
|
|
+ Expanded(
|
|
|
+ child: Text(
|
|
|
+ data.distance.toString(),
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ )),
|
|
|
+ verticalDivider(),
|
|
|
+ Expanded(
|
|
|
+ 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(),
|
|
|
+ Container(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ width: _userFlagWidth,
|
|
|
+ child: Icon(Icons.flag, color: data.flag.color)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ))
|
|
|
+ ],
|
|
|
+ )));
|
|
|
}
|
|
|
|
|
|
- Widget verticalDivider({bool show = true}){
|
|
|
+ Widget verticalDivider({bool show = true}) {
|
|
|
return VerticalDivider(
|
|
|
width: 3,
|
|
|
indent: 14,
|
|
|
endIndent: 14,
|
|
|
- color: show? Colors.white: Colors.transparent,
|
|
|
+ color: show ? Colors.white : Colors.transparent,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
static const _userIndexWidth = 56.0;
|
|
|
static const _userNameWidth = 84.0;
|
|
|
- static const _userResultWidth = 52.0;
|
|
|
- static const _userTimeWidth = 62.0;
|
|
|
+ static const _userResultWidth = 62.0;
|
|
|
+ static const _userTimeWidth = 92.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);
|
|
|
+
|
|
|
+ Pace get pace => Pace(distance, duration);
|
|
|
+ var flag = Flag.red;
|
|
|
+}
|
|
|
+
|
|
|
+class RankActiveInfo {
|
|
|
+ var id = 0;
|
|
|
+ var name = '';
|
|
|
+ var userList = <RankUserInfo>[];
|
|
|
+}
|
|
|
+
|
|
|
+extension UserRankInfoExt on ToOrienteerRankInfo {
|
|
|
+ RankUserInfo toModel() {
|
|
|
+ return RankUserInfo()
|
|
|
+ ..id = oId
|
|
|
+ ..name = oName
|
|
|
+ ..routeName = courseName
|
|
|
+ ..state = switch (state) {
|
|
|
+ 1 => GameState.finish,
|
|
|
+ 2 => GameState.processing,
|
|
|
+ _ => GameState.unFinish
|
|
|
+ }
|
|
|
+ ..startAt = startAt.toModel()
|
|
|
+ ..duration = duration.toModel()
|
|
|
+ ..distance = distance.meter;
|
|
|
+ }
|
|
|
}
|