teto service now caching data + TL match view

Also added weights constants for nerd stats
This commit is contained in:
dan63047 2023-06-21 22:17:39 +03:00
parent 5d5523ce06
commit ffbe76e5cc
6 changed files with 1006 additions and 87 deletions

View File

@ -5,6 +5,18 @@ import 'dart:developer' as developer;
import 'package:http/http.dart' as http;
import 'dart:convert';
const double noTrRd = 60.9;
const double apmWeight = 1;
const double ppsWeight = 45;
const double vsWeight = 0.444;
const double appWeight = 185;
const double dssWeight = 175;
const double dspWeight = 450;
const double appdspWeight = 140;
const double vsapmWeight = 60;
const double cheeseWeight = 1.25;
const double gbeWeight = 315;
Duration doubleSecondsToDuration(double value) {
value = value * 1000000;
return Duration(microseconds: value.floor());
@ -578,42 +590,46 @@ class TetraLeagueAlphaRecord{
}
class EndContextMulti {
String? userId;
String? username;
int? naturalOrder;
int? inputs;
int? piecesPlaced;
Handling? handling;
int? points;
int? wins;
double? secondary;
List<double>? secondaryTracking;
double? tertiary;
List<double>? tertiaryTracking;
double? extra;
List<double>? extraTracking;
bool? success;
late String userId;
late String username;
late int naturalOrder;
late int inputs;
late int piecesPlaced;
late Handling handling;
late int points;
late int wins;
late double secondary;
late List<double> secondaryTracking;
late double tertiary;
late List<double> tertiaryTracking;
late double extra;
late List<double> extraTracking;
late bool success;
late NerdStats nerdStats;
late EstTr estTr;
late Playstyle playstyle;
EndContextMulti(
{this.userId,
this.naturalOrder,
this.inputs,
this.piecesPlaced,
this.handling,
this.points,
this.wins,
this.secondary,
this.secondaryTracking,
this.tertiary,
this.tertiaryTracking,
this.extra,
this.extraTracking,
this.success});
{required this.userId,
required this.username,
required this.naturalOrder,
required this.inputs,
required this.piecesPlaced,
required this.handling,
required this.points,
required this.wins,
required this.secondary,
required this.secondaryTracking,
required this.tertiary,
required this.tertiaryTracking,
required this.extra,
required this.extraTracking,
required this.success});
EndContextMulti.fromJson(Map<String, dynamic> json) {
userId = json['user']['_id'];
username = json['user']['username'];
handling = json['handling'] != null ? Handling.fromJson(json['handling']) : null;
handling = Handling.fromJson(json['handling']);
success = json['success'];
inputs = json['inputs'];
piecesPlaced = json['piecesplaced'];
@ -626,15 +642,16 @@ class EndContextMulti {
tertiaryTracking = json['points']['tertiaryAvgTracking'].cast<double>();
extra = json['points']['extra']['vs'];
extraTracking = json['points']['extraAvgTracking']['aggregatestats___vsscore'].cast<double>();
nerdStats = NerdStats(secondary, tertiary, extra);
estTr = EstTr(secondary, tertiary, extra, noTrRd, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
playstyle = Playstyle(secondary, tertiary, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['user']['_id'] = userId;
data['user']['username'] = username;
if (handling != null) {
data['handling'] = handling!.toJson();
}
data['handling'] = handling.toJson();
data['success'] = success;
data['inputs'] = inputs;
data['piecesplaced'] = piecesPlaced;

View File

@ -29,6 +29,8 @@ const String createTetrioUsersToTrack = '''
class TetrioService extends DB {
Map<String, List<TetrioPlayer>> _players = {};
final Map<String, TetrioPlayer> _playersCache = {};
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
static final TetrioService _shared = TetrioService._sharedInstance();
factory TetrioService() => _shared;
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController;
@ -41,16 +43,16 @@ class TetrioService extends DB {
@override
Future<void> open() async {
await super.open();
await _cachePlayers();
await _loadPlayers();
}
Stream<Map<String, List<TetrioPlayer>>> get allPlayers => _tetrioStreamController.stream;
Future<void> _cachePlayers() async {
Future<void> _loadPlayers() async {
final allPlayers = await getAllPlayers();
_players = allPlayers.toList().first; // ???
_tetrioStreamController.add(_players);
developer.log("_cachePlayers: $_players", name: "services/tetrio_crud");
developer.log("_loadPlayers: $_players", name: "services/tetrio_crud");
}
Future<void> deletePlayer(String id) async {
@ -72,6 +74,19 @@ class TetrioService extends DB {
}
Future<TetraLeagueAlphaStream> getTLStream(String userID) async {
try{
var cached = _tlStreamsCache.entries.firstWhere((element) => element.value.userId == userID);
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
developer.log("getTLStream: Stream $userID retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
return cached.value;
}else{
_tlStreamsCache.remove(cached.key);
developer.log("getTLStream: Cached stream $userID expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
}
}catch(e){
developer.log("getTLStream: Trying to retrieve stream $userID", name: "services/tetrio_crud");
}
var url = Uri.https('ch.tetr.io', 'api/streams/league_userrecent_${userID.toLowerCase().trim()}');
final response = await http.get(url);
@ -83,13 +98,15 @@ class TetrioService extends DB {
// await ensureDbIsOpen();
// storeState(player);
// }
developer.log("getTLStream: $userID stream retrieved and cached", name: "services/tetrio_crud");
_tlStreamsCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = stream;
return stream;
} else {
developer.log("getTLStream User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw Exception("User doesn't exist");
}
} else {
developer.log("getTLStream Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode);
developer.log("getTLStream Failed to fetch stream", name: "services/tetrio_crud", error: response.statusCode);
throw Exception('Failed to fetch player');
}
}
@ -208,6 +225,19 @@ class TetrioService extends DB {
}
Future<TetrioPlayer> fetchPlayer(String user, bool addToDB) async {
try{
var cached = _playersCache.entries.firstWhere((element) => element.value.userId == user || element.value.username == user);
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){
developer.log("fetchPlayer: User $user retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
return cached.value;
}else{
_playersCache.remove(cached.key);
developer.log("fetchPlayer: Cached user $user expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
}
}catch(e){
developer.log("fetchPlayer: Trying to retrieve $user", name: "services/tetrio_crud");
}
var url = Uri.https('ch.tetr.io', 'api/users/${user.toLowerCase().trim()}');
final response = await http.get(url);
@ -219,13 +249,15 @@ class TetrioService extends DB {
await ensureDbIsOpen();
storeState(player);
}
developer.log("fetchPlayer: $user retrieved and cached", name: "services/tetrio_crud");
_playersCache[jsonDecode(response.body)['cache']['cached_until'].toString()] = player;
return player;
} else {
developer.log("fetchTetrioPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw Exception("User doesn't exist");
}
} else {
developer.log("fetchTetrioPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode);
developer.log("fetchPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode);
throw Exception('Failed to fetch player');
}
}

View File

@ -547,43 +547,43 @@ class CompareState extends State<CompareView> {
RadarEntry(
value: theGreenSide!
.tlSeason1.apm! *
1),
apmWeight),
RadarEntry(
value: theGreenSide!
.tlSeason1.pps! *
45),
ppsWeight),
RadarEntry(
value: theGreenSide!
.tlSeason1.vs! *
0.444),
vsWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.app *
185),
appWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.dss *
175),
dssWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.dsp *
450),
dspWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.appdsp *
140),
appdspWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.vsapm *
60),
vsapmWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.cheese *
1.25),
cheeseWeight),
RadarEntry(
value: theGreenSide!.tlSeason1
.nerdStats!.gbe *
315),
gbeWeight),
],
),
RadarDataSet(

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:developer' as developer;
import 'package:flutter/services.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/services/tetrio_crud.dart';
import 'package:tetra_stats/services/crud_exceptions.dart';
import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView;
import 'package:tetra_stats/widgets/stat_sell_num.dart';
import 'package:tetra_stats/widgets/tl_thingy.dart';
import 'package:tetra_stats/widgets/user_thingy.dart';
@ -81,7 +81,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
_getPreferences()
.then((value) => changePlayer(prefs.getString("player") ?? "dan63047"));
super.initState();
developer.log("Main view initialized", name: "main_view");
}
@override
@ -89,7 +88,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
_tabController.dispose();
_scrollController.dispose();
super.dispose();
developer.log("Main view disposed", name: "main_view");
}
Future<void> _getPreferences() async {
@ -155,10 +153,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
),
PopupMenuButton(
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
// const PopupMenuItem(
// value: "/compare",
// child: Text('Compare'),
// ),
const PopupMenuItem(
value: "/states",
child: Text('Show stored data'),
@ -182,7 +176,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
child: FutureBuilder<TetrioPlayer>(
future: me,
builder: (context, snapshot) {
developer.log("builder ($context): $snapshot", name: "main_view");
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Center(
@ -225,10 +218,7 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
isScrollable: true,
tabs: myTabs,
onTap: (int tabId) {
setState(() {
developer.log("Tab changed to $tabId",
name: "main_view");
});
setState(() {});
},
),
),
@ -350,8 +340,6 @@ class _NavDrawerState extends State<NavDrawer> {
leading: const Icon(Icons.home),
title: Text(homePlayerNickname),
onTap: () {
developer.log("Navigator changed player",
name: "main_view");
widget.changePlayer(
prefs.getString("player") ?? "dan63047");
Navigator.of(context).pop();
@ -367,8 +355,6 @@ class _NavDrawerState extends State<NavDrawer> {
title: Text(
allPlayers[keys[index]]?.last.username as String),
onTap: () {
developer.log("Navigator changed player",
name: "main_view");
widget.changePlayer(keys[index]);
Navigator.of(context).pop();
},
@ -401,10 +387,7 @@ class _TLRecords extends StatelessWidget {
child: CircularProgressIndicator(color: Colors.white));
case ConnectionState.done:
if (snapshot.hasError) {
return Text(snapshot.error.toString(),
style: TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28));
return Text(snapshot.error.toString(), style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28));
} else {
return ListView(
physics: const ClampingScrollPhysics(),
@ -414,7 +397,7 @@ class _TLRecords extends StatelessWidget {
style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28,)),
title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != userID).username!}"),
title: Text("vs. ${value.endContext.firstWhere((element) => element.userId != userID).username}"),
subtitle: Text(dateFormat.format(value.timestamp!)),
trailing: Column(mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -422,14 +405,14 @@ class _TLRecords extends StatelessWidget {
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).tertiary)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).tertiary)} PPS", style: TextStyle(height: 1.1)),
Text("${f2.format(value.endContext.firstWhere((element) => element.userId == userID).extra)} : ${f2.format(value.endContext.firstWhere((element) => element.userId != userID).extra)} VS", style: TextStyle(height: 1.1)),
]),
onTap: (){},
onTap: (){Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TlMatchResultView(record: value, initPlayerId: userID),
),
);},
)]
: [
Text("No records",
style: TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28))
],
: [const Text("No records",style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28))],
);
}
}

View File

@ -0,0 +1,887 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
final DateFormat dateFormat = DateFormat.yMMMd().add_Hms();
class TlMatchResultView extends StatefulWidget {
final TetraLeagueAlphaRecord record;
final String initPlayerId;
const TlMatchResultView({Key? key, required this.record, required this.initPlayerId})
: super(key: key);
@override
State<StatefulWidget> createState() => TlMatchResultState();
}
class TlMatchResultState extends State<TlMatchResultView> {
late ScrollController _scrollController;
@override
void initState(){
_scrollController = ScrollController();
super.initState();
}
@override
Widget build(BuildContext context) {
bool bigScreen = MediaQuery.of(context).size.width > 768;
return Scaffold(
appBar: AppBar(
title: Text(
"${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} vs. ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} in TL match ${dateFormat.format(widget.record.timestamp!)}"),
),
backgroundColor: Colors.black,
body: SafeArea(
child: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value) {
return [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.green, Colors.transparent],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
stops: [0.0, 0.4],
)),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Column(children: [
Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username, style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28)),
Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).points.toString(), style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 42))
]),
),
),
),
const Padding(
padding: EdgeInsets.only(top: 16),
child: Text("VS"),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red, Colors.transparent],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
stops: [0.0, 0.4],
)),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Column(children: [
Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username, style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28)),
Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).points.toString(), style: const TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 42))
]),
),
),
),
],
),
),
),
const SliverToBoxAdapter(
child: Divider(),
)
];
},
body: ListView(
children: [
Column(
children: [
CompareThingy(
label: "APM",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondary,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondary,
fractionDigits: 2,
higherIsBetter: true,
),
CompareThingy(
label: "PPS",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiary,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiary,
fractionDigits: 2,
higherIsBetter: true,
),
CompareThingy(
label: "VS",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extra,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extra,
fractionDigits: 2,
higherIsBetter: true,
),
],
),
const Divider(),
Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text("Nerd Stats",
style: TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: bigScreen ? 42 : 28)),
),
CompareThingy(
label: "APP",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.app,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.app,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "VS/APM",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.vsapm,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.vsapm,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "DS/S",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dss,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dss,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "DS/P",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dsp,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dsp,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "APP + DS/P",
greenSide:
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.appdsp,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.appdsp,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "Cheese",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.cheese,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.cheese,
fractionDigits: 2,
higherIsBetter: true,
),
CompareThingy(
label: "Garbage Eff.",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.gbe,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.gbe,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "Weighted APP",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.nyaapp,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.nyaapp,
fractionDigits: 3,
higherIsBetter: true,
),
CompareThingy(
label: "Area",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.area,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.area,
fractionDigits: 2,
higherIsBetter: true,
),
CompareThingy(
label: "Est. of TR",
greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).estTr.esttr,
redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).estTr.esttr,
fractionDigits: 2,
higherIsBetter: true,
),
Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceAround,
spacing: 25,
crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge,
children: [
Padding(
padding:
const EdgeInsets.fromLTRB(20, 20, 20, 20),
child: SizedBox(
height: 300,
width: 300,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
ticksTextStyle: const TextStyle(
color: Colors.transparent,
fontSize: 10),
radarBorderData: const BorderSide(
color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(
color: Colors.white24, width: 1),
tickBorderData: const BorderSide(
color: Colors.transparent, width: 1),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(
text: 'APM',
angle: angle,
);
case 1:
return RadarChartTitle(
text: 'PPS',
angle: angle,
);
case 2:
return RadarChartTitle(
text: 'VS', angle: angle);
case 3:
return RadarChartTitle(
text: 'APP',
angle: angle + 180);
case 4:
return RadarChartTitle(
text: 'DS/S',
angle: angle + 180);
case 5:
return RadarChartTitle(
text: 'DS/P',
angle: angle + 180);
case 6:
return RadarChartTitle(
text: 'APP+DS/P',
angle: angle + 180);
case 7:
return RadarChartTitle(
text: 'VS/APM',
angle: angle + 180);
case 8:
return RadarChartTitle(
text: 'Cheese', angle: angle);
case 9:
return RadarChartTitle(
text: 'Gb Eff.', angle: angle);
default:
return const RadarChartTitle(
text: '');
}
},
dataSets: [
RadarDataSet(
fillColor: const Color.fromARGB(
115, 76, 175, 79),
borderColor: Colors.green,
dataEntries: [
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondary * apmWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiary * ppsWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extra * vsWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.app * appWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dss * dssWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dsp * dspWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.appdsp * appdspWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.vsapm * vsapmWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.cheese * cheeseWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.gbe * gbeWeight),
],
),
RadarDataSet(
fillColor: const Color.fromARGB(
115, 244, 67, 54),
borderColor: Colors.red,
dataEntries: [
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondary * apmWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiary * ppsWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extra * vsWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.app * appWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dss * dssWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dsp * dspWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.appdsp * appdspWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.vsapm * vsapmWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.cheese * cheeseWeight),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.gbe * gbeWeight),
],
),
RadarDataSet(
fillColor: Colors.transparent,
borderColor: Colors.transparent,
dataEntries: [
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
],
)
],
),
swapAnimationDuration: const Duration(
milliseconds: 150), // Optional
swapAnimationCurve:
Curves.linear, // Optional
),
),
),
Padding(
padding:
const EdgeInsets.fromLTRB(20, 20, 20, 20),
child: SizedBox(
height: 300,
width: 300,
child: RadarChart(
RadarChartData(
radarShape: RadarShape.polygon,
tickCount: 4,
ticksTextStyle: const TextStyle(
color: Colors.transparent,
fontSize: 10),
radarBorderData: const BorderSide(
color: Colors.transparent, width: 1),
gridBorderData: const BorderSide(
color: Colors.white24, width: 1),
tickBorderData: const BorderSide(
color: Colors.transparent, width: 1),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(
text: 'Opener',
angle: angle,
);
case 1:
return RadarChartTitle(
text: 'Stride',
angle: angle,
);
case 2:
return RadarChartTitle(
text: 'Inf Ds',
angle: angle + 180);
case 3:
return RadarChartTitle(
text: 'Plonk', angle: angle);
default:
return const RadarChartTitle(
text: '');
}
},
dataSets: [
RadarDataSet(
fillColor: const Color.fromARGB(
115, 76, 175, 79),
borderColor: Colors.green,
dataEntries: [
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.opener),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.stride),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.infds),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.plonk),
],
),
RadarDataSet(
fillColor: const Color.fromARGB(
115, 244, 67, 54),
borderColor: Colors.red,
dataEntries: [
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.opener),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.stride),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.infds),
RadarEntry(value: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.plonk),
],
),
RadarDataSet(
fillColor: Colors.transparent,
borderColor: Colors.transparent,
dataEntries: [
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
const RadarEntry(value: 0),
],
),
RadarDataSet(
fillColor: Colors.transparent,
borderColor: Colors.transparent,
dataEntries: [
const RadarEntry(value: 1),
const RadarEntry(value: 1),
const RadarEntry(value: 1),
const RadarEntry(value: 1),
],
)
],
),
swapAnimationDuration: const Duration(
milliseconds: 150), // Optional
swapAnimationCurve:
Curves.linear, // Optional
),
),
)
],
)
],
)
],
)
),
),
);
}
}
class CompareThingy extends StatelessWidget {
final num greenSide;
final num redSide;
final String label;
final bool higherIsBetter;
final int? fractionDigits;
const CompareThingy(
{super.key,
required this.greenSide,
required this.redSide,
required this.label,
required this.higherIsBetter,
this.fractionDigits});
String verdict(num greenSide, num redSide, int fraction) {
var f = NumberFormat("+#,###.##;-#,###.##");
f.maximumFractionDigits = fraction;
return f.format((greenSide - redSide));
}
@override
Widget build(BuildContext context) {
var f = NumberFormat("#,###.##");
f.maximumFractionDigits = fractionDigits ?? 0;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [Colors.green, Colors.transparent],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [
0.0,
higherIsBetter
? greenSide > redSide
? 0.6
: 0
: greenSide < redSide
? 0.6
: 0
],
)),
child: Text(
f.format(greenSide),
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.start,
),
)),
Column(
children: [
Text(
label,
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center,
),
Text(
verdict(greenSide, redSide,
fractionDigits != null ? fractionDigits! + 2 : 0),
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
],
),
Expanded(
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [Colors.red, Colors.transparent],
begin: Alignment.centerRight,
end: Alignment.centerLeft,
stops: [
0.0,
higherIsBetter
? redSide > greenSide
? 0.6
: 0
: redSide < greenSide
? 0.6
: 0
],
)),
child: Text(
f.format(redSide),
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.end,
),
)),
],
),
);
}
}
class CompareBoolThingy extends StatelessWidget {
final bool greenSide;
final bool redSide;
final String label;
final bool trueIsBetter;
const CompareBoolThingy(
{super.key,
required this.greenSide,
required this.redSide,
required this.label,
required this.trueIsBetter});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
child: Row(children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [Colors.green, Colors.transparent],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [
0.0,
trueIsBetter
? greenSide
? 0.6
: 0
: !greenSide
? 0.6
: 0
],
)),
child: Text(
greenSide ? "Yes" : "No",
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.start,
),
)),
Column(
children: [
Text(
label,
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center,
),
const Text(
"---",
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
],
),
Expanded(
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [Colors.red, Colors.transparent],
begin: Alignment.centerRight,
end: Alignment.centerLeft,
stops: [
0.0,
trueIsBetter
? redSide
? 0.6
: 0
: !redSide
? 0.6
: 0
],
)),
child: Text(
redSide ? "Yes" : "No",
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.end,
),
)),
]),
);
}
}
class CompareDurationThingy extends StatelessWidget {
final Duration greenSide;
final Duration redSide;
final String label;
final bool higherIsBetter;
const CompareDurationThingy(
{super.key,
required this.greenSide,
required this.redSide,
required this.label,
required this.higherIsBetter});
Duration verdict(Duration greenSide, Duration redSide) {
return greenSide - redSide;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Text(
greenSide.toString(),
style: const TextStyle(
fontSize: 22,
),
textAlign: TextAlign.start,
)),
Column(
children: [
Text(
label,
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.center,
),
Text(
verdict(greenSide, redSide).toString(),
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
],
),
Expanded(
child: Text(
redSide.toString(),
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.end,
)),
],
),
);
}
}
class CompareRegTimeThingy extends StatelessWidget {
final DateTime? greenSide;
final DateTime? redSide;
final String label;
final int? fractionDigits;
const CompareRegTimeThingy(
{super.key,
required this.greenSide,
required this.redSide,
required this.label,
this.fractionDigits});
String verdict(DateTime? greenSide, DateTime? redSide) {
var f = NumberFormat("#,### days later;#,### days before");
String result = "---";
if (greenSide != null && redSide != null)
result = f.format(greenSide.difference(redSide).inDays);
return result;
}
@override
Widget build(BuildContext context) {
DateFormat f = DateFormat.yMMMd();
return Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [Colors.green, Colors.transparent],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [
0.0,
greenSide == null
? 0.6
: redSide != null && greenSide!.isBefore(redSide!)
? 0.6
: 0
],
)),
child: Text(
greenSide != null ? f.format(greenSide!) : "From beginning",
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.start,
),
)),
Column(
children: [
Text(
label,
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center,
),
Text(
verdict(greenSide, redSide),
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
],
),
Expanded(
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: const [Colors.red, Colors.transparent],
begin: Alignment.centerRight,
end: Alignment.centerLeft,
stops: [
0.0,
redSide == null
? 0.6
: greenSide != null && redSide!.isBefore(greenSide!)
? 0.6
: 0
],
)),
child: Text(
redSide != null ? f.format(redSide!) : "From beginning",
style: const TextStyle(
fontSize: 22,
shadows: <Shadow>[
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 3.0,
color: Colors.black,
),
Shadow(
offset: Offset(0.0, 0.0),
blurRadius: 8.0,
color: Colors.black,
),
],
),
textAlign: TextAlign.end,
),
)),
],
),
);
}
}

View File

@ -199,16 +199,16 @@ class TLThingy extends StatelessWidget {
dataSets: [
RadarDataSet(
dataEntries: [
RadarEntry(value: tl.apm! * 1),
RadarEntry(value: tl.pps! * 45),
RadarEntry(value: tl.vs! * 0.444),
RadarEntry(value: tl.nerdStats!.app * 185),
RadarEntry(value: tl.nerdStats!.dss * 175),
RadarEntry(value: tl.nerdStats!.dsp * 450),
RadarEntry(value: tl.nerdStats!.appdsp * 140),
RadarEntry(value: tl.nerdStats!.vsapm * 60),
RadarEntry(value: tl.nerdStats!.cheese * 1.25),
RadarEntry(value: tl.nerdStats!.gbe * 315),
RadarEntry(value: tl.apm! * apmWeight),
RadarEntry(value: tl.pps! * ppsWeight),
RadarEntry(value: tl.vs! * vsWeight),
RadarEntry(value: tl.nerdStats!.app * appWeight),
RadarEntry(value: tl.nerdStats!.dss * dssWeight),
RadarEntry(value: tl.nerdStats!.dsp * dspWeight),
RadarEntry(value: tl.nerdStats!.appdsp * appdspWeight),
RadarEntry(value: tl.nerdStats!.vsapm * vsWeight),
RadarEntry(value: tl.nerdStats!.cheese * cheeseWeight),
RadarEntry(value: tl.nerdStats!.gbe * gbeWeight),
],
),
RadarDataSet(