diff --git a/lib/data_objects/summaries.dart b/lib/data_objects/summaries.dart index 08f5db6..31540b4 100644 --- a/lib/data_objects/summaries.dart +++ b/lib/data_objects/summaries.dart @@ -6,7 +6,7 @@ import 'package:tetra_stats/data_objects/tetra_league.dart'; import 'package:tetra_stats/data_objects/tetrio_constants.dart'; import 'package:tetra_stats/data_objects/tetrio_zen.dart'; -class Summaries{ +class Summaries { late String id; RecordSingle? sprint; RecordSingle? blitz; @@ -21,19 +21,42 @@ class Summaries{ Summaries(this.id, this.league, this.zen); - Summaries.fromJson(Map json, String i){ + Summaries.fromJson(Map json, String i) { id = i; - if (json['40l']['record'] != null) sprint = RecordSingle.fromJson(json['40l']['record'], json['40l']['rank'], json['40l']['rank_local']); - if (json['blitz']['record'] != null) blitz = RecordSingle.fromJson(json['blitz']['record'], json['blitz']['rank'], json['40l']['rank_local']); - if (json['zenith']['record'] != null) zenith = RecordSingle.fromJson(json['zenith']['record'], json['zenith']['rank'], json['zenith']['rank_local']); - if (json['zenith']['best']['record'] != null) zenithCareerBest = RecordSingle.fromJson(json['zenith']['best']['record'], json['zenith']['best']['rank'], -1); - if (json['zenithex']['record'] != null) zenithEx = RecordSingle.fromJson(json['zenithex']['record'], json['zenithex']['rank'], json['zenithex']['rank_local']); - if (json['zenithex']['best']['record'] != null) zenithCareerBest = RecordSingle.fromJson(json['zenithex']['best']['record'], json['zenith']['best']['rank'], -1); - achievements = [for (var achievement in json['achievements']) Achievement.fromJson(achievement)]; - league = TetraLeague.fromJson(json['league'], DateTime.now(), currentSeason, i); - if (json['league']['past'].isNotEmpty) for (var key in json['league']['past'].keys){ - pastLeague[int.parse(key)] = TetraLeague.fromJson(json['league']['past'][key], DateTime(1970), int.parse(json['league']['past'][key]['season']), i); - } + if (json['40l']['record'] != null) + sprint = RecordSingle.fromJson(json['40l']['record'], json['40l']['rank'], + json['40l']['rank_local']); + if (json['blitz']['record'] != null) + blitz = RecordSingle.fromJson(json['blitz']['record'], + json['blitz']['rank'], json['40l']['rank_local']); + if (json['zenith']['record'] != null) + zenith = RecordSingle.fromJson(json['zenith']['record'], + json['zenith']['rank'], json['zenith']['rank_local']); + if (json['zenith']['best']['record'] != null) + zenithCareerBest = RecordSingle.fromJson( + json['zenith']['best']['record'], json['zenith']['best']['rank'], -1); + if (json['zenithex']['record'] != null) + zenithEx = RecordSingle.fromJson(json['zenithex']['record'], + json['zenithex']['rank'], json['zenithex']['rank_local']); + if (json['zenithex']['best']['record'] != null) + zenithCareerBest = RecordSingle.fromJson( + json['zenithex']['best']['record'], + json['zenith']['best']['rank'], + -1); + achievements = [ + for (var achievement in json['achievements']) + Achievement.fromJson(achievement) + ]; + league = + TetraLeague.fromJson(json['league'], DateTime.now(), currentSeason, i); + if (json['league']['past'].isNotEmpty) + for (var key in json['league']['past'].keys) { + pastLeague[int.parse(key)] = TetraLeague.fromJson( + json['league']['past'][key], + null, + int.parse(json['league']['past'][key]['season']), + i); + } zen = TetrioZen.fromJson(json['zen']); } } diff --git a/lib/data_objects/tetra_league.dart b/lib/data_objects/tetra_league.dart index 0419fd8..c509d07 100644 --- a/lib/data_objects/tetra_league.dart +++ b/lib/data_objects/tetra_league.dart @@ -57,22 +57,35 @@ class TetraLeague { this.apm, this.pps, this.vs, - required this.season}){ - nerdStats = (apm != null && pps != null && vs != null) ? NerdStats(apm!, pps!, vs!) : null; - estTr = (nerdStats != null) ? EstTr(apm!, pps!, vs!, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe) : null; - playstyle =(nerdStats != null) ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) : null; - } + required this.season}) { + nerdStats = (apm != null && pps != null && vs != null) + ? NerdStats(apm!, pps!, vs!) + : null; + estTr = (nerdStats != null) + ? EstTr(apm!, pps!, vs!, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, + nerdStats!.gbe) + : null; + playstyle = (nerdStats != null) + ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, + nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) + : null; + } double get winrate => gamesWon / gamesPlayed; double get s1tr => gxe * 250; - TetraLeague.fromJson(Map json, ts, int s, String i) { - timestamp = ts; + TetraLeague.fromJson( + Map json, DateTime? ts, int s, String i) { + timestamp = ts != null ? ts : seasonEnds[s - 1]; season = s; id = i; gamesPlayed = json['gamesplayed'] ?? 0; gamesWon = json['gameswon'] ?? 0; - tr = json['tr'] != null ? json['tr'].toDouble() : json['rating'] != null ? json['rating'].toDouble() : -1; + tr = json['tr'] != null + ? json['tr'].toDouble() + : json['rating'] != null + ? json['rating'].toDouble() + : -1; glicko = json['glicko']?.toDouble(); rd = json['rd'] != null ? json['rd']!.toDouble() : noTrRd; gxe = json['gxe'] != null ? json['gxe'].toDouble() : -1; @@ -81,38 +94,67 @@ class TetraLeague { apm = json['apm']?.toDouble(); pps = json['pps']?.toDouble(); vs = json['vs']?.toDouble(); - decaying = switch(json['decaying'].runtimeType){ + decaying = switch (json['decaying'].runtimeType) { int => json['decaying'] == 1, bool => json['decaying'], _ => false }; standing = json['standing'] ?? json['placement'] ?? -1; - percentile = json['percentile'] != null ? json['percentile'].toDouble() : rankCutoffs[rank]; + percentile = json['percentile'] != null + ? json['percentile'].toDouble() + : rankCutoffs[rank]; standingLocal = json['standing_local'] ?? -1; prevRank = json['prev_rank']; prevAt = json['prev_at'] ?? -1; nextRank = json['next_rank']; nextAt = json['next_at'] ?? -1; percentileRank = json['percentile_rank'] ?? rank; - nerdStats = (apm != null && pps != null && vs != null) ? NerdStats(apm!, pps!, vs!) : null; - estTr = (nerdStats != null) ? EstTr(apm!, pps!, vs!, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, nerdStats!.gbe) : null; - playstyle = (nerdStats != null) ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) : null; + nerdStats = (apm != null && pps != null && vs != null) + ? NerdStats(apm!, pps!, vs!) + : null; + estTr = (nerdStats != null) + ? EstTr(apm!, pps!, vs!, nerdStats!.app, nerdStats!.dss, nerdStats!.dsp, + nerdStats!.gbe) + : null; + playstyle = (nerdStats != null) + ? Playstyle(apm!, pps!, nerdStats!.app, nerdStats!.vsapm, + nerdStats!.dsp, nerdStats!.gbe, estTr!.srarea, estTr!.statrank) + : null; } @override - bool operator ==(covariant TetraLeague other) => gamesPlayed == other.gamesPlayed && rd == other.rd; + bool operator ==(covariant TetraLeague other) => + gamesPlayed == other.gamesPlayed && rd == other.rd; - bool lessStrictCheck (covariant TetraLeague other) => gamesPlayed == other.gamesPlayed && glicko == other.glicko; + bool lessStrictCheck(covariant TetraLeague other) => + gamesPlayed == other.gamesPlayed && glicko == other.glicko; double? get esttracc => (estTr != null) ? estTr!.esttr - tr : null; - TetrioPlayerFromLeaderboard convertToPlayerFromLeaderboard(String id) => TetrioPlayerFromLeaderboard( - id, "", "user", -1, null, timestamp, gamesPlayed, gamesWon, - tr, gxe, glicko??0, rd??noTrRd, rank, bestRank, apm??0, pps??0, vs??0, decaying); + TetrioPlayerFromLeaderboard convertToPlayerFromLeaderboard(String id) => + TetrioPlayerFromLeaderboard( + id, + "", + "user", + -1, + null, + timestamp, + gamesPlayed, + gamesWon, + tr, + gxe, + glicko ?? 0, + rd ?? noTrRd, + rank, + bestRank, + apm ?? 0, + pps ?? 0, + vs ?? 0, + decaying); Map toJson() { final Map data = {}; - data['id'] = id+timestamp.millisecondsSinceEpoch.toRadixString(16); + data['id'] = id + timestamp.millisecondsSinceEpoch.toRadixString(16); if (gamesPlayed > 0) data['gamesplayed'] = gamesPlayed; if (gamesWon > 0) data['gameswon'] = gamesWon; if (tr >= 0) data['tr'] = tr; diff --git a/lib/data_objects/tetrio_constants.dart b/lib/data_objects/tetrio_constants.dart index b397d38..07bbcc3 100644 --- a/lib/data_objects/tetrio_constants.dart +++ b/lib/data_objects/tetrio_constants.dart @@ -13,7 +13,24 @@ const double vsapmWeight = 60; const double cheeseWeight = 1.25; const double gbeWeight = 315; const List ranks = [ - "d", "d+", "c-", "c", "c+", "b-", "b", "b+", "a-", "a", "a+", "s-", "s", "s+", "ss", "u", "x", "x+" + "d", + "d+", + "c-", + "c", + "c+", + "b-", + "b", + "b+", + "a-", + "a", + "a+", + "s-", + "s", + "s+", + "ss", + "u", + "x", + "x+" ]; const Map rankCutoffs = { "x+": 0.002, @@ -57,6 +74,7 @@ const Map rankTargets = { "d+": 800.00, "d": 0.00, }; + // DateTime seasonStart = DateTime.utc(2024, 08, 16, 18); //DateTime seasonEnd = DateTime.utc(2024, 07, 26, 15); enum Stats { @@ -89,7 +107,7 @@ enum Stats { stride, stridemMinusPlonk, openerMinusInfDS - } +} const Map chartsShortTitles = { Stats.tr: "TR", @@ -120,69 +138,81 @@ const Map chartsShortTitles = { Stats.infDS: "Inf. DS", Stats.stride: "Stride", Stats.stridemMinusPlonk: "Stride - Plonk", - Stats.openerMinusInfDS: "Opener - Inf. DS" - }; - -const Map rankColors = { // thanks osk for const rankColors at https://ch.tetr.io/res/js/base.js:458 - 'x+': Color(0xFF643C8D), - 'x': Color(0xFFFF45FF), - 'u': Color(0xFFFF3813), - 'ss': Color(0xFFDB8B1F), - 's+': Color(0xFFD8AF0E), - 's': Color(0xFFE0A71B), - 's-': Color(0xFFB2972B), - 'a+': Color(0xFF1FA834), - 'a': Color(0xFF46AD51), - 'a-': Color(0xFF3BB687), - 'b+': Color(0xFF4F99C0), - 'b': Color(0xFF4F64C9), - 'b-': Color(0xFF5650C7), - 'c+': Color(0xFF552883), - 'c': Color(0xFF733E8F), - 'c-': Color(0xFF79558C), - 'd+': Color(0xFF8E6091), - 'd': Color(0xFF907591), - 'z': Color(0xFF375433) + Stats.openerMinusInfDS: "Opener - Inf. DS" }; -const Map sprintAverages = { // based on https://discord.com/channels/673303546107658242/674421736162197515/1244287342965952562 - 'x': Duration(seconds: 25, milliseconds: 144), - 'u': Duration(seconds: 36, milliseconds: 115), - 'ss': Duration(seconds: 46, milliseconds: 396), - 's+': Duration(seconds: 55, milliseconds: 056), - 's': Duration(seconds: 61, milliseconds: 892), - 's-': Duration(seconds: 68, milliseconds: 918), - 'a+': Duration(seconds: 76, milliseconds: 187), - 'a': Duration(seconds: 83, milliseconds: 529), - 'a-': Duration(seconds: 88, milliseconds: 608), - 'b+': Duration(seconds: 97, milliseconds: 626), - 'b': Duration(seconds: 104, milliseconds: 687), - 'b-': Duration(seconds: 113, milliseconds: 372), - 'c+': Duration(seconds: 123, milliseconds: 461), - 'c': Duration(seconds: 135, milliseconds: 326), - 'c-': Duration(seconds: 147, milliseconds: 056), - 'd+': Duration(seconds: 156, milliseconds: 757), - 'd': Duration(seconds: 167, milliseconds: 393), - //'z': Duration(seconds: 66, milliseconds: 802) +const Map rankColors = { + // thanks osk for const rankColors at https://ch.tetr.io/res/js/base.js:458 + 'x+': Color(0xFF643C8D), + 'x': Color(0xFFFF45FF), + 'u': Color(0xFFFF3813), + 'ss': Color(0xFFDB8B1F), + 's+': Color(0xFFD8AF0E), + 's': Color(0xFFE0A71B), + 's-': Color(0xFFB2972B), + 'a+': Color(0xFF1FA834), + 'a': Color(0xFF46AD51), + 'a-': Color(0xFF3BB687), + 'b+': Color(0xFF4F99C0), + 'b': Color(0xFF4F64C9), + 'b-': Color(0xFF5650C7), + 'c+': Color(0xFF552883), + 'c': Color(0xFF733E8F), + 'c-': Color(0xFF79558C), + 'd+': Color(0xFF8E6091), + 'd': Color(0xFF907591), + 'z': Color(0xFF375433) +}; + +const Map sprintAverages = { + // based on https://discord.com/channels/673303546107658242/674421736162197515/1244287342965952562 + 'x': Duration(seconds: 25, milliseconds: 144), + 'u': Duration(seconds: 36, milliseconds: 115), + 'ss': Duration(seconds: 46, milliseconds: 396), + 's+': Duration(seconds: 55, milliseconds: 056), + 's': Duration(seconds: 61, milliseconds: 892), + 's-': Duration(seconds: 68, milliseconds: 918), + 'a+': Duration(seconds: 76, milliseconds: 187), + 'a': Duration(seconds: 83, milliseconds: 529), + 'a-': Duration(seconds: 88, milliseconds: 608), + 'b+': Duration(seconds: 97, milliseconds: 626), + 'b': Duration(seconds: 104, milliseconds: 687), + 'b-': Duration(seconds: 113, milliseconds: 372), + 'c+': Duration(seconds: 123, milliseconds: 461), + 'c': Duration(seconds: 135, milliseconds: 326), + 'c-': Duration(seconds: 147, milliseconds: 056), + 'd+': Duration(seconds: 156, milliseconds: 757), + 'd': Duration(seconds: 167, milliseconds: 393), + //'z': Duration(seconds: 66, milliseconds: 802) }; const Map blitzAverages = { 'x': 600715, - 'u': 379418, - 'ss': 233740, - 's+': 158295, - 's': 125164, - 's-': 100933, - 'a+': 83593, - 'a': 68613, - 'a-': 60219, - 'b+': 51197, - 'b': 44171, - 'b-': 39045, - 'c+': 34130, - 'c': 28931, - 'c-': 25095, - 'd+': 22944, - 'd': 20728, - //'z': 72084 -}; \ No newline at end of file + 'u': 379418, + 'ss': 233740, + 's+': 158295, + 's': 125164, + 's-': 100933, + 'a+': 83593, + 'a': 68613, + 'a-': 60219, + 'b+': 51197, + 'b': 44171, + 'b-': 39045, + 'c+': 34130, + 'c': 28931, + 'c-': 25095, + 'd+': 22944, + 'd': 20728, + //'z': 72084 +}; + +List seasonStarts = [ + DateTime.utc(2020, DateTime.april, 18, 4), // Source = twitter or something + DateTime.utc( + 2024, DateTime.august, 16, 18, 41, 10) // Source = osk status page +]; + +List seasonEnds = [ + DateTime.utc(2024, DateTime.july, 26, 15) // Source - TETR.IO discord guild +]; diff --git a/lib/utils/colors_functions.dart b/lib/utils/colors_functions.dart index 70277d9..8ec5900 100644 --- a/lib/utils/colors_functions.dart +++ b/lib/utils/colors_functions.dart @@ -7,4 +7,8 @@ Color getColorOfRank(int rank){ if (rank <= 9) return Colors.blueAccent; if (rank <= 99) return Colors.greenAccent; return Colors.grey; +} + +Color getDifferenceColor(num diff){ + return diff.isNegative ? Colors.redAccent : Colors.greenAccent; } \ No newline at end of file diff --git a/lib/utils/numers_formats.dart b/lib/utils/numers_formats.dart index 097b705..f359edb 100644 --- a/lib/utils/numers_formats.dart +++ b/lib/utils/numers_formats.dart @@ -1,6 +1,7 @@ import 'package:intl/intl.dart'; import 'package:tetra_stats/gen/strings.g.dart'; +final NumberFormat compareIntf = NumberFormat("+#,###;-#,###")..maximumFractionDigits = 0; final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3; final NumberFormat comparef2 = NumberFormat("+#,###.##;-#,###.##")..maximumFractionDigits = 2; final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0); diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index 37aa103..7e17387 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -243,7 +243,7 @@ class _DestinationGraphsState extends State { bool _smooth = false; final List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"]; int _chartsIndex = 0; - late List>> chartsData; + late List>>> historyData; //Duration postSeasonLeft = seasonStart.difference(DateTime.now()); @override @@ -282,10 +282,7 @@ class _DestinationGraphsState extends State { super.initState(); } - Future>>> getChartsData(bool fetchHistory) async { - List states = []; - Set uniqueTL = {}; - + Future>>>> getHistoryData(bool fetchHistory) async { if(fetchHistory){ try{ var history = await teto.fetchAndsaveTLHistory(widget.searchFor); @@ -301,49 +298,47 @@ class _DestinationGraphsState extends State { } } - //states.addAll(await teto.getPlayer(widget.searchFor)); - // for (var element in states) { - // if (element.tlSeason1 != null && uniqueTL.isNotEmpty && uniqueTL.last != element.tlSeason1) uniqueTL.add(element.tlSeason1!); - // if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1!); - // } + List> states = await Future.wait>([ + teto.getStates(widget.searchFor, season: 1), teto.getStates(widget.searchFor, season: 2), + ]); - if (uniqueTL.length >= 2){ - chartsData = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.tr)], child: Text(t.statCellNum.tr)), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rd!)], child: const Text("Rating Deviation")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.apm != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.pps != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.vs != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.vsapm)], child: const Text("VS/APM")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.opener)], child: const Text("Opener")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.plonk)], child: const Text("Plonk")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.infds)], child: const Text("Inf. DS")), - DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.stride)], child: const Text("Stride")), - ]; + if (states.length >= 2){ + historyData = [for (List s in states) >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid + DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.tr)], child: Text(t.statCellNum.tr)), + DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.glicko!)], child: const Text("Glicko")), + DropdownMenuItem(value: [for (var tl in s) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rd!)], child: const Text("Rating Deviation")), + DropdownMenuItem(value: [for (var tl in s) if (tl.apm != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.pps != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.vs != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.vsapm)], child: const Text("VS/APM")), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.nerdStats != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.estTr != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.esttracc != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.opener)], child: const Text("Opener")), + DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.plonk)], child: const Text("Plonk")), + DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.infds)], child: const Text("Inf. DS")), + DropdownMenuItem(value: [for (var tl in s) if (tl.playstyle != null) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.playstyle!.stride)], child: const Text("Stride")), + ]]; }else{ - chartsData = []; + historyData = []; } fetchData = false; - return chartsData; + return historyData; } @override Widget build(BuildContext context) { - return FutureBuilder>>>( - future: getChartsData(fetchData), + return FutureBuilder>>>>( + future: getHistoryData(fetchData), builder: (context, snapshot) { switch (snapshot.connectionState){ case ConnectionState.none: @@ -352,7 +347,7 @@ class _DestinationGraphsState extends State { return const Center(child: CircularProgressIndicator()); case ConnectionState.done: if (snapshot.hasData && snapshot.data!.isNotEmpty){ - List<_HistoryChartSpot> selectedGraph = snapshot.data![_chartsIndex].value!; + List<_HistoryChartSpot> selectedGraph = snapshot.data![currentSeason-1][_chartsIndex].value!; yAxisTitle = _historyShortTitles[_chartsIndex]; return SingleChildScrollView( scrollDirection: Axis.vertical, @@ -384,11 +379,11 @@ class _DestinationGraphsState extends State { children: [ const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))), DropdownButton( - items: chartsData, - value: chartsData[_chartsIndex].value, + items: historyData[currentSeason-1], + value: historyData[currentSeason-1][_chartsIndex].value, onChanged: (value) { setState(() { - _chartsIndex = chartsData.indexWhere((element) => element.value == value); + _chartsIndex = historyData[currentSeason-1].indexWhere((element) => element.value == value); }); } ), @@ -411,7 +406,7 @@ class _DestinationGraphsState extends State { ], ), ), - if(chartsData[_chartsIndex].value!.length > 1) Card( + if(historyData[currentSeason-1][_chartsIndex].value!.length > 1) Card( child: SizedBox( width: MediaQuery.of(context).size.width - 88, height: MediaQuery.of(context).size.height - 60, @@ -427,7 +422,7 @@ class _DestinationGraphsState extends State { series: [ if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>( enableTooltip: true, - dataSource: chartsData[_chartsIndex].value!, + dataSource: historyData[currentSeason-1][_chartsIndex].value!, animationDuration: 0, opacity: _smooth ? 0 : 1, xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed, @@ -436,14 +431,14 @@ class _DestinationGraphsState extends State { trendlines:[ Trendline( isVisible: _smooth, - period: (chartsData[_chartsIndex].value!.length/175).floor(), + period: (historyData[currentSeason-1][_chartsIndex].value!.length/175).floor(), type: TrendlineType.movingAverage, color: Theme.of(context).colorScheme.primary) ], ) else StepLineSeries<_HistoryChartSpot, DateTime>( enableTooltip: true, - dataSource: chartsData[_chartsIndex].value!, + dataSource: historyData[currentSeason-1][_chartsIndex].value!, animationDuration: 0, opacity: _smooth ? 0 : 1, xValueMapper: (_HistoryChartSpot data, _) => data.timestamp, @@ -452,7 +447,7 @@ class _DestinationGraphsState extends State { trendlines:[ Trendline( isVisible: _smooth, - period: (chartsData[_chartsIndex].value!.length/175).floor(), + period: (historyData[currentSeason-1][_chartsIndex].value!.length/175).floor(), type: TrendlineType.movingAverage, color: Theme.of(context).colorScheme.primary) ], @@ -462,7 +457,7 @@ class _DestinationGraphsState extends State { ) ), ) - else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column( + else if (historyData[currentSeason-1][_chartsIndex].value!.length <= 1) Center(child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), @@ -525,11 +520,12 @@ class DestinationHome extends StatefulWidget{ class FetchResults{ bool success; TetrioPlayer? player; + List states; Summaries? summaries; Cutoffs? cutoffs; Exception? exception; - FetchResults(this.success, this.player, this.summaries, this.cutoffs, this.exception); + FetchResults(this.success, this.player, this.states, this.summaries, this.cutoffs, this.exception); } class RecordSummary extends StatelessWidget{ @@ -623,7 +619,19 @@ class LeagueCard extends StatelessWidget{ child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text(showSeasonNumber ? "Season ${league.season}" : "Tetra League", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, height: 0.9)), + if (showSeasonNumber) Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text("Season ${league.season}", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, height: 0.9)), + Spacer(), + Text( + "${seasonStarts.elementAtOrNull(league.season - 1) != null ? timestamp(seasonStarts[league.season - 1]) : "---"} — ${seasonEnds.elementAtOrNull(league.season - 1) != null ? timestamp(seasonEnds[league.season - 1]) : "---"}", + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey)), + ], + ) + else Text("Tetra League", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, height: 0.9)), const Divider(color: Color.fromARGB(50, 158, 158, 158)), TLRatingThingy(userID: "", tlData: league, showPositions: true), const Divider(color: Color.fromARGB(50, 158, 158, 158)), @@ -658,7 +666,7 @@ class _DestinationHomeState extends State { player = await teto.fetchPlayer(widget.searchFor); // Otherwise it's probably a user id or username } }on TetrioPlayerNotExist{ - return FetchResults(false, null, null, null, TetrioPlayerNotExist()); + return FetchResults(false, null, [], null, null, TetrioPlayerNotExist()); } late Summaries summaries; late Cutoffs cutoffs; @@ -666,9 +674,16 @@ class _DestinationHomeState extends State { teto.fetchSummaries(player.userId), teto.fetchCutoffsBeanserver(), ]); + List states = await teto.getStates(player.userId, season: currentSeason); summaries = requests[0]; cutoffs = requests[1]; - return FetchResults(true, player, summaries, cutoffs, null); + + bool isTracking = await teto.isPlayerTracking(player.userId); + if (isTracking){ // if tracked - save data to local DB + await teto.storeState(summaries.league); + } + + return FetchResults(true, player, states, summaries, cutoffs, null); } Widget getOverviewCard(Summaries summaries){ @@ -863,7 +878,8 @@ class _DestinationHomeState extends State { ); } - Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs){ + Widget getTetraLeagueCard(TetraLeague data, Cutoffs? cutoffs, List states){ + TetraLeague? toCompare = states.length >= 2 ? states.elementAtOrNull(states.length-2) : null; return Column( children: [ Card( @@ -876,13 +892,13 @@ class _DestinationHomeState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(t.tetraLeague, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), - //Text("${t.seasonStarts} ${countdown(postSeasonLeft)}", textAlign: TextAlign.center) + //Text("${states.last.timestamp} ${states.last.tr}", textAlign: TextAlign.center) ], ), ), ), ), - TetraLeagueThingy(league: data, cutoffs: cutoffs), + TetraLeagueThingy(league: data, toCompare: toCompare, cutoffs: cutoffs), if (data.nerdStats != null) Card( //surfaceTintColor: rankColors[data.rank], child: Row( @@ -894,7 +910,7 @@ class _DestinationHomeState extends State { ], ), ), - if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!), + if (data.nerdStats != null) NerdStatsThingy(nerdStats: data.nerdStats!, oldNerdStats: toCompare?.nerdStats), if (data.nerdStats != null) GraphsThingy(nerdStats: data.nerdStats!, playstyle: data.playstyle!, apm: data.apm!, pps: data.pps!, vs: data.vs!) ], ); @@ -1581,7 +1597,7 @@ class _DestinationHomeState extends State { child: switch (rightCard){ Cards.overview => getOverviewCard(snapshot.data!.summaries!), Cards.tetraLeague => switch (cardMod){ - CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs), + CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league, snapshot.data!.cutoffs, snapshot.data!.states), CardMod.ex => getPreviousSeasonsList(snapshot.data!.summaries!.pastLeague), CardMod.records => getRecentTLrecords(widget.constraints), _ => const Center(child: Text("huh?")) @@ -2065,12 +2081,10 @@ class BadgesThingy extends StatelessWidget{ icon: Image.asset( "res/tetrio_badges/${badge.badgeId}.png", height: 32, - width: 32, errorBuilder: (context, error, stackTrace) { return Image.network( kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png", height: 32, - width: 32, errorBuilder:(context, error, stackTrace) { return Image.asset("res/icons/kagari.png", height: 32, width: 32); } @@ -2407,9 +2421,10 @@ class _SearchDrawerState extends State { class TetraLeagueThingy extends StatelessWidget{ final TetraLeague league; + final TetraLeague? toCompare; final Cutoffs? cutoffs; - const TetraLeagueThingy({super.key, required this.league, this.cutoffs}); + const TetraLeagueThingy({super.key, required this.league, this.toCompare, this.cutoffs}); @override Widget build(BuildContext context) { @@ -2422,8 +2437,8 @@ class TetraLeagueThingy extends StatelessWidget{ tlData: league, previousRankTRcutoff: cutoffs != null ? cutoffs!.tr[league.rank != "z" ? league.rank : league.percentileRank] : null, nextRankTRcutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.tr[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null, - nextRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null, - previousRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null, + previousRankTRcutoffTarget: league.rank != "z" ? rankTargets[league.rank] : null, + nextRankTRcutoffTarget: (league.rank != "z" && league.rank != "x+") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(league.rank)+1)] : null, previousGlickoCutoff: cutoffs != null ? cutoffs!.glicko[league.rank != "z" ? league.rank : league.percentileRank] : null, nextRankGlickoCutoff: cutoffs != null ? (league.rank != "z" ? league.rank == "x+" : league.percentileRank == "x+") ? 25000 : cutoffs!.glicko[ranks.elementAtOrNull(ranks.indexOf(league.rank != "z" ? league.rank : league.percentileRank)+1)] : null, ), @@ -2438,16 +2453,19 @@ class TetraLeagueThingy extends StatelessWidget{ defaultColumnWidth:const IntrinsicColumnWidth(), children: [ TableRow(children: [ - const Text("APM: ", style: TextStyle(fontSize: 21)), Text(f2.format(league.apm??0.00), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" APM", style: TextStyle(fontSize: 21)), + if (toCompare != null) Text(" (${comparef2.format(league.apm!-toCompare!.apm!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.apm!-toCompare!.apm!))) ]), TableRow(children: [ - const Text("PPS: ", style: TextStyle(fontSize: 21)), Text(f2.format(league.pps??0.00), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" PPS", style: TextStyle(fontSize: 21)), + if (toCompare != null) Text(" (${comparef2.format(league.pps!-toCompare!.pps!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.pps!-toCompare!.pps!))) ]), TableRow(children: [ - const Text("VS: ", style: TextStyle(fontSize: 21)), Text(f2.format(league.vs??0.00), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), + const Text(" VS", style: TextStyle(fontSize: 21)), + if (toCompare != null) Text(" (${comparef2.format(league.vs!-toCompare!.vs!)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.vs!-toCompare!.vs!))) ]) ], ), @@ -2480,6 +2498,10 @@ class TetraLeagueThingy extends StatelessWidget{ GaugeAnnotation(widget: Container(child: Text(t.statCellNum.winrate, textAlign: TextAlign.center)), angle: 270,positionFactor: 0.4 + ), + if (toCompare != null) GaugeAnnotation(widget: Container(child: + Text(comparef2.format((league.winrate-toCompare!.winrate)*100), textAlign: TextAlign.center, style: TextStyle(color: getDifferenceColor(league.winrate-toCompare!.winrate)))), + angle: 90,positionFactor: 0.45 ) ], ) @@ -2495,17 +2517,20 @@ class TetraLeagueThingy extends StatelessWidget{ TableRow(children: [ //Text("VS: ", style: TextStyle(fontSize: 21)), Text("№ ${league.standingLocal.isNegative ? "---" : intf.format(league.standingLocal)}", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: league.standingLocal.isNegative ? Colors.grey : Colors.white)), - Text(" local", style: TextStyle(fontSize: 21, color: league.standingLocal.isNegative ? Colors.grey : Colors.white)) + Text(" local", style: TextStyle(fontSize: 21, color: league.standingLocal.isNegative ? Colors.grey : Colors.white)), + if (toCompare != null) Text(" (${compareIntf.format(league.standingLocal-toCompare!.standingLocal)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: getDifferenceColor(league.standingLocal-toCompare!.standingLocal))) ]), TableRow(children: [ //Text("APM: ", style: TextStyle(fontSize: 21)), Text(intf.format(league.gamesPlayed), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" Games", style: TextStyle(fontSize: 21)) + const Text(" Games", style: TextStyle(fontSize: 21)), + if (toCompare != null) Text(" (${comparef2.format(league.gamesPlayed-toCompare!.gamesPlayed)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)) ]), TableRow(children: [ //Text("PPS: ", style: TextStyle(fontSize: 21)), Text(intf.format(league.gamesWon), textAlign: TextAlign.right, style: const TextStyle(fontSize: 21)), - const Text(" Won", style: TextStyle(fontSize: 21)) + const Text(" Won", style: TextStyle(fontSize: 21)), + if (toCompare != null) Text(" (${comparef2.format(league.gamesWon-toCompare!.gamesWon)})", textAlign: TextAlign.right, style: TextStyle(fontSize: 21, color: Colors.grey)) ]) ], ), @@ -2521,8 +2546,9 @@ class TetraLeagueThingy extends StatelessWidget{ class NerdStatsThingy extends StatelessWidget{ final NerdStats nerdStats; + final NerdStats? oldNerdStats; - const NerdStatsThingy({super.key, required this.nerdStats}); + const NerdStatsThingy({super.key, required this.nerdStats, this.oldNerdStats}); @override Widget build(BuildContext context) { @@ -2530,7 +2556,7 @@ class NerdStatsThingy extends StatelessWidget{ child: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0), + padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -2565,7 +2591,7 @@ class NerdStatsThingy extends StatelessWidget{ children: [ const TextSpan(text: "APP\n"), TextSpan(text: f3.format(nerdStats.app), style: const TextStyle(fontSize: 25, fontFamily: "Eurostile Round Extended", fontWeight: FontWeight.w100)), - //TextSpan(text: "\nAPP"), + if (oldNerdStats != null) TextSpan(text: "\n${comparef.format(nerdStats.app - oldNerdStats!.app)}", style: TextStyle(color: getDifferenceColor(nerdStats.app - oldNerdStats!.app))), ] ))), angle: 270,positionFactor: 0.5 @@ -2595,6 +2621,7 @@ class NerdStatsThingy extends StatelessWidget{ children: [ const TextSpan(text: "VS/APM\n"), TextSpan(text: f3.format(nerdStats.vsapm), style: const TextStyle(fontSize: 25, fontFamily: "Eurostile Round Extended", fontWeight: FontWeight.w100)), + if (oldNerdStats != null) TextSpan(text: "\n${comparef.format(nerdStats.vsapm - oldNerdStats!.vsapm)}", style: TextStyle(color: getDifferenceColor(nerdStats.vsapm - oldNerdStats!.vsapm))), ] ))), angle: 90,positionFactor: 0.5 @@ -2608,15 +2635,17 @@ class NerdStatsThingy extends StatelessWidget{ Expanded( child: Wrap( alignment: WrapAlignment.center, - spacing: 10, + spacing: 10.0, + runSpacing: 10.0, + runAlignment: WrapAlignment.start, children: [ - GaugetThingy(value: nerdStats.dss, min: 0, max: 1.0, tickInterval: .2, label: "DS/S", sideSize: 128.0, fractionDigits: 3), - GaugetThingy(value: nerdStats.dsp, min: 0, max: 1.0, tickInterval: .2, label: "DS/P", sideSize: 128.0, fractionDigits: 3), - GaugetThingy(value: nerdStats.appdsp, min: 0, max: 1.2, tickInterval: .2, label: "APP+DS/P", sideSize: 128.0, fractionDigits: 3), - GaugetThingy(value: nerdStats.cheese, min: -80, max: 80, tickInterval: 40, label: "Cheese", sideSize: 128.0, fractionDigits: 2), - GaugetThingy(value: nerdStats.gbe, min: 0, max: 1.0, tickInterval: .2, label: "GbE", sideSize: 128.0, fractionDigits: 3), - GaugetThingy(value: nerdStats.nyaapp, min: 0, max: 1.2, tickInterval: .2, label: "wAPP", sideSize: 128.0, fractionDigits: 3), - GaugetThingy(value: nerdStats.area, min: 0, max: 1000, tickInterval: 100, label: "Area", sideSize: 128.0, fractionDigits: 1), + GaugetThingy(value: nerdStats.dss, oldValue: oldNerdStats?.dss, min: 0, max: 1.0, tickInterval: .2, label: "DS/S", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true), + GaugetThingy(value: nerdStats.dsp, oldValue: oldNerdStats?.dsp, min: 0, max: 1.0, tickInterval: .2, label: "DS/P", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true), + GaugetThingy(value: nerdStats.appdsp, oldValue: oldNerdStats?.appdsp, min: 0, max: 1.2, tickInterval: .2, label: "APP+DS/P", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true), + GaugetThingy(value: nerdStats.cheese, oldValue: oldNerdStats?.cheese, min: -80, max: 80, tickInterval: 40, label: "Cheese", sideSize: 128.0, fractionDigits: 2, moreIsBetter: false), + GaugetThingy(value: nerdStats.gbe, oldValue: oldNerdStats?.gbe, min: 0, max: 1.0, tickInterval: .2, label: "GbE", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true), + GaugetThingy(value: nerdStats.nyaapp, oldValue: oldNerdStats?.nyaapp, min: 0, max: 1.2, tickInterval: .2, label: "wAPP", sideSize: 128.0, fractionDigits: 3, moreIsBetter: true), + GaugetThingy(value: nerdStats.area, oldValue: oldNerdStats?.area, min: 0, max: 1000, tickInterval: 100, label: "Area", sideSize: 128.0, fractionDigits: 1, moreIsBetter: true), ], ), ) @@ -2667,12 +2696,14 @@ class GaugetThingy extends StatelessWidget{ final double value; final double min; final double max; + final double? oldValue; + final bool moreIsBetter; final double tickInterval; final String label; final double sideSize; final int fractionDigits; - GaugetThingy({super.key, required this.value, required this.min, required this.max, required this.tickInterval, required this.label, required this.sideSize, required this.fractionDigits}); + GaugetThingy({super.key, required this.value, required this.min, required this.max, this.oldValue, required this.tickInterval, required this.label, required this.sideSize, required this.fractionDigits, required this.moreIsBetter}); @override Widget build(BuildContext context) { @@ -2704,6 +2735,10 @@ class GaugetThingy extends StatelessWidget{ GaugeAnnotation(widget: Container(child: Text(label, textAlign: TextAlign.center, style: const TextStyle(height: .9))), angle: 270,positionFactor: 0.4 + ), + if (oldValue != null) GaugeAnnotation(widget: Container(child: + Text(comparef2.format(value-oldValue!), textAlign: TextAlign.center, style: TextStyle(color: getDifferenceColor(moreIsBetter ? value-oldValue! : oldValue!-value)))), + angle: 90,positionFactor: 0.45 ) ], ) diff --git a/lib/widgets/graphs.dart b/lib/widgets/graphs.dart index 2e42f32..35e5153 100644 --- a/lib/widgets/graphs.dart +++ b/lib/widgets/graphs.dart @@ -166,17 +166,17 @@ class MyRadarChartPainter extends RadarChartPainter{ ); } - _ticksTextPaint - ..text = TextSpan( - text: percentage.format(tick), - style: Utils().getThemeAwareTextStyle(context, data.ticksTextStyle), - ) - ..textDirection = TextDirection.ltr - ..layout(maxWidth: size.width); - canvasWrapper.drawText( - _ticksTextPaint, - Offset(centerX + 5, centerY - tickRadius - _ticksTextPaint.height/2), - ); + // _ticksTextPaint + // ..text = TextSpan( + // text: percentage.format(tick), + // style: Utils().getThemeAwareTextStyle(context, data.ticksTextStyle), + // ) + // ..textDirection = TextDirection.ltr + // ..layout(maxWidth: size.width); + // canvasWrapper.drawText( + // _ticksTextPaint, + // Offset(centerX + 5, centerY - tickRadius - _ticksTextPaint.height/2), + // ); }, ); } @@ -302,12 +302,12 @@ class Graphs extends StatelessWidget{ width: 310, child: MyRadarChart( RadarChartData( - radarShape: RadarShape.polygon, + radarShape: RadarShape.circle, tickCount: 4, - ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10), - radarBorderData: const BorderSide(color: Colors.transparent, width: 1), + radarBackgroundColor: Colors.black.withAlpha(170), + radarBorderData: const BorderSide(color: Colors.white24, width: 1), gridBorderData: const BorderSide(color: Colors.white24, width: 1), - tickBorderData: const BorderSide(color: Colors.transparent, width: 1), + tickBorderData: const BorderSide(color: Colors.white24, width: 1), getTitle: (index, angle) { switch (index) { case 0: @@ -336,7 +336,7 @@ class Graphs extends StatelessWidget{ }, dataSets: [ RadarDataSet( - fillColor: Theme.of(context).colorScheme.primary.withAlpha(100), + fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), borderColor: Theme.of(context).colorScheme.primary, dataEntries: [ RadarEntry(value: apm * apmWeight), @@ -381,12 +381,12 @@ class Graphs extends StatelessWidget{ width: 310, child: MyRadarChart( RadarChartData( - radarShape: RadarShape.polygon, + radarShape: RadarShape.circle, tickCount: 4, - ticksTextStyle: const TextStyle(color: Colors.white24, fontSize: 10), - radarBorderData: const BorderSide(color: Colors.transparent, width: 1), + radarBackgroundColor: Colors.black.withAlpha(170), + radarBorderData: const BorderSide(color: Colors.white24, width: 1), gridBorderData: const BorderSide(color: Colors.white24, width: 1), - tickBorderData: const BorderSide(color: Colors.transparent, width: 1), + tickBorderData: const BorderSide(color: Colors.white24, width: 1), titleTextStyle: const TextStyle(height: 1.1), radarTouchData: RadarTouchData(), getTitle: (index, angle) { @@ -405,7 +405,7 @@ class Graphs extends StatelessWidget{ }, dataSets: [ RadarDataSet( - fillColor: Theme.of(context).colorScheme.primary.withAlpha(100), + fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), borderColor: Theme.of(context).colorScheme.primary, dataEntries: [ RadarEntry(value: playstyle.opener), @@ -438,12 +438,12 @@ class Graphs extends StatelessWidget{ width: 310, child: MyRadarChart( RadarChartData( - radarShape: RadarShape.polygon, + radarShape: RadarShape.circle, tickCount: 4, - ticksTextStyle: const TextStyle(color: Colors.white24, fontSize: 10), - radarBorderData: const BorderSide(color: Colors.transparent, width: 1), + radarBackgroundColor: Colors.black.withAlpha(170), + radarBorderData: const BorderSide(color: Colors.white24, width: 1), gridBorderData: const BorderSide(color: Colors.white24, width: 1), - tickBorderData: const BorderSide(color: Colors.transparent, width: 1), + tickBorderData: const BorderSide(color: Colors.white24, width: 1), titleTextStyle: const TextStyle(height: 1.1), radarTouchData: RadarTouchData(), getTitle: (index, angle) { @@ -462,7 +462,7 @@ class Graphs extends StatelessWidget{ }, dataSets: [ RadarDataSet( - fillColor: Theme.of(context).colorScheme.primary.withAlpha(100), + fillColor: Theme.of(context).colorScheme.primary.withAlpha(170), borderColor: Theme.of(context).colorScheme.primary, dataEntries: [ RadarEntry(value: attack),