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 now = DateTime.now(); filterStartAt.value = DateTime(now.year, now.month, now.day); workFlushData(); } Future 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 = []; 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 filterStartAt = DateTime.now().obs; final activeList = [].obs; final Rx selectActive = Rx(null); } class PersonalRankPage extends StatelessWidget { PersonalRankPage({super.key}); @override Widget build(BuildContext context) { return GetBuilder( init: PersonalRankController(), builder: (c) { 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, c))), const SizedBox(width: 20), Expanded(child: Obx(() => eUserList(context, c))) ], )), ); }); } Future _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}'); } // 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 ?? []; 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((t) { return eUserCard(context, c, t.$1 + 1, t.$2); }).toList(), )) ], ))) ], ); } Widget eUserListTitle(BuildContext context) { return DefaultTextStyle( style: const TextStyle( color: Color(0xff98d8ff), fontSize: 20, 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 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)), verticalDivider(show: false), const Expanded(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, textAlign: TextAlign.center, )), 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}) { return VerticalDivider( width: 3, indent: 14, endIndent: 14, color: show ? Colors.white : Colors.transparent, ); } static const _userIndexWidth = 56.0; static const _userNameWidth = 84.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 = []; } 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; } }