Experimental new feature

This commit is contained in:
dan63047 2024-03-07 01:34:15 +03:00
parent 0648ca9a5d
commit f95ffb59aa
13 changed files with 439 additions and 255 deletions

View File

@ -1139,6 +1139,73 @@ class News {
}
}
class PlayerLeaderboardPosition{
late LeaderboardPosition apm;
late LeaderboardPosition pps;
late LeaderboardPosition vs;
late LeaderboardPosition gamesPlayed;
late LeaderboardPosition gamesWon;
late LeaderboardPosition winrate;
late LeaderboardPosition app;
late LeaderboardPosition vsapm;
late LeaderboardPosition dss;
late LeaderboardPosition dsp;
late LeaderboardPosition appdsp;
late LeaderboardPosition cheese;
late LeaderboardPosition gbe;
late LeaderboardPosition nyaapp;
late LeaderboardPosition area;
late LeaderboardPosition estTr;
late LeaderboardPosition accOfEst;
PlayerLeaderboardPosition({
required this.apm,
required this.pps,
required this.vs,
required this.gamesPlayed,
required this.gamesWon,
required this.winrate,
required this.app,
required this.vsapm,
required this.dss,
required this.dsp,
required this.appdsp,
required this.cheese,
required this.gbe,
required this.nyaapp,
required this.area,
required this.estTr,
required this.accOfEst
});
PlayerLeaderboardPosition.fromSearchResults(List<LeaderboardPosition> results){
apm = results[0];
pps = results[1];
vs = results[2];
gamesPlayed = results[3];
gamesWon = results[4];
winrate = results[5];
app = results[6];
vsapm = results[7];
dss = results[8];
dsp = results[9];
appdsp = results[10];
cheese = results[11];
gbe = results[12];
nyaapp = results[13];
area = results[14];
estTr = results[15];
accOfEst = results[16];
}
}
class LeaderboardPosition{
int position;
double percentage;
LeaderboardPosition(this.position, this.percentage);
}
class TetrioPlayersLeaderboard {
late String type;
late DateTime timestamp;
@ -1163,6 +1230,20 @@ class TetrioPlayersLeaderboard {
return lb;
}
List<TetrioPlayerFromLeaderboard> getStatRankingSequel(Stats stat){
List<TetrioPlayerFromLeaderboard> lb = List.from(leaderboard);
lb.sort(((a, b) {
if (a.getStatByEnum(stat) > b.getStatByEnum(stat)){
return -1;
}else if (a.getStatByEnum(stat) == b.getStatByEnum(stat)){
return 0;
}else{
return 1;
}
}));
return lb;
}
List<dynamic> getAverageOfRank(String rank){ // i tried to refactor it and that's was terrible
if (rank.isNotEmpty && !rankCutoffs.keys.contains(rank)) throw Exception("Invalid rank");
List<TetrioPlayerFromLeaderboard> filtredLeaderboard = List.from(leaderboard);
@ -1753,6 +1834,19 @@ class TetrioPlayersLeaderboard {
}
}
PlayerLeaderboardPosition? getLeaderboardPosition(String userID) {
if (leaderboard.indexWhere((element) => element.userId == userID) == -1) return null;
List<Stats> stats = [Stats.apm, Stats.pps, Stats.vs, Stats.gp, Stats.gw, Stats.wr,
Stats.app, Stats.vsapm, Stats.dss, Stats.dsp, Stats.appdsp, Stats.cheese, Stats.gbe, Stats.nyaapp, Stats.area, Stats.eTR, Stats.acceTR];
List<LeaderboardPosition> results = [];
for (Stats stat in stats) {
List<TetrioPlayerFromLeaderboard> sortedLeaderboard = getStatRanking(leaderboard, stat, reversed: false);
int position = sortedLeaderboard.indexWhere((element) => element.userId == userID) + 1;
results.add(LeaderboardPosition(position, position / sortedLeaderboard.length));
}
return PlayerLeaderboardPosition.fromSearchResults(results);
}
Map<String, List<dynamic>> get averages => {
'x': getAverageOfRank("x"),
'u': getAverageOfRank("u"),

View File

@ -74,6 +74,7 @@ class TetrioService extends DB {
final Map<String, Map<String, dynamic>> _recordsCache = {};
final Map<String, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
final Map<String, List<News>> _newsCache = {};
final Map<String, Map<String, double?>> _topTRcache = {};
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
@ -142,6 +143,14 @@ class TetrioService extends DB {
db.insert(tetrioTLReplayStatsTable, {idCol: replay.id, "data": jsonEncode(replay.toJson())});
}
void cacheLeaderboardPositions(String userID, PlayerLeaderboardPosition positions){
_lbPositions[userID] = positions;
}
PlayerLeaderboardPosition? getCachedLeaderboardPositions(String userID){
return _lbPositions[userID];
}
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
/// as string and as binary.
@ -504,6 +513,7 @@ class TetrioService extends DB {
switch (response.statusCode) {
case 200:
_lbPositions.clear();
var rawJson = jsonDecode(response.body);
if (rawJson['success']) { // if api confirmed that everything ok
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));

View File

@ -0,0 +1,7 @@
import 'package:intl/intl.dart';
import 'package:tetra_stats/gen/strings.g.dart';
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);

View File

@ -13,7 +13,6 @@ double? vs;
NerdStats? nerdStats;
EstTr? estTr;
Playstyle? playstyle;
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
late String oldWindowTitle;
class CalcView extends StatefulWidget {

View File

@ -31,6 +31,7 @@ import 'package:go_router/go_router.dart';
Future<List> me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up
TetrioPlayersLeaderboard? everyone;
PlayerLeaderboardPosition? meAmongEveryone;
String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for
String _titleNickname = "dan63047";
final TetrioService teto = TetrioService(); // thing, that manadge our local DB
@ -168,8 +169,14 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
news = requests[2] as List<News>;
topTR = requests.elementAtOrNull(3) as double?; // No TR - no Top TR
// Get tetra League leaderboard if needed
// if(prefs.getBool("loadLeaderboard") == true) everyone = await teto.fetchTLLeaderboard();
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
if (meAmongEveryone == null && prefs.getBool("showPositions") == true){
// Get tetra League leaderboard
everyone = teto.getCachedLeaderboard();
everyone ??= await teto.fetchTLLeaderboard();
meAmongEveryone = await compute(everyone!.getLeaderboardPosition, me.userId);
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
}
// Making list of Tetra League matches
List<TetraLeagueAlphaRecord> tlMatches = [];
@ -394,7 +401,15 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
body: TabBarView(
controller: _tabController,
children: [
TLThingy(tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId, states: snapshot.data![2], topTR: snapshot.data![7], bot: snapshot.data![0].role == "bot", guest: snapshot.data![0].role == "anon"),
TLThingy(
tl: snapshot.data![0].tlSeason1,
userID: snapshot.data![0].userId,
states: snapshot.data![2],
topTR: snapshot.data![7],
bot: snapshot.data![0].role == "bot",
guest: snapshot.data![0].role == "anon",
lbPositions: meAmongEveryone
),
_TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]),
_History(states: snapshot.data![2], update: _justUpdate),
_RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank),
@ -958,71 +973,58 @@ class _RecordThingy extends StatelessWidget {
else if (record!.stream.contains("blitz")) Text(t.blitz, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
// show main metric
Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.spaceAround,
crossAxisAlignment: WrapCrossAlignment.center,
clipBehavior: Clip.hardEdge,
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.alphabetic,
children: [
// Show grade based on closest rank average
if (record!.stream.contains("40l")) Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96)
else if (record!.stream.contains("blitz")) Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96),
if (record!.stream.contains("40l")) Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 48)
else if (record!.stream.contains("blitz")) Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 48),
// TODO: I'm not sure abour that element. Maybe, it could be done differenly
Column(
children: [
// Show result
if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
// Show difference between rank average
if (record!.stream.contains("40l") && (rank != null && rank != "z")) Text(
"${readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!)} ${sprintBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: sprintBetterThanRankAverage??false ?
Colors.greenAccent :
Colors.redAccent
)
)
else if (record!.stream.contains("40l") && (rank == null || rank == "z")) Text(
"${readableTimeDifference(record!.endContext!.finalTime, closestAverageSprint.value)} ${sprintBetterThanClosestAverage ? "better" : "worse"} than ${closestAverageSprint.key!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: sprintBetterThanClosestAverage ?
Colors.greenAccent :
Colors.redAccent
)
)
else if (record!.stream.contains("blitz") && (rank != null && rank != "z")) Text(
"${readableIntDifference(record!.endContext!.score, blitzAverages[rank]!)} ${blitzBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: blitzBetterThanRankAverage??false ?
Colors.greenAccent :
Colors.redAccent
)
)
else if (record!.stream.contains("blitz") && (rank == null || rank == "z")) Text(
"${readableIntDifference(record!.endContext!.score, closestAverageBlitz.value)} ${blitzBetterThanClosestAverage ? "better" : "worse"} than ${closestAverageBlitz.key!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: blitzBetterThanClosestAverage ?
Colors.greenAccent :
Colors.redAccent
)
),
],
),
// Show result
if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
],
),
// if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28))
// else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)),
// // Compare with averages
// if (record!.stream.contains("40l") && rank != null) RichText(text: TextSpan(text: "${readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!)} ${sprintBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average", style: TextStyle(fontFamily: "Eurostile Round", color: sprintBetterThanRankAverage??false ? Colors.green : Colors.red)))
// //Text("${record!.endContext!.finalTime - sprintAverages[rank]!}; ${sprintAverages[rank]}; ${get40lTime((record!.endContext!.finalTime - sprintAverages[rank]!).inMicroseconds)}")
// else if (record!.stream.contains("blitz")) Text("${closestAverageBlitz}; ${blitzAverages[rank]}"),
// Show difference between rank average
if (record!.stream.contains("40l") && (rank != null && rank != "z")) Text(
"${readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!)} ${sprintBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: sprintBetterThanRankAverage??false ?
Colors.greenAccent :
Colors.redAccent
)
)
else if (record!.stream.contains("40l") && (rank == null || rank == "z")) Text(
"${readableTimeDifference(record!.endContext!.finalTime, closestAverageSprint.value)} ${sprintBetterThanClosestAverage ? "better" : "worse"} than ${closestAverageSprint.key!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: sprintBetterThanClosestAverage ?
Colors.greenAccent :
Colors.redAccent
)
)
else if (record!.stream.contains("blitz") && (rank != null && rank != "z")) Text(
"${readableIntDifference(record!.endContext!.score, blitzAverages[rank]!)} ${blitzBetterThanRankAverage??false ? "better" : "worse"} than ${rank!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: blitzBetterThanRankAverage??false ?
Colors.greenAccent :
Colors.redAccent
)
)
else if (record!.stream.contains("blitz") && (rank == null || rank == "z")) Text(
"${readableIntDifference(record!.endContext!.score, closestAverageBlitz.value)} ${blitzBetterThanClosestAverage ? "better" : "worse"} than ${closestAverageBlitz.key!.toUpperCase()} rank average",
textAlign: TextAlign.center,
style: TextStyle(
color: blitzBetterThanClosestAverage ?
Colors.greenAccent :
Colors.redAccent
)
),
// Show rank if presented
if (record!.rank != null) StatCellNum(playerStat: record!.rank!, playerStatLabel: "Leaderboard Placement", isScreenBig: bigScreen, higherIsBetter: false),
@ -1054,7 +1056,8 @@ class _RecordThingy extends StatelessWidget {
// List of actions
Padding(padding: const EdgeInsets.fromLTRB(0, 16, 0, 48),
child: SizedBox(width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85,
child: Container(width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85,
constraints: BoxConstraints(maxWidth: 452),
child: Column(crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(

View File

@ -8,7 +8,6 @@ import 'package:tetra_stats/views/tl_match_view.dart';
import 'package:window_manager/window_manager.dart';
final TetrioService teto = TetrioService();
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
late String oldWindowTitle;
class MatchesView extends StatefulWidget {

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/views/rank_averages_view.dart';
import 'package:window_manager/window_manager.dart';
import 'main_view.dart'; // lol
@ -40,7 +40,6 @@ class RanksAverages extends State<RankAveragesView> {
@override
Widget build(BuildContext context) {
final NumberFormat f2 = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode)..maximumFractionDigits = 2;
return Scaffold(
appBar: AppBar(
title: Text(t.rankAveragesViewTitle),

View File

@ -26,7 +26,7 @@ class SettingsState extends State<SettingsView> {
late SharedPreferences prefs;
final TetrioService teto = TetrioService();
String defaultNickname = "Checking...";
late bool loadLeaderboard;
late bool showPositions;
final TextEditingController _playertext = TextEditingController();
@override
@ -47,10 +47,10 @@ class SettingsState extends State<SettingsView> {
Future<void> _getPreferences() async {
prefs = await SharedPreferences.getInstance();
if (prefs.getBool("loadLeaderboard") != null) {
loadLeaderboard = prefs.getBool("loadLeaderboard")!;
if (prefs.getBool("showPositions") != null) {
showPositions = prefs.getBool("showPositions")!;
} else {
loadLeaderboard = false;
showPositions = false;
}
_setDefaultNickname(prefs.getString("player"));
}
@ -266,12 +266,12 @@ class SettingsState extends State<SettingsView> {
onTap: () {
Navigator.pushNamed(context, "/customization");
},),
ListTile(title: Text("Load leaderboard on startup"),
subtitle: Text("That will allow app to show additional stats, like..."),
trailing: Switch(value: loadLeaderboard, onChanged: (bool value){
prefs.setBool("loadLeaderboard", value);
ListTile(title: Text("Show LB position for each stat"),
subtitle: Text("That will impact on app performance..."),
trailing: Switch(value: showPositions, onChanged: (bool value){
prefs.setBool("showPositions", value);
setState(() {
loadLeaderboard = value;
showPositions = value;
});
}),),
const Divider(),

View File

@ -71,7 +71,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: Text('Fetching...'));
return const Center(child: CircularProgressIndicator());
case ConnectionState.done:
final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, _sortBy, reversed: reversed, country: _country);
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle("Tetra Stats: ${t.tlLeaderboard} - ${t.players(n: allPlayers!.length)}");

View File

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/widgets/tl_thingy.dart';
class GaugetNum extends StatelessWidget {
final num playerStat;
final num? oldPlayerStat;
final bool higherIsBetter;
final List<GaugeRange> ranges;
final double minimum;
final double maximum;
final String playerStatLabel;
final String? okText;
final String? alertTitle;
final List<Widget>? alertWidgets;
final LeaderboardPosition? pos;
const GaugetNum(
{super.key,
required this.playerStat,
required this.playerStatLabel,
this.alertWidgets,
this.oldPlayerStat,
required this.higherIsBetter,
required this.minimum,
required this.maximum,
required this.ranges,
this.okText, this.alertTitle, this.pos});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 120,
child: SfRadialGauge(
title: GaugeTitle(text: playerStatLabel),
axes: [RadialAxis(
startAngle: 180,
endAngle: 360,
showLabels: false,
showTicks: false,
radiusFactor: 2.1,
centerY: 0.5,
minimum: minimum,
maximum: maximum,
ranges: ranges,
pointers: [
NeedlePointer(
value: playerStat as double,
enableAnimation: true,
needleLength: 0.9,
needleStartWidth: 2,
needleEndWidth: 15,
knobStyle: const KnobStyle(color: Colors.transparent),
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
],
annotations: [GaugeAnnotation(
widget: TextButton(child: Text(f3.format(playerStat),
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)),
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(alertTitle??playerStatLabel, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView(child: ListBody(children: alertWidgets!)),
actions: <Widget>[
TextButton(
child: Text(okText??t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05),
if (oldPlayerStat != null || pos != null) GaugeAnnotation(
widget: RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
children: [
if (oldPlayerStat != null) TextSpan(text: comparef.format(playerStat - oldPlayerStat!), style: TextStyle(
color: higherIsBetter ?
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
),),
if ((oldTl != null && oldTl!.gamesPlayed > 0) && pos != null) const TextSpan(text: ""),
if (pos != null) TextSpan(text: pos!.position >= 1000 ? "Top ${f2.format(pos!.percentage*100)}%" : "${pos!.position}")
]
),
),
positionFactor: 0.05)],
)],),
);
}
}

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
class TrailingStats extends StatelessWidget{
final double yourAPM;
@ -14,7 +13,6 @@ class TrailingStats extends StatelessWidget{
@override
Widget build(BuildContext context) {
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100);
return Table(
defaultColumnWidth: const IntrinsicColumnWidth(),

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
class StatCellNum extends StatelessWidget {
const StatCellNum(
@ -12,7 +14,7 @@ class StatCellNum extends StatelessWidget {
this.fractionDigits,
this.oldPlayerStat,
required this.higherIsBetter,
this.okText, this.alertTitle});
this.okText, this.alertTitle, this.pos});
final num playerStat;
final num? oldPlayerStat;
@ -23,11 +25,11 @@ class StatCellNum extends StatelessWidget {
final String? alertTitle;
final List<Widget>? alertWidgets;
final int? fractionDigits;
final LeaderboardPosition? pos;
@override
Widget build(BuildContext context) {
NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = fractionDigits ?? 0;
NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
NumberFormat fractionf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0)..maximumIntegerDigits = 0;
num fraction = playerStat.isNegative ? 1 - (playerStat - playerStat.floor()) : playerStat - playerStat.floor();
int integer = playerStat.isNegative ? (playerStat + fraction).toInt() : (playerStat - fraction).toInt();
@ -48,11 +50,20 @@ class StatCellNum extends StatelessWidget {
)
)
),
if (oldPlayerStat != null) Text(comparef.format(playerStat - oldPlayerStat!), style: TextStyle(
color: higherIsBetter ?
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
),),
if (oldPlayerStat != null || pos != null) RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
children: [
if (oldPlayerStat != null) TextSpan(text: comparef.format(playerStat - oldPlayerStat!), style: TextStyle(
color: higherIsBetter ?
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
),),
if (oldPlayerStat != null && pos != null) const TextSpan(text: ""),
if (pos != null) TextSpan(text: pos!.position >= 1000 ? "Top ${f2.format(pos!.percentage*100)}%" : "${pos!.position}")
]
),
),
alertWidgets == null
? Text(
playerStatLabel,

View File

@ -3,13 +3,14 @@ import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/widgets/gauget_num.dart';
import 'package:tetra_stats/widgets/graphs.dart';
import 'package:tetra_stats/widgets/stat_sell_num.dart';
var fDiff = NumberFormat("+#,###.###;-#,###.###");
var intFDiff = NumberFormat("+#,###;-#,###");
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
late RangeValues _currentRangeValues;
TetraLeagueAlpha? oldTl;
late TetraLeagueAlpha currentTl;
@ -23,7 +24,8 @@ class TLThingy extends StatefulWidget {
final bool bot;
final bool guest;
final double? topTR;
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR});
final PlayerLeaderboardPosition? lbPositions;
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR, this.lbPositions});
@override
State<TLThingy> createState() => _TLThingyState();
@ -47,6 +49,8 @@ class _TLThingyState extends State<TLThingy> {
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
NumberFormat fractionfEstTR = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..maximumIntegerDigits = 0;
NumberFormat fractionfEstTRAcc = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3)..maximumIntegerDigits = 0;
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
return LayoutBuilder(builder: (context, constraints) {
bool bigScreen = constraints.maxWidth > 768;
@ -159,13 +163,13 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge,
children: [
if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm),
if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps),
if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs),
if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm, pos: widget.lbPositions?.apm),
if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps, pos: widget.lbPositions?.pps),
if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs, pos: widget.lbPositions?.vs),
if (currentTl.standingLocal > 0) StatCellNum(playerStat: currentTl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal),
StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed),
StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon),
StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null),
StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed, pos: widget.lbPositions?.gamesPlayed),
StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon, pos: widget.lbPositions?.gamesWon),
StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null, pos: widget.lbPositions?.winrate),
],
),
),
@ -176,138 +180,31 @@ class _TLThingyState extends State<TLThingy> {
Padding(
padding: const EdgeInsets.fromLTRB(0, 40, 0, 0),
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.center,
spacing: 35,
crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge,
children: [
SizedBox(
width: 200,
height: 120,
child: SfRadialGauge(
title: GaugeTitle(text: t.statCellNum.app),
axes: [RadialAxis(
startAngle: 180,
endAngle: 360,
showLabels: false,
showTicks: false,
radiusFactor: 2.1,
centerY: 0.5,
minimum: 0,
maximum: 1,
ranges: [
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
],
pointers: [
NeedlePointer(
value: currentTl.nerdStats!.app,
enableAnimation: true,
needleLength: 0.9,
needleStartWidth: 2,
needleEndWidth: 15,
knobStyle: const KnobStyle(color: Colors.transparent),
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
],
annotations: [GaugeAnnotation(
widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.app),
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)),
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(t.statCellNum.app,
style: const TextStyle(
fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView(
child: ListBody(children: [
Text(t.statCellNum.appDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.app}")
]),
),
actions: <Widget>[
TextButton(
child: Text(t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,),
if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle(
color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ?
Colors.redAccent :
Colors.greenAccent
),), positionFactor: 0.05,)],
)],),
),
SizedBox(
width: 200,
height: 120,
child: SfRadialGauge(
title: const GaugeTitle(text: "VS / APM"),
axes: [RadialAxis(
startAngle: 180,
endAngle: 360,
showTicks: false,
showLabels: false,
radiusFactor: 2.1,
centerY: 0.5,
minimum: 1.8,
maximum: 2.4,
ranges: [
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
],
pointers: [
NeedlePointer(
value: currentTl.nerdStats!.vsapm,
enableAnimation: true,
needleLength: 0.9,
needleStartWidth: 2,
needleEndWidth: 15,
knobStyle: const KnobStyle(color: Colors.transparent),
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
],
annotations: [GaugeAnnotation(
widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.vsapm),
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)),
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text("VS / APM",
style: TextStyle(
fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView(
child: ListBody(children: [
Text(t.statCellNum.vsapmDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}")
]),
),
actions: <Widget>[
TextButton(
child: Text(t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05),
if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm), style: TextStyle(
color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ?
Colors.redAccent :
Colors.greenAccent
),), positionFactor: 0.05,)],
)],),
),]),
direction: Axis.horizontal,
alignment: WrapAlignment.center,
spacing: 35,
crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge,
children: [
GaugetNum(playerStat: currentTl.nerdStats!.app, playerStatLabel: t.statCellNum.app, higherIsBetter: true, minimum: 0, maximum: 1, ranges: [
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
], alertWidgets: [
Text(t.statCellNum.appDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.app}")
], oldPlayerStat: oldTl?.nerdStats?.app, pos: widget.lbPositions?.app),
GaugetNum(playerStat: currentTl.nerdStats!.vsapm, playerStatLabel: "VS / APM", higherIsBetter: true, minimum: 1.8, maximum: 2.4, ranges: [
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
], alertWidgets: [
Text(t.statCellNum.vsapmDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}")
], oldPlayerStat: oldTl?.nerdStats?.vsapm, pos: widget.lbPositions?.vsapm)
]),
),
Wrap(
direction: Axis.horizontal,
@ -317,6 +214,7 @@ class _TLThingyState extends State<TLThingy> {
clipBehavior: Clip.hardEdge,
children: [
StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss,
pos: widget.lbPositions?.dss,
alertWidgets: [Text(t.statCellNum.dssDescription),
Text("${t.formula}: (VS / 100) - (APM / 60)"),
Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),],
@ -324,6 +222,7 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.dss,),
StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp,
pos: widget.lbPositions?.dsp,
alertWidgets: [Text(t.statCellNum.dspDescription),
Text("${t.formula}: DS/S / PPS"),
Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),],
@ -331,6 +230,7 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.dsp,),
StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp,
pos: widget.lbPositions?.appdsp,
alertWidgets: [Text(t.statCellNum.appdspDescription),
Text("${t.formula}: APP + DS/P"),
Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),],
@ -338,6 +238,7 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.appdsp,),
StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese,
pos: widget.lbPositions?.cheese,
alertWidgets: [Text(t.statCellNum.cheeseDescription),
Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"),
Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),],
@ -345,6 +246,7 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.cheese,),
StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe,
pos: widget.lbPositions?.gbe,
alertWidgets: [Text(t.statCellNum.gbeDescription),
Text("${t.formula}: APP * DS/P * 2"),
Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),],
@ -352,6 +254,7 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.gbe,),
StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp,
pos: widget.lbPositions?.nyaapp,
alertWidgets: [Text(t.statCellNum.nyaappDescription),
Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"),
Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),],
@ -359,6 +262,7 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.nyaapp,),
StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area,
pos: widget.lbPositions?.area,
alertWidgets: [Text(t.statCellNum.areaDescription),
Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"),
Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),],
@ -370,42 +274,104 @@ class _TLThingyState extends State<TLThingy> {
),
if (currentTl.estTr != null)
Padding(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 48),
child: SizedBox(
padding: const EdgeInsets.fromLTRB(0, 48, 0, 48),
child: Container(
//alignment: Alignment.center,
width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
constraints: BoxConstraints(maxWidth: 768),
child: Wrap(
alignment: WrapAlignment.spaceBetween,
spacing: 20,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:",
style: const TextStyle(fontSize: 24),
Text(t.statCellNum.estOfTR, style: TextStyle(height: 0.1),),
RichText(
text: TextSpan(
text: intf.format(currentTl.estTr!.esttr.truncate()),
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500),
children: [TextSpan(text: fractionfEstTR.format(currentTl.estTr!.esttr - currentTl.estTr!.esttr.truncate()).substring(1), style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
),
),
Text(
f2.format(currentTl.estTr!.esttr),
style: const TextStyle(fontSize: 24),
if (oldTl?.estTr?.esttr != null || widget.lbPositions != null) RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5),
children: [
if (oldTl?.estTr?.esttr != null) TextSpan(text: comparef.format(currentTl.estTr!.esttr - oldTl!.estTr!.esttr), style: TextStyle(
color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent
),),
if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: ""),
if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr.position >= 1000 ? "Top ${f2.format(widget.lbPositions!.estTr.percentage*100)}%" : "${widget.lbPositions!.estTr.position}")
]
),
),
],
),
if (currentTl.rating >= 0)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
],),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(t.statCellNum.accOfEst, style: const TextStyle(height: 0.1),),
RichText(
text: TextSpan(
text: (currentTl.esttracc != null) ? intFDiff.format(currentTl.esttracc!.truncate()) : "-",
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500),
children: [
TextSpan(text: (currentTl.esttracc != null) ? fractionfEstTRAcc.format(currentTl.esttracc!.isNegative ? 1 - (currentTl.esttracc! - currentTl.esttracc!.truncate()) : (currentTl.esttracc! - currentTl.esttracc!.truncate())).substring(1) : ".---", style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))
]
),
),
if (oldTl?.esttracc != null || widget.lbPositions != null) RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5),
children: [
Text(
"${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:",
style: const TextStyle(fontSize: 24),
),
Text(
fDiff.format(currentTl.esttracc!),
style: const TextStyle(fontSize: 24),
),
],
if (oldTl?.esttracc != null) TextSpan(text: comparef.format(currentTl.esttracc! - oldTl!.esttracc!), style: TextStyle(
color: oldTl!.esttracc! > currentTl.esttracc! ? Colors.redAccent : Colors.greenAccent
),),
if (oldTl?.esttracc != null && widget.lbPositions?.accOfEst != null) const TextSpan(text: ""),
if (widget.lbPositions?.accOfEst != null) TextSpan(text: widget.lbPositions!.accOfEst.position >= 1000 ? "Top ${f2.format(widget.lbPositions!.accOfEst.percentage*100)}%" : "${widget.lbPositions!.accOfEst.position}")
]
),
),
],)
],
),
),
)
// child: Container(
// width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85,
// constraints: BoxConstraints(maxWidth: 452),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// "${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:",
// style: const TextStyle(fontSize: 24),
// ),
// Text(
// f2.format(currentTl.estTr!.esttr),
// style: const TextStyle(fontSize: 24),
// ),
// ],
// ),
// if (currentTl.rating >= 0)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// "${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:",
// style: const TextStyle(fontSize: 24),
// ),
// Text(
// fDiff.format(currentTl.esttracc!),
// style: const TextStyle(fontSize: 24),
// ),
// ],
// ),
// ],
// ),
// ),
),
if (currentTl.nerdStats != null) Graphs(currentTl.apm!, currentTl.pps!, currentTl.vs!, currentTl.nerdStats!, currentTl.playstyle!)
]