|
@ -0,0 +1,147 @@
|
||||||
|
import 'dart:math';
|
||||||
|
// I reimplemented kenany/glicko2-lite in dart lol
|
||||||
|
// Don't look here lol
|
||||||
|
|
||||||
|
List<double> scale(double rating, double rd, double options) {
|
||||||
|
double mu = (rating - options) / 173.7178;
|
||||||
|
double phi = rd / 173.7178;
|
||||||
|
return [ mu, phi ];
|
||||||
|
}
|
||||||
|
|
||||||
|
double g(phi) {
|
||||||
|
return 1 / sqrt(1 + 3 * pow(phi, 2) / pow(pi, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
double e(double mu, double muj, double phij) {
|
||||||
|
return 1 / (1 + exp(-g(phij) * (mu - muj)));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, double>> scaleOpponents(double mu, List<List<double>> opponents, double rating) {
|
||||||
|
return opponents.map((opp) {
|
||||||
|
var scaled = scale(opp[0], opp[1], rating);
|
||||||
|
return {
|
||||||
|
"muj": scaled[0],
|
||||||
|
"phij": scaled[1],
|
||||||
|
"gphij": g(scaled[1]),
|
||||||
|
"emmp": e(mu, scaled[0], scaled[1]),
|
||||||
|
"score": opp[2]
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
double updateRating(List<Map<String, double>> opponents) {
|
||||||
|
double value = pow(opponents.first["gphij"]!, 2) * opponents.first["emmp"]! * (1 - opponents.first["emmp"]!);
|
||||||
|
opponents.skip(1).forEach((element) {
|
||||||
|
value += pow(element["gphij"]!, 2) * element["emmp"]! * (1 - element["emmp"]!);
|
||||||
|
});
|
||||||
|
return 1 / value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double computeDelta(v, List<Map<String, double>> opponents) {
|
||||||
|
double value = opponents.first["gphij"]! * (opponents.first["score"]! - opponents.first["emmp"]!);
|
||||||
|
opponents.skip(1).forEach((element) {
|
||||||
|
value += opponents.first["gphij"]! * (opponents.first["score"]! - opponents.first["emmp"]!);
|
||||||
|
});
|
||||||
|
return v * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function volF(double phi, double v, double delta, double a, double tau) {
|
||||||
|
num phi2 = pow(phi, 2);
|
||||||
|
num d2 = pow(delta, 2);
|
||||||
|
|
||||||
|
return (x) {
|
||||||
|
double ex = exp(x);
|
||||||
|
double a2 = phi2 + v + ex;
|
||||||
|
double p2 = (x - a) / pow(tau, 2);
|
||||||
|
double p1 = (ex * (d2 - phi2 - v - ex)) / (2 * pow(a2, 2));
|
||||||
|
return p1 - p2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
double computeVolatility(double sigma, double phi, double v, double delta, double options) {
|
||||||
|
// 5.1
|
||||||
|
double a = log(pow(sigma, 2));
|
||||||
|
Function f = volF(phi, v, delta, a, options);
|
||||||
|
|
||||||
|
// 5.2
|
||||||
|
double b;
|
||||||
|
if (pow(delta, 2) > pow(phi, 2) + v) {
|
||||||
|
b = log(pow(delta, 2) - pow(phi, 2) - v);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
double k = 1;
|
||||||
|
while (f(a - k * options) < 0) {
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
b = a - k * options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.3
|
||||||
|
double fa = f(a);
|
||||||
|
double fb = f(b);
|
||||||
|
|
||||||
|
// 5.4
|
||||||
|
while ((b - a).abs() > 0.000001) {
|
||||||
|
double c = a + (a - b) * fa / (fb - fa);
|
||||||
|
double fc = f(c);
|
||||||
|
|
||||||
|
if (fc * fb <= 0) {
|
||||||
|
a = b;
|
||||||
|
fa = fb;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fa /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
b = c;
|
||||||
|
fb = fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.5
|
||||||
|
return exp(a / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
double phiStar(sigmap, phi) {
|
||||||
|
return sqrt(pow(sigmap, 2) + pow(phi, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, double> newRating(phis, mu, v, opponents) {
|
||||||
|
double phip = 1 / sqrt(1 / pow(phis, 2) + 1 / v);
|
||||||
|
double value = opponents.first["gphij"]! * (opponents.first["score"]! - opponents.first["emmp"]!);
|
||||||
|
opponents.skip(1).forEach((element) {
|
||||||
|
value += element["gphij"]! * (element["score"]! - element["emmp"]!);
|
||||||
|
});
|
||||||
|
double mup = mu + pow(phip, 2) * value;
|
||||||
|
return { "mu": mup, "phi": phip };
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> unscale(mup, phip, options) {
|
||||||
|
double rating = 173.7178 * mup + options["rating"];
|
||||||
|
double rd = 173.7178 * phip;
|
||||||
|
return [ rating, rd ];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> rate(double rating, double rd, double sigma, List<List<double>> opponents, Map<String, double> options) {
|
||||||
|
Map<String, double> opts = { "rating": options["rating"]??1500, "tau": options["tau"]??0.5 };
|
||||||
|
|
||||||
|
// Step 2
|
||||||
|
List<double> scaled = scale(rating, rd, opts["rating"]!);
|
||||||
|
List<Map<String, double>> scaledOpponents = scaleOpponents(scaled[0], opponents, opts["rating"]!);
|
||||||
|
|
||||||
|
// Step 3
|
||||||
|
double v = updateRating(scaledOpponents);
|
||||||
|
|
||||||
|
// Step 4
|
||||||
|
double delta = computeDelta(v, scaledOpponents);
|
||||||
|
|
||||||
|
// Step 5
|
||||||
|
double sigmap = computeVolatility(sigma, scaled[1], v, delta, opts["tau"]!);
|
||||||
|
|
||||||
|
// Step 6
|
||||||
|
double phis = phiStar(sigmap, scaled[1]);
|
||||||
|
|
||||||
|
// Step 7
|
||||||
|
Map<String, double> updated = newRating(phis, scaled[0], v, scaledOpponents);
|
||||||
|
|
||||||
|
return unscale(updated['mu'], updated['phi'], opts)..add(sigmap);
|
||||||
|
}
|
|
@ -17,6 +17,9 @@ const double appdspWeight = 140;
|
||||||
const double vsapmWeight = 60;
|
const double vsapmWeight = 60;
|
||||||
const double cheeseWeight = 1.25;
|
const double cheeseWeight = 1.25;
|
||||||
const double gbeWeight = 315;
|
const double gbeWeight = 315;
|
||||||
|
const List<String> ranks = [
|
||||||
|
"d", "d+", "c-", "c", "c+", "b-", "b", "b+", "a-", "a", "a+", "s-", "s", "s+", "ss", "u", "x"
|
||||||
|
];
|
||||||
const Map<String, double> rankCutoffs = {
|
const Map<String, double> rankCutoffs = {
|
||||||
"x": 0.01,
|
"x": 0.01,
|
||||||
"u": 0.05,
|
"u": 0.05,
|
||||||
|
@ -39,7 +42,7 @@ const Map<String, double> rankCutoffs = {
|
||||||
"": 0.5
|
"": 0.5
|
||||||
};
|
};
|
||||||
const Map<String, double> rankTargets = {
|
const Map<String, double> rankTargets = {
|
||||||
"x": 24008,
|
"x": 24503.75, // where that comes from?
|
||||||
"u": 23038,
|
"u": 23038,
|
||||||
"ss": 21583,
|
"ss": 21583,
|
||||||
"s+": 20128,
|
"s+": 20128,
|
||||||
|
@ -138,45 +141,87 @@ const Map<String, Color> rankColors = { // thanks osk for const rankColors at ht
|
||||||
'z': Color(0xFF375433)
|
'z': Color(0xFF375433)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Map<String, Duration> sprintAverages = { // based on https://discord.com/channels/673303546107658242/917098364787650590/1214231970259673098
|
// const Map<String, Duration> sprintAverages = { // old data, based on https://discord.com/channels/673303546107658242/917098364787650590/1214231970259673098
|
||||||
'x': Duration(seconds: 25, milliseconds: 413),
|
// 'x': Duration(seconds: 25, milliseconds: 413),
|
||||||
'u': Duration(seconds: 34, milliseconds: 549),
|
// 'u': Duration(seconds: 34, milliseconds: 549),
|
||||||
'ss': Duration(seconds: 43, milliseconds: 373),
|
// 'ss': Duration(seconds: 43, milliseconds: 373),
|
||||||
's+': Duration(seconds: 54, milliseconds: 027),
|
// 's+': Duration(seconds: 54, milliseconds: 027),
|
||||||
's': Duration(seconds: 60, milliseconds: 412),
|
// 's': Duration(seconds: 60, milliseconds: 412),
|
||||||
's-': Duration(seconds: 67, milliseconds: 381),
|
// 's-': Duration(seconds: 67, milliseconds: 381),
|
||||||
'a+': Duration(seconds: 73, milliseconds: 694),
|
// 'a+': Duration(seconds: 73, milliseconds: 694),
|
||||||
'a': Duration(seconds: 81, milliseconds: 166),
|
// 'a': Duration(seconds: 81, milliseconds: 166),
|
||||||
'a-': Duration(seconds: 88, milliseconds: 334),
|
// 'a-': Duration(seconds: 88, milliseconds: 334),
|
||||||
'b+': Duration(seconds: 93, milliseconds: 741),
|
// 'b+': Duration(seconds: 93, milliseconds: 741),
|
||||||
'b': Duration(seconds: 98, milliseconds: 354),
|
// 'b': Duration(seconds: 98, milliseconds: 354),
|
||||||
'b-': Duration(seconds: 109, milliseconds: 610),
|
// 'b-': Duration(seconds: 109, milliseconds: 610),
|
||||||
'c+': Duration(seconds: 124, milliseconds: 641),
|
// 'c+': Duration(seconds: 124, milliseconds: 641),
|
||||||
'c': Duration(seconds: 126, milliseconds: 104),
|
// 'c': Duration(seconds: 126, milliseconds: 104),
|
||||||
'c-': Duration(seconds: 145, milliseconds: 865),
|
// 'c-': Duration(seconds: 145, milliseconds: 865),
|
||||||
'd+': Duration(seconds: 154, milliseconds: 338),
|
// 'd+': Duration(seconds: 154, milliseconds: 338),
|
||||||
'd': Duration(seconds: 162, milliseconds: 063),
|
// 'd': Duration(seconds: 162, milliseconds: 063),
|
||||||
|
// //'z': Duration(seconds: 66, milliseconds: 802)
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const Map<String, int> blitzAverages = {
|
||||||
|
// 'x': 626494,
|
||||||
|
// 'u': 406059,
|
||||||
|
// 'ss': 243166,
|
||||||
|
// 's+': 168636,
|
||||||
|
// 's': 121594,
|
||||||
|
// 's-': 107845,
|
||||||
|
// 'a+': 87142,
|
||||||
|
// 'a': 73413,
|
||||||
|
// 'a-': 60799,
|
||||||
|
// 'b+': 55417,
|
||||||
|
// 'b': 47608,
|
||||||
|
// 'b-': 40534,
|
||||||
|
// 'c+': 34200,
|
||||||
|
// 'c': 32535,
|
||||||
|
// 'c-': 25808,
|
||||||
|
// 'd+': 23345,
|
||||||
|
// 'd': 23063,
|
||||||
|
// //'z': 72084
|
||||||
|
// };
|
||||||
|
|
||||||
|
const Map<String, Duration> 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)
|
//'z': Duration(seconds: 66, milliseconds: 802)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Map<String, int> blitzAverages = {
|
const Map<String, int> blitzAverages = {
|
||||||
'x': 626494,
|
'x': 600715,
|
||||||
'u': 406059,
|
'u': 379418,
|
||||||
'ss': 243166,
|
'ss': 233740,
|
||||||
's+': 168636,
|
's+': 158295,
|
||||||
's': 121594,
|
's': 125164,
|
||||||
's-': 107845,
|
's-': 100933,
|
||||||
'a+': 87142,
|
'a+': 83593,
|
||||||
'a': 73413,
|
'a': 68613,
|
||||||
'a-': 60799,
|
'a-': 60219,
|
||||||
'b+': 55417,
|
'b+': 51197,
|
||||||
'b': 47608,
|
'b': 44171,
|
||||||
'b-': 40534,
|
'b-': 39045,
|
||||||
'c+': 34200,
|
'c+': 34130,
|
||||||
'c': 32535,
|
'c': 28931,
|
||||||
'c-': 25808,
|
'c-': 25095,
|
||||||
'd+': 23345,
|
'd+': 22944,
|
||||||
'd': 23063,
|
'd': 20728,
|
||||||
//'z': 72084
|
//'z': 72084
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -753,16 +798,15 @@ class EstTr {
|
||||||
srarea = (_apm * 0) + (_pps * 135) + (_vs * 0) + (_app * 290) + (_dss * 0) + (_dsp * 700) + (_gbe * 0);
|
srarea = (_apm * 0) + (_pps * 135) + (_vs * 0) + (_app * 290) + (_dss * 0) + (_dsp * 700) + (_gbe * 0);
|
||||||
statrank = 11.2 * atan((srarea - 93) / 130) + 1;
|
statrank = 11.2 * atan((srarea - 93) / 130) + 1;
|
||||||
if (statrank <= 0) statrank = 0.001;
|
if (statrank <= 0) statrank = 0.001;
|
||||||
estglicko = (4.0867 * srarea + 186.68);
|
//estglicko = (4.0867 * srarea + 186.68);
|
||||||
double ntemp = _pps*(150+(((_vs/_apm) - 1.66)*35))+_app*290+_dsp*700;
|
double ntemp = _pps*(150+(((_vs/_apm) - 1.66)*35))+_app*290+_dsp*700;
|
||||||
|
estglicko = 0.000013*pow(ntemp, 3) - 0.0196 *pow(ntemp, 2) + (12.645*ntemp)-1005.4;
|
||||||
esttr = 25000 /
|
esttr = 25000 /
|
||||||
(
|
(
|
||||||
1 + pow(10, (
|
1 + pow(10, (
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
1500-(
|
1500-estglicko
|
||||||
0.000013*pow(ntemp, 3) - 0.0196 *pow(ntemp, 2) + (12.645*ntemp)-1005.4
|
|
||||||
)
|
|
||||||
)*pi
|
)*pi
|
||||||
)/sqrt(
|
)/sqrt(
|
||||||
(
|
(
|
||||||
|
@ -1246,6 +1290,8 @@ class TetrioPlayersLeaderboard {
|
||||||
lb.removeWhere((element) => element.country != country);
|
lb.removeWhere((element) => element.country != country);
|
||||||
}
|
}
|
||||||
lb.sort(((a, b) {
|
lb.sort(((a, b) {
|
||||||
|
if (a.getStatByEnum(stat).isNaN) return 1;
|
||||||
|
if (b.getStatByEnum(stat).isNaN) return -1;
|
||||||
if (a.getStatByEnum(stat) > b.getStatByEnum(stat)){
|
if (a.getStatByEnum(stat) > b.getStatByEnum(stat)){
|
||||||
return reversed ? 1 : -1;
|
return reversed ? 1 : -1;
|
||||||
}else if (a.getStatByEnum(stat) == b.getStatByEnum(stat)){
|
}else if (a.getStatByEnum(stat) == b.getStatByEnum(stat)){
|
||||||
|
@ -1257,20 +1303,6 @@ class TetrioPlayersLeaderboard {
|
||||||
return lb;
|
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
|
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");
|
if (rank.isNotEmpty && !rankCutoffs.keys.contains(rank)) throw Exception("Invalid rank");
|
||||||
List<TetrioPlayerFromLeaderboard> filtredLeaderboard = List.from(leaderboard);
|
List<TetrioPlayerFromLeaderboard> filtredLeaderboard = List.from(leaderboard);
|
||||||
|
@ -1853,11 +1885,12 @@ class TetrioPlayersLeaderboard {
|
||||||
"avgStride": avgStride,
|
"avgStride": avgStride,
|
||||||
"avgInfDS": avgInfDS,
|
"avgInfDS": avgInfDS,
|
||||||
"toEnterTR": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating : lowestTR,
|
"toEnterTR": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].rating : lowestTR,
|
||||||
|
"toEnterGlicko": rank.toLowerCase() != "z" ? leaderboard[(leaderboard.length * rankCutoffs[rank]!).floor()-1].glicko : 0,
|
||||||
"entries": filtredLeaderboard
|
"entries": filtredLeaderboard
|
||||||
}];
|
}];
|
||||||
}else{
|
}else{
|
||||||
return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: 0, pps: 0, vs: 0, glicko: 0, rd: noTrRd, gamesPlayed: 0, gamesWon: 0, bestRank: rank, decaying: false, rating: 0, rank: rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
|
return [TetraLeagueAlpha(timestamp: DateTime.now(), apm: 0, pps: 0, vs: 0, glicko: 0, rd: noTrRd, gamesPlayed: 0, gamesWon: 0, bestRank: rank, decaying: false, rating: 0, rank: rank, percentileRank: rank, percentile: rankCutoffs[rank]!, standing: -1, standingLocal: -1, nextAt: -1, prevAt: -1),
|
||||||
{"players": filtredLeaderboard.length, "lowestTR": 0, "toEnterTR": 0}];
|
{"players": filtredLeaderboard.length, "lowestTR": 0, "toEnterTR": 0, "toEnterGlicko": 0}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1926,6 +1959,26 @@ class TetrioPlayersLeaderboard {
|
||||||
'd': getAverageOfRank("d")[1]["toEnterTR"]
|
'd': getAverageOfRank("d")[1]["toEnterTR"]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Map<String, double> get cutoffsGlicko => {
|
||||||
|
'x': getAverageOfRank("x")[1]["toEnterGlicko"],
|
||||||
|
'u': getAverageOfRank("u")[1]["toEnterGlicko"],
|
||||||
|
'ss': getAverageOfRank("ss")[1]["toEnterGlicko"],
|
||||||
|
's+': getAverageOfRank("s+")[1]["toEnterGlicko"],
|
||||||
|
's': getAverageOfRank("s")[1]["toEnterGlicko"],
|
||||||
|
's-': getAverageOfRank("s-")[1]["toEnterGlicko"],
|
||||||
|
'a+': getAverageOfRank("a+")[1]["toEnterGlicko"],
|
||||||
|
'a': getAverageOfRank("a")[1]["toEnterGlicko"],
|
||||||
|
'a-': getAverageOfRank("a-")[1]["toEnterGlicko"],
|
||||||
|
'b+': getAverageOfRank("b+")[1]["toEnterGlicko"],
|
||||||
|
'b': getAverageOfRank("b")[1]["toEnterGlicko"],
|
||||||
|
'b-': getAverageOfRank("b-")[1]["toEnterGlicko"],
|
||||||
|
'c+': getAverageOfRank("c+")[1]["toEnterGlicko"],
|
||||||
|
'c': getAverageOfRank("c")[1]["toEnterGlicko"],
|
||||||
|
'c-': getAverageOfRank("c-")[1]["toEnterGlicko"],
|
||||||
|
'd+': getAverageOfRank("d+")[1]["toEnterGlicko"],
|
||||||
|
'd': getAverageOfRank("d")[1]["toEnterGlicko"]
|
||||||
|
};
|
||||||
|
|
||||||
TetrioPlayersLeaderboard.fromJson(List<dynamic> json, String t, DateTime ts) {
|
TetrioPlayersLeaderboard.fromJson(List<dynamic> json, String t, DateTime ts) {
|
||||||
type = t;
|
type = t;
|
||||||
timestamp = ts;
|
timestamp = ts;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
|
||||||
|
|
||||||
import 'tetrio.dart';
|
import 'tetrio.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 2
|
/// Locales: 2
|
||||||
/// Strings: 1120 (560 per locale)
|
/// Strings: 1144 (572 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2024-03-24 at 14:28 UTC
|
/// Built on 2024-05-28 at 20:38 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
@ -216,7 +216,12 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String verdictGeneral({required Object n, required Object verdict, required Object rank}) => '${n} ${verdict} than ${rank} rank average';
|
String verdictGeneral({required Object n, required Object verdict, required Object rank}) => '${n} ${verdict} than ${rank} rank average';
|
||||||
String get verdictBetter => 'better';
|
String get verdictBetter => 'better';
|
||||||
String get verdictWorse => 'worse';
|
String get verdictWorse => 'worse';
|
||||||
|
String get smooth => 'Smooth';
|
||||||
String gamesUntilRanked({required Object left}) => '${left} games until being ranked';
|
String gamesUntilRanked({required Object left}) => '${left} games until being ranked';
|
||||||
|
String numOfVictories({required Object wins}) => '~${wins} victories';
|
||||||
|
String get promotionOnNextWin => 'Promotion on next win';
|
||||||
|
String numOfdefeats({required Object losses}) => '~${losses} defeats';
|
||||||
|
String get demotionOnNextLoss => 'Demotion on next loss';
|
||||||
String get nerdStats => 'Nerd Stats';
|
String get nerdStats => 'Nerd Stats';
|
||||||
String get playersYouTrack => 'Players you track';
|
String get playersYouTrack => 'Players you track';
|
||||||
String get formula => 'Formula';
|
String get formula => 'Formula';
|
||||||
|
@ -298,6 +303,9 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String get calc => 'Calc';
|
String get calc => 'Calc';
|
||||||
String get calcViewNoValues => 'Enter values to calculate the stats';
|
String get calcViewNoValues => 'Enter values to calculate the stats';
|
||||||
String get rankAveragesViewTitle => 'Ranks cutoff and average stats';
|
String get rankAveragesViewTitle => 'Ranks cutoff and average stats';
|
||||||
|
String get sprintAndBlitsViewTitle => '40 lines and Blitz averages';
|
||||||
|
String sprintAndBlitsRelevance({required Object date}) => 'Relevance: ${date}';
|
||||||
|
String get rank => 'Rank';
|
||||||
String get averages => 'Averages';
|
String get averages => 'Averages';
|
||||||
String get lbViewZeroEntrys => 'Empty list';
|
String get lbViewZeroEntrys => 'Empty list';
|
||||||
String get lbViewOneEntry => 'There is only one player';
|
String get lbViewOneEntry => 'There is only one player';
|
||||||
|
@ -335,6 +343,7 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
|
||||||
String currentAxis({required Object axis}) => '${axis} axis:';
|
String currentAxis({required Object axis}) => '${axis} axis:';
|
||||||
String get p1nkl0bst3rAlert => 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
String get p1nkl0bst3rAlert => 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
||||||
String get notForWeb => 'Function is not available for web version';
|
String get notForWeb => 'Function is not available for web version';
|
||||||
|
late final _StringsGraphsEn graphs = _StringsGraphsEn._(_root);
|
||||||
late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root);
|
late final _StringsStatCellNumEn statCellNum = _StringsStatCellNumEn._(_root);
|
||||||
Map<String, String> get playerRole => {
|
Map<String, String> get playerRole => {
|
||||||
'user': 'User',
|
'user': 'User',
|
||||||
|
@ -634,6 +643,19 @@ class _StringsNewsPartsEn {
|
||||||
String unknownNews({required Object type}) => 'Unknown news of type ${type}';
|
String unknownNews({required Object type}) => 'Unknown news of type ${type}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: graphs
|
||||||
|
class _StringsGraphsEn {
|
||||||
|
_StringsGraphsEn._(this._root);
|
||||||
|
|
||||||
|
final Translations _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
String get attack => 'Attack';
|
||||||
|
String get speed => 'Speed';
|
||||||
|
String get defense => 'Defense';
|
||||||
|
String get cheese => 'Cheese';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: statCellNum
|
// Path: statCellNum
|
||||||
class _StringsStatCellNumEn {
|
class _StringsStatCellNumEn {
|
||||||
_StringsStatCellNumEn._(this._root);
|
_StringsStatCellNumEn._(this._root);
|
||||||
|
@ -690,7 +712,7 @@ class _StringsStatCellNumEn {
|
||||||
String get nyaappDescription => '(Abbreviated as wAPP) Essentially, a measure of your ability to send cheese while still maintaining a high APP.\nInvented by Wertj.';
|
String get nyaappDescription => '(Abbreviated as wAPP) Essentially, a measure of your ability to send cheese while still maintaining a high APP.\nInvented by Wertj.';
|
||||||
String get area => 'Area';
|
String get area => 'Area';
|
||||||
String get areaDescription => 'How much space your shape takes up on the graph, if you exclude the cheese and vs/apm sections';
|
String get areaDescription => 'How much space your shape takes up on the graph, if you exclude the cheese and vs/apm sections';
|
||||||
String get estOfTR => 'Est. of TR';
|
String get estOfTR => 'Estimated TR';
|
||||||
String get estOfTRShort => 'Est. TR';
|
String get estOfTRShort => 'Est. TR';
|
||||||
String get accOfEst => 'Accuracy';
|
String get accOfEst => 'Accuracy';
|
||||||
String get accOfEstShort => 'Acc.';
|
String get accOfEstShort => 'Acc.';
|
||||||
|
@ -763,16 +785,16 @@ class _StringsErrorsEn {
|
||||||
String forbiddenSub({required Object nickname}) => 'If you are using VPN or Proxy, turn it off. If this does not help, reach out to ${nickname}';
|
String forbiddenSub({required Object nickname}) => 'If you are using VPN or Proxy, turn it off. If this does not help, reach out to ${nickname}';
|
||||||
String get tooManyRequests => 'You have been rate limited.';
|
String get tooManyRequests => 'You have been rate limited.';
|
||||||
String get tooManyRequestsSub => 'Wait a few moments and try again';
|
String get tooManyRequestsSub => 'Wait a few moments and try again';
|
||||||
String get internal => 'Something happend on the tetr.io side';
|
String get internal => 'Something happened on the tetr.io side';
|
||||||
String get internalSub => 'osk, probably, already aware about it';
|
String get internalSub => 'osk, probably, already aware about it';
|
||||||
String get internalWebVersion => 'Something happend on the tetr.io side (or on oskware_bridge, idk honestly)';
|
String get internalWebVersion => 'Something happened on the tetr.io side (or on oskware_bridge, idk honestly)';
|
||||||
String get internalWebVersionSub => 'If osk status page says that everything is ok, let dan63047 know about this issue';
|
String get internalWebVersionSub => 'If osk status page says that everything is ok, let dan63047 know about this issue';
|
||||||
String get oskwareBridge => 'Something happend with oskware_bridge';
|
String get oskwareBridge => 'Something happened with oskware_bridge';
|
||||||
String get oskwareBridgeSub => 'Let dan63047 know';
|
String get oskwareBridgeSub => 'Let dan63047 know';
|
||||||
String get p1nkl0bst3rForbidden => 'Third party API blocked your IP address';
|
String get p1nkl0bst3rForbidden => 'Third party API blocked your IP address';
|
||||||
String get p1nkl0bst3rTooManyRequests => 'Too many requests to third party API. Try again later';
|
String get p1nkl0bst3rTooManyRequests => 'Too many requests to third party API. Try again later';
|
||||||
String get p1nkl0bst3rinternal => 'Something happend on the p1nkl0bst3r side';
|
String get p1nkl0bst3rinternal => 'Something happened on the p1nkl0bst3r side';
|
||||||
String get p1nkl0bst3rinternalWebVersion => 'Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
|
String get p1nkl0bst3rinternalWebVersion => 'Something happened on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
|
||||||
String get replayAlreadySaved => 'Replay already saved';
|
String get replayAlreadySaved => 'Replay already saved';
|
||||||
String get replayExpired => 'Replay expired and not available anymore';
|
String get replayExpired => 'Replay expired and not available anymore';
|
||||||
String get replayRejected => 'Third party API blocked your IP address';
|
String get replayRejected => 'Third party API blocked your IP address';
|
||||||
|
@ -870,7 +892,12 @@ class _StringsRu implements Translations {
|
||||||
@override String verdictGeneral({required Object verdict, required Object rank, required Object n}) => '${verdict} среднего ${rank} ранга на ${n}';
|
@override String verdictGeneral({required Object verdict, required Object rank, required Object n}) => '${verdict} среднего ${rank} ранга на ${n}';
|
||||||
@override String get verdictBetter => 'Лучше';
|
@override String get verdictBetter => 'Лучше';
|
||||||
@override String get verdictWorse => 'Хуже';
|
@override String get verdictWorse => 'Хуже';
|
||||||
|
@override String get smooth => 'Гладкий';
|
||||||
@override String gamesUntilRanked({required Object left}) => '${left} матчей до получения рейтинга';
|
@override String gamesUntilRanked({required Object left}) => '${left} матчей до получения рейтинга';
|
||||||
|
@override String numOfVictories({required Object wins}) => '~${wins} побед';
|
||||||
|
@override String get promotionOnNextWin => 'Повышение после следующей победы';
|
||||||
|
@override String numOfdefeats({required Object losses}) => '~${losses} поражений';
|
||||||
|
@override String get demotionOnNextLoss => 'Понижение после следующего поражения';
|
||||||
@override String get nerdStats => 'Для задротов';
|
@override String get nerdStats => 'Для задротов';
|
||||||
@override String get playersYouTrack => 'Отслеживаемые игроки';
|
@override String get playersYouTrack => 'Отслеживаемые игроки';
|
||||||
@override String get formula => 'Формула';
|
@override String get formula => 'Формула';
|
||||||
|
@ -952,6 +979,9 @@ class _StringsRu implements Translations {
|
||||||
@override String get calc => 'Считать';
|
@override String get calc => 'Считать';
|
||||||
@override String get calcViewNoValues => 'Введите значения, чтобы посчитать статистику';
|
@override String get calcViewNoValues => 'Введите значения, чтобы посчитать статистику';
|
||||||
@override String get rankAveragesViewTitle => 'Требования рангов и средние значения';
|
@override String get rankAveragesViewTitle => 'Требования рангов и средние значения';
|
||||||
|
@override String get sprintAndBlitsViewTitle => 'Средние результаты 40 линий и блица';
|
||||||
|
@override String sprintAndBlitsRelevance({required Object date}) => 'Актуальность: ${date}';
|
||||||
|
@override String get rank => 'Ранг';
|
||||||
@override String get averages => 'Средние значения';
|
@override String get averages => 'Средние значения';
|
||||||
@override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста';
|
@override String get lbViewZeroEntrys => 'Рейтинговая таблица пуста';
|
||||||
@override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок';
|
@override String get lbViewOneEntry => 'В рейтинговой таблице всего один игрок';
|
||||||
|
@ -989,6 +1019,7 @@ class _StringsRu implements Translations {
|
||||||
@override String currentAxis({required Object axis}) => 'Ось ${axis}:';
|
@override String currentAxis({required Object axis}) => 'Ось ${axis}:';
|
||||||
@override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
@override String get p1nkl0bst3rAlert => 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
||||||
@override String get notForWeb => 'Функция недоступна для веб версии';
|
@override String get notForWeb => 'Функция недоступна для веб версии';
|
||||||
|
@override late final _StringsGraphsRu graphs = _StringsGraphsRu._(_root);
|
||||||
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
|
@override late final _StringsStatCellNumRu statCellNum = _StringsStatCellNumRu._(_root);
|
||||||
@override Map<String, String> get playerRole => {
|
@override Map<String, String> get playerRole => {
|
||||||
'user': 'Пользователь',
|
'user': 'Пользователь',
|
||||||
|
@ -1288,6 +1319,19 @@ class _StringsNewsPartsRu implements _StringsNewsPartsEn {
|
||||||
@override String unknownNews({required Object type}) => 'Неизвестная новость типа ${type}';
|
@override String unknownNews({required Object type}) => 'Неизвестная новость типа ${type}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path: graphs
|
||||||
|
class _StringsGraphsRu implements _StringsGraphsEn {
|
||||||
|
_StringsGraphsRu._(this._root);
|
||||||
|
|
||||||
|
@override final _StringsRu _root; // ignore: unused_field
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
@override String get attack => 'Атака';
|
||||||
|
@override String get speed => 'Скорость';
|
||||||
|
@override String get defense => 'Защита';
|
||||||
|
@override String get cheese => 'Сыр';
|
||||||
|
}
|
||||||
|
|
||||||
// Path: statCellNum
|
// Path: statCellNum
|
||||||
class _StringsStatCellNumRu implements _StringsStatCellNumEn {
|
class _StringsStatCellNumRu implements _StringsStatCellNumEn {
|
||||||
_StringsStatCellNumRu._(this._root);
|
_StringsStatCellNumRu._(this._root);
|
||||||
|
@ -1516,7 +1560,12 @@ extension on Translations {
|
||||||
case 'verdictGeneral': return ({required Object n, required Object verdict, required Object rank}) => '${n} ${verdict} than ${rank} rank average';
|
case 'verdictGeneral': return ({required Object n, required Object verdict, required Object rank}) => '${n} ${verdict} than ${rank} rank average';
|
||||||
case 'verdictBetter': return 'better';
|
case 'verdictBetter': return 'better';
|
||||||
case 'verdictWorse': return 'worse';
|
case 'verdictWorse': return 'worse';
|
||||||
|
case 'smooth': return 'Smooth';
|
||||||
case 'gamesUntilRanked': return ({required Object left}) => '${left} games until being ranked';
|
case 'gamesUntilRanked': return ({required Object left}) => '${left} games until being ranked';
|
||||||
|
case 'numOfVictories': return ({required Object wins}) => '~${wins} victories';
|
||||||
|
case 'promotionOnNextWin': return 'Promotion on next win';
|
||||||
|
case 'numOfdefeats': return ({required Object losses}) => '~${losses} defeats';
|
||||||
|
case 'demotionOnNextLoss': return 'Demotion on next loss';
|
||||||
case 'nerdStats': return 'Nerd Stats';
|
case 'nerdStats': return 'Nerd Stats';
|
||||||
case 'playersYouTrack': return 'Players you track';
|
case 'playersYouTrack': return 'Players you track';
|
||||||
case 'formula': return 'Formula';
|
case 'formula': return 'Formula';
|
||||||
|
@ -1598,6 +1647,9 @@ extension on Translations {
|
||||||
case 'calc': return 'Calc';
|
case 'calc': return 'Calc';
|
||||||
case 'calcViewNoValues': return 'Enter values to calculate the stats';
|
case 'calcViewNoValues': return 'Enter values to calculate the stats';
|
||||||
case 'rankAveragesViewTitle': return 'Ranks cutoff and average stats';
|
case 'rankAveragesViewTitle': return 'Ranks cutoff and average stats';
|
||||||
|
case 'sprintAndBlitsViewTitle': return '40 lines and Blitz averages';
|
||||||
|
case 'sprintAndBlitsRelevance': return ({required Object date}) => 'Relevance: ${date}';
|
||||||
|
case 'rank': return 'Rank';
|
||||||
case 'averages': return 'Averages';
|
case 'averages': return 'Averages';
|
||||||
case 'lbViewZeroEntrys': return 'Empty list';
|
case 'lbViewZeroEntrys': return 'Empty list';
|
||||||
case 'lbViewOneEntry': return 'There is only one player';
|
case 'lbViewOneEntry': return 'There is only one player';
|
||||||
|
@ -1635,6 +1687,10 @@ extension on Translations {
|
||||||
case 'currentAxis': return ({required Object axis}) => '${axis} axis:';
|
case 'currentAxis': return ({required Object axis}) => '${axis} axis:';
|
||||||
case 'p1nkl0bst3rAlert': return 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
case 'p1nkl0bst3rAlert': return 'That data was retrived from third party API maintained by p1nkl0bst3r';
|
||||||
case 'notForWeb': return 'Function is not available for web version';
|
case 'notForWeb': return 'Function is not available for web version';
|
||||||
|
case 'graphs.attack': return 'Attack';
|
||||||
|
case 'graphs.speed': return 'Speed';
|
||||||
|
case 'graphs.defense': return 'Defense';
|
||||||
|
case 'graphs.cheese': return 'Cheese';
|
||||||
case 'statCellNum.xpLevel': return 'XP Level';
|
case 'statCellNum.xpLevel': return 'XP Level';
|
||||||
case 'statCellNum.xpProgress': return 'Progress to next level';
|
case 'statCellNum.xpProgress': return 'Progress to next level';
|
||||||
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Progress from 0 XP to level ${n}';
|
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Progress from 0 XP to level ${n}';
|
||||||
|
@ -1684,7 +1740,7 @@ extension on Translations {
|
||||||
case 'statCellNum.nyaappDescription': return '(Abbreviated as wAPP) Essentially, a measure of your ability to send cheese while still maintaining a high APP.\nInvented by Wertj.';
|
case 'statCellNum.nyaappDescription': return '(Abbreviated as wAPP) Essentially, a measure of your ability to send cheese while still maintaining a high APP.\nInvented by Wertj.';
|
||||||
case 'statCellNum.area': return 'Area';
|
case 'statCellNum.area': return 'Area';
|
||||||
case 'statCellNum.areaDescription': return 'How much space your shape takes up on the graph, if you exclude the cheese and vs/apm sections';
|
case 'statCellNum.areaDescription': return 'How much space your shape takes up on the graph, if you exclude the cheese and vs/apm sections';
|
||||||
case 'statCellNum.estOfTR': return 'Est. of TR';
|
case 'statCellNum.estOfTR': return 'Estimated TR';
|
||||||
case 'statCellNum.estOfTRShort': return 'Est. TR';
|
case 'statCellNum.estOfTRShort': return 'Est. TR';
|
||||||
case 'statCellNum.accOfEst': return 'Accuracy';
|
case 'statCellNum.accOfEst': return 'Accuracy';
|
||||||
case 'statCellNum.accOfEstShort': return 'Acc.';
|
case 'statCellNum.accOfEstShort': return 'Acc.';
|
||||||
|
@ -1738,16 +1794,16 @@ extension on Translations {
|
||||||
case 'errors.forbiddenSub': return ({required Object nickname}) => 'If you are using VPN or Proxy, turn it off. If this does not help, reach out to ${nickname}';
|
case 'errors.forbiddenSub': return ({required Object nickname}) => 'If you are using VPN or Proxy, turn it off. If this does not help, reach out to ${nickname}';
|
||||||
case 'errors.tooManyRequests': return 'You have been rate limited.';
|
case 'errors.tooManyRequests': return 'You have been rate limited.';
|
||||||
case 'errors.tooManyRequestsSub': return 'Wait a few moments and try again';
|
case 'errors.tooManyRequestsSub': return 'Wait a few moments and try again';
|
||||||
case 'errors.internal': return 'Something happend on the tetr.io side';
|
case 'errors.internal': return 'Something happened on the tetr.io side';
|
||||||
case 'errors.internalSub': return 'osk, probably, already aware about it';
|
case 'errors.internalSub': return 'osk, probably, already aware about it';
|
||||||
case 'errors.internalWebVersion': return 'Something happend on the tetr.io side (or on oskware_bridge, idk honestly)';
|
case 'errors.internalWebVersion': return 'Something happened on the tetr.io side (or on oskware_bridge, idk honestly)';
|
||||||
case 'errors.internalWebVersionSub': return 'If osk status page says that everything is ok, let dan63047 know about this issue';
|
case 'errors.internalWebVersionSub': return 'If osk status page says that everything is ok, let dan63047 know about this issue';
|
||||||
case 'errors.oskwareBridge': return 'Something happend with oskware_bridge';
|
case 'errors.oskwareBridge': return 'Something happened with oskware_bridge';
|
||||||
case 'errors.oskwareBridgeSub': return 'Let dan63047 know';
|
case 'errors.oskwareBridgeSub': return 'Let dan63047 know';
|
||||||
case 'errors.p1nkl0bst3rForbidden': return 'Third party API blocked your IP address';
|
case 'errors.p1nkl0bst3rForbidden': return 'Third party API blocked your IP address';
|
||||||
case 'errors.p1nkl0bst3rTooManyRequests': return 'Too many requests to third party API. Try again later';
|
case 'errors.p1nkl0bst3rTooManyRequests': return 'Too many requests to third party API. Try again later';
|
||||||
case 'errors.p1nkl0bst3rinternal': return 'Something happend on the p1nkl0bst3r side';
|
case 'errors.p1nkl0bst3rinternal': return 'Something happened on the p1nkl0bst3r side';
|
||||||
case 'errors.p1nkl0bst3rinternalWebVersion': return 'Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
|
case 'errors.p1nkl0bst3rinternalWebVersion': return 'Something happened on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
|
||||||
case 'errors.replayAlreadySaved': return 'Replay already saved';
|
case 'errors.replayAlreadySaved': return 'Replay already saved';
|
||||||
case 'errors.replayExpired': return 'Replay expired and not available anymore';
|
case 'errors.replayExpired': return 'Replay expired and not available anymore';
|
||||||
case 'errors.replayRejected': return 'Third party API blocked your IP address';
|
case 'errors.replayRejected': return 'Third party API blocked your IP address';
|
||||||
|
@ -2096,7 +2152,12 @@ extension on _StringsRu {
|
||||||
case 'verdictGeneral': return ({required Object verdict, required Object rank, required Object n}) => '${verdict} среднего ${rank} ранга на ${n}';
|
case 'verdictGeneral': return ({required Object verdict, required Object rank, required Object n}) => '${verdict} среднего ${rank} ранга на ${n}';
|
||||||
case 'verdictBetter': return 'Лучше';
|
case 'verdictBetter': return 'Лучше';
|
||||||
case 'verdictWorse': return 'Хуже';
|
case 'verdictWorse': return 'Хуже';
|
||||||
|
case 'smooth': return 'Гладкий';
|
||||||
case 'gamesUntilRanked': return ({required Object left}) => '${left} матчей до получения рейтинга';
|
case 'gamesUntilRanked': return ({required Object left}) => '${left} матчей до получения рейтинга';
|
||||||
|
case 'numOfVictories': return ({required Object wins}) => '~${wins} побед';
|
||||||
|
case 'promotionOnNextWin': return 'Повышение после следующей победы';
|
||||||
|
case 'numOfdefeats': return ({required Object losses}) => '~${losses} поражений';
|
||||||
|
case 'demotionOnNextLoss': return 'Понижение после следующего поражения';
|
||||||
case 'nerdStats': return 'Для задротов';
|
case 'nerdStats': return 'Для задротов';
|
||||||
case 'playersYouTrack': return 'Отслеживаемые игроки';
|
case 'playersYouTrack': return 'Отслеживаемые игроки';
|
||||||
case 'formula': return 'Формула';
|
case 'formula': return 'Формула';
|
||||||
|
@ -2178,6 +2239,9 @@ extension on _StringsRu {
|
||||||
case 'calc': return 'Считать';
|
case 'calc': return 'Считать';
|
||||||
case 'calcViewNoValues': return 'Введите значения, чтобы посчитать статистику';
|
case 'calcViewNoValues': return 'Введите значения, чтобы посчитать статистику';
|
||||||
case 'rankAveragesViewTitle': return 'Требования рангов и средние значения';
|
case 'rankAveragesViewTitle': return 'Требования рангов и средние значения';
|
||||||
|
case 'sprintAndBlitsViewTitle': return 'Средние результаты 40 линий и блица';
|
||||||
|
case 'sprintAndBlitsRelevance': return ({required Object date}) => 'Актуальность: ${date}';
|
||||||
|
case 'rank': return 'Ранг';
|
||||||
case 'averages': return 'Средние значения';
|
case 'averages': return 'Средние значения';
|
||||||
case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста';
|
case 'lbViewZeroEntrys': return 'Рейтинговая таблица пуста';
|
||||||
case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок';
|
case 'lbViewOneEntry': return 'В рейтинговой таблице всего один игрок';
|
||||||
|
@ -2215,6 +2279,10 @@ extension on _StringsRu {
|
||||||
case 'currentAxis': return ({required Object axis}) => 'Ось ${axis}:';
|
case 'currentAxis': return ({required Object axis}) => 'Ось ${axis}:';
|
||||||
case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
case 'p1nkl0bst3rAlert': return 'Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r';
|
||||||
case 'notForWeb': return 'Функция недоступна для веб версии';
|
case 'notForWeb': return 'Функция недоступна для веб версии';
|
||||||
|
case 'graphs.attack': return 'Атака';
|
||||||
|
case 'graphs.speed': return 'Скорость';
|
||||||
|
case 'graphs.defense': return 'Защита';
|
||||||
|
case 'graphs.cheese': return 'Сыр';
|
||||||
case 'statCellNum.xpLevel': return 'Уровень\nопыта';
|
case 'statCellNum.xpLevel': return 'Уровень\nопыта';
|
||||||
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
case 'statCellNum.xpProgress': return 'Прогресс до следующего уровня';
|
||||||
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';
|
case 'statCellNum.xpFrom0ToLevel': return ({required Object n}) => 'Прогресс от 0 XP до ${n} уровня';
|
||||||
|
|
|
@ -70,6 +70,7 @@ class TetrioService extends DB {
|
||||||
|
|
||||||
// I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
|
// I'm trying to send as less requests, as possible, so i'm caching the results of those requests.
|
||||||
// Usually those maps looks like this: {"cached_until_unix_milliseconds": Object}
|
// Usually those maps looks like this: {"cached_until_unix_milliseconds": Object}
|
||||||
|
// TODO: Make a proper caching system
|
||||||
final Map<String, TetrioPlayer> _playersCache = {};
|
final Map<String, TetrioPlayer> _playersCache = {};
|
||||||
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
final Map<String, Map<String, dynamic>> _recordsCache = {};
|
||||||
final Map<String, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
|
final Map<String, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
|
||||||
|
@ -77,6 +78,8 @@ class TetrioService extends DB {
|
||||||
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
|
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
|
||||||
final Map<String, List<News>> _newsCache = {};
|
final Map<String, List<News>> _newsCache = {};
|
||||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
final Map<String, Map<String, double?>> _topTRcache = {};
|
||||||
|
final Map<String, List<Map<String, double>>> _cutoffsCache = {};
|
||||||
|
final Map<String, TetrioPlayerFromLeaderboard> _topOneFromLB = {};
|
||||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
|
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
|
||||||
/// Thing, that sends every request to the API endpoints
|
/// Thing, that sends every request to the API endpoints
|
||||||
final client = kDebugMode ? UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client()) : UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
final client = kDebugMode ? UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client()) : UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
||||||
|
@ -297,6 +300,119 @@ class TetrioService extends DB {
|
||||||
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
|
||||||
// so i'm going to document only unique differences between them
|
// so i'm going to document only unique differences between them
|
||||||
|
|
||||||
|
Future<List<Map<String, double>>> fetchCutoffs() async {
|
||||||
|
try{
|
||||||
|
var cached = _cutoffsCache.entries.first;
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
||||||
|
developer.log("fetchCutoffs: Cutoffs retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{ // if cache expired
|
||||||
|
_topTRcache.remove(cached.key);
|
||||||
|
developer.log("fetchCutoffs: Cutoffs expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){ // actually going to obtain
|
||||||
|
developer.log("fetchCutoffs: Trying to retrieve Cutoffs", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri url;
|
||||||
|
if (kIsWeb) {
|
||||||
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLCutoffs"});
|
||||||
|
} else {
|
||||||
|
url = Uri.https('api.p1nkl0bst3r.xyz', 'rankcutoff', {"users": null});
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
final response = await client.get(url);
|
||||||
|
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 200:
|
||||||
|
Map<String, dynamic> rawData = jsonDecode(response.body);
|
||||||
|
Map<String, dynamic> data = rawData["cutoffs"] as Map<String, dynamic>;
|
||||||
|
Map<String, double> trCutoffs = {};
|
||||||
|
Map<String, double> glickoCutoffs = {};
|
||||||
|
for (String rank in data.keys){
|
||||||
|
trCutoffs[rank] = data[rank]["rating"];
|
||||||
|
glickoCutoffs[rank] = data[rank]["glicko"];
|
||||||
|
}
|
||||||
|
_cutoffsCache[(rawData["ts"] + 300000).toString()] = [trCutoffs, glickoCutoffs];
|
||||||
|
return [trCutoffs, glickoCutoffs];
|
||||||
|
case 404:
|
||||||
|
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
|
return [];
|
||||||
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
|
case 403:
|
||||||
|
throw P1nkl0bst3rForbidden();
|
||||||
|
case 429:
|
||||||
|
throw P1nkl0bst3rTooManyRequests();
|
||||||
|
case 418:
|
||||||
|
throw TetrioOskwareBridgeProblem();
|
||||||
|
case 500:
|
||||||
|
case 502:
|
||||||
|
case 503:
|
||||||
|
case 504:
|
||||||
|
throw P1nkl0bst3rInternalProblem();
|
||||||
|
default:
|
||||||
|
developer.log("fetchCutoffs: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
|
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||||
|
}
|
||||||
|
} on http.ClientException catch (e, s) { // If local http client fails
|
||||||
|
developer.log("$e, $s");
|
||||||
|
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TetrioPlayerFromLeaderboard> fetchTopOneFromTheLeaderboard() async {
|
||||||
|
try{
|
||||||
|
var cached = _topOneFromLB.entries.first;
|
||||||
|
if (DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true).isAfter(DateTime.now())){ // if not expired
|
||||||
|
developer.log("fetchTopOneFromTheLeaderboard: Leader retrieved from cache, that expires ${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)}", name: "services/tetrio_crud");
|
||||||
|
return cached.value;
|
||||||
|
}else{ // if cache expired
|
||||||
|
_topTRcache.remove(cached.key);
|
||||||
|
developer.log("fetchTopOneFromTheLeaderboard: Leader expired (${DateTime.fromMillisecondsSinceEpoch(int.parse(cached.key.toString()), isUtc: true)})", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
}catch(e){ // actually going to obtain
|
||||||
|
developer.log("fetchTopOneFromTheLeaderboard: Trying to retrieve leader", name: "services/tetrio_crud");
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri url;
|
||||||
|
if (kIsWeb) {
|
||||||
|
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "TLTopOne"});
|
||||||
|
} else {
|
||||||
|
url = Uri.https('ch.tetr.io', 'api/users/lists/league', {"after": "25000", "limit": "1"});
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
final response = await client.get(url);
|
||||||
|
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 200:
|
||||||
|
var rawJson = jsonDecode(response.body);
|
||||||
|
return TetrioPlayerFromLeaderboard.fromJson(rawJson["data"]["users"][0], DateTime.fromMillisecondsSinceEpoch(rawJson["cache"]["cached_at"]));
|
||||||
|
case 404:
|
||||||
|
throw TetrioPlayerNotExist();
|
||||||
|
// if not 200 or 404 - throw a unique for each code exception
|
||||||
|
case 403:
|
||||||
|
throw TetrioForbidden();
|
||||||
|
case 429:
|
||||||
|
throw TetrioTooManyRequests();
|
||||||
|
case 418:
|
||||||
|
throw TetrioOskwareBridgeProblem();
|
||||||
|
case 500:
|
||||||
|
case 502:
|
||||||
|
case 503:
|
||||||
|
case 504:
|
||||||
|
throw P1nkl0bst3rInternalProblem();
|
||||||
|
default:
|
||||||
|
developer.log("fetchTopOneFromTheLeaderboard: Failed to fetch top one", name: "services/tetrio_crud", error: response.statusCode);
|
||||||
|
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||||
|
}
|
||||||
|
} on http.ClientException catch (e, s) { // If local http client fails
|
||||||
|
developer.log("$e, $s");
|
||||||
|
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
|
/// Retrieves Tetra League history from p1nkl0bst3r api for a player with given [id]. Returns a list of states
|
||||||
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
|
/// (state = instance of [TetrioPlayer] at some point of time). Can throw an exception if fails to retrieve data.
|
||||||
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
Future<List<TetrioPlayer>> fetchAndsaveTLHistory(String id) async {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
|
||||||
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
|
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
|
||||||
|
final NumberFormat comparef2 = NumberFormat("+#,###.##;-#,###.##")..maximumFractionDigits = 2;
|
||||||
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
|
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
|
||||||
final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
|
final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
|
||||||
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
|
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
|
||||||
|
|
|
@ -23,14 +23,12 @@ class CalcView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CalcState extends State<CalcView> {
|
class CalcState extends State<CalcView> {
|
||||||
late ScrollController _scrollController;
|
|
||||||
TextEditingController ppsController = TextEditingController();
|
TextEditingController ppsController = TextEditingController();
|
||||||
TextEditingController apmController = TextEditingController();
|
TextEditingController apmController = TextEditingController();
|
||||||
TextEditingController vsController = TextEditingController();
|
TextEditingController vsController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_scrollController = ScrollController();
|
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
||||||
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
||||||
windowManager.setTitle("Tetra Stats: ${t.statsCalc}");
|
windowManager.setTitle("Tetra Stats: ${t.statsCalc}");
|
||||||
|
@ -109,7 +107,7 @@ class CalcState extends State<CalcView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(),
|
const Divider(),
|
||||||
if (nerdStats == null) Text(t.calcViewNoValues)
|
if (nerdStats == null) Text(t.calcViewNoValues)
|
||||||
else Column(children: [
|
else Column(children: [
|
||||||
_ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
_ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
|
||||||
|
|
|
@ -257,7 +257,7 @@ class CompareState extends State<CompareView> {
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 768),
|
constraints: const BoxConstraints(maxWidth: 768),
|
||||||
|
@ -317,7 +317,7 @@ class CompareState extends State<CompareView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(),
|
const Divider(),
|
||||||
if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column(
|
if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column(
|
||||||
children: [
|
children: [
|
||||||
if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned")
|
if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned")
|
||||||
|
@ -820,6 +820,7 @@ class CompareThingy extends StatelessWidget {
|
||||||
colors: const [Colors.green, Colors.transparent],
|
colors: const [Colors.green, Colors.transparent],
|
||||||
begin: Alignment.centerLeft,
|
begin: Alignment.centerLeft,
|
||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
|
transform: const GradientRotation(0.6),
|
||||||
stops: [
|
stops: [
|
||||||
0.0,
|
0.0,
|
||||||
higherIsBetter
|
higherIsBetter
|
||||||
|
@ -830,7 +831,8 @@ class CompareThingy extends StatelessWidget {
|
||||||
? 0.6
|
? 0.6
|
||||||
: 0
|
: 0
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
(prefix ?? "") + f.format(greenSide) + (postfix ?? ""),
|
(prefix ?? "") + f.format(greenSide) + (postfix ?? ""),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
@ -838,7 +840,12 @@ class CompareThingy extends StatelessWidget {
|
||||||
shadows: <Shadow>[
|
shadows: <Shadow>[
|
||||||
Shadow(
|
Shadow(
|
||||||
offset: Offset(0.0, 0.0),
|
offset: Offset(0.0, 0.0),
|
||||||
blurRadius: 3.0,
|
blurRadius: 1.0,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(0.0, 0.0),
|
||||||
|
blurRadius: 2.0,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
Shadow(
|
Shadow(
|
||||||
|
@ -874,6 +881,7 @@ class CompareThingy extends StatelessWidget {
|
||||||
colors: const [Colors.red, Colors.transparent],
|
colors: const [Colors.red, Colors.transparent],
|
||||||
begin: Alignment.centerRight,
|
begin: Alignment.centerRight,
|
||||||
end: Alignment.centerLeft,
|
end: Alignment.centerLeft,
|
||||||
|
transform: const GradientRotation(-0.6),
|
||||||
stops: [
|
stops: [
|
||||||
0.0,
|
0.0,
|
||||||
higherIsBetter
|
higherIsBetter
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/main.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
late String oldWindowTitle;
|
late String oldWindowTitle;
|
||||||
|
|
|
@ -7,10 +7,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/services/tetrio_crud.dart';
|
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||||
|
@ -19,6 +18,7 @@ import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
import 'package:tetra_stats/utils/text_shadow.dart';
|
||||||
import 'package:tetra_stats/views/ranks_averages_view.dart' show RankAveragesView;
|
import 'package:tetra_stats/views/ranks_averages_view.dart' show RankAveragesView;
|
||||||
|
import 'package:tetra_stats/views/sprint_and_blitz_averages.dart';
|
||||||
import 'package:tetra_stats/views/tl_leaderboard_view.dart' show TLLeaderboardView;
|
import 'package:tetra_stats/views/tl_leaderboard_view.dart' show TLLeaderboardView;
|
||||||
import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView;
|
import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView;
|
||||||
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
import 'package:tetra_stats/widgets/finesse_thingy.dart';
|
||||||
|
@ -35,6 +35,8 @@ import 'package:go_router/go_router.dart';
|
||||||
final TetrioService teto = TetrioService(); // thing, that manadge our local DB
|
final TetrioService teto = TetrioService(); // thing, that manadge our local DB
|
||||||
int _chartsIndex = 0;
|
int _chartsIndex = 0;
|
||||||
bool _gamesPlayedInsteadOfDateAndTime = false;
|
bool _gamesPlayedInsteadOfDateAndTime = false;
|
||||||
|
late ZoomPanBehavior _zoomPanBehavior;
|
||||||
|
bool _smooth = false;
|
||||||
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"];
|
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"];
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode);
|
final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode);
|
||||||
|
@ -81,11 +83,14 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
TetrioPlayersLeaderboard? everyone;
|
TetrioPlayersLeaderboard? everyone;
|
||||||
PlayerLeaderboardPosition? meAmongEveryone;
|
PlayerLeaderboardPosition? meAmongEveryone;
|
||||||
TetraLeagueAlpha? rankAverages;
|
TetraLeagueAlpha? rankAverages;
|
||||||
|
double? thatRankCutoff;
|
||||||
|
double? nextRankCutoff;
|
||||||
|
double? thatRankGlickoCutoff;
|
||||||
|
double? nextRankGlickoCutoff;
|
||||||
String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for
|
String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for
|
||||||
String _titleNickname = "dan63047";
|
String _titleNickname = "";
|
||||||
/// Each dropdown menu item contains list of dots for the graph
|
/// Each dropdown menu item contains list of dots for the graph
|
||||||
var chartsData = <DropdownMenuItem<List<FlSpot>>>[];
|
List<DropdownMenuItem<List<_HistoryChartSpot>>> chartsData = [];
|
||||||
var chartsDataGamesPlayed = <DropdownMenuItem<List<FlSpot>>>[];
|
|
||||||
//var tableData = <TableRow>[];
|
//var tableData = <TableRow>[];
|
||||||
final bodyGlobalKey = GlobalKey();
|
final bodyGlobalKey = GlobalKey();
|
||||||
bool _showSearchBar = false;
|
bool _showSearchBar = false;
|
||||||
|
@ -102,13 +107,18 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
_scrollController = ScrollController();
|
_scrollController = ScrollController();
|
||||||
_tabController = TabController(length: 6, vsync: this);
|
_tabController = TabController(length: 6, vsync: this);
|
||||||
_wideScreenTabController = TabController(length: 4, vsync: this);
|
_wideScreenTabController = TabController(length: 4, vsync: this);
|
||||||
|
_zoomPanBehavior = ZoomPanBehavior(
|
||||||
|
enablePinching: true,
|
||||||
|
enableSelectionZooming: true,
|
||||||
|
enableMouseWheelZooming : true,
|
||||||
|
enablePanning: true,
|
||||||
|
);
|
||||||
// We need to show something
|
// We need to show something
|
||||||
if (widget.player != null){ // if we have user input,
|
if (widget.player != null){ // if we have user input,
|
||||||
changePlayer(widget.player!); // it's gonna be user input
|
changePlayer(widget.player!); // it's gonna be user input
|
||||||
}else{
|
}else{
|
||||||
_getPreferences() // otherwise, checking for preferences
|
_getPreferences() // otherwise, checking for preferences
|
||||||
.then((value) => changePlayer(prefs.getString("player") ?? "dan63047")); // no preferences - loading me
|
.then((value) => changePlayer(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc")); // no preferences - loading me
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -166,26 +176,41 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
late TetraLeagueAlphaStream tlStream;
|
late TetraLeagueAlphaStream tlStream;
|
||||||
late Map<String, dynamic> records;
|
late Map<String, dynamic> records;
|
||||||
late List<News> news;
|
late List<News> news;
|
||||||
|
late TetrioPlayerFromLeaderboard? topOne;
|
||||||
late double? topTR;
|
late double? topTR;
|
||||||
requests = await Future.wait([ // all at once
|
requests = await Future.wait([ // all at once
|
||||||
teto.fetchTLStream(_searchFor),
|
teto.fetchTLStream(_searchFor),
|
||||||
teto.fetchRecords(_searchFor),
|
teto.fetchRecords(_searchFor),
|
||||||
teto.fetchNews(_searchFor),
|
teto.fetchNews(_searchFor),
|
||||||
|
prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
|
||||||
|
(me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
|
||||||
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
if (me.tlSeason1.gamesPlayed > 9) teto.fetchTopTR(_searchFor) // can retrieve this only if player has TR
|
||||||
]);
|
]);
|
||||||
tlStream = requests[0] as TetraLeagueAlphaStream;
|
tlStream = requests[0] as TetraLeagueAlphaStream;
|
||||||
records = requests[1] as Map<String, dynamic>;
|
records = requests[1] as Map<String, dynamic>;
|
||||||
news = requests[2] as List<News>;
|
news = requests[2] as List<News>;
|
||||||
topTR = requests.elementAtOrNull(3) as double?; // No TR - no Top TR
|
topOne = requests[4] as TetrioPlayerFromLeaderboard?;
|
||||||
|
topTR = requests.elementAtOrNull(5) as double?; // No TR - no Top TR
|
||||||
|
|
||||||
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
|
meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId);
|
||||||
if (meAmongEveryone == null && prefs.getBool("showPositions") == true){
|
if (prefs.getBool("showPositions") == true){
|
||||||
// Get tetra League leaderboard
|
// Get tetra League leaderboard
|
||||||
everyone = teto.getCachedLeaderboard();
|
everyone = teto.getCachedLeaderboard();
|
||||||
everyone ??= await teto.fetchTLLeaderboard();
|
everyone ??= await teto.fetchTLLeaderboard();
|
||||||
|
if (meAmongEveryone == null){
|
||||||
meAmongEveryone = await compute(everyone!.getLeaderboardPosition, me);
|
meAmongEveryone = await compute(everyone!.getLeaderboardPosition, me);
|
||||||
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Map<String, double>? cutoffs = prefs.getBool("showPositions") == true ? everyone!.cutoffs : (requests[3] as List<Map<String, double>>).elementAtOrNull(0);
|
||||||
|
Map<String, double>? cutoffsGlicko = prefs.getBool("showPositions") == true ? everyone!.cutoffsGlicko : (requests[3] as List<Map<String, double>>).elementAtOrNull(1);
|
||||||
|
|
||||||
|
if (me.tlSeason1.gamesPlayed > 9) {
|
||||||
|
thatRankCutoff = cutoffs?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||||
|
thatRankGlickoCutoff = cutoffsGlicko?[me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank];
|
||||||
|
nextRankCutoff = (me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? topOne?.rating??25000 : cutoffs?[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
|
||||||
|
nextRankGlickoCutoff = (me.tlSeason1.rank != "z" ? me.tlSeason1.rank == "x" : me.tlSeason1.percentileRank == "x") ? topOne?.glicko??double.infinity : cutoffsGlicko?[ranks.elementAtOrNull(ranks.indexOf(me.tlSeason1.rank != "z" ? me.tlSeason1.rank : me.tlSeason1.percentileRank)+1)];
|
||||||
|
}
|
||||||
|
|
||||||
if (everyone != null && me.tlSeason1.gamesPlayed > 9) rankAverages = everyone?.averages[me.tlSeason1.percentileRank]?[0];
|
if (everyone != null && me.tlSeason1.gamesPlayed > 9) rankAverages = everyone?.averages[me.tlSeason1.percentileRank]?[0];
|
||||||
|
|
||||||
|
@ -254,58 +279,30 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1);
|
if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1);
|
||||||
}
|
}
|
||||||
// Also i need previous Tetra League State for comparison if avaliable
|
// Also i need previous Tetra League State for comparison if avaliable
|
||||||
// tableData = [
|
|
||||||
// TableRow(children: [ Text("Date & Time"), Text("Tr"), Text("Glicko"), Text("RD"), Text("GP"), Text("GW"), Text("APM"), Text("PPS"), Text("VS"), Text("APP"), Text("VS/APM"), Text("DS/S"), Text("DS/P"), Text("APP+DS/P"), Text("Cheese"), Text("GbE"), Text("wAPP"), Text("Area"), Text("eTR"), Text("±eTR"), Text("Opener"), Text("Plonk"), Text("Inf. DS"), Text("Stride")],
|
|
||||||
// decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.white)))),
|
|
||||||
// for (var state in states) TableRow(children: [Text(dateFormat.format(state.tlSeason1.timestamp)), Text(f4.format(state.tlSeason1.rating)), Text(f4.format(state.tlSeason1.glicko)), Text(f4.format(state.tlSeason1.rd)), Text(f0.format(state.tlSeason1.gamesPlayed)), Text(f0.format(state.tlSeason1.gamesWon)), Text(f2.format(state.tlSeason1.apm)), Text(f2.format(state.tlSeason1.pps)), Text(state.tlSeason1.vs != null ? f2.format(state.tlSeason1.vs) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.app) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.vsapm) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.dss) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.dsp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.appdsp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.cheese) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.gbe) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.nyaapp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.area) : "---"), Text(state.tlSeason1.estTr != null ? f4.format(state.tlSeason1.estTr?.esttr) : "---"), Text(state.tlSeason1.esttracc != null ? f4.format(state.tlSeason1.esttracc) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.opener) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.plonk) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.infds) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.stride) : "---")]),
|
|
||||||
// ];
|
|
||||||
if (uniqueTL.length >= 2){
|
if (uniqueTL.length >= 2){
|
||||||
compareWith = uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2);
|
compareWith = uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2);
|
||||||
chartsData = <DropdownMenuItem<List<FlSpot>>>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid
|
chartsData = <DropdownMenuItem<List<_HistoryChartSpot>>>[ // 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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rating)], child: Text(t.statCellNum.tr)),
|
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) _HistoryChartSpot(tl.timestamp, tl.gamesPlayed, tl.rank, tl.rating)], child: Text(t.statCellNum.tr)),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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.glicko!)], child: const Text("Glicko")),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rd!)], child: const Text("Rating Deviation")),
|
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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))),
|
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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.pps!)], child: Text(t.statCellNum.pps.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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.vs!)], child: Text(t.statCellNum.vs.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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.appdsp)], child: const Text("APP + DS/P")),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.vsapm)], child: const Text("VS/APM")),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.nerdStats!.area)], child: Text(t.statCellNum.area.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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.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) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.opener)], child: const Text("Opener")),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.plonk)], child: const Text("Plonk")),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), 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!.infds)], child: const Text("Inf. DS")),
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.stride)], child: const Text("Stride")),
|
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")),
|
||||||
];
|
|
||||||
chartsDataGamesPlayed = <DropdownMenuItem<List<FlSpot>>>[ // 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) FlSpot(tl.gamesPlayed.toDouble(), tl.rating)], child: Text(t.statCellNum.tr)),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.glicko!)], child: const Text("Glicko")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.rd!)], child: const Text("Rating Deviation")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.apm != null) FlSpot(tl.gamesPlayed.toDouble(), tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.pps != null) FlSpot(tl.gamesPlayed.toDouble(), tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.vs != null) FlSpot(tl.gamesPlayed.toDouble(), tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.vsapm)], child: const Text("VS/APM")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) FlSpot(tl.gamesPlayed.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) FlSpot(tl.gamesPlayed.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.opener)], child: const Text("Opener")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.plonk)], child: const Text("Plonk")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.infds)], child: const Text("Inf. DS")),
|
|
||||||
DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.stride)], child: const Text("Stride")),
|
|
||||||
];
|
];
|
||||||
}else{
|
}else{
|
||||||
compareWith = null;
|
compareWith = null;
|
||||||
|
@ -465,6 +462,12 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
topTR: snapshot.data![7],
|
topTR: snapshot.data![7],
|
||||||
bot: snapshot.data![0].role == "bot",
|
bot: snapshot.data![0].role == "bot",
|
||||||
guest: snapshot.data![0].role == "anon",
|
guest: snapshot.data![0].role == "anon",
|
||||||
|
thatRankCutoff: thatRankCutoff,
|
||||||
|
thatRankCutoffGlicko: thatRankGlickoCutoff,
|
||||||
|
thatRankTarget: snapshot.data![0].tlSeason1.rank != "z" ? rankTargets[snapshot.data![0].tlSeason1.rank] : null,
|
||||||
|
nextRankCutoff: nextRankCutoff,
|
||||||
|
nextRankCutoffGlicko: nextRankGlickoCutoff,
|
||||||
|
nextRankTarget: (snapshot.data![0].tlSeason1.rank != "z" && snapshot.data![0].tlSeason1.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![0].tlSeason1.rank)+1)] : null,
|
||||||
averages: rankAverages,
|
averages: rankAverages,
|
||||||
lbPositions: meAmongEveryone
|
lbPositions: meAmongEveryone
|
||||||
),
|
),
|
||||||
|
@ -474,7 +477,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
child: _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true,)
|
child: _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true,)
|
||||||
),
|
),
|
||||||
],),
|
],),
|
||||||
_History(chartsData: chartsData, chartsDataGamesPlayed: chartsDataGamesPlayed, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
||||||
_TwoRecordsThingy(sprint: snapshot.data![1]['sprint'], blitz: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank,),
|
_TwoRecordsThingy(sprint: snapshot.data![1]['sprint'], blitz: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank,),
|
||||||
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
||||||
] : [
|
] : [
|
||||||
|
@ -485,11 +488,17 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
topTR: snapshot.data![7],
|
topTR: snapshot.data![7],
|
||||||
bot: snapshot.data![0].role == "bot",
|
bot: snapshot.data![0].role == "bot",
|
||||||
guest: snapshot.data![0].role == "anon",
|
guest: snapshot.data![0].role == "anon",
|
||||||
|
thatRankCutoff: thatRankCutoff,
|
||||||
|
thatRankCutoffGlicko: thatRankGlickoCutoff,
|
||||||
|
thatRankTarget: snapshot.data![0].tlSeason1.rank != "z" ? rankTargets[snapshot.data![0].tlSeason1.rank] : null,
|
||||||
|
nextRankCutoff: nextRankCutoff,
|
||||||
|
nextRankCutoffGlicko: nextRankGlickoCutoff,
|
||||||
|
nextRankTarget: (snapshot.data![0].tlSeason1.rank != "z" && snapshot.data![0].tlSeason1.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![0].tlSeason1.rank)+1)] : null,
|
||||||
averages: rankAverages,
|
averages: rankAverages,
|
||||||
lbPositions: meAmongEveryone
|
lbPositions: meAmongEveryone
|
||||||
),
|
),
|
||||||
_TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched),
|
_TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched),
|
||||||
_History(chartsData: chartsData, chartsDataGamesPlayed: chartsDataGamesPlayed, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
_History(chartsData: chartsData, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0),
|
||||||
_RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
_RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
||||||
_RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
_RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank),
|
||||||
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
_OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],)
|
||||||
|
@ -541,7 +550,7 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
|
||||||
Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||||
if (subText != null) Padding(
|
if (subText != null) Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: Text(subText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18)),
|
child: Text(subText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18), textAlign: TextAlign.center),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -624,7 +633,7 @@ class _NavDrawerState extends State<NavDrawer> {
|
||||||
leading: const Icon(Icons.home),
|
leading: const Icon(Icons.home),
|
||||||
title: Text(homePlayerNickname),
|
title: Text(homePlayerNickname),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.changePlayer(prefs.getString("player") ?? "dan63047"); // changes player on main view to the one from preferences
|
widget.changePlayer(prefs.getString("player") ?? "6098518e3d5155e6ec429cdc"); // changes player on main view to the one from preferences
|
||||||
Navigator.of(context).pop(); // and then NavDrawer closes itself.
|
Navigator.of(context).pop(); // and then NavDrawer closes itself.
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -657,6 +666,20 @@ class _NavDrawerState extends State<NavDrawer> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: ListTile( // Rank averages button
|
||||||
|
leading: const Icon(Icons.bar_chart),
|
||||||
|
title: Text(t.sprintAndBlitsViewTitle),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const SprintAndBlitzView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
const SliverToBoxAdapter(child: Divider())
|
const SliverToBoxAdapter(child: Divider())
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -752,8 +775,7 @@ class _TLRecords extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _History extends StatelessWidget{
|
class _History extends StatelessWidget{
|
||||||
final List<DropdownMenuItem<List<FlSpot>>> chartsData;
|
final List<DropdownMenuItem<List<_HistoryChartSpot>>> chartsData;
|
||||||
final List<DropdownMenuItem<List<FlSpot>>> chartsDataGamesPlayed;
|
|
||||||
final String userID;
|
final String userID;
|
||||||
final Function update;
|
final Function update;
|
||||||
final Function changePlayer;
|
final Function changePlayer;
|
||||||
|
@ -761,7 +783,7 @@ class _History extends StatelessWidget{
|
||||||
|
|
||||||
/// Widget, that can show history of some stat of the player on the graph.
|
/// Widget, that can show history of some stat of the player on the graph.
|
||||||
/// Requires player [states], which is list of states and function [update], which rebuild widgets
|
/// Requires player [states], which is list of states and function [update], which rebuild widgets
|
||||||
const _History({required this.chartsData, required this.chartsDataGamesPlayed, required this.userID, required this.changePlayer, required this.update, required this.wasActiveInTL});
|
const _History({required this.chartsData, required this.userID, required this.changePlayer, required this.update, required this.wasActiveInTL});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -776,16 +798,16 @@ class _History extends StatelessWidget{
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||||
|
//List<_HistoryChartSpot> selectedGraph = _gamesPlayedInsteadOfDateAndTime ? chartsDataGamesPlayed[_chartsIndex].value! : chartsData[_chartsIndex].value!;
|
||||||
|
List<_HistoryChartSpot> selectedGraph = chartsData[_chartsIndex].value!;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
primary: true,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -815,9 +837,22 @@ class _History extends StatelessWidget{
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (selectedGraph.length > 300) Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Checkbox(value: _smooth,
|
||||||
|
checkColor: Colors.black,
|
||||||
|
onChanged: ((value) {
|
||||||
|
_smooth = value!;
|
||||||
|
update();
|
||||||
|
})),
|
||||||
|
Text(t.smooth, style: const TextStyle(color: Colors.white, fontSize: 22))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: _gamesPlayedInsteadOfDateAndTime ? chartsDataGamesPlayed[_chartsIndex].value! : chartsData[_chartsIndex].value!, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact())
|
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: selectedGraph, smooth: _smooth, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact())
|
||||||
else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column(
|
else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
@ -828,13 +863,21 @@ class _History extends StatelessWidget{
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _HistoryChartSpot{
|
||||||
|
final DateTime timestamp;
|
||||||
|
final int gamesPlayed;
|
||||||
|
final String rank;
|
||||||
|
final double stat;
|
||||||
|
const _HistoryChartSpot(this.timestamp, this.gamesPlayed, this.rank, this.stat);
|
||||||
|
}
|
||||||
|
|
||||||
class _HistoryChartThigy extends StatefulWidget{
|
class _HistoryChartThigy extends StatefulWidget{
|
||||||
final List<FlSpot> data;
|
final List<_HistoryChartSpot> data;
|
||||||
|
final bool smooth;
|
||||||
final String yAxisTitle;
|
final String yAxisTitle;
|
||||||
final bool bigScreen;
|
final bool bigScreen;
|
||||||
final double leftSpace;
|
final double leftSpace;
|
||||||
|
@ -844,7 +887,7 @@ class _HistoryChartThigy extends StatefulWidget{
|
||||||
/// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes.
|
/// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes.
|
||||||
/// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format
|
/// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format
|
||||||
/// for left titles
|
/// for left titles
|
||||||
const _HistoryChartThigy({required this.data, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat, this.xFormat});
|
const _HistoryChartThigy({required this.data, required this.smooth, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat, this.xFormat});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_HistoryChartThigy> createState() => _HistoryChartThigyState();
|
State<_HistoryChartThigy> createState() => _HistoryChartThigyState();
|
||||||
|
@ -853,292 +896,117 @@ class _HistoryChartThigy extends StatefulWidget{
|
||||||
class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
class _HistoryChartThigyState extends State<_HistoryChartThigy> {
|
||||||
late String previousAxisTitle;
|
late String previousAxisTitle;
|
||||||
late bool previousGamesPlayedInsteadOfDateAndTime;
|
late bool previousGamesPlayedInsteadOfDateAndTime;
|
||||||
late double minX;
|
late TooltipBehavior _tooltipBehavior;
|
||||||
late double maxX;
|
|
||||||
late double minY;
|
|
||||||
late double actualMinY;
|
|
||||||
late double maxY;
|
|
||||||
late double actualMaxY;
|
|
||||||
late double xScale;
|
|
||||||
late double yScale;
|
|
||||||
String headerTooltip = t.pseudoTooltipHeaderInit;
|
|
||||||
String footerTooltip = t.pseudoTooltipFooterInit;
|
|
||||||
int hoveredPointId = -1;
|
|
||||||
double scaleFactor = 5e2;
|
|
||||||
double dragFactor = 7e2;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState(){
|
void initState(){
|
||||||
super.initState();
|
super.initState();
|
||||||
minX = widget.data.first.x;
|
_tooltipBehavior = TooltipBehavior(
|
||||||
maxX = widget.data.last.x;
|
color: Colors.black,
|
||||||
setMinMaxY();
|
borderColor: Colors.white,
|
||||||
|
enable: true,
|
||||||
|
animationDuration: 0,
|
||||||
|
builder: (dynamic data, dynamic point, dynamic series,
|
||||||
|
int pointIndex, int seriesIndex) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"${f4.format(data.stat)} ${widget.yAxisTitle}",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(_gamesPlayedInsteadOfDateAndTime ? t.gamesPlayed(games: t.games(n: data.gamesPlayed)) : _dateFormat.format(data.timestamp))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
previousAxisTitle = widget.yAxisTitle;
|
previousAxisTitle = widget.yAxisTitle;
|
||||||
previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime;
|
previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime;
|
||||||
actualMaxY = maxY;
|
|
||||||
actualMinY = minY;
|
|
||||||
recalculateScales();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose(){
|
void dispose(){
|
||||||
super.dispose();
|
super.dispose();
|
||||||
actualMinY = 0;
|
|
||||||
minY = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates and assignes maximum and minimum values in list of dots
|
|
||||||
void setMinMaxY(){
|
|
||||||
actualMinY = widget.data.reduce((value, element){
|
|
||||||
num n = min(value.y, element.y);
|
|
||||||
if (value.y == n) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).y;
|
|
||||||
actualMaxY = widget.data.reduce((value, element){
|
|
||||||
num n = max(value.y, element.y);
|
|
||||||
if (value.y == n) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).y;
|
|
||||||
minY = actualMinY;
|
|
||||||
maxY = actualMaxY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates and assignes scales, which is difference between maximum and minimum visible axis value
|
|
||||||
void recalculateScales(){
|
|
||||||
xScale = maxX - minX;
|
|
||||||
yScale = maxY - minY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accepts [dragUpdDet] and changes minX, maxX, minY, maxY based on that
|
|
||||||
void dragHandler(DragUpdateDetails dragUpdDet){
|
|
||||||
setState(() {
|
|
||||||
// Changing min and max values according to drag delta and considering scales
|
|
||||||
minX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
|
||||||
maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
|
||||||
minY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
|
||||||
maxY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
|
||||||
|
|
||||||
// If values are out of bounds - putting them back
|
|
||||||
if (minX < widget.data.first.x) {
|
|
||||||
minX = widget.data.first.x;
|
|
||||||
maxX = widget.data.first.x + xScale;
|
|
||||||
}
|
|
||||||
if (maxX > widget.data.last.x) {
|
|
||||||
maxX = widget.data.last.x;
|
|
||||||
minX = maxX - xScale;
|
|
||||||
}
|
|
||||||
if(minY < actualMinY){
|
|
||||||
minY = actualMinY;
|
|
||||||
maxY = actualMinY + yScale;
|
|
||||||
}
|
|
||||||
if(maxY > actualMaxY){
|
|
||||||
maxY = actualMaxY;
|
|
||||||
minY = actualMaxY - yScale;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accepts scale [details] and changes minX, maxX, minY, maxY in a way to change xScale and yScale.
|
|
||||||
/// [graphKey] required for sizes calculations, as well, as [graphStartX] and [graphEndX].
|
|
||||||
/// Not used yet, because GestureDetector works like shit
|
|
||||||
void scaleHandler(ScaleUpdateDetails details, GlobalKey<State<StatefulWidget>> graphKey, double graphStartX, double graphEndX){
|
|
||||||
RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox;
|
|
||||||
|
|
||||||
// calculating relative position of scale gesture
|
|
||||||
Offset graphPosition = graphBox.localToGlobal(Offset.zero);
|
|
||||||
// 0 - very left position of graph; 1 - very right position of graph
|
|
||||||
double gesturePosRelativeX = (details.focalPoint.dx - graphStartX) / (graphEndX - graphStartX);
|
|
||||||
// 0 - very top position of graph; 1 - very bottom position of graph
|
|
||||||
double gesturePosRelativeY = (details.focalPoint.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height
|
|
||||||
|
|
||||||
double newMinX, newMaxX, newMinY, newMaxY; // calcutating new values based on gesture and considering scales
|
|
||||||
newMinX = minX - (xScale / scaleFactor) * (details.horizontalScale-1) * gesturePosRelativeX;
|
|
||||||
newMaxX = maxX + (xScale / scaleFactor) * (details.horizontalScale-1) * (1-gesturePosRelativeX);
|
|
||||||
newMinY = minY - (yScale / scaleFactor) * (details.horizontalScale-1) * (1-gesturePosRelativeY);
|
|
||||||
newMaxY = maxY + (yScale / scaleFactor) * (details.horizontalScale-1) * gesturePosRelativeY;
|
|
||||||
|
|
||||||
// cancel changes if minimum is more, than maximun
|
|
||||||
if ((newMaxX - newMinX).isNegative) return;
|
|
||||||
if ((newMaxY - newMinY).isNegative) return;
|
|
||||||
|
|
||||||
// apply changes if everything ok + can't go past boundaries
|
|
||||||
setState(() {
|
|
||||||
minX = max(newMinX, widget.data.first.x);
|
|
||||||
maxX = min(newMaxX, widget.data.last.x);
|
|
||||||
minY = max(newMinY, actualMinY);
|
|
||||||
maxY = min(newMaxY, actualMaxY);
|
|
||||||
recalculateScales();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
GlobalKey graphKey = GlobalKey();
|
|
||||||
if ((previousAxisTitle != widget.yAxisTitle) || (previousGamesPlayedInsteadOfDateAndTime != _gamesPlayedInsteadOfDateAndTime)) {
|
if ((previousAxisTitle != widget.yAxisTitle) || (previousGamesPlayedInsteadOfDateAndTime != _gamesPlayedInsteadOfDateAndTime)) {
|
||||||
minX = widget.data.first.x;
|
|
||||||
maxX = widget.data.last.x;
|
|
||||||
recalculateScales();
|
|
||||||
setMinMaxY();
|
|
||||||
previousAxisTitle = widget.yAxisTitle;
|
previousAxisTitle = widget.yAxisTitle;
|
||||||
previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime;
|
previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime;
|
||||||
setState((){});
|
setState((){});
|
||||||
}
|
}
|
||||||
double xInterval = widget.bigScreen ? max(1, xScale / 8) : max(1, xScale / 4); // how far away xTitles should be between each other
|
|
||||||
EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
|
EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
|
||||||
double graphStartX = padding.left+widget.leftSpace;
|
|
||||||
double graphEndX = MediaQuery.sizeOf(context).width - padding.right;
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height - 104,
|
height: MediaQuery.of(context).size.height - 104,
|
||||||
|
child: Padding( padding: padding,
|
||||||
child: Listener(
|
child: Listener(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onPointerSignal: (signal) {
|
onPointerSignal: (signal) {
|
||||||
if (signal is PointerScrollEvent) {
|
if (signal is PointerScrollEvent) {
|
||||||
RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox;
|
|
||||||
|
|
||||||
// calculating relative position of pointer
|
|
||||||
Offset graphPosition = graphBox.localToGlobal(Offset.zero);
|
|
||||||
// 0 - very left position of graph; 1 - very right position of graph
|
|
||||||
double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX);
|
|
||||||
// 0 - very top position of graph; 1 - very bottom position of graph
|
|
||||||
double scrollPosRelativeY = (signal.position.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height
|
|
||||||
|
|
||||||
double newMinX, newMaxX, newMinY, newMaxY; // calcutating new values based on pointer position and considering scales
|
|
||||||
newMinX = minX - (xScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeX;
|
|
||||||
newMaxX = maxX + (xScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeX);
|
|
||||||
newMinY = minY - (yScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeY);
|
|
||||||
newMaxY = maxY + (yScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeY;
|
|
||||||
|
|
||||||
// cancel changes if minimum is more, than maximun
|
|
||||||
if ((newMaxX - newMinX).isNegative) return;
|
|
||||||
if ((newMaxY - newMinY).isNegative) return;
|
|
||||||
|
|
||||||
// apply changes if everything ok + can't go past boundaries
|
|
||||||
setState(() {
|
setState(() {
|
||||||
minX = max(newMinX, widget.data.first.x);
|
|
||||||
maxX = min(newMaxX, widget.data.last.x);
|
|
||||||
minY = max(newMinY, actualMinY);
|
|
||||||
maxY = min(newMaxY, actualMaxY);
|
|
||||||
recalculateScales();
|
|
||||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child:
|
child: SfCartesianChart(
|
||||||
GestureDetector(
|
tooltipBehavior: _tooltipBehavior,
|
||||||
behavior: HitTestBehavior.translucent,
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
onDoubleTap: () {
|
primaryXAxis: _gamesPlayedInsteadOfDateAndTime ? const NumericAxis() : const DateTimeAxis(),
|
||||||
setState(() {
|
primaryYAxis: const NumericAxis(
|
||||||
minX = widget.data.first.x;
|
rangePadding: ChartRangePadding.additional,
|
||||||
maxX = widget.data.last.x;
|
|
||||||
minY = actualMinY;
|
|
||||||
maxY = actualMaxY;
|
|
||||||
recalculateScales();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// TODO: onScaleUpdate:(details) => scaleHandler(details, graphKey, graphStartX, graphEndX),
|
|
||||||
// TODO: Figure out wtf is going on with gestures
|
|
||||||
// TODO: Somehow highlight touched spot (handleBuiltInTouches breaks getTooltipItems and getTouchedSpotIndicator)
|
|
||||||
child: Padding( padding: padding,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
LineChart(
|
|
||||||
key: graphKey,
|
|
||||||
LineChartData(
|
|
||||||
lineBarsData: [LineChartBarData(spots: widget.data)],
|
|
||||||
clipData: const FlClipData.all(),
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
gridData: FlGridData(verticalInterval: xInterval),
|
|
||||||
minX: minX,
|
|
||||||
maxX: maxX,
|
|
||||||
minY: minY,
|
|
||||||
maxY: maxY,
|
|
||||||
titlesData: FlTitlesData(topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
||||||
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
||||||
bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){
|
|
||||||
return value != meta.min && value != meta.max ? SideTitleWidget(
|
|
||||||
axisSide: meta.axisSide,
|
|
||||||
child: Text(widget.xFormat != null && _gamesPlayedInsteadOfDateAndTime ? widget.xFormat!.format(value.round()) : DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))),
|
|
||||||
) : Container();
|
|
||||||
})),
|
|
||||||
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: widget.leftSpace, getTitlesWidget: (double value, TitleMeta meta){
|
|
||||||
return value != meta.min && value != meta.max ? SideTitleWidget(
|
|
||||||
axisSide: meta.axisSide,
|
|
||||||
child: Text(widget.yFormat.format(value)),
|
|
||||||
) : Container();
|
|
||||||
}))),
|
|
||||||
lineTouchData: LineTouchData(
|
|
||||||
handleBuiltInTouches: false,
|
|
||||||
touchCallback:(touchEvent, touchResponse) {
|
|
||||||
if (touchEvent is FlPanUpdateEvent){
|
|
||||||
dragHandler(touchEvent.details);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (touchEvent is FlPointerHoverEvent){
|
|
||||||
setState(() {
|
|
||||||
if (touchResponse?.lineBarSpots?.first == null) {
|
|
||||||
hoveredPointId = -1; // not hovering over any point
|
|
||||||
} else {
|
|
||||||
hoveredPointId = touchResponse!.lineBarSpots!.first.spotIndex;
|
|
||||||
headerTooltip = "${f4.format(touchResponse.lineBarSpots!.first.y)} ${widget.yAxisTitle}";
|
|
||||||
footerTooltip = _gamesPlayedInsteadOfDateAndTime ? "${f0.format(touchResponse.lineBarSpots!.first.x)} games played" : _dateFormat.format(DateTime.fromMillisecondsSinceEpoch(touchResponse.lineBarSpots!.first.x.floor()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (touchEvent is FlPointerExitEvent){
|
|
||||||
setState(() {hoveredPointId = -1;});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
Padding(
|
series: <CartesianSeries>[
|
||||||
padding: EdgeInsets.only(left: widget.leftSpace),
|
if (_gamesPlayedInsteadOfDateAndTime) StepLineSeries<_HistoryChartSpot, int>(
|
||||||
child: Column(
|
enableTooltip: true,
|
||||||
children: [
|
// splineType: SplineType.cardinal,
|
||||||
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 24, color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(headerTooltip)),
|
// cardinalSplineTension: 0.2,
|
||||||
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round", color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(footerTooltip)),
|
dataSource: widget.data,
|
||||||
|
animationDuration: 0,
|
||||||
|
opacity: _smooth ? 0 : 1,
|
||||||
|
xValueMapper: (_HistoryChartSpot data, _) => data.gamesPlayed,
|
||||||
|
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||||
|
trendlines:<Trendline>[
|
||||||
|
Trendline(
|
||||||
|
isVisible: _smooth,
|
||||||
|
period: (widget.data.length/175).floor(),
|
||||||
|
type: TrendlineType.movingAverage,
|
||||||
|
color: Colors.blue)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else StepLineSeries<_HistoryChartSpot, DateTime>(
|
||||||
|
enableTooltip: true,
|
||||||
|
// splineType: SplineType.cardinal,
|
||||||
|
// cardinalSplineTension: 0.2,
|
||||||
|
dataSource: widget.data,
|
||||||
|
animationDuration: 0,
|
||||||
|
opacity: _smooth ? 0 : 1,
|
||||||
|
xValueMapper: (_HistoryChartSpot data, _) => data.timestamp,
|
||||||
|
yValueMapper: (_HistoryChartSpot data, _) => data.stat,
|
||||||
|
trendlines:<Trendline>[
|
||||||
|
Trendline(
|
||||||
|
isVisible: _smooth,
|
||||||
|
period: (widget.data.length/175).floor(),
|
||||||
|
type: TrendlineType.movingAverage,
|
||||||
|
color: Colors.blue)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// class _HistoryTableThingy extends StatelessWidget{
|
|
||||||
// final List<TableRow> tableData;
|
|
||||||
|
|
||||||
// const _HistoryTableThingy(this.tableData);
|
|
||||||
// // :tf:
|
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
|
||||||
// return LayoutBuilder(builder: (context, constraints){
|
|
||||||
// return Table(
|
|
||||||
// defaultColumnWidth: FixedColumnWidth(75),
|
|
||||||
// columnWidths: {
|
|
||||||
// 0: FixedColumnWidth(170),
|
|
||||||
// 1: FixedColumnWidth(100),
|
|
||||||
// 2: FixedColumnWidth(90),
|
|
||||||
// 18: FixedColumnWidth(100),
|
|
||||||
// 19: FixedColumnWidth(90),
|
|
||||||
// },
|
|
||||||
// children: tableData,
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
class _TwoRecordsThingy extends StatelessWidget {
|
class _TwoRecordsThingy extends StatelessWidget {
|
||||||
final RecordSingle? sprint;
|
final RecordSingle? sprint;
|
||||||
final RecordSingle? blitz;
|
final RecordSingle? blitz;
|
||||||
|
@ -1228,7 +1096,7 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (sprint != null) FinesseThingy(sprint?.endContext?.finesse, sprint?.endContext?.finessePercentage),
|
if (sprint != null) FinesseThingy(sprint?.endContext?.finesse, sprint?.endContext?.finessePercentage),
|
||||||
if (sprint != null) LineclearsThingy(sprint!.endContext!.clears, sprint!.endContext!.lines, sprint!.endContext!.holds, sprint!.endContext!.tSpins),
|
if (sprint != null) LineclearsThingy(sprint!.endContext!.clears, sprint!.endContext!.lines, sprint!.endContext!.holds, sprint!.endContext!.tSpins),
|
||||||
if (sprint != null) Text("${sprint!.endContext!.inputs} KP • ${f2.format(sprint!.endContext!.kps)} KpS")
|
if (sprint != null) Text("${sprint!.endContext!.inputs} KP • ${f2.format(sprint!.endContext!.kps)} KPS")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
|
@ -1289,7 +1157,7 @@ class _TwoRecordsThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (blitz != null) FinesseThingy(blitz?.endContext?.finesse, blitz?.endContext?.finessePercentage),
|
if (blitz != null) FinesseThingy(blitz?.endContext?.finesse, blitz?.endContext?.finessePercentage),
|
||||||
if (blitz != null) LineclearsThingy(blitz!.endContext!.clears, blitz!.endContext!.lines, blitz!.endContext!.holds, blitz!.endContext!.tSpins),
|
if (blitz != null) LineclearsThingy(blitz!.endContext!.clears, blitz!.endContext!.lines, blitz!.endContext!.holds, blitz!.endContext!.tSpins),
|
||||||
if (blitz != null) Text("${blitz!.endContext!.piecesPlaced} P • ${blitz!.endContext!.inputs} KP • ${f2.format(blitz!.endContext!.kpp)} KpP • ${f2.format(blitz!.endContext!.kps)} KpS")
|
if (blitz != null) Text("${blitz!.endContext!.piecesPlaced} P • ${blitz!.endContext!.inputs} KP • ${f2.format(blitz!.endContext!.kpp)} KPP • ${f2.format(blitz!.endContext!.kps)} KPS")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
@ -1405,8 +1273,8 @@ class _RecordThingy extends StatelessWidget {
|
||||||
),
|
),
|
||||||
FinesseThingy(record?.endContext?.finesse, record?.endContext?.finessePercentage),
|
FinesseThingy(record?.endContext?.finesse, record?.endContext?.finessePercentage),
|
||||||
LineclearsThingy(record!.endContext!.clears, record!.endContext!.lines, record!.endContext!.holds, record!.endContext!.tSpins),
|
LineclearsThingy(record!.endContext!.clears, record!.endContext!.lines, record!.endContext!.holds, record!.endContext!.tSpins),
|
||||||
if (record!.stream.contains("40l")) Text("${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kps)} KpS"),
|
if (record!.stream.contains("40l")) Text("${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kps)} KPS"),
|
||||||
if (record!.stream.contains("blitz")) Text("${record!.endContext!.piecesPlaced} P • ${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kpp)} KpP • ${f2.format(record!.endContext!.kps)} KpS")
|
if (record!.stream.contains("blitz")) Text("${record!.endContext!.piecesPlaced} P • ${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kpp)} KPP • ${f2.format(record!.endContext!.kps)} KPS")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -8,13 +6,16 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/views/main_view.dart' show MainView;
|
import 'package:tetra_stats/views/main_view.dart' show MainView;
|
||||||
import 'package:tetra_stats/utils/text_shadow.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
|
||||||
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
|
var _chartsShortTitlesDropdowns = <DropdownMenuItem>[for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value),)];
|
||||||
Stats _chartsX = Stats.tr;
|
Stats _chartsX = Stats.tr;
|
||||||
Stats _chartsY = Stats.apm;
|
Stats _chartsY = Stats.apm;
|
||||||
|
late TooltipBehavior _tooltipBehavior;
|
||||||
|
late ZoomPanBehavior _zoomPanBehavior;
|
||||||
List<DropdownMenuItem> _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
List<DropdownMenuItem> _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
|
||||||
|
List<_MyScatterSpot> _spots = [];
|
||||||
Stats _sortBy = Stats.tr;
|
Stats _sortBy = Stats.tr;
|
||||||
late List<TetrioPlayerFromLeaderboard> they;
|
late List<TetrioPlayerFromLeaderboard> they;
|
||||||
bool _reversed = false;
|
bool _reversed = false;
|
||||||
|
@ -48,7 +49,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
late double yScale;
|
late double yScale;
|
||||||
String headerTooltip = t.pseudoTooltipHeaderInit;
|
String headerTooltip = t.pseudoTooltipHeaderInit;
|
||||||
String footerTooltip = t.pseudoTooltipFooterInit;
|
String footerTooltip = t.pseudoTooltipFooterInit;
|
||||||
int hoveredPointId = -1;
|
ValueNotifier<int> hoveredPointId = ValueNotifier<int>(-1);
|
||||||
double scaleFactor = 5e2;
|
double scaleFactor = 5e2;
|
||||||
double dragFactor = 7e2;
|
double dragFactor = 7e2;
|
||||||
|
|
||||||
|
@ -56,6 +57,37 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
void initState() {
|
void initState() {
|
||||||
_scrollController = ScrollController();
|
_scrollController = ScrollController();
|
||||||
_tabController = TabController(length: 6, vsync: this);
|
_tabController = TabController(length: 6, vsync: this);
|
||||||
|
_zoomPanBehavior = ZoomPanBehavior(
|
||||||
|
enablePinching: true,
|
||||||
|
enableSelectionZooming: true,
|
||||||
|
enableMouseWheelZooming : true,
|
||||||
|
enablePanning: true,
|
||||||
|
);
|
||||||
|
_tooltipBehavior = TooltipBehavior(
|
||||||
|
color: Colors.black,
|
||||||
|
borderColor: Colors.white,
|
||||||
|
enable: true,
|
||||||
|
animationDuration: 0,
|
||||||
|
builder: (dynamic data, dynamic point, dynamic series,
|
||||||
|
int pointIndex, int seriesIndex) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"${data.nickname} (${data.rank.toUpperCase()})",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('${_f4.format(data.x)} ${chartsShortTitles[_chartsX]}\n${_f4.format(data.y)} ${chartsShortTitles[_chartsY]}')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
||||||
windowManager.getTitle().then((value) => _oldWindowTitle = value);
|
windowManager.getTitle().then((value) => _oldWindowTitle = value);
|
||||||
windowManager.setTitle("Tetra Stats: ${widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())}");
|
windowManager.setTitle("Tetra Stats: ${widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())}");
|
||||||
|
@ -63,56 +95,22 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
super.initState();
|
super.initState();
|
||||||
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
||||||
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
||||||
recalculateBoundaries();
|
createSpots();
|
||||||
resetScale();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void recalculateBoundaries(){
|
void createSpots(){
|
||||||
actualMinX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
_spots = [
|
||||||
num n = min(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX));
|
for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"])
|
||||||
if (value.getStatByEnum(_chartsX) == n) {
|
if (entry.apm != 0.0 && entry.vs != 0.0) // prevents from ScatterChart "Offset argument contained a NaN value." exception
|
||||||
return value;
|
_MyScatterSpot(
|
||||||
} else {
|
entry.getStatByEnum(_chartsX).toDouble(),
|
||||||
return element;
|
entry.getStatByEnum(_chartsY).toDouble(),
|
||||||
}
|
entry.userId,
|
||||||
}).getStatByEnum(_chartsX).toDouble();
|
entry.username,
|
||||||
actualMaxX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
entry.rank,
|
||||||
num n = max(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX));
|
rankColors[entry.rank]??Colors.white
|
||||||
if (value.getStatByEnum(_chartsX) == n) {
|
)
|
||||||
return value;
|
];
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).getStatByEnum(_chartsX).toDouble();
|
|
||||||
actualMinY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
|
||||||
num n = min(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
|
|
||||||
if (value.getStatByEnum(_chartsY) == n) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).getStatByEnum(_chartsY).toDouble();
|
|
||||||
actualMaxY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
|
|
||||||
num n = max(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
|
|
||||||
if (value.getStatByEnum(_chartsY) == n) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}).getStatByEnum(_chartsY).toDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetScale(){
|
|
||||||
maxX = actualMaxX;
|
|
||||||
minX = actualMinX;
|
|
||||||
maxY = actualMaxY;
|
|
||||||
minY = actualMinY;
|
|
||||||
recalculateScales();
|
|
||||||
}
|
|
||||||
|
|
||||||
void recalculateScales(){
|
|
||||||
xScale = maxX - minX;
|
|
||||||
yScale = maxY - minY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -123,46 +121,15 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void dragHandler(DragUpdateDetails dragUpdDet){
|
|
||||||
setState(() {
|
|
||||||
minX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
|
||||||
maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
|
|
||||||
minY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
|
||||||
maxY += (yScale / dragFactor) * dragUpdDet.delta.dy;
|
|
||||||
|
|
||||||
if (minX < actualMinX) {
|
|
||||||
minX = actualMinX;
|
|
||||||
maxX = actualMinX + xScale;
|
|
||||||
}
|
|
||||||
if (maxX > actualMaxX) {
|
|
||||||
maxX = actualMaxX;
|
|
||||||
minX = maxX - xScale;
|
|
||||||
}
|
|
||||||
if(minY < actualMinY){
|
|
||||||
minY = actualMinY;
|
|
||||||
maxY = actualMinY + yScale;
|
|
||||||
}
|
|
||||||
if(maxY > actualMaxY){
|
|
||||||
maxY = actualMaxY;
|
|
||||||
minY = actualMaxY - yScale;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _justUpdate() {
|
void _justUpdate() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
GlobalKey graphKey = GlobalKey();
|
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||||
EdgeInsets padding = bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
|
|
||||||
double graphStartX = padding.left;
|
|
||||||
double graphEndX = MediaQuery.sizeOf(context).width - padding.right;
|
|
||||||
if (previousAxisTitles != _chartsX.toString()+_chartsY.toString()){
|
if (previousAxisTitles != _chartsX.toString()+_chartsY.toString()){
|
||||||
recalculateBoundaries();
|
createSpots();
|
||||||
resetScale();
|
|
||||||
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
|
||||||
}
|
}
|
||||||
final t = Translations.of(context);
|
final t = Translations.of(context);
|
||||||
|
@ -230,7 +197,8 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
Wrap(
|
Wrap(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
spacing: 25,
|
crossAxisAlignment: WrapCrossAlignment.end,
|
||||||
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -271,115 +239,41 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
IconButton(onPressed: () => _zoomPanBehavior.reset(), icon: const Icon(Icons.refresh), alignment: Alignment.center,)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (widget.rank[1]["entries"].length > 1)
|
if (widget.rank[1]["entries"].length > 1)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: MediaQuery.of(context).size.height - 104,
|
height: MediaQuery.of(context).size.height - 104,
|
||||||
|
child: Padding(
|
||||||
|
padding: bigScreen ? const EdgeInsets.fromLTRB(40, 10, 40, 20) : const EdgeInsets.fromLTRB(0, 10, 16, 20),
|
||||||
child: Listener(
|
child: Listener(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onPointerSignal: (signal) {
|
onPointerSignal: (signal) {
|
||||||
if (signal is PointerScrollEvent) {
|
if (signal is PointerScrollEvent) {
|
||||||
RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox;
|
|
||||||
Offset graphPosition = graphBox.localToGlobal(Offset.zero);
|
|
||||||
double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX);
|
|
||||||
double scrollPosRelativeY = (signal.position.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height
|
|
||||||
double newMinX, newMaxX, newMinY, newMaxY;
|
|
||||||
newMinX = minX - (xScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeX;
|
|
||||||
newMaxX = maxX + (xScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeX);
|
|
||||||
newMinY = minY - (yScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeY);
|
|
||||||
newMaxY = maxY + (yScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeY;
|
|
||||||
if ((newMaxX - newMinX).isNegative) return;
|
|
||||||
if ((newMaxY - newMinY).isNegative) return;
|
|
||||||
setState(() {
|
setState(() {
|
||||||
minX = max(newMinX, actualMinX);
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy); // TODO: find a better way to stop scrolling in NestedScrollView
|
||||||
maxX = min(newMaxX, actualMaxX);
|
|
||||||
minY = max(newMinY, actualMinY);
|
|
||||||
maxY = min(newMaxY, actualMaxY);
|
|
||||||
recalculateScales();
|
|
||||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy);
|
|
||||||
});
|
});
|
||||||
}},
|
|
||||||
child: GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onDoubleTap: () {
|
|
||||||
setState(() {
|
|
||||||
minX = actualMinX;
|
|
||||||
maxX = actualMaxX;
|
|
||||||
minY = actualMinY;
|
|
||||||
maxY = actualMaxY;
|
|
||||||
recalculateScales();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// TODO: Figure out wtf is going on with gestures
|
|
||||||
child: Padding(
|
|
||||||
padding: bigScreen ? const EdgeInsets.fromLTRB(40, 40, 40, 48) : const EdgeInsets.fromLTRB(0, 40, 16, 48),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
ScatterChart(
|
|
||||||
key: graphKey,
|
|
||||||
ScatterChartData(
|
|
||||||
minX: minX,
|
|
||||||
maxX: maxX,
|
|
||||||
minY: minY,
|
|
||||||
maxY: maxY,
|
|
||||||
clipData: const FlClipData.all(),
|
|
||||||
scatterSpots: [
|
|
||||||
for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"])
|
|
||||||
if (entry.apm != 0.0 && entry.vs != 0.0) // prevents from ScatterChart "Offset argument contained a NaN value." exception
|
|
||||||
_MyScatterSpot(
|
|
||||||
entry.getStatByEnum(_chartsX).toDouble(),
|
|
||||||
entry.getStatByEnum(_chartsY).toDouble(),
|
|
||||||
entry.userId,
|
|
||||||
entry.username,
|
|
||||||
dotPainter: FlDotCirclePainter(color: rankColors[entry.rank]??Colors.white, radius: 3))
|
|
||||||
],
|
|
||||||
scatterTouchData: ScatterTouchData(
|
|
||||||
handleBuiltInTouches: false,
|
|
||||||
touchCallback:(touchEvent, touchResponse) {
|
|
||||||
if (touchEvent is FlPanUpdateEvent){
|
|
||||||
dragHandler(touchEvent.details);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (touchEvent is FlPointerHoverEvent){
|
|
||||||
setState(() {
|
|
||||||
if (touchResponse?.touchedSpot == null) {
|
|
||||||
hoveredPointId = -1;
|
|
||||||
} else {
|
|
||||||
hoveredPointId = touchResponse!.touchedSpot!.spotIndex;
|
|
||||||
_MyScatterSpot castedPoint = touchResponse.touchedSpot!.spot as _MyScatterSpot;
|
|
||||||
headerTooltip = castedPoint.nickname;
|
|
||||||
footerTooltip = "${_f4.format(castedPoint.x)} ${chartsShortTitles[_chartsX]}; ${_f4.format(castedPoint.y)} ${chartsShortTitles[_chartsY]}";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (touchEvent is FlPointerExitEvent){
|
|
||||||
setState(() {hoveredPointId = -1;});
|
|
||||||
}
|
|
||||||
if (touchEvent is FlTapUpEvent && touchResponse?.touchedSpot?.spot != null){
|
|
||||||
_MyScatterSpot spot = touchResponse!.touchedSpot!.spot as _MyScatterSpot;
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: spot.nickname), maintainState: false));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
child: SfCartesianChart(
|
||||||
),
|
tooltipBehavior: _tooltipBehavior,
|
||||||
swapAnimationDuration: const Duration(milliseconds: 150), // Optional
|
zoomPanBehavior: _zoomPanBehavior,
|
||||||
swapAnimationCurve: Curves.linear, // Optional
|
//primaryXAxis: CategoryAxis(),
|
||||||
),
|
series: [
|
||||||
Padding(
|
ScatterSeries(
|
||||||
padding: EdgeInsets.fromLTRB(graphStartX+8, padding.top/2+8, 0, 0),
|
enableTooltip: true,
|
||||||
child: Column(
|
dataSource: _spots,
|
||||||
children: [
|
animationDuration: 0,
|
||||||
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 24, color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(headerTooltip)),
|
pointColorMapper: (data, _) => data.color,
|
||||||
AnimatedDefaultTextStyle(style: TextStyle(fontFamily: "Eurostile Round", color: Color.fromARGB(hoveredPointId == -1 ? 100 : 255, 255, 255, 255), shadows: hoveredPointId != -1 ? textShadow : null), duration: Durations.medium1, curve: Curves.elasticInOut, child: Text(footerTooltip)),
|
xValueMapper: (data, _) => data.x,
|
||||||
],
|
yValueMapper: (data, _) => data.y,
|
||||||
),
|
onPointTap: (point) => Navigator.push(context, MaterialPageRoute(builder: (context) => MainView(player: _spots[point.pointIndex!].nickname), maintainState: false)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
))
|
))
|
||||||
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
|
else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28)))
|
||||||
],
|
],
|
||||||
|
@ -424,7 +318,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
checkColor: Colors.black,
|
checkColor: Colors.black,
|
||||||
onChanged: ((value) {
|
onChanged: ((value) {
|
||||||
_reversed = value!;
|
_reversed = value!;
|
||||||
setState(() {});
|
setState(() {
|
||||||
|
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -441,7 +337,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
value: _country,
|
value: _country,
|
||||||
onChanged: ((value) {
|
onChanged: ((value) {
|
||||||
_country = value;
|
_country = value;
|
||||||
setState(() {});
|
setState(() {
|
||||||
|
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -456,7 +354,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(they[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
title: Text(they[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||||
subtitle: Text(_sortBy == Stats.tr ? "${_f2.format(they[index].apm)} APM, ${_f2.format(they[index].pps)} PPS, ${_f2.format(they[index].vs)} VS, ${_f2.format(they[index].nerdStats.app)} APP, ${_f2.format(they[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(they[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}"),
|
subtitle: Text(
|
||||||
|
_sortBy == Stats.tr ? "${_f2.format(they[index].apm)} APM, ${_f2.format(they[index].pps)} PPS, ${_f2.format(they[index].vs)} VS, ${_f2.format(they[index].nerdStats.app)} APP, ${_f2.format(they[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(they[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
|
||||||
|
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
@ -635,10 +535,12 @@ class _ListEntry extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyScatterSpot extends ScatterSpot {
|
class _MyScatterSpot{
|
||||||
|
num x;
|
||||||
|
num y;
|
||||||
String id;
|
String id;
|
||||||
String nickname;
|
String nickname;
|
||||||
//Color color;
|
String rank;
|
||||||
//FlDotPainter painter = FlDotCirclePainter(color: color, radius: 2);
|
Color color;
|
||||||
_MyScatterSpot(super.x, super.y, this.id, this.nickname, {super.dotPainter});
|
_MyScatterSpot(this.x, this.y, this.id, this.nickname, this.rank, this.color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ class RanksAverages extends State<RankAveragesView> {
|
||||||
leading: Image.asset("res/tetrio_tl_alpha_ranks/${keys[index]}.png", height: 48),
|
leading: Image.asset("res/tetrio_tl_alpha_ranks/${keys[index]}.png", height: 48),
|
||||||
title: Text(t.players(n: averages[keys[index]]?[1]["players"]), style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
title: Text(t.players(n: averages[keys[index]]?[1]["players"]), style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
||||||
subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM",
|
subtitle: Text("${f2.format(averages[keys[index]]?[0].apm)} APM, ${f2.format(averages[keys[index]]?[0].pps)} PPS, ${f2.format(averages[keys[index]]?[0].vs)} VS, ${f2.format(averages[keys[index]]?[0].nerdStats.app)} APP, ${f2.format(averages[keys[index]]?[0].nerdStats.vsapm)} VS/APM",
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
|
trailing: Text("${f2.format(averages[keys[index]]?[1]["toEnterTR"])} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
|
||||||
onTap: (){
|
onTap: (){
|
||||||
if (averages[keys[index]]?[1]["players"] > 0) {
|
if (averages[keys[index]]?[1]["players"] > 0) {
|
||||||
|
|
|
@ -48,11 +48,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
|
|
||||||
Future<void> _getPreferences() async {
|
Future<void> _getPreferences() async {
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.getBool("showPositions") != null) {
|
showPositions = prefs.getBool("showPositions") ?? false;
|
||||||
showPositions = prefs.getBool("showPositions")!;
|
|
||||||
} else {
|
|
||||||
showPositions = false;
|
|
||||||
}
|
|
||||||
_setDefaultNickname(prefs.getString("player"));
|
_setDefaultNickname(prefs.getString("player"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +72,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
|
|
||||||
Future<void> _removePlayer() async {
|
Future<void> _removePlayer() async {
|
||||||
await prefs.remove('player');
|
await prefs.remove('player');
|
||||||
await _setDefaultNickname("dan63047");
|
await _setDefaultNickname("6098518e3d5155e6ec429cdc");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -97,7 +93,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.exportDB),
|
title: Text(t.exportDB),
|
||||||
subtitle: Text(t.exportDBDescription),
|
subtitle: Text(t.exportDBDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (kIsWeb){
|
if (kIsWeb){
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
||||||
|
@ -151,7 +147,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(t.importDB),
|
title: Text(t.importDB),
|
||||||
subtitle: Text(t.importDBDescription),
|
subtitle: Text(t.importDBDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (kIsWeb){
|
if (kIsWeb){
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.notForWeb)));
|
||||||
|
@ -262,13 +258,13 @@ class SettingsState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(title: Text(t.customization),
|
ListTile(title: Text(t.customization),
|
||||||
subtitle: Text(t.customizationDescription),
|
subtitle: Text(t.customizationDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.go("/customization");
|
context.go("/customization");
|
||||||
},),
|
},),
|
||||||
ListTile(title: Text(t.lbStats),
|
ListTile(title: Text(t.lbStats),
|
||||||
subtitle: Text(t.lbStatsDescription),
|
subtitle: Text(t.lbStatsDescription, style: const TextStyle(fontFamily: "Eurostile Round Condensed", color: Colors.grey)),
|
||||||
trailing: Switch(value: showPositions, onChanged: (bool value){
|
trailing: Switch(value: showPositions, onChanged: (bool value){
|
||||||
prefs.setBool("showPositions", value);
|
prefs.setBool("showPositions", value);
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -280,7 +276,7 @@ class SettingsState extends State<SettingsView> {
|
||||||
onTap: (){
|
onTap: (){
|
||||||
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
|
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
|
||||||
},
|
},
|
||||||
title: Text(t.aboutApp),
|
title: Text(t.aboutApp, style: const TextStyle(fontWeight: FontWeight.w500),),
|
||||||
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
|
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
|
||||||
trailing: const Icon(Icons.arrow_right)
|
trailing: const Icon(Icons.arrow_right)
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
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/text_shadow.dart';
|
||||||
|
import 'package:tetra_stats/views/main_view.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
late String oldWindowTitle;
|
||||||
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode);
|
||||||
|
|
||||||
|
class SprintAndBlitzView extends StatefulWidget {
|
||||||
|
const SprintAndBlitzView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => SprintAndBlitzState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SprintAndBlitzState extends State<SprintAndBlitzView> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS){
|
||||||
|
windowManager.getTitle().then((value) => oldWindowTitle = value);
|
||||||
|
windowManager.setTitle("Tetra Stats: ${t.settings}");
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose(){
|
||||||
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle(oldWindowTitle);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = Translations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(t.sprintAndBlitsViewTitle),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Table(
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
|
border: TableBorder.all(color: Colors.grey.shade900),
|
||||||
|
columnWidths: {0: const FixedColumnWidth(48)},
|
||||||
|
children: [
|
||||||
|
TableRow(
|
||||||
|
children: [
|
||||||
|
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(t.sprint, textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(t.blitz, textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
for (MapEntry<String, Duration> sprintEntry in sprintAverages.entries) TableRow(
|
||||||
|
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[sprintEntry.key]!.withAlpha(100), rankColors[sprintEntry.key]!.withAlpha(200)])),
|
||||||
|
children: [
|
||||||
|
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/${sprintEntry.key}.png", height: 48)),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(get40lTime(sprintEntry.value.inMicroseconds), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Text(NumberFormat.decimalPattern().format(blitzAverages[sprintEntry.key]), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(t.sprintAndBlitsRelevance(date: dateFormat.format(DateTime(2024, 5, 26))))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,6 +75,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
|
||||||
case ConnectionState.done:
|
case ConnectionState.done:
|
||||||
final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, _sortBy, reversed: reversed, country: _country);
|
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)}");
|
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle("Tetra Stats: ${t.tlLeaderboard} - ${t.players(n: allPlayers!.length)}");
|
||||||
|
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
||||||
return NestedScrollView(
|
return NestedScrollView(
|
||||||
headerSliverBuilder: (context, value) {
|
headerSliverBuilder: (context, value) {
|
||||||
String howManyPlayers(int numberOfPlayers) => Intl.plural(
|
String howManyPlayers(int numberOfPlayers) => Intl.plural(
|
||||||
|
@ -170,18 +171,26 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
|
||||||
},
|
},
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: allPlayers!.length,
|
itemCount: allPlayers!.length,
|
||||||
|
prototypeItem: ListTile(
|
||||||
|
leading: Text("0", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)),
|
||||||
|
title: Text("ehhh...", style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
|
||||||
|
trailing: Container(height: bigScreen ? 48 : 36, width: 1,),
|
||||||
|
subtitle: Text("eh..."),
|
||||||
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
bool bigScreen = MediaQuery.of(context).size.width > 768;
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Text((index+1).toString(), style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) : null),
|
leading: Text(
|
||||||
title: Text(allPlayers[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
|
(index+1).toString(),
|
||||||
|
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 28 : 24, height: 0.9)
|
||||||
|
),
|
||||||
|
title: Text(allPlayers[index].username, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round Extended" : "Eurostile Round", height: 0.9)),
|
||||||
subtitle: Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
|
subtitle: Text(_sortBy == Stats.tr ? "${f2.format(allPlayers[index].apm)} APM, ${f2.format(allPlayers[index].pps)} PPS, ${f2.format(allPlayers[index].vs)} VS, ${f2.format(allPlayers[index].nerdStats.app)} APP, ${f2.format(allPlayers[index].nerdStats.vsapm)} VS/APM" : "${_f4.format(allPlayers[index].getStatByEnum(_sortBy))} ${chartsShortTitles[_sortBy]}",
|
||||||
style: TextStyle(fontFamily: "Eurostile Round Condensed", color: _sortBy == Stats.tr ? Colors.grey : null)),
|
style: TextStyle(fontFamily: "Eurostile Round Condensed", fontSize: bigScreen ? null : 12, color: _sortBy == Stats.tr ? Colors.grey : null)),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text("${f2.format(allPlayers[index].rating)} TR", style: bigScreen ? const TextStyle(fontSize: 28) : null),
|
Text("${f2.format(allPlayers[index].rating)} TR", style: TextStyle(fontSize: bigScreen ? 28 : 22)),
|
||||||
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 16),
|
Image.asset("res/tetrio_tl_alpha_ranks/${allPlayers[index].rank}.png", height: bigScreen ? 48 : 36),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
|
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
|
||||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||||
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
|
import 'package:tetra_stats/views/compare_view.dart' show CompareThingy, CompareBoolThingy;
|
||||||
|
@ -62,8 +61,11 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildComparison(bool bigScreen, bool showMobileSelector){
|
Widget buildComparison(double width, bool showMobileSelector){
|
||||||
return FutureBuilder(future: replayData, builder: (context, snapshot){
|
bool bigScreen = width >= 768;
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: FutureBuilder(future: replayData, builder: (context, snapshot){
|
||||||
late Duration time;
|
late Duration time;
|
||||||
late String readableTime;
|
late String readableTime;
|
||||||
late String reason;
|
late String reason;
|
||||||
|
@ -229,12 +231,12 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
|
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
|
||||||
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kpp :
|
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kpp :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][redSidePlayer].kpp,
|
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][redSidePlayer].kpp,
|
||||||
label: "KpP", higherIsBetter: false, fractionDigits: 2,),
|
label: "KPP", higherIsBetter: false, fractionDigits: 2,),
|
||||||
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kps :
|
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kps :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps,
|
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps,
|
||||||
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kps :
|
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kps :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kps : snapshot.data!.stats[roundSelector][redSidePlayer].kps,
|
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kps : snapshot.data!.stats[roundSelector][redSidePlayer].kps,
|
||||||
label: "KpS", higherIsBetter: true, fractionDigits: 2,),
|
label: "KPS", higherIsBetter: true, fractionDigits: 2,),
|
||||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][greenSidePlayer].linesCleared,
|
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][greenSidePlayer].linesCleared,
|
||||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][redSidePlayer].linesCleared,
|
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].linesCleared : snapshot.data!.stats[roundSelector][redSidePlayer].linesCleared,
|
||||||
label: "Lines Cleared", higherIsBetter: true),
|
label: "Lines Cleared", higherIsBetter: true),
|
||||||
|
@ -245,7 +247,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
|
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
|
||||||
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].spp :
|
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].spp :
|
||||||
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][redSidePlayer].spp,
|
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][redSidePlayer].spp,
|
||||||
label: "SpP", higherIsBetter: true, fractionDigits: 2,),
|
label: "SPP", higherIsBetter: true, fractionDigits: 2,),
|
||||||
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][greenSidePlayer].finessePercentage * 100,
|
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][greenSidePlayer].finessePercentage * 100,
|
||||||
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][redSidePlayer].finessePercentage * 100,
|
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].finessePercentage * 100 : snapshot.data!.stats[roundSelector][redSidePlayer].finessePercentage * 100,
|
||||||
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),
|
label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true),
|
||||||
|
@ -477,7 +479,8 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildRoundSelector(double width){
|
Widget buildRoundSelector(double width){
|
||||||
|
@ -684,21 +687,17 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 768),
|
constraints: const BoxConstraints(maxWidth: 768),
|
||||||
child: buildComparison(viewportWidth > 768, true)
|
child: buildComparison(viewportWidth, true)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
double comparisonWidth = viewportWidth - 450 - 16;
|
||||||
|
comparisonWidth = comparisonWidth > 768 ? 768 : comparisonWidth;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
buildComparison(comparisonWidth, false),
|
||||||
width: 768,
|
buildRoundSelector(450)
|
||||||
child: buildComparison(true, false)
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 768),
|
|
||||||
child: buildRoundSelector(max(viewportWidth-768-16, 200)),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,16 +21,17 @@ class FinesseThingy extends StatelessWidget{
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
children: [
|
children: [
|
||||||
Text("f", style: TextStyle(
|
const Text("f", style: TextStyle(
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
fontSize: 65,
|
fontSize: 65,
|
||||||
height: 1.2,
|
height: 1.2,
|
||||||
)),
|
)),
|
||||||
Positioned(child: Text("inesse", style: TextStyle(fontFamily: "Eurostile Round Extended")), left: 25, top: 20),
|
const Positioned(left: 25, top: 20, child: Text("inesse", style: TextStyle(fontFamily: "Eurostile Round Extended"))),
|
||||||
Positioned(
|
Positioned(
|
||||||
|
right: 0, top: 20,
|
||||||
child: Text("${finesse != null ? finesse!.faults : "---"}F", style: TextStyle(
|
child: Text("${finesse != null ? finesse!.faults : "---"}F", style: TextStyle(
|
||||||
color: getFinesseColor()
|
color: getFinesseColor()
|
||||||
)), right: 0, top: 20),
|
))),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
child: Text("${finesse != null ? f2.format(finessePercentage! * 100) : "---.--"}%", style: TextStyle(
|
child: Text("${finesse != null ? f2.format(finessePercentage! * 100) : "---.--"}%", style: TextStyle(
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
import 'package:tetra_stats/gen/strings.g.dart';
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
import 'package:tetra_stats/utils/colors_functions.dart';
|
import 'package:tetra_stats/utils/colors_functions.dart';
|
||||||
import 'package:tetra_stats/utils/numers_formats.dart';
|
import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/widgets/tl_thingy.dart';
|
|
||||||
|
|
||||||
class GaugetNum extends StatelessWidget {
|
class GaugetNum extends StatelessWidget {
|
||||||
final num playerStat;
|
final num playerStat;
|
||||||
|
@ -98,7 +97,7 @@ class GaugetNum extends StatelessWidget {
|
||||||
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
|
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
|
||||||
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
|
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
|
||||||
),),
|
),),
|
||||||
if ((oldTl != null && oldTl!.gamesPlayed > 0) && pos != null) const TextSpan(text: " • "),
|
if (oldPlayerStat != null && pos != null) const TextSpan(text: " • "),
|
||||||
if (pos != null) TextSpan(text: pos!.position >= 1000 ? "${t.top} ${f2.format(pos!.percentage*100)}%" : "№${pos!.position}", style: TextStyle(color: getColorOfRank(pos!.position)))
|
if (pos != null) TextSpan(text: pos!.position >= 1000 ? "${t.top} ${f2.format(pos!.percentage*100)}%" : "№${pos!.position}", style: TextStyle(color: getColorOfRank(pos!.position)))
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.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/utils/numers_formats.dart';
|
||||||
|
|
||||||
class Graphs extends StatelessWidget{
|
class Graphs extends StatelessWidget{
|
||||||
|
@ -20,6 +21,10 @@ class Graphs extends StatelessWidget{
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
double attack = apm / 60 * 0.4;
|
||||||
|
double speed = pps / 3.75;
|
||||||
|
double defense = nerdStats.dss * 1.15;
|
||||||
|
double cheese = nerdStats.cheese / 110;
|
||||||
return Wrap(
|
return Wrap(
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
|
@ -27,7 +32,7 @@ class Graphs extends StatelessWidget{
|
||||||
crossAxisAlignment: WrapCrossAlignment.start,
|
crossAxisAlignment: WrapCrossAlignment.start,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
if (true) Padding( // vs graph
|
||||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
|
@ -86,7 +91,7 @@ class Graphs extends StatelessWidget{
|
||||||
borderColor: Colors.transparent,
|
borderColor: Colors.transparent,
|
||||||
dataEntries: [
|
dataEntries: [
|
||||||
const RadarEntry(value: 0),
|
const RadarEntry(value: 0),
|
||||||
const RadarEntry(value: 0),
|
const RadarEntry(value: 180),
|
||||||
const RadarEntry(value: 0),
|
const RadarEntry(value: 0),
|
||||||
const RadarEntry(value: 0),
|
const RadarEntry(value: 0),
|
||||||
const RadarEntry(value: 0),
|
const RadarEntry(value: 0),
|
||||||
|
@ -104,7 +109,7 @@ class Graphs extends StatelessWidget{
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding( // psq graph
|
||||||
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 310,
|
height: 310,
|
||||||
|
@ -126,7 +131,7 @@ class Graphs extends StatelessWidget{
|
||||||
case 1:
|
case 1:
|
||||||
return RadarChartTitle(text: 'Stride\n${percentage.format(playstyle.stride)}', angle: 0, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'Stride\n${percentage.format(playstyle.stride)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
case 2:
|
case 2:
|
||||||
return RadarChartTitle(text: 'Inf Ds\n${percentage.format(playstyle.infds)}', angle: angle + 180, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'Inf DS\n${percentage.format(playstyle.infds)}', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
case 3:
|
case 3:
|
||||||
return RadarChartTitle(text: 'Plonk\n${percentage.format(playstyle.plonk)}', angle: 0, positionPercentageOffset: 0.05);
|
return RadarChartTitle(text: 'Plonk\n${percentage.format(playstyle.plonk)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
default:
|
default:
|
||||||
|
@ -147,19 +152,9 @@ class Graphs extends StatelessWidget{
|
||||||
borderColor: Colors.transparent,
|
borderColor: Colors.transparent,
|
||||||
dataEntries: [
|
dataEntries: [
|
||||||
const RadarEntry(value: 0),
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 1),
|
||||||
const RadarEntry(value: 0),
|
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),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -169,6 +164,59 @@ class Graphs extends StatelessWidget{
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding( // sq graph
|
||||||
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 310,
|
||||||
|
width: 310,
|
||||||
|
child: RadarChart(
|
||||||
|
RadarChartData(
|
||||||
|
radarShape: RadarShape.polygon,
|
||||||
|
tickCount: 4,
|
||||||
|
ticksTextStyle: const TextStyle(color: Colors.white24, 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),
|
||||||
|
titleTextStyle: const TextStyle(height: 1.1),
|
||||||
|
radarTouchData: RadarTouchData(),
|
||||||
|
getTitle: (index, angle) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return RadarChartTitle(text: '${t.graphs.attack}\n${f2.format(apm)} APM', angle: 0, positionPercentageOffset: 0.05);
|
||||||
|
case 1:
|
||||||
|
return RadarChartTitle(text: '${t.graphs.speed}\n${f2.format(pps)} PPS', angle: 0, positionPercentageOffset: 0.05);
|
||||||
|
case 2:
|
||||||
|
return RadarChartTitle(text: '${t.graphs.defense}\n${f2.format(nerdStats.dss)} DS/S', angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
|
case 3:
|
||||||
|
return RadarChartTitle(text: '${t.graphs.cheese}\n${f3.format(nerdStats.cheese)}', angle: 0, positionPercentageOffset: 0.05);
|
||||||
|
default:
|
||||||
|
return const RadarChartTitle(text: '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataSets: [
|
||||||
|
RadarDataSet(
|
||||||
|
dataEntries: [
|
||||||
|
RadarEntry(value: attack),
|
||||||
|
RadarEntry(value: speed),
|
||||||
|
RadarEntry(value: defense),
|
||||||
|
RadarEntry(value: cheese),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RadarDataSet(
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
borderColor: Colors.transparent,
|
||||||
|
dataEntries: [
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 1.2),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
import 'package:tetra_stats/data_objects/glicko.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 TLProgress extends StatelessWidget{
|
||||||
|
final TetraLeagueAlpha tlData;
|
||||||
|
final String? nextRank;
|
||||||
|
final String? previousRank;
|
||||||
|
final double? nextRankTRcutoff;
|
||||||
|
final double? previousRankTRcutoff;
|
||||||
|
final double? nextRankGlickoCutoff;
|
||||||
|
final double? previousGlickoCutoff;
|
||||||
|
final double? nextRankTRcutoffTarget;
|
||||||
|
final double? previousRankTRcutoffTarget;
|
||||||
|
|
||||||
|
const TLProgress({super.key, required this.tlData, this.nextRank, this.previousRank, this.nextRankTRcutoff, this.previousRankTRcutoff, this.nextRankGlickoCutoff, this.previousGlickoCutoff, this.nextRankTRcutoffTarget, this.previousRankTRcutoffTarget});
|
||||||
|
|
||||||
|
double getBarPosition(){
|
||||||
|
return min(max(0, 1 - (tlData.standing - tlData.nextAt)/(tlData.prevAt - tlData.nextAt)), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double? getBarTR(double tr){
|
||||||
|
return min(max(0, (tr - previousRankTRcutoff!)/(nextRankTRcutoff! - previousRankTRcutoff!)), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (nextRank == null && previousRank == null && nextRankTRcutoff == null && previousRankTRcutoff == null && nextRankGlickoCutoff == null && previousGlickoCutoff == null && nextRankTRcutoffTarget == null && previousRankTRcutoffTarget == null) return Container();
|
||||||
|
final glickoForWin = rate(tlData.glicko!, tlData.rd!, 0.06, [[tlData.glicko!, tlData.rd!, 1]], {})[0]-tlData.glicko!;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: 48,
|
||||||
|
child: Stack(
|
||||||
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Positioned(left: 0,
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
||||||
|
children: [
|
||||||
|
if (tlData.prevAt > 0) TextSpan(text: "№ ${f0.format(tlData.prevAt)}"),
|
||||||
|
if (tlData.prevAt > 0 && previousRankTRcutoff != null) const TextSpan(text: "\n"),
|
||||||
|
if (previousRankTRcutoff != null) TextSpan(text: "${f2.format(previousRankTRcutoff)} (${comparef2.format(previousRankTRcutoff!-tlData.rating)}) TR"),
|
||||||
|
if ((tlData.prevAt > 0 || previousRankTRcutoff != null) && previousGlickoCutoff != null) const TextSpan(text: "\n"),
|
||||||
|
if (previousGlickoCutoff != null) TextSpan(text: (tlData.standing > tlData.prevAt || ((tlData.glicko!-previousGlickoCutoff!)/glickoForWin < 0.5 && tlData.percentileRank != "d")) ? t.demotionOnNextLoss : t.numOfdefeats(losses: f2.format((tlData.glicko!-previousGlickoCutoff!)/glickoForWin)), style: TextStyle(color: (tlData.standing > tlData.prevAt || ((tlData.glicko!-previousGlickoCutoff!)/glickoForWin < 0.5 && tlData.percentileRank != "d")) ? Colors.redAccent : null))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(right: 0,
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round", fontSize: 12),
|
||||||
|
children: [
|
||||||
|
if (tlData.nextAt > 0) TextSpan(text: "№ ${f0.format(tlData.nextAt)}"),
|
||||||
|
if (tlData.nextAt > 0 && nextRankTRcutoff != null) const TextSpan(text: "\n"),
|
||||||
|
if (nextRankTRcutoff != null) TextSpan(text: "${f2.format(nextRankTRcutoff)} (${comparef2.format(nextRankTRcutoff!-tlData.rating)}) TR"),
|
||||||
|
if ((tlData.nextAt > 0 || nextRankTRcutoff != null) && nextRankGlickoCutoff != null) const TextSpan(text: "\n"),
|
||||||
|
if (nextRankGlickoCutoff != null) TextSpan(text: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? t.promotionOnNextWin : t.numOfVictories(wins: f2.format((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin)), style: TextStyle(color: (tlData.standing < tlData.nextAt || ((nextRankGlickoCutoff!-tlData.glicko!)/glickoForWin < 0.5 && tlData.percentileRank != "x")) ? Colors.greenAccent : null))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],),
|
||||||
|
),
|
||||||
|
SfLinearGauge(
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
interval: 1,
|
||||||
|
ranges: [
|
||||||
|
if (previousRankTRcutoff != null && nextRankTRcutoff != null) LinearGaugeRange(endValue: getBarTR(tlData.rating)!, color: Colors.cyanAccent, position: LinearElementPosition.cross)
|
||||||
|
else if (tlData.standing != -1) LinearGaugeRange(endValue: getBarPosition(), color: Colors.cyanAccent, position: LinearElementPosition.cross),
|
||||||
|
if (previousRankTRcutoff != null && previousRankTRcutoffTarget != null) LinearGaugeRange(endValue: getBarTR(previousRankTRcutoffTarget!)!, color: Colors.greenAccent, position: LinearElementPosition.inside),
|
||||||
|
if (nextRankTRcutoff != null && nextRankTRcutoffTarget != null && previousRankTRcutoff != null) LinearGaugeRange(startValue: getBarTR(nextRankTRcutoffTarget!)!, endValue: 1, color: Colors.yellowAccent, position: LinearElementPosition.inside)
|
||||||
|
],
|
||||||
|
markerPointers: [
|
||||||
|
LinearShapePointer(value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), position: LinearElementPosition.cross, shapeType: LinearShapePointerType.diamond, color: Colors.white, height: 20),
|
||||||
|
if (tlData.standing != -1) LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: (previousRankTRcutoff != null && nextRankTRcutoff != null) ? getBarTR(tlData.rating)! : getBarPosition(), child: Text("№ ${NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(tlData.standing)}", style: const TextStyle(fontSize: 14),))
|
||||||
|
],
|
||||||
|
isMirrored: true,
|
||||||
|
showTicks: true,
|
||||||
|
showLabels: false
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,14 +9,11 @@ import 'package:tetra_stats/utils/numers_formats.dart';
|
||||||
import 'package:tetra_stats/widgets/gauget_num.dart';
|
import 'package:tetra_stats/widgets/gauget_num.dart';
|
||||||
import 'package:tetra_stats/widgets/graphs.dart';
|
import 'package:tetra_stats/widgets/graphs.dart';
|
||||||
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
import 'package:tetra_stats/widgets/stat_sell_num.dart';
|
||||||
|
import 'package:tetra_stats/widgets/tl_progress_bar.dart';
|
||||||
|
|
||||||
var fDiff = NumberFormat("+#,###.###;-#,###.###");
|
var fDiff = NumberFormat("+#,###.###;-#,###.###");
|
||||||
var intFDiff = NumberFormat("+#,###;-#,###");
|
var intFDiff = NumberFormat("+#,###;-#,###");
|
||||||
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||||
late RangeValues _currentRangeValues;
|
|
||||||
TetraLeagueAlpha? oldTl;
|
|
||||||
late TetraLeagueAlpha currentTl;
|
|
||||||
late List<TetrioPlayer> sortedStates;
|
|
||||||
|
|
||||||
class TLThingy extends StatefulWidget {
|
class TLThingy extends StatefulWidget {
|
||||||
final TetraLeagueAlpha tl;
|
final TetraLeagueAlpha tl;
|
||||||
|
@ -29,10 +26,12 @@ class TLThingy extends StatefulWidget {
|
||||||
final PlayerLeaderboardPosition? lbPositions;
|
final PlayerLeaderboardPosition? lbPositions;
|
||||||
final TetraLeagueAlpha? averages;
|
final TetraLeagueAlpha? averages;
|
||||||
final double? thatRankCutoff;
|
final double? thatRankCutoff;
|
||||||
|
final double? thatRankCutoffGlicko;
|
||||||
final double? thatRankTarget;
|
final double? thatRankTarget;
|
||||||
final double? nextRankCutoff;
|
final double? nextRankCutoff;
|
||||||
|
final double? nextRankCutoffGlicko;
|
||||||
final double? nextRankTarget;
|
final double? nextRankTarget;
|
||||||
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, this.averages, this.nextRankCutoff = 25000, this.thatRankCutoff = 0, this.nextRankTarget = 25000, this.thatRankTarget = 0});
|
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, this.averages, this.nextRankCutoff, this.thatRankCutoff, this.thatRankCutoffGlicko, this.nextRankCutoffGlicko, this.nextRankTarget, this.thatRankTarget});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TLThingy> createState() => _TLThingyState();
|
State<TLThingy> createState() => _TLThingyState();
|
||||||
|
@ -40,17 +39,17 @@ class TLThingy extends StatefulWidget {
|
||||||
|
|
||||||
class _TLThingyState extends State<TLThingy> {
|
class _TLThingyState extends State<TLThingy> {
|
||||||
late bool oskKagariGimmick;
|
late bool oskKagariGimmick;
|
||||||
|
late TetraLeagueAlpha? oldTl;
|
||||||
|
late TetraLeagueAlpha currentTl;
|
||||||
|
late RangeValues _currentRangeValues;
|
||||||
|
late List<TetrioPlayer> sortedStates;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_currentRangeValues = const RangeValues(0, 1);
|
_currentRangeValues = const RangeValues(0, 1);
|
||||||
sortedStates = widget.states.reversed.toList();
|
sortedStates = widget.states.reversed.toList();
|
||||||
oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
|
oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
|
||||||
try{
|
oldTl = sortedStates.elementAtOrNull(1)?.tlSeason1;
|
||||||
oldTl = sortedStates[1].tlSeason1;
|
|
||||||
}on RangeError{
|
|
||||||
oldTl = null;
|
|
||||||
}
|
|
||||||
currentTl = widget.tl;
|
currentTl = widget.tl;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -139,20 +138,16 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (currentTl.gamesPlayed >= 10 && currentTl.rd! < 100 && currentTl.nextAt >=0 && currentTl.prevAt >= 0) Padding(
|
if (currentTl.gamesPlayed > 9) TLProgress(
|
||||||
padding: const EdgeInsets.all(8.0),
|
tlData: currentTl,
|
||||||
child: SfLinearGauge(
|
previousRankTRcutoff: widget.thatRankCutoff,
|
||||||
minimum: currentTl.nextAt.toDouble(),
|
previousGlickoCutoff: widget.thatRankCutoffGlicko,
|
||||||
maximum: currentTl.prevAt.toDouble(),
|
previousRank: widget.tl.prevRank,
|
||||||
interval: currentTl.prevAt.toDouble() - currentTl.nextAt.toDouble(),
|
previousRankTRcutoffTarget: widget.thatRankTarget,
|
||||||
ranges: [LinearGaugeRange(startValue: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), endValue: currentTl.prevAt.toDouble(), color: Colors.cyanAccent,)],
|
nextRankTRcutoff: widget.nextRankCutoff,
|
||||||
markerPointers: [LinearShapePointer(value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), position: LinearElementPosition.inside, shapeType: LinearShapePointerType.triangle, color: Colors.white, height: 20),
|
nextRankGlickoCutoff: widget.nextRankCutoffGlicko,
|
||||||
LinearWidgetPointer(offset: 4, position: LinearElementPosition.outside, value: currentTl.standing.toDouble() <= currentTl.prevAt.toDouble() ? currentTl.standing.toDouble() : currentTl.prevAt.toDouble(), child: Text(NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0).format(currentTl.standing)))],
|
nextRankTRcutoffTarget: widget.nextRankTarget,
|
||||||
isAxisInversed: true,
|
nextRank: widget.tl.nextRank
|
||||||
isMirrored: true,
|
|
||||||
showTicks: true,
|
|
||||||
showLabels: true
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (currentTl.gamesPlayed < 10)
|
if (currentTl.gamesPlayed < 10)
|
||||||
Text(t.gamesUntilRanked(left: 10 - currentTl.gamesPlayed),
|
Text(t.gamesUntilRanked(left: 10 - currentTl.gamesPlayed),
|
||||||
|
@ -325,7 +320,7 @@ class _TLThingyState extends State<TLThingy> {
|
||||||
),),
|
),),
|
||||||
if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: " • "),
|
if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: " • "),
|
||||||
if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "${t.top} ${f2.format(widget.lbPositions!.estTr!.percentage*100)}%" : "№${widget.lbPositions!.estTr!.position}", style: TextStyle(color: getColorOfRank(widget.lbPositions!.estTr!.position))),
|
if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "${t.top} ${f2.format(widget.lbPositions!.estTr!.percentage*100)}%" : "№${widget.lbPositions!.estTr!.position}", style: TextStyle(color: getColorOfRank(widget.lbPositions!.estTr!.position))),
|
||||||
if (widget.lbPositions?.estTr != null) const TextSpan(text: " • "),
|
if (widget.lbPositions?.estTr != null || oldTl?.estTr?.esttr != null) const TextSpan(text: " • "),
|
||||||
TextSpan(text: "Glicko: ${f2.format(currentTl.estTr!.estglicko)}")
|
TextSpan(text: "Glicko: ${f2.format(currentTl.estTr!.estglicko)}")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
@ -52,7 +53,7 @@ class UserThingy extends StatelessWidget {
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
children: [
|
children: [
|
||||||
if (player.bannerRevision != null)
|
if (player.bannerRevision != null)
|
||||||
Image.network("https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
|
Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBanner&user=${player.userId}&rv=${player.bannerRevision}" : "https://tetr.io/user-content/banners/${player.userId}.jpg?rv=${player.bannerRevision}",
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
height: bannerHeight,
|
height: bannerHeight,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
@ -90,7 +91,7 @@ class UserThingy extends StatelessWidget {
|
||||||
child: player.role == "banned"
|
child: player.role == "banned"
|
||||||
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
? Image.asset("res/avatars/tetrio_banned.png", fit: BoxFit.fitHeight, height: pfpHeight,)
|
||||||
: player.avatarRevision != null
|
: player.avatarRevision != null
|
||||||
? Image.network("https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
|
? Image.network(kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioProfilePicture&user=${player.userId}&rv=${player.avatarRevision}" : "https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
|
||||||
// TODO: osk banner can cause memory leak
|
// TODO: osk banner can cause memory leak
|
||||||
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
fit: BoxFit.fitHeight, height: 128, errorBuilder: (context, error, stackTrace) {
|
||||||
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
developer.log("Error with building profile picture", name: "main_view", error: error, stackTrace: stackTrace);
|
||||||
|
@ -410,7 +411,7 @@ class UserThingy extends StatelessWidget {
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
developer.log("Error with building $badge", name: "main_view", error: error, stackTrace: stackTrace);
|
developer.log("Error with building $badge", name: "main_view", error: error, stackTrace: stackTrace);
|
||||||
return Image.network(
|
return Image.network(
|
||||||
"https://tetr.io/res/badges/${badge.badgeId}.png",
|
kIsWeb ? "https://ts.dan63.by/oskware_bridge.php?endpoint=TetrioBadge&badge=${badge.badgeId}" : "https://tetr.io/res/badges/${badge.badgeId}.png",
|
||||||
height: 32,
|
height: 32,
|
||||||
width: 32,
|
width: 32,
|
||||||
errorBuilder:(context, error, stackTrace) {
|
errorBuilder:(context, error, stackTrace) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||||
|
import 'package:tetra_stats/gen/strings.g.dart';
|
||||||
|
|
||||||
class VsGraphs extends StatelessWidget{
|
class VsGraphs extends StatelessWidget{
|
||||||
final double greenAPM;
|
final double greenAPM;
|
||||||
|
@ -205,6 +206,71 @@ class VsGraphs extends StatelessWidget{
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding( // sq graph
|
||||||
|
padding: const EdgeInsets.fromLTRB(18, 0, 18, 44),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 310,
|
||||||
|
width: 310,
|
||||||
|
child: RadarChart(
|
||||||
|
RadarChartData(
|
||||||
|
radarShape: RadarShape.polygon,
|
||||||
|
tickCount: 4,
|
||||||
|
ticksTextStyle: const TextStyle(color: Colors.white24, 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),
|
||||||
|
titleTextStyle: const TextStyle(height: 1.1),
|
||||||
|
radarTouchData: RadarTouchData(),
|
||||||
|
getTitle: (index, angle) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return RadarChartTitle(text: t.graphs.attack, angle: 0, positionPercentageOffset: 0.05);
|
||||||
|
case 1:
|
||||||
|
return RadarChartTitle(text: t.graphs.speed, angle: 0, positionPercentageOffset: 0.05);
|
||||||
|
case 2:
|
||||||
|
return RadarChartTitle(text: t.graphs.defense, angle: angle + 180, positionPercentageOffset: 0.05);
|
||||||
|
case 3:
|
||||||
|
return RadarChartTitle(text: t.graphs.cheese, angle: 0, positionPercentageOffset: 0.05);
|
||||||
|
default:
|
||||||
|
return const RadarChartTitle(text: '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataSets: [
|
||||||
|
RadarDataSet(
|
||||||
|
fillColor: const Color.fromARGB(115, 76, 175, 79),
|
||||||
|
borderColor: Colors.green,
|
||||||
|
dataEntries: [
|
||||||
|
RadarEntry(value: greenAPM / 60 * 0.4),
|
||||||
|
RadarEntry(value: greenPPS / 3.75),
|
||||||
|
RadarEntry(value: greenNerdStats.dss * 1.15),
|
||||||
|
RadarEntry(value: greenNerdStats.cheese / 110),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RadarDataSet(
|
||||||
|
fillColor: const Color.fromARGB(115, 244, 67, 54),
|
||||||
|
borderColor: Colors.red,
|
||||||
|
dataEntries: [
|
||||||
|
RadarEntry(value: redAPM / 60 * 0.4),
|
||||||
|
RadarEntry(value: redPPS / 3.75),
|
||||||
|
RadarEntry(value: redNerdStats.dss * 1.15),
|
||||||
|
RadarEntry(value: redNerdStats.cheese / 110),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
RadarDataSet(
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
borderColor: Colors.transparent,
|
||||||
|
dataEntries: [
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 1.2),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
const RadarEntry(value: 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -866,6 +866,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
syncfusion_flutter_charts:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: syncfusion_flutter_charts
|
||||||
|
sha256: ab73109c586f5ec2b01adc2672026a1fb3f93b2b5f6061ba8d7126c119061002
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "24.2.9"
|
||||||
syncfusion_flutter_core:
|
syncfusion_flutter_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -2,7 +2,7 @@ name: tetra_stats
|
||||||
description: Track your and other player stats in TETR.IO
|
description: Track your and other player stats in TETR.IO
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.5.0+16
|
version: 1.5.3+19
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0'
|
sdk: '>=3.0.0'
|
||||||
|
@ -43,6 +43,7 @@ dependencies:
|
||||||
flutter_markdown: ^0.6.18
|
flutter_markdown: ^0.6.18
|
||||||
flutter_colorpicker: ^1.0.3
|
flutter_colorpicker: ^1.0.3
|
||||||
go_router: ^13.0.0
|
go_router: ^13.0.0
|
||||||
|
syncfusion_flutter_charts: ^24.2.9
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -81,7 +81,12 @@
|
||||||
"verdictGeneral": "$n $verdict than $rank rank average",
|
"verdictGeneral": "$n $verdict than $rank rank average",
|
||||||
"verdictBetter": "better",
|
"verdictBetter": "better",
|
||||||
"verdictWorse": "worse",
|
"verdictWorse": "worse",
|
||||||
|
"smooth": "Smooth",
|
||||||
"gamesUntilRanked": "${left} games until being ranked",
|
"gamesUntilRanked": "${left} games until being ranked",
|
||||||
|
"numOfVictories": "~${wins} victories",
|
||||||
|
"promotionOnNextWin": "Promotion on next win",
|
||||||
|
"numOfdefeats": "~${losses} defeats",
|
||||||
|
"demotionOnNextLoss": "Demotion on next loss",
|
||||||
"nerdStats": "Nerd Stats",
|
"nerdStats": "Nerd Stats",
|
||||||
"playersYouTrack": "Players you track",
|
"playersYouTrack": "Players you track",
|
||||||
"formula": "Formula",
|
"formula": "Formula",
|
||||||
|
@ -163,6 +168,9 @@
|
||||||
"calc": "Calc",
|
"calc": "Calc",
|
||||||
"calcViewNoValues": "Enter values to calculate the stats",
|
"calcViewNoValues": "Enter values to calculate the stats",
|
||||||
"rankAveragesViewTitle": "Ranks cutoff and average stats",
|
"rankAveragesViewTitle": "Ranks cutoff and average stats",
|
||||||
|
"sprintAndBlitsViewTitle": "40 lines and Blitz averages",
|
||||||
|
"sprintAndBlitsRelevance": "Relevance: ${date}",
|
||||||
|
"rank": "Rank",
|
||||||
"averages": "Averages",
|
"averages": "Averages",
|
||||||
"lbViewZeroEntrys": "Empty list",
|
"lbViewZeroEntrys": "Empty list",
|
||||||
"lbViewOneEntry": "There is only one player",
|
"lbViewOneEntry": "There is only one player",
|
||||||
|
@ -200,6 +208,12 @@
|
||||||
"currentAxis": "$axis axis:",
|
"currentAxis": "$axis axis:",
|
||||||
"p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r",
|
"p1nkl0bst3rAlert": "That data was retrived from third party API maintained by p1nkl0bst3r",
|
||||||
"notForWeb": "Function is not available for web version",
|
"notForWeb": "Function is not available for web version",
|
||||||
|
"graphs": {
|
||||||
|
"attack": "Attack",
|
||||||
|
"speed": "Speed",
|
||||||
|
"defense": "Defense",
|
||||||
|
"cheese": "Cheese"
|
||||||
|
},
|
||||||
"statCellNum":{
|
"statCellNum":{
|
||||||
"xpLevel": "XP Level",
|
"xpLevel": "XP Level",
|
||||||
"xpProgress": "Progress to next level",
|
"xpProgress": "Progress to next level",
|
||||||
|
@ -250,7 +264,7 @@
|
||||||
"nyaappDescription": "(Abbreviated as wAPP) Essentially, a measure of your ability to send cheese while still maintaining a high APP.\nInvented by Wertj.",
|
"nyaappDescription": "(Abbreviated as wAPP) Essentially, a measure of your ability to send cheese while still maintaining a high APP.\nInvented by Wertj.",
|
||||||
"area": "Area",
|
"area": "Area",
|
||||||
"areaDescription": "How much space your shape takes up on the graph, if you exclude the cheese and vs/apm sections",
|
"areaDescription": "How much space your shape takes up on the graph, if you exclude the cheese and vs/apm sections",
|
||||||
"estOfTR": "Est. of TR",
|
"estOfTR": "Estimated TR",
|
||||||
"estOfTRShort": "Est. TR",
|
"estOfTRShort": "Est. TR",
|
||||||
"accOfEst": "Accuracy",
|
"accOfEst": "Accuracy",
|
||||||
"accOfEstShort": "Acc."
|
"accOfEstShort": "Acc."
|
||||||
|
@ -312,16 +326,16 @@
|
||||||
"forbiddenSub": "If you are using VPN or Proxy, turn it off. If this does not help, reach out to $nickname",
|
"forbiddenSub": "If you are using VPN or Proxy, turn it off. If this does not help, reach out to $nickname",
|
||||||
"tooManyRequests": "You have been rate limited.",
|
"tooManyRequests": "You have been rate limited.",
|
||||||
"tooManyRequestsSub": "Wait a few moments and try again",
|
"tooManyRequestsSub": "Wait a few moments and try again",
|
||||||
"internal": "Something happend on the tetr.io side",
|
"internal": "Something happened on the tetr.io side",
|
||||||
"internalSub": "osk, probably, already aware about it",
|
"internalSub": "osk, probably, already aware about it",
|
||||||
"internalWebVersion": "Something happend on the tetr.io side (or on oskware_bridge, idk honestly)",
|
"internalWebVersion": "Something happened on the tetr.io side (or on oskware_bridge, idk honestly)",
|
||||||
"internalWebVersionSub": "If osk status page says that everything is ok, let dan63047 know about this issue",
|
"internalWebVersionSub": "If osk status page says that everything is ok, let dan63047 know about this issue",
|
||||||
"oskwareBridge": "Something happend with oskware_bridge",
|
"oskwareBridge": "Something happened with oskware_bridge",
|
||||||
"oskwareBridgeSub": "Let dan63047 know",
|
"oskwareBridgeSub": "Let dan63047 know",
|
||||||
"p1nkl0bst3rForbidden": "Third party API blocked your IP address",
|
"p1nkl0bst3rForbidden": "Third party API blocked your IP address",
|
||||||
"p1nkl0bst3rTooManyRequests": "Too many requests to third party API. Try again later",
|
"p1nkl0bst3rTooManyRequests": "Too many requests to third party API. Try again later",
|
||||||
"p1nkl0bst3rinternal": "Something happend on the p1nkl0bst3r side",
|
"p1nkl0bst3rinternal": "Something happened on the p1nkl0bst3r side",
|
||||||
"p1nkl0bst3rinternalWebVersion": "Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)",
|
"p1nkl0bst3rinternalWebVersion": "Something happened on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)",
|
||||||
"replayAlreadySaved": "Replay already saved",
|
"replayAlreadySaved": "Replay already saved",
|
||||||
"replayExpired": "Replay expired and not available anymore",
|
"replayExpired": "Replay expired and not available anymore",
|
||||||
"replayRejected": "Third party API blocked your IP address"
|
"replayRejected": "Third party API blocked your IP address"
|
||||||
|
|
|
@ -81,7 +81,12 @@
|
||||||
"verdictGeneral": "$verdict среднего $rank ранга на $n",
|
"verdictGeneral": "$verdict среднего $rank ранга на $n",
|
||||||
"verdictBetter": "Лучше",
|
"verdictBetter": "Лучше",
|
||||||
"verdictWorse": "Хуже",
|
"verdictWorse": "Хуже",
|
||||||
|
"smooth": "Гладкий",
|
||||||
"gamesUntilRanked": "${left} матчей до получения рейтинга",
|
"gamesUntilRanked": "${left} матчей до получения рейтинга",
|
||||||
|
"numOfVictories": "~${wins} побед",
|
||||||
|
"promotionOnNextWin": "Повышение после следующей победы",
|
||||||
|
"numOfdefeats": "~${losses} поражений",
|
||||||
|
"demotionOnNextLoss": "Понижение после следующего поражения",
|
||||||
"nerdStats": "Для задротов",
|
"nerdStats": "Для задротов",
|
||||||
"playersYouTrack": "Отслеживаемые игроки",
|
"playersYouTrack": "Отслеживаемые игроки",
|
||||||
"formula": "Формула",
|
"formula": "Формула",
|
||||||
|
@ -163,6 +168,9 @@
|
||||||
"calc": "Считать",
|
"calc": "Считать",
|
||||||
"calcViewNoValues": "Введите значения, чтобы посчитать статистику",
|
"calcViewNoValues": "Введите значения, чтобы посчитать статистику",
|
||||||
"rankAveragesViewTitle": "Требования рангов и средние значения",
|
"rankAveragesViewTitle": "Требования рангов и средние значения",
|
||||||
|
"sprintAndBlitsViewTitle": "Средние результаты 40 линий и блица",
|
||||||
|
"sprintAndBlitsRelevance": "Актуальность: ${date}",
|
||||||
|
"rank": "Ранг",
|
||||||
"averages": "Средние значения",
|
"averages": "Средние значения",
|
||||||
"lbViewZeroEntrys": "Рейтинговая таблица пуста",
|
"lbViewZeroEntrys": "Рейтинговая таблица пуста",
|
||||||
"lbViewOneEntry": "В рейтинговой таблице всего один игрок",
|
"lbViewOneEntry": "В рейтинговой таблице всего один игрок",
|
||||||
|
@ -200,6 +208,12 @@
|
||||||
"currentAxis": "Ось $axis:",
|
"currentAxis": "Ось $axis:",
|
||||||
"p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r",
|
"p1nkl0bst3rAlert": "Эти данные были получены из стороннего API, который поддерживается p1nkl0bst3r",
|
||||||
"notForWeb": "Функция недоступна для веб версии",
|
"notForWeb": "Функция недоступна для веб версии",
|
||||||
|
"graphs": {
|
||||||
|
"attack": "Атака",
|
||||||
|
"speed": "Скорость",
|
||||||
|
"defense": "Защита",
|
||||||
|
"cheese": "Сыр"
|
||||||
|
},
|
||||||
"statCellNum": {
|
"statCellNum": {
|
||||||
"xpLevel": "Уровень\nопыта",
|
"xpLevel": "Уровень\nопыта",
|
||||||
"xpProgress": "Прогресс до следующего уровня",
|
"xpProgress": "Прогресс до следующего уровня",
|
||||||
|
|
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.8 KiB |