Merge pull request #84 from dan63047/master

Sync Stable with master
This commit is contained in:
dan63047 2024-04-08 20:41:08 +03:00 committed by GitHub
commit 395405bfa3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 3637 additions and 1709 deletions

View File

@ -16,6 +16,7 @@ You can [download an app](https://github.com/dan63047/TetraStats/releases), or [
- Stats Calculator - Stats Calculator
- Player history in charts - Player history in charts
- Tetra League matches history - Tetra League matches history
- Time-weighted stats in Tetra League matches
# Special thanks # Special thanks
- **kerrmunism** — formulas - **kerrmunism** — formulas

View File

@ -25,6 +25,12 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
@ -53,11 +59,17 @@ android {
versionName flutterVersionName versionName flutterVersionName
} }
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. signingConfig signingConfigs.release
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
} }
} }
} }

View File

@ -0,0 +1,25 @@
//import 'dart:convert';
//import 'dart:io';
//import 'package:path_provider/path_provider.dart';
//import 'tetrio_multiplayer_replay.dart';
/// That thing allows me to test my new staff i'm trying to implement
//void main() async {
// List<Tetromino> queue = List.from(tetrominoes);
// TetrioRNG rng = TetrioRNG(0);
// queue = rng.shuffleList(queue);
// print(queue);
// queue = List.from(tetrominoes);
// queue = rng.shuffleList(queue);
// print(queue);
// var downloadPath = await getDownloadsDirectory();
// ReplayData replay = ReplayData.fromJson(jsonDecode(File("${downloadPath!.path}/65b504a9ade6d287b8427af0").readAsStringSync()));
// List<List<Tetromino>> board = [for (var i = 0 ; i < 40; i++) [for (var i = 0 ; i < 10; i++) Tetromino.empty]];
// print(replay.rawJson);
//print("");
// exit(0);
//}

View File

@ -38,6 +38,25 @@ const Map<String, double> rankCutoffs = {
"z": -1, "z": -1,
"": 0.5 "": 0.5
}; };
const Map<String, double> rankTargets = {
"x": 24008,
"u": 23038,
"ss": 21583,
"s+": 20128,
"s": 18673,
"s-": 16975,
"a+": 15035,
"a": 13095,
"a-": 11155,
"b+": 9215,
"b": 7275,
"b-": 5335,
"c+": 3880,
"c": 2425,
"c-": 1213,
"d+": 606,
"d": 0,
};
enum Stats { enum Stats {
tr, tr,
glicko, glicko,
@ -59,6 +78,7 @@ enum Stats {
area, area,
eTR, eTR,
acceTR, acceTR,
acceTRabs,
opener, opener,
plonk, plonk,
infDS, infDS,
@ -88,6 +108,7 @@ const Map<Stats, String> chartsShortTitles = {
Stats.area: "Area", Stats.area: "Area",
Stats.eTR: "eTR", Stats.eTR: "eTR",
Stats.acceTR: "±eTR", Stats.acceTR: "±eTR",
Stats.acceTRabs: "+eTR absolute",
Stats.opener: "Opener", Stats.opener: "Opener",
Stats.plonk: "Plonk", Stats.plonk: "Plonk",
Stats.infDS: "Inf. DS", Stats.infDS: "Inf. DS",
@ -117,6 +138,48 @@ 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
'x': Duration(seconds: 25, milliseconds: 413),
'u': Duration(seconds: 34, milliseconds: 549),
'ss': Duration(seconds: 43, milliseconds: 373),
's+': Duration(seconds: 54, milliseconds: 027),
's': Duration(seconds: 60, milliseconds: 412),
's-': Duration(seconds: 67, milliseconds: 381),
'a+': Duration(seconds: 73, milliseconds: 694),
'a': Duration(seconds: 81, milliseconds: 166),
'a-': Duration(seconds: 88, milliseconds: 334),
'b+': Duration(seconds: 93, milliseconds: 741),
'b': Duration(seconds: 98, milliseconds: 354),
'b-': Duration(seconds: 109, milliseconds: 610),
'c+': Duration(seconds: 124, milliseconds: 641),
'c': Duration(seconds: 126, milliseconds: 104),
'c-': Duration(seconds: 145, milliseconds: 865),
'd+': Duration(seconds: 154, milliseconds: 338),
'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
};
String getStatNameByEnum(Stats stat){ String getStatNameByEnum(Stats stat){
return t[stat.name]; return t[stat.name];
} }
@ -271,6 +334,10 @@ class TetrioPlayer {
return tlSeason1.lessStrictCheck(other.tlSeason1); return tlSeason1.lessStrictCheck(other.tlSeason1);
} }
TetrioPlayerFromLeaderboard convertToPlayerFromLeaderboard() => TetrioPlayerFromLeaderboard(
userId, username, role, xp, country, supporterTier > 0, verified, state, gamesPlayed, gamesWon,
tlSeason1.rating, tlSeason1.glicko??0, tlSeason1.rd??noTrRd, tlSeason1.rank, tlSeason1.bestRank, tlSeason1.apm??0, tlSeason1.pps??0, tlSeason1.vs??0, tlSeason1.decaying);
@override @override
String toString() { String toString() {
return "$username ($state)"; return "$username ($state)";
@ -318,6 +385,8 @@ class TetrioPlayer {
return tlSeason1.estTr?.esttr; return tlSeason1.estTr?.esttr;
case Stats.acceTR: case Stats.acceTR:
return tlSeason1.esttracc; return tlSeason1.esttracc;
case Stats.acceTRabs:
return tlSeason1.esttracc?.abs();
case Stats.opener: case Stats.opener:
return tlSeason1.playstyle?.opener; return tlSeason1.playstyle?.opener;
case Stats.plonk: case Stats.plonk:
@ -1097,6 +1166,73 @@ class News {
} }
} }
class PlayerLeaderboardPosition{
late LeaderboardPosition? apm;
late LeaderboardPosition? pps;
late LeaderboardPosition? vs;
late LeaderboardPosition? gamesPlayed;
late LeaderboardPosition? gamesWon;
late LeaderboardPosition? winrate;
late LeaderboardPosition? app;
late LeaderboardPosition? vsapm;
late LeaderboardPosition? dss;
late LeaderboardPosition? dsp;
late LeaderboardPosition? appdsp;
late LeaderboardPosition? cheese;
late LeaderboardPosition? gbe;
late LeaderboardPosition? nyaapp;
late LeaderboardPosition? area;
late LeaderboardPosition? estTr;
late LeaderboardPosition? accOfEst;
PlayerLeaderboardPosition({
required this.apm,
required this.pps,
required this.vs,
required this.gamesPlayed,
required this.gamesWon,
required this.winrate,
required this.app,
required this.vsapm,
required this.dss,
required this.dsp,
required this.appdsp,
required this.cheese,
required this.gbe,
required this.nyaapp,
required this.area,
required this.estTr,
required this.accOfEst
});
PlayerLeaderboardPosition.fromSearchResults(List<LeaderboardPosition?> results){
apm = results[0];
pps = results[1];
vs = results[2];
gamesPlayed = results[3];
gamesWon = results[4];
winrate = results[5];
app = results[6];
vsapm = results[7];
dss = results[8];
dsp = results[9];
appdsp = results[10];
cheese = results[11];
gbe = results[12];
nyaapp = results[13];
area = results[14];
estTr = results[15];
accOfEst = results[16];
}
}
class LeaderboardPosition{
int position;
double percentage;
LeaderboardPosition(this.position, this.percentage);
}
class TetrioPlayersLeaderboard { class TetrioPlayersLeaderboard {
late String type; late String type;
late DateTime timestamp; late DateTime timestamp;
@ -1121,6 +1257,20 @@ 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);
@ -1711,6 +1861,30 @@ class TetrioPlayersLeaderboard {
} }
} }
PlayerLeaderboardPosition? getLeaderboardPosition(TetrioPlayer user) {
if (user.tlSeason1.gamesPlayed == 0) return null;
bool fakePositions = false;
late List<TetrioPlayerFromLeaderboard> copyOfLeaderboard;
if (leaderboard.indexWhere((element) => element.userId == user.userId) == -1){
fakePositions =true;
copyOfLeaderboard = List.of(leaderboard);
copyOfLeaderboard.add(user.convertToPlayerFromLeaderboard());
}
List<Stats> stats = [Stats.apm, Stats.pps, Stats.vs, Stats.gp, Stats.gw, Stats.wr,
Stats.app, Stats.vsapm, Stats.dss, Stats.dsp, Stats.appdsp, Stats.cheese, Stats.gbe, Stats.nyaapp, Stats.area, Stats.eTR, Stats.acceTR];
List<LeaderboardPosition?> results = [];
for (Stats stat in stats) {
List<TetrioPlayerFromLeaderboard> sortedLeaderboard = getStatRanking(fakePositions ? copyOfLeaderboard : leaderboard, stat, reversed: stat == Stats.cheese ? true : false);
int position = sortedLeaderboard.indexWhere((element) => element.userId == user.userId) + 1;
if (position == 0) {
results.add(null);
} else {
results.add(LeaderboardPosition(fakePositions ? 1001 : position, position / sortedLeaderboard.length));
}
}
return PlayerLeaderboardPosition.fromSearchResults(results);
}
Map<String, List<dynamic>> get averages => { Map<String, List<dynamic>> get averages => {
'x': getAverageOfRank("x"), 'x': getAverageOfRank("x"),
'u': getAverageOfRank("u"), 'u': getAverageOfRank("u"),
@ -1732,6 +1906,26 @@ class TetrioPlayersLeaderboard {
'z': getAverageOfRank("z") 'z': getAverageOfRank("z")
}; };
Map<String, double> get cutoffs => {
'x': getAverageOfRank("x")[1]["toEnterTR"],
'u': getAverageOfRank("u")[1]["toEnterTR"],
'ss': getAverageOfRank("ss")[1]["toEnterTR"],
's+': getAverageOfRank("s+")[1]["toEnterTR"],
's': getAverageOfRank("s")[1]["toEnterTR"],
's-': getAverageOfRank("s-")[1]["toEnterTR"],
'a+': getAverageOfRank("a+")[1]["toEnterTR"],
'a': getAverageOfRank("a")[1]["toEnterTR"],
'a-': getAverageOfRank("a-")[1]["toEnterTR"],
'b+': getAverageOfRank("b+")[1]["toEnterTR"],
'b': getAverageOfRank("b")[1]["toEnterTR"],
'b-': getAverageOfRank("b-")[1]["toEnterTR"],
'c+': getAverageOfRank("c+")[1]["toEnterTR"],
'c': getAverageOfRank("c")[1]["toEnterTR"],
'c-': getAverageOfRank("c-")[1]["toEnterTR"],
'd+': getAverageOfRank("d+")[1]["toEnterTR"],
'd': getAverageOfRank("d")[1]["toEnterTR"]
};
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;
@ -1785,7 +1979,11 @@ class TetrioPlayerFromLeaderboard {
this.apm, this.apm,
this.pps, this.pps,
this.vs, this.vs,
this.decaying); this.decaying){
nerdStats = NerdStats(apm, pps, vs);
estTr = EstTr(apm, pps, vs, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
playstyle = Playstyle(apm, pps, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank);
}
double get winrate => gamesWon / gamesPlayed; double get winrate => gamesWon / gamesPlayed;
double get esttracc => estTr.esttr - rating; double get esttracc => estTr.esttr - rating;
@ -1857,6 +2055,8 @@ class TetrioPlayerFromLeaderboard {
return estTr.esttr; return estTr.esttr;
case Stats.acceTR: case Stats.acceTR:
return esttracc; return esttracc;
case Stats.acceTRabs:
return esttracc.abs();
case Stats.opener: case Stats.opener:
return playstyle.opener; return playstyle.opener;
case Stats.plonk: case Stats.plonk:

View File

@ -1,4 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:vector_math/vector_math_64.dart';
import 'tetrio.dart'; import 'tetrio.dart';
// I want to implement those fancy TWC stats // I want to implement those fancy TWC stats
@ -148,10 +150,29 @@ class ReplayStats{
} }
} }
class AggregateStats{
double apm;
double pps;
double vs;
late NerdStats nerdStats;
late EstTr estTr;
late Playstyle playstyle;
double spp;
double kpp;
double kps;
AggregateStats(this.apm, this.pps, this.vs, this.spp, this.kpp, this.kps){
nerdStats = NerdStats(apm, pps, vs);
estTr = EstTr(apm, pps, vs, nerdStats.app, nerdStats.dss, nerdStats.dsp, nerdStats.gbe);
playstyle = Playstyle(apm, pps, nerdStats.app, nerdStats.vsapm, nerdStats.dsp, nerdStats.gbe, estTr.srarea, estTr.statrank);
}
}
class ReplayData{ class ReplayData{
late String id; late String id;
late Map<dynamic, dynamic> rawJson; late Map<dynamic, dynamic> rawJson;
late List<EndContextMulti> endcontext; late List<EndContextMulti> endcontext;
late List<AggregateStats> timeWeightedStats;
late List<List<ReplayStats>> stats; late List<List<ReplayStats>> stats;
late List<ReplayStats> totalStats; late List<ReplayStats> totalStats;
late List<List<String>> roundWinners; late List<List<String>> roundWinners;
@ -178,20 +199,45 @@ class ReplayData{
totalLength = 0; totalLength = 0;
stats = []; stats = [];
roundWinners = []; roundWinners = [];
int roundID = 0;
List<double> APMmultipliedByWeights = [0, 0];
List<double> PPSmultipliedByWeights = [0, 0];
List<double> VSmultipliedByWeights = [0, 0];
List<double> SPPmultipliedByWeights = [0, 0];
List<double> KPPmultipliedByWeights = [0, 0];
List<double> KPSmultipliedByWeights = [0, 0];
totalStats = [ReplayStats.createEmpty(), ReplayStats.createEmpty()]; totalStats = [ReplayStats.createEmpty(), ReplayStats.createEmpty()];
for(var round in json['data']) { for(var round in json['data']) {
int firstInEndContext = round['replays'][0]["events"].last['data']['export']['options']['gameid'].startsWith(endcontext[0].userId) ? 0 : 1; int firstInEndContext = round['replays'][0]["events"].last['data']['export']['options']['gameid'].startsWith(endcontext[0].userId) ? 0 : 1;
int secondInEndContext = round['replays'][1]["events"].last['data']['export']['options']['gameid'].startsWith(endcontext[1].userId) ? 1 : 0; int secondInEndContext = round['replays'][1]["events"].last['data']['export']['options']['gameid'].startsWith(endcontext[1].userId) ? 1 : 0;
roundLengths.add(max(round['replays'][0]['frames'], round['replays'][1]['frames'])); int roundLength = max(round['replays'][0]['frames'], round['replays'][1]['frames']);
roundLengths.add(roundLength);
totalLength = totalLength + max(round['replays'][0]['frames'], round['replays'][1]['frames']); totalLength = totalLength + max(round['replays'][0]['frames'], round['replays'][1]['frames']);
APMmultipliedByWeights[0] += endcontext[0].secondaryTracking[roundID]*roundLength;
APMmultipliedByWeights[1] += endcontext[1].secondaryTracking[roundID]*roundLength;
PPSmultipliedByWeights[0] += endcontext[0].tertiaryTracking[roundID]*roundLength;
PPSmultipliedByWeights[1] += endcontext[1].tertiaryTracking[roundID]*roundLength;
VSmultipliedByWeights[0] += endcontext[0].extraTracking[roundID]*roundLength;
VSmultipliedByWeights[1] += endcontext[1].extraTracking[roundID]*roundLength;
int winner = round['board'].indexWhere((element) => element['success'] == true); int winner = round['board'].indexWhere((element) => element['success'] == true);
roundWinners.add([round['board'][winner]['id'], round['board'][winner]['username']]); roundWinners.add([round['board'][winner]['id'], round['board'][winner]['username']]);
ReplayStats playerOne = ReplayStats.fromJson(round['replays'][firstInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][secondInEndContext]['events']), round['replays'][firstInEndContext]['frames']); // (events contain recived attacks) ReplayStats playerOne = ReplayStats.fromJson(round['replays'][firstInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][secondInEndContext]['events']), round['replays'][firstInEndContext]['frames']); // (events contain recived attacks)
ReplayStats playerTwo = ReplayStats.fromJson(round['replays'][secondInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][firstInEndContext]['events']), round['replays'][secondInEndContext]['frames']); ReplayStats playerTwo = ReplayStats.fromJson(round['replays'][secondInEndContext]['events'].last['data']['export']['stats'], biggestSpikeFromReplay(round['replays'][firstInEndContext]['events']), round['replays'][secondInEndContext]['frames']);
SPPmultipliedByWeights[0] += playerOne.spp*roundLength;
SPPmultipliedByWeights[1] += playerTwo.spp*roundLength;
KPPmultipliedByWeights[0] += playerOne.kpp*roundLength;
KPPmultipliedByWeights[1] += playerTwo.kpp*roundLength;
KPSmultipliedByWeights[0] += playerOne.kps*roundLength;
KPSmultipliedByWeights[1] += playerTwo.kps*roundLength;
stats.add([playerOne, playerTwo]); stats.add([playerOne, playerTwo]);
totalStats[0] = totalStats[0] + playerOne; totalStats[0] = totalStats[0] + playerOne;
totalStats[1] = totalStats[1] + playerTwo; totalStats[1] = totalStats[1] + playerTwo;
roundID ++;
} }
timeWeightedStats = [
AggregateStats(APMmultipliedByWeights[0]/totalLength, PPSmultipliedByWeights[0]/totalLength, VSmultipliedByWeights[0]/totalLength, SPPmultipliedByWeights[0]/totalLength, KPPmultipliedByWeights[0]/totalLength, KPSmultipliedByWeights[0]/totalLength),
AggregateStats(APMmultipliedByWeights[1]/totalLength, PPSmultipliedByWeights[1]/totalLength, VSmultipliedByWeights[1]/totalLength, SPPmultipliedByWeights[1]/totalLength, KPPmultipliedByWeights[1]/totalLength, KPSmultipliedByWeights[1]/totalLength)
];
} }
Map<String, dynamic> toJson(){ Map<String, dynamic> toJson(){
@ -212,3 +258,402 @@ class ReplayData{
return data; return data;
} }
} }
// can't belive i have to implement that difficult shit
// List<Event> readEventList(Map<dynamic, dynamic> json){
// List<Event> events = [];
// int id = 0;
// for (var event in json['data'][0]['replays'][0]['events']){
// int frame = event["frame"];
// EventType type = EventType.values.byName(event['type']);
// switch (type) {
// case EventType.start:
// events.add(Event(id, frame, type));
// break;
// case EventType.full:
// events.add(EventFull(id, frame, type, DataFull.fromJson(event["data"])));
// break;
// case EventType.targets:
// // TODO
// break;
// case EventType.keydown:
// events.add(EventKeyPress(id, frame, type,
// Keypress(
// KeyType.values.byName(event['data']['key']),
// event['data']['subframe'],
// false)
// ));
// break;
// case EventType.keyup:
// events.add(EventKeyPress(id, frame, type,
// Keypress(
// KeyType.values.byName(event['data']['key']),
// event['data']['subframe'],
// true)
// ));
// break;
// case EventType.end:
// // TODO: Handle this case.
// case EventType.ige:
// // TODO: Handle this case.
// case EventType.exit:
// // TODO: Handle this case.
// }
// id++;
// }
// return [];
// }
// enum EventType
// {
// start,
// end,
// full,
// keydown,
// keyup,
// targets,
// ige,
// exit
// }
// enum KeyType
// {
// moveLeft,
// moveRight,
// softDrop,
// rotateCCW,
// rotateCW,
// rotate180,
// hardDrop,
// hold,
// chat,
// exit,
// retry
// }
// class Event{
// int id;
// int frame;
// EventType type;
// //dynamic data;
// Event(this.id, this.frame, this.type);
// @override
// String toString(){
// return "E#$id f#$frame: $type";
// }
// }
// class Keypress{
// KeyType key;
// double subframe;
// bool released;
// Keypress(this.key, this.subframe, this.released);
// }
// class EventKeyPress extends Event{
// Keypress data;
// EventKeyPress(super.id, super.frame, super.type, this.data);
// }
// class IGE{
// int id;
// int frame;
// String type;
// int amount;
// IGE(this.id, this.frame, this.type, this.amount);
// }
// class EventIGE extends Event{
// IGE data;
// EventIGE(super.id, super.frame, super.type, this.data);
// }
// class Hold
// {
// String? piece;
// bool locked;
// Hold(this.piece, this.locked);
// }
// class DataFullOptions{
// int? version;
// bool? seedRandom;
// int? seed;
// double? g;
// int? stock;
// int? gMargin;
// double? gIncrease;
// double? garbageMultiplier;
// int? garbageMargin;
// double? garbageIncrease;
// int? garbageCap;
// double? garbageCapIncrease;
// int? garbageCapMax;
// int? garbageHoleSize;
// String? garbageBlocking; // TODO: enum
// bool? hasGarbage;
// int? locktime;
// int? garbageSpeed;
// int? forfeitTime;
// int? are;
// int? areLineclear;
// bool? infiniteMovement;
// int? lockresets;
// bool? allow180;
// bool? btbChaining;
// bool? allclears;
// bool? clutch;
// bool? noLockout;
// String? passthrough;
// int? boardwidth;
// int? boardheight;
// Handling? handling;
// int? boardbuffer;
// DataFullOptions.fromJson(Map<String, dynamic> json){
// version = json["version"];
// seedRandom = json["seed_random"];
// seed = json["seed"];
// g = json["g"];
// stock = json["stock"];
// gMargin = json["gmargin"];
// gIncrease = json["gincrease"];
// garbageMultiplier = json["garbagemultiplier"];
// garbageCapIncrease = json["garbagecapincrease"];
// garbageCapMax = json["garbagecapmax"];
// garbageHoleSize = json["garbageholesize"];
// garbageBlocking = json["garbageblocking"];
// hasGarbage = json["hasgarbage"];
// locktime = json["locktime"];
// garbageSpeed = json["garbagespeed"];
// forfeitTime = json["forfeit_time"];
// are = json["are"];
// areLineclear = json["lineclear_are"];
// infiniteMovement = json["infinitemovement"];
// lockresets = json["lockresets"];
// allow180 = json["allow180"];
// btbChaining = json["b2bchaining"];
// allclears = json["allclears"];
// clutch = json["clutch"];
// noLockout = json["nolockout"];
// passthrough = json["passthrough"];
// boardwidth = json["boardwidth"];
// boardheight = json["boardheight"];
// handling = Handling.fromJson(json["handling"]);
// boardbuffer = json["boardbuffer"];
// }
// }
// class DataFullStats
// {
// double? seed;
// int? lines;
// int? levelLines;
// int? levelLinesNeeded;
// int? inputs;
// int? holds;
// int? score;
// int? zenLevel;
// int? zenProgress;
// int? level;
// int? combo;
// int? currentComboPower;
// int? topCombo;
// int? btb;
// int? topbtb;
// int? tspins;
// int? piecesPlaced;
// Clears? clears;
// Garbage? garbage;
// int? kills;
// Finesse? finesse;
// DataFullStats.fromJson(Map<String, dynamic> json){
// seed = json["seed"];
// lines = json["lines"];
// levelLines = json["level_lines"];
// levelLinesNeeded = json["level_lines_needed"];
// inputs = json["inputs"];
// holds = json["holds"];
// score = json["score"];
// zenLevel = json["zenlevel"];
// zenProgress = json["zenprogress"];
// level = json["level"];
// combo = json["combo"];
// currentComboPower = json["currentcombopower"];
// topCombo = json["topcombo"];
// btb = json["btb"];
// topbtb = json["topbtb"];
// tspins = json["tspins"];
// piecesPlaced = json["piecesplaced"];
// clears = Clears.fromJson(json["clears"]);
// garbage = Garbage.fromJson(json["garbage"]);
// kills = json["kills"];
// finesse = Finesse.fromJson(json["finesse"]);
// }
// }
// class DataFullGame
// {
// List<List<String?>>? board;
// List<String>? bag;
// double? g;
// bool? playing;
// Hold? hold;
// String? piece;
// Handling? handling;
// DataFullGame.fromJson(Map<String, dynamic> json){
// board = json["board"];
// bag = json["bag"];
// hold = Hold(json["hold"]["piece"], json["hold"]["locked"]);
// g = json["g"];
// handling = Handling.fromJson(json["handling"]);
// }
// }
// class DataFull{
// bool? successful;
// String? gameOverReason;
// int? fire;
// DataFullOptions? options;
// DataFullStats? stats;
// DataFullGame? game;
// DataFull.fromJson(Map<String, dynamic> json){
// successful = json["successful"];
// gameOverReason = json["gameoverreason"];
// fire = json["fire"];
// options = DataFullOptions.fromJson(json["options"]);
// stats = DataFullStats.fromJson(json["stats"]);
// game = DataFullGame.fromJson(json["game"]);
// }
// }
// class EventFull extends Event{
// DataFull data;
// EventFull(super.id, super.frame, super.type, this.data);
// }
// class TetrioRNG{
// late double _t;
// TetrioRNG(int seed){
// _t = seed % 2147483647;
// if (_t <= 0) _t += 2147483646;
// }
// int next(){
// _t = 16807 * _t % 2147483647;
// return _t.toInt();
// }
// double nextFloat(){
// return (next() - 1) / 2147483646;
// }
// List<Tetromino> shuffleList(List<Tetromino> array){
// int length = array.length;
// if (length == 0) return [];
// for (; --length > 0;){
// int swapIndex = ((nextFloat()) * (length + 1)).toInt();
// Tetromino tmp = array[length];
// array[length] = array[swapIndex];
// array[swapIndex] = tmp;
// }
// return array;
// }
// }
// enum Tetromino{
// Z,
// L,
// O,
// S,
// I,
// J,
// T,
// garbage,
// empty
// }
// List<Tetromino> tetrominoes = [Tetromino.Z, Tetromino.L, Tetromino.O, Tetromino.S, Tetromino.I, Tetromino.J, Tetromino.T];
// List<List<List<Vector2>>> shapes = [
// [ // Z
// [Vector2(0, 0), Vector2(1, 0), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(2, 0), Vector2(1, 1), Vector2(2, 1), Vector2(1, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(1, 2), Vector2(2, 2)],
// [Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), Vector2(0, 2)]
// ],
// [ // L
// [Vector2(2, 0), Vector2(0, 1), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(1, 2), Vector2(2, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(0, 2)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(1, 1), Vector2(1, 2)]
// ],
// [ // O
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1)]
// ],
// [ // S
// [Vector2(1, 0), Vector2(2, 0), Vector2(0, 1), Vector2(1, 1)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(2, 1), Vector2(2, 2)],
// [Vector2(1, 1), Vector2(2, 1), Vector2(0, 2), Vector2(1, 2)],
// [Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 2)]
// ],
// [ // I
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(3, 1)],
// [Vector2(2, 0), Vector2(2, 1), Vector2(2, 2), Vector2(2, 3)],
// [Vector2(0, 2), Vector2(1, 2), Vector2(2, 2), Vector2(3, 2)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(1, 2), Vector2(1, 3)]
// ],
// [ // J
// [Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(1, 0), Vector2(2, 0), Vector2(1, 1), Vector2(1, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(2, 2)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(0, 2), Vector2(1, 2)]
// ],
// [ // T
// [Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), Vector2(2, 1)],
// [Vector2(1, 0), Vector2(1, 1), Vector2(2, 1), Vector2(1, 2)],
// [Vector2(0, 1), Vector2(1, 1), Vector2(2, 1), Vector2(1, 2)],
// [Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 2)]
// ]
// ];
// List<Vector2> spawnPositionFixes = [Vector2(1, 1), Vector2(1, 1), Vector2(0, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1)];
// const Map<String, double> garbage = {
// "single": 0,
// "double": 1,
// "triple": 2,
// "quad": 4,
// "penta": 5,
// "t-spin": 0,
// "t-spin single": 2,
// "t-spin double": 4,
// "t-spin triple": 6,
// "t-spin quad": 10,
// "t-spin penta": 12,
// "t-spin mini": 0,
// "t-spin mini single": 0,
// "t-spin mini double": 1,
// "allclear": 10
// };
// int btbBonus = 1;
// double btbLog = 0.8;
// double comboBonus = 0.25;
// int comboMinifier = 1;
// double comboMinifierLog = 1.25;
// List<int> comboTable = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5];

View File

@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 2 /// Locales: 2
/// Strings: 1018 (509 per locale) /// Strings: 1120 (560 per locale)
/// ///
/// Built on 2024-02-08 at 20:30 UTC /// Built on 2024-03-24 at 14:28 UTC
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
@ -181,6 +181,14 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get stoppedBeingTracked => 'Removed from tracking list!'; String get stoppedBeingTracked => 'Removed from tracking list!';
String get tlLeaderboard => 'Tetra League leaderboard'; String get tlLeaderboard => 'Tetra League leaderboard';
String get noRecords => 'No records'; String get noRecords => 'No records';
String noOldRecords({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: 'No records',
one: 'Only ${n} record',
two: 'Only ${n} records',
few: 'Only ${n} records',
many: 'Only ${n} records',
other: 'Only ${n} records',
);
String get noRecord => 'No record'; String get noRecord => 'No record';
String get botRecord => 'Bots are not allowed to set records'; String get botRecord => 'Bots are not allowed to set records';
String get anonRecord => 'Guests are not allowed to set records'; String get anonRecord => 'Guests are not allowed to set records';
@ -204,7 +212,10 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String supporter({required Object tier}) => 'Supporter tier ${tier}'; String supporter({required Object tier}) => 'Supporter tier ${tier}';
String comparingWith({required Object newDate, required Object oldDate}) => 'Data from ${newDate} comparing with ${oldDate}'; String comparingWith({required Object newDate, required Object oldDate}) => 'Data from ${newDate} comparing with ${oldDate}';
String get top => 'Top'; String get top => 'Top';
String get topRank => 'Top Rank'; String get topRank => 'Top rank';
String verdictGeneral({required Object n, required Object verdict, required Object rank}) => '${n} ${verdict} than ${rank} rank average';
String get verdictBetter => 'better';
String get verdictWorse => 'worse';
String gamesUntilRanked({required Object left}) => '${left} games until being ranked'; String gamesUntilRanked({required Object left}) => '${left} games until being ranked';
String get nerdStats => 'Nerd Stats'; String get nerdStats => 'Nerd Stats';
String get playersYouTrack => 'Players you track'; String get playersYouTrack => 'Players you track';
@ -228,8 +239,14 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get yourIDAlertTitle => 'Your nickname in TETR.IO'; String get yourIDAlertTitle => 'Your nickname in TETR.IO';
String get yourIDText => 'When app loads, it will retrieve data for this account'; String get yourIDText => 'When app loads, it will retrieve data for this account';
String get language => 'Language'; String get language => 'Language';
String get customization => 'Customization';
String get customizationDescription => 'There is only one toggle, planned to add more settings';
String get lbStats => 'Show leaderboard based stats';
String get lbStatsDescription => 'That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values';
String get aboutApp => 'About app'; String get aboutApp => 'About app';
String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy'; String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
String get oskKagari => 'Osk Kagari gimmick';
String get oskKagariDescription => 'If on, osk\'s rank on main view will be rendered as :kagari:';
String stateViewTitle({required Object nickname, required Object date}) => '${nickname} account on ${date}'; String stateViewTitle({required Object nickname, required Object date}) => '${nickname} account on ${date}';
String statesViewTitle({required Object number, required Object nickname}) => '${number} states of ${nickname} account'; String statesViewTitle({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
String matchesViewTitle({required Object nickname}) => '${nickname} TL matches'; String matchesViewTitle({required Object nickname}) => '${nickname} TL matches';
@ -254,10 +271,14 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get openReplay => 'Open replay in TETR.IO'; String get openReplay => 'Open replay in TETR.IO';
String replaySaved({required Object path}) => 'Replay saved to ${path}'; String replaySaved({required Object path}) => 'Replay saved to ${path}';
String get match => 'Match'; String get match => 'Match';
String get timeWeightedmatch => 'Match (time-weighted)';
String roundNumber({required Object n}) => 'Round ${n}'; String roundNumber({required Object n}) => 'Round ${n}';
String get statsFor => 'Stats for'; String get statsFor => 'Stats for';
String get numberOfRounds => 'Number of rounds';
String get matchLength => 'Match Length'; String get matchLength => 'Match Length';
String get roundLength => 'Round Length'; String get roundLength => 'Round Length';
String get matchStats => 'Match stats';
String get timeWeightedmatchStats => 'Time-weighted match stats';
String get replayIssue => 'Can\'t process replay'; String get replayIssue => 'Can\'t process replay';
String get matchIsTooOld => 'Replay is not available'; String get matchIsTooOld => 'Replay is not available';
String get winner => 'Winner'; String get winner => 'Winner';
@ -294,6 +315,15 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
many: '${n} players', many: '${n} players',
other: '${n} players', other: '${n} players',
); );
String games({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} games',
one: '${n} game',
two: '${n} games',
few: '${n} games',
many: '${n} games',
other: '${n} games',
);
String gamesPlayed({required Object games}) => '${games} played';
String get chart => 'Chart'; String get chart => 'Chart';
String get entries => 'Entries'; String get entries => 'Entries';
String get minimums => 'Minimums'; String get minimums => 'Minimums';
@ -675,8 +705,30 @@ class _StringsNumOfGameActionsEn {
// Translations // Translations
String get pc => 'All Clears'; String get pc => 'All Clears';
String get hold => 'Holds'; String get hold => 'Holds';
String get tspinsTotal => 'T-spins total'; String inputs({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
String get lineClears => 'Line clears'; zero: '${n} key presses',
one: '${n} key press',
two: '${n} key presses',
few: '${n} key presses',
many: '${n} key presses',
other: '${n} key presses',
);
String tspinsTotal({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} T-spins total',
one: '${n} T-spin total',
two: '${n} T-spins total',
few: '${n} T-spins total',
many: '${n} T-spins total',
other: '${n} T-spins total',
);
String lineClears({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} lines cleared',
one: '${n} line cleared',
two: '${n} lines cleared',
few: '${n} lines cleared',
many: '${n} lines cleared',
other: '${n} lines cleared',
);
} }
// Path: popupActions // Path: popupActions
@ -700,21 +752,30 @@ class _StringsErrorsEn {
// Translations // Translations
String connection({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}'; String connection({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}';
String get noSuchUser => 'No such user'; String get noSuchUser => 'No such user';
String get noSuchUserSub => 'Either you mistyped something, or the account no longer exists';
String get discordNotAssigned => 'No user assigned to given Discord ID';
String get discordNotAssignedSub => 'Make sure you provided valid ID';
String get history => 'History for that player is missing'; String get history => 'History for that player is missing';
String get actionSuggestion => 'Perhaps, you want to';
String get p1nkl0bst3rTLmatches => 'No Tetra League matches was found'; String get p1nkl0bst3rTLmatches => 'No Tetra League matches was found';
String get clientException => 'No internet connection'; String get clientException => 'No internet connection';
String get forbidden => 'Your IP address is blocked.\nChange IP address or reach out to osk'; String get forbidden => 'Your IP address is blocked';
String get tooManyRequests => 'You have been rate limited. Try again later'; 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 tooManyRequestsSub => 'Wait a few moments and try again';
String get internal => 'Something happend on the tetr.io side'; String get internal => 'Something happend on the tetr.io side';
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 happend on the tetr.io side (or on oskware_bridge, idk honestly)';
String get oskwareBridge => 'Something happend with oskware_bridge. Let dan63047 know'; String get internalWebVersionSub => 'If osk status page says that everything is ok, let dan63047 know about this issue';
String get p1nkl0bst3rForbidden => 'Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r'; String get oskwareBridge => 'Something happend with oskware_bridge';
String get oskwareBridgeSub => 'Let dan63047 know';
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 happend on the p1nkl0bst3r side';
String get p1nkl0bst3rinternalWebVersion => 'Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)'; String get p1nkl0bst3rinternalWebVersion => 'Something happend 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.\nChange IP address or reach out to szy'; String get replayRejected => 'Third party API blocked your IP address';
} }
// Path: <root> // Path: <root>
@ -774,6 +835,14 @@ class _StringsRu implements Translations {
@override String get compare => 'Сравнить'; @override String get compare => 'Сравнить';
@override String get tlLeaderboard => 'Рейтинговая таблица'; @override String get tlLeaderboard => 'Рейтинговая таблица';
@override String get noRecords => 'Нет записей'; @override String get noRecords => 'Нет записей';
@override String noOldRecords({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: 'Нет записей',
one: 'Всего один матч',
two: 'Всего ${n} матча',
few: 'Всего ${n} матча',
many: 'Всего ${n} матчей',
other: '${n} матчей',
);
@override String get noRecord => 'Нет рекорда'; @override String get noRecord => 'Нет рекорда';
@override String get botRecord => 'Ботам нельзя ставить рекорды'; @override String get botRecord => 'Ботам нельзя ставить рекорды';
@override String get anonRecord => 'Гостям нельзя ставить рекорды'; @override String get anonRecord => 'Гостям нельзя ставить рекорды';
@ -797,7 +866,10 @@ class _StringsRu implements Translations {
@override String get assignedManualy => 'Этот значок был присвоен вручную администрацией TETR.IO'; @override String get assignedManualy => 'Этот значок был присвоен вручную администрацией TETR.IO';
@override String comparingWith({required Object newDate, required Object oldDate}) => 'Данные от ${newDate} в сравнении с данными от ${oldDate}'; @override String comparingWith({required Object newDate, required Object oldDate}) => 'Данные от ${newDate} в сравнении с данными от ${oldDate}';
@override String get top => 'Топ'; @override String get top => 'Топ';
@override String get topRank => 'Топ Ранг'; @override String get topRank => 'Топ ранг';
@override String verdictGeneral({required Object verdict, required Object rank, required Object n}) => '${verdict} среднего ${rank} ранга на ${n}';
@override String get verdictBetter => 'Лучше';
@override String get verdictWorse => 'Хуже';
@override String gamesUntilRanked({required Object left}) => '${left} матчей до получения рейтинга'; @override String gamesUntilRanked({required Object left}) => '${left} матчей до получения рейтинга';
@override String get nerdStats => 'Для задротов'; @override String get nerdStats => 'Для задротов';
@override String get playersYouTrack => 'Отслеживаемые игроки'; @override String get playersYouTrack => 'Отслеживаемые игроки';
@ -821,8 +893,14 @@ class _StringsRu implements Translations {
@override String get yourIDAlertTitle => 'Ваш ник в TETR.IO'; @override String get yourIDAlertTitle => 'Ваш ник в TETR.IO';
@override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.'; @override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.';
@override String get language => 'Язык (Language)'; @override String get language => 'Язык (Language)';
@override String get customization => 'Кастомизация';
@override String get customizationDescription => 'Здесь только один переключатель, в планах добавить больше';
@override String get lbStats => 'Показывать статистику, основанную на рейтинговой таблице';
@override String get lbStatsDescription => 'Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате';
@override String get aboutApp => 'О приложении'; @override String get aboutApp => 'О приложении';
@override String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy'; @override String aboutAppText({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
@override String get oskKagari => '"Оск Кагари" прикол';
@override String get oskKagariDescription => 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
@override String stateViewTitle({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}'; @override String stateViewTitle({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
@override String statesViewTitle({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}'; @override String statesViewTitle({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
@override String matchesViewTitle({required Object nickname}) => 'Матчи аккаунта ${nickname}'; @override String matchesViewTitle({required Object nickname}) => 'Матчи аккаунта ${nickname}';
@ -847,10 +925,14 @@ class _StringsRu implements Translations {
@override String get openReplay => 'Открыть повтор в TETR.IO'; @override String get openReplay => 'Открыть повтор в TETR.IO';
@override String replaySaved({required Object path}) => 'Повтор сохранён по пути ${path}'; @override String replaySaved({required Object path}) => 'Повтор сохранён по пути ${path}';
@override String get match => 'Матч'; @override String get match => 'Матч';
@override String get timeWeightedmatch => 'Матч (взвешенная по времени)';
@override String roundNumber({required Object n}) => 'Раунд ${n}'; @override String roundNumber({required Object n}) => 'Раунд ${n}';
@override String get statsFor => 'Статистика за'; @override String get statsFor => 'Статистика за';
@override String get numberOfRounds => 'Количество раундов';
@override String get matchLength => 'Продолжительность матча'; @override String get matchLength => 'Продолжительность матча';
@override String get roundLength => 'Продолжительность раунда'; @override String get roundLength => 'Продолжительность раунда';
@override String get matchStats => 'Статистика матча';
@override String get timeWeightedmatchStats => 'Взвешенная по времени cтатистика матча';
@override String get replayIssue => 'Ошибка обработки повтора'; @override String get replayIssue => 'Ошибка обработки повтора';
@override String get matchIsTooOld => 'Информация о повторе недоступна'; @override String get matchIsTooOld => 'Информация о повторе недоступна';
@override String get winner => 'Победитель'; @override String get winner => 'Победитель';
@ -887,6 +969,15 @@ class _StringsRu implements Translations {
many: '${n} игроков', many: '${n} игроков',
other: '${n} игроков', other: '${n} игроков',
); );
@override String games({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} игр',
one: '${n} игра',
two: '${n} игры',
few: '${n} игры',
many: '${n} игр',
other: '${n} игр',
);
@override String gamesPlayed({required Object games}) => '${games} сыграно';
@override String get chart => 'График'; @override String get chart => 'График';
@override String get entries => 'Список'; @override String get entries => 'Список';
@override String get minimums => 'Минимумы'; @override String get minimums => 'Минимумы';
@ -1268,8 +1359,30 @@ class _StringsNumOfGameActionsRu implements _StringsNumOfGameActionsEn {
// Translations // Translations
@override String get pc => 'Все чисто'; @override String get pc => 'Все чисто';
@override String get hold => 'В запас'; @override String get hold => 'В запас';
@override String get tspinsTotal => 'T-spins всего'; @override String inputs({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
@override String get lineClears => 'Линий очищено'; zero: '${n} нажатий клавиш',
one: '${n} нажатие на клавишу',
two: '${n} нажатия на клавишы',
few: '${n} нажатия на клавишы',
many: '${n} нажатий на клавиш',
other: '${n} нажатий на клавиш',
);
@override String tspinsTotal({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} T-спинов всего',
one: 'Всего ${n} T-спин',
two: '${n} T-спина всего',
few: '${n} T-спина всего',
many: '${n} T-спинов всего',
other: '${n} T-спинов всего',
);
@override String lineClears({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} линий очищено',
one: '${n} линия очищена',
two: '${n} линии очищено',
few: '${n} линии очищено',
many: '${n} линий очищено',
other: '${n} линий очищено',
);
} }
// Path: popupActions // Path: popupActions
@ -1293,21 +1406,30 @@ class _StringsErrorsRu implements _StringsErrorsEn {
// Translations // Translations
@override String connection({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}'; @override String connection({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
@override String get noSuchUser => 'Нет такого пользователя'; @override String get noSuchUser => 'Нет такого пользователя';
@override String get noSuchUserSub => 'Либо вы ошиблись при вводе, либо аккаунта больше не существует';
@override String get discordNotAssigned => 'К данному Discord ID не привязан аккаунт';
@override String get discordNotAssignedSub => 'Убедитесь в том, что вы вставили правильный ID';
@override String get history => 'История данного игрока отсутствует'; @override String get history => 'История данного игрока отсутствует';
@override String get actionSuggestion => 'Возможно, вы хотите';
@override String get p1nkl0bst3rTLmatches => 'Старых матчей Тетра Лиги не было найдено'; @override String get p1nkl0bst3rTLmatches => 'Старых матчей Тетра Лиги не было найдено';
@override String get clientException => 'Нет соединения с интернетом'; @override String get clientException => 'Нет соединения с интернетом';
@override String get forbidden => 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом'; @override String get forbidden => 'Ваш IP адрес заблокирован';
@override String get tooManyRequests => 'Слишком много запросов. Попробуйте позже'; @override String forbiddenSub({required Object nickname}) => 'Если у вас работает VPN или прокси, выключите его. Если это не помогло, свяжитесь с ${nickname}';
@override String get tooManyRequests => 'Слишком много запросов';
@override String get tooManyRequestsSub => 'Подождите немного и попробуйте снова';
@override String get internal => 'Что-то случилось на стороне tetr.io'; @override String get internal => 'Что-то случилось на стороне tetr.io';
@override String get internalSub => 'Скорее всего, osk уже в курсе об этом';
@override String get internalWebVersion => 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)'; @override String get internalWebVersion => 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)';
@override String get oskwareBridge => 'Что-то случилось с oskware_bridge. Дайте dan63047 знать'; @override String get internalWebVersionSub => 'Если статус страница osk-а говорит, что всё ок - свяжитесь с dan63047';
@override String get p1nkl0bst3rForbidden => 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом'; @override String get oskwareBridge => 'Что-то случилось с oskware_bridge';
@override String get oskwareBridgeSub => 'Дайте dan63047 знать';
@override String get p1nkl0bst3rForbidden => 'Стороннее API заблокировало ваш IP адрес';
@override String get p1nkl0bst3rTooManyRequests => 'Слишком много запросов к стороннему API. Попробуйте позже'; @override String get p1nkl0bst3rTooManyRequests => 'Слишком много запросов к стороннему API. Попробуйте позже';
@override String get p1nkl0bst3rinternal => 'Что-то случилось на стороне p1nkl0bst3r-а'; @override String get p1nkl0bst3rinternal => 'Что-то случилось на стороне p1nkl0bst3r-а';
@override String get p1nkl0bst3rinternalWebVersion => 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)'; @override String get p1nkl0bst3rinternalWebVersion => 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)';
@override String get replayAlreadySaved => 'Повтор уже сохранён'; @override String get replayAlreadySaved => 'Повтор уже сохранён';
@override String get replayExpired => 'Повтор истёк и больше недоступен'; @override String get replayExpired => 'Повтор истёк и больше недоступен';
@override String get replayRejected => 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с szy'; @override String get replayRejected => 'Стороннее API заблокировало ваш IP адрес';
} }
/// Flat map(s) containing all translations. /// Flat map(s) containing all translations.
@ -1359,6 +1481,14 @@ extension on Translations {
case 'stoppedBeingTracked': return 'Removed from tracking list!'; case 'stoppedBeingTracked': return 'Removed from tracking list!';
case 'tlLeaderboard': return 'Tetra League leaderboard'; case 'tlLeaderboard': return 'Tetra League leaderboard';
case 'noRecords': return 'No records'; case 'noRecords': return 'No records';
case 'noOldRecords': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: 'No records',
one: 'Only ${n} record',
two: 'Only ${n} records',
few: 'Only ${n} records',
many: 'Only ${n} records',
other: 'Only ${n} records',
);
case 'noRecord': return 'No record'; case 'noRecord': return 'No record';
case 'botRecord': return 'Bots are not allowed to set records'; case 'botRecord': return 'Bots are not allowed to set records';
case 'anonRecord': return 'Guests are not allowed to set records'; case 'anonRecord': return 'Guests are not allowed to set records';
@ -1382,7 +1512,10 @@ extension on Translations {
case 'supporter': return ({required Object tier}) => 'Supporter tier ${tier}'; case 'supporter': return ({required Object tier}) => 'Supporter tier ${tier}';
case 'comparingWith': return ({required Object newDate, required Object oldDate}) => 'Data from ${newDate} comparing with ${oldDate}'; case 'comparingWith': return ({required Object newDate, required Object oldDate}) => 'Data from ${newDate} comparing with ${oldDate}';
case 'top': return 'Top'; case 'top': return 'Top';
case 'topRank': return 'Top Rank'; case 'topRank': return 'Top rank';
case 'verdictGeneral': return ({required Object n, required Object verdict, required Object rank}) => '${n} ${verdict} than ${rank} rank average';
case 'verdictBetter': return 'better';
case 'verdictWorse': return 'worse';
case 'gamesUntilRanked': return ({required Object left}) => '${left} games until being ranked'; case 'gamesUntilRanked': return ({required Object left}) => '${left} games until being ranked';
case 'nerdStats': return 'Nerd Stats'; case 'nerdStats': return 'Nerd Stats';
case 'playersYouTrack': return 'Players you track'; case 'playersYouTrack': return 'Players you track';
@ -1406,8 +1539,14 @@ extension on Translations {
case 'yourIDAlertTitle': return 'Your nickname in TETR.IO'; case 'yourIDAlertTitle': return 'Your nickname in TETR.IO';
case 'yourIDText': return 'When app loads, it will retrieve data for this account'; case 'yourIDText': return 'When app loads, it will retrieve data for this account';
case 'language': return 'Language'; case 'language': return 'Language';
case 'customization': return 'Customization';
case 'customizationDescription': return 'There is only one toggle, planned to add more settings';
case 'lbStats': return 'Show leaderboard based stats';
case 'lbStatsDescription': return 'That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values';
case 'aboutApp': return 'About app'; case 'aboutApp': return 'About app';
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy'; case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy';
case 'oskKagari': return 'Osk Kagari gimmick';
case 'oskKagariDescription': return 'If on, osk\'s rank on main view will be rendered as :kagari:';
case 'stateViewTitle': return ({required Object nickname, required Object date}) => '${nickname} account on ${date}'; case 'stateViewTitle': return ({required Object nickname, required Object date}) => '${nickname} account on ${date}';
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} states of ${nickname} account'; case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
case 'matchesViewTitle': return ({required Object nickname}) => '${nickname} TL matches'; case 'matchesViewTitle': return ({required Object nickname}) => '${nickname} TL matches';
@ -1432,10 +1571,14 @@ extension on Translations {
case 'openReplay': return 'Open replay in TETR.IO'; case 'openReplay': return 'Open replay in TETR.IO';
case 'replaySaved': return ({required Object path}) => 'Replay saved to ${path}'; case 'replaySaved': return ({required Object path}) => 'Replay saved to ${path}';
case 'match': return 'Match'; case 'match': return 'Match';
case 'timeWeightedmatch': return 'Match (time-weighted)';
case 'roundNumber': return ({required Object n}) => 'Round ${n}'; case 'roundNumber': return ({required Object n}) => 'Round ${n}';
case 'statsFor': return 'Stats for'; case 'statsFor': return 'Stats for';
case 'numberOfRounds': return 'Number of rounds';
case 'matchLength': return 'Match Length'; case 'matchLength': return 'Match Length';
case 'roundLength': return 'Round Length'; case 'roundLength': return 'Round Length';
case 'matchStats': return 'Match stats';
case 'timeWeightedmatchStats': return 'Time-weighted match stats';
case 'replayIssue': return 'Can\'t process replay'; case 'replayIssue': return 'Can\'t process replay';
case 'matchIsTooOld': return 'Replay is not available'; case 'matchIsTooOld': return 'Replay is not available';
case 'winner': return 'Winner'; case 'winner': return 'Winner';
@ -1472,6 +1615,15 @@ extension on Translations {
many: '${n} players', many: '${n} players',
other: '${n} players', other: '${n} players',
); );
case 'games': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} games',
one: '${n} game',
two: '${n} games',
few: '${n} games',
many: '${n} games',
other: '${n} games',
);
case 'gamesPlayed': return ({required Object games}) => '${games} played';
case 'chart': return 'Chart'; case 'chart': return 'Chart';
case 'entries': return 'Entries'; case 'entries': return 'Entries';
case 'minimums': return 'Minimums'; case 'minimums': return 'Minimums';
@ -1546,28 +1698,59 @@ extension on Translations {
case 'playerRole.anon': return 'Anonymous'; case 'playerRole.anon': return 'Anonymous';
case 'numOfGameActions.pc': return 'All Clears'; case 'numOfGameActions.pc': return 'All Clears';
case 'numOfGameActions.hold': return 'Holds'; case 'numOfGameActions.hold': return 'Holds';
case 'numOfGameActions.tspinsTotal': return 'T-spins total'; case 'numOfGameActions.inputs': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
case 'numOfGameActions.lineClears': return 'Line clears'; zero: '${n} key presses',
one: '${n} key press',
two: '${n} key presses',
few: '${n} key presses',
many: '${n} key presses',
other: '${n} key presses',
);
case 'numOfGameActions.tspinsTotal': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} T-spins total',
one: '${n} T-spin total',
two: '${n} T-spins total',
few: '${n} T-spins total',
many: '${n} T-spins total',
other: '${n} T-spins total',
);
case 'numOfGameActions.lineClears': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
zero: '${n} lines cleared',
one: '${n} line cleared',
two: '${n} lines cleared',
few: '${n} lines cleared',
many: '${n} lines cleared',
other: '${n} lines cleared',
);
case 'popupActions.cancel': return 'Cancel'; case 'popupActions.cancel': return 'Cancel';
case 'popupActions.submit': return 'Submit'; case 'popupActions.submit': return 'Submit';
case 'popupActions.ok': return 'OK'; case 'popupActions.ok': return 'OK';
case 'errors.connection': return ({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}'; case 'errors.connection': return ({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}';
case 'errors.noSuchUser': return 'No such user'; case 'errors.noSuchUser': return 'No such user';
case 'errors.noSuchUserSub': return 'Either you mistyped something, or the account no longer exists';
case 'errors.discordNotAssigned': return 'No user assigned to given Discord ID';
case 'errors.discordNotAssignedSub': return 'Make sure you provided valid ID';
case 'errors.history': return 'History for that player is missing'; case 'errors.history': return 'History for that player is missing';
case 'errors.actionSuggestion': return 'Perhaps, you want to';
case 'errors.p1nkl0bst3rTLmatches': return 'No Tetra League matches was found'; case 'errors.p1nkl0bst3rTLmatches': return 'No Tetra League matches was found';
case 'errors.clientException': return 'No internet connection'; case 'errors.clientException': return 'No internet connection';
case 'errors.forbidden': return 'Your IP address is blocked.\nChange IP address or reach out to osk'; case 'errors.forbidden': return 'Your IP address is blocked';
case 'errors.tooManyRequests': return 'You have been rate limited. Try again later'; 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.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 happend on the tetr.io side';
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 happend on the tetr.io side (or on oskware_bridge, idk honestly)';
case 'errors.oskwareBridge': return 'Something happend with oskware_bridge. Let dan63047 know'; case 'errors.internalWebVersionSub': return 'If osk status page says that everything is ok, let dan63047 know about this issue';
case 'errors.p1nkl0bst3rForbidden': return 'Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r'; case 'errors.oskwareBridge': return 'Something happend with oskware_bridge';
case 'errors.oskwareBridgeSub': return 'Let dan63047 know';
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 happend 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 happend 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.\nChange IP address or reach out to szy'; case 'errors.replayRejected': return 'Third party API blocked your IP address';
case 'countries.': return 'Not selected'; case 'countries.': return 'Not selected';
case 'countries.AF': return 'Afghanistan'; case 'countries.AF': return 'Afghanistan';
case 'countries.AX': return 'Åland Islands'; case 'countries.AX': return 'Åland Islands';
@ -1878,6 +2061,14 @@ extension on _StringsRu {
case 'compare': return 'Сравнить'; case 'compare': return 'Сравнить';
case 'tlLeaderboard': return 'Рейтинговая таблица'; case 'tlLeaderboard': return 'Рейтинговая таблица';
case 'noRecords': return 'Нет записей'; case 'noRecords': return 'Нет записей';
case 'noOldRecords': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: 'Нет записей',
one: 'Всего один матч',
two: 'Всего ${n} матча',
few: 'Всего ${n} матча',
many: 'Всего ${n} матчей',
other: '${n} матчей',
);
case 'noRecord': return 'Нет рекорда'; case 'noRecord': return 'Нет рекорда';
case 'botRecord': return 'Ботам нельзя ставить рекорды'; case 'botRecord': return 'Ботам нельзя ставить рекорды';
case 'anonRecord': return 'Гостям нельзя ставить рекорды'; case 'anonRecord': return 'Гостям нельзя ставить рекорды';
@ -1901,7 +2092,10 @@ extension on _StringsRu {
case 'assignedManualy': return 'Этот значок был присвоен вручную администрацией TETR.IO'; case 'assignedManualy': return 'Этот значок был присвоен вручную администрацией TETR.IO';
case 'comparingWith': return ({required Object newDate, required Object oldDate}) => 'Данные от ${newDate} в сравнении с данными от ${oldDate}'; case 'comparingWith': return ({required Object newDate, required Object oldDate}) => 'Данные от ${newDate} в сравнении с данными от ${oldDate}';
case 'top': return 'Топ'; case 'top': return 'Топ';
case 'topRank': return 'Топ Ранг'; case 'topRank': return 'Топ ранг';
case 'verdictGeneral': return ({required Object verdict, required Object rank, required Object n}) => '${verdict} среднего ${rank} ранга на ${n}';
case 'verdictBetter': return 'Лучше';
case 'verdictWorse': return 'Хуже';
case 'gamesUntilRanked': return ({required Object left}) => '${left} матчей до получения рейтинга'; case 'gamesUntilRanked': return ({required Object left}) => '${left} матчей до получения рейтинга';
case 'nerdStats': return 'Для задротов'; case 'nerdStats': return 'Для задротов';
case 'playersYouTrack': return 'Отслеживаемые игроки'; case 'playersYouTrack': return 'Отслеживаемые игроки';
@ -1925,8 +2119,14 @@ extension on _StringsRu {
case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO'; case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO';
case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.'; case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.';
case 'language': return 'Язык (Language)'; case 'language': return 'Язык (Language)';
case 'customization': return 'Кастомизация';
case 'customizationDescription': return 'Здесь только один переключатель, в планах добавить больше';
case 'lbStats': return 'Показывать статистику, основанную на рейтинговой таблице';
case 'lbStatsDescription': return 'Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате';
case 'aboutApp': return 'О приложении'; case 'aboutApp': return 'О приложении';
case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy'; case 'aboutAppText': return ({required Object appName, required Object packageName, required Object version, required Object buildNumber}) => '${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy';
case 'oskKagari': return '"Оск Кагари" прикол';
case 'oskKagariDescription': return 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
case 'stateViewTitle': return ({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}'; case 'stateViewTitle': return ({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}'; case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
case 'matchesViewTitle': return ({required Object nickname}) => 'Матчи аккаунта ${nickname}'; case 'matchesViewTitle': return ({required Object nickname}) => 'Матчи аккаунта ${nickname}';
@ -1951,10 +2151,14 @@ extension on _StringsRu {
case 'openReplay': return 'Открыть повтор в TETR.IO'; case 'openReplay': return 'Открыть повтор в TETR.IO';
case 'replaySaved': return ({required Object path}) => 'Повтор сохранён по пути ${path}'; case 'replaySaved': return ({required Object path}) => 'Повтор сохранён по пути ${path}';
case 'match': return 'Матч'; case 'match': return 'Матч';
case 'timeWeightedmatch': return 'Матч (взвешенная по времени)';
case 'roundNumber': return ({required Object n}) => 'Раунд ${n}'; case 'roundNumber': return ({required Object n}) => 'Раунд ${n}';
case 'statsFor': return 'Статистика за'; case 'statsFor': return 'Статистика за';
case 'numberOfRounds': return 'Количество раундов';
case 'matchLength': return 'Продолжительность матча'; case 'matchLength': return 'Продолжительность матча';
case 'roundLength': return 'Продолжительность раунда'; case 'roundLength': return 'Продолжительность раунда';
case 'matchStats': return 'Статистика матча';
case 'timeWeightedmatchStats': return 'Взвешенная по времени cтатистика матча';
case 'replayIssue': return 'Ошибка обработки повтора'; case 'replayIssue': return 'Ошибка обработки повтора';
case 'matchIsTooOld': return 'Информация о повторе недоступна'; case 'matchIsTooOld': return 'Информация о повторе недоступна';
case 'winner': return 'Победитель'; case 'winner': return 'Победитель';
@ -1991,6 +2195,15 @@ extension on _StringsRu {
many: '${n} игроков', many: '${n} игроков',
other: '${n} игроков', other: '${n} игроков',
); );
case 'games': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} игр',
one: '${n} игра',
two: '${n} игры',
few: '${n} игры',
many: '${n} игр',
other: '${n} игр',
);
case 'gamesPlayed': return ({required Object games}) => '${games} сыграно';
case 'chart': return 'График'; case 'chart': return 'График';
case 'entries': return 'Список'; case 'entries': return 'Список';
case 'minimums': return 'Минимумы'; case 'minimums': return 'Минимумы';
@ -2065,28 +2278,59 @@ extension on _StringsRu {
case 'playerRole.anon': return 'Аноним'; case 'playerRole.anon': return 'Аноним';
case 'numOfGameActions.pc': return 'Все чисто'; case 'numOfGameActions.pc': return 'Все чисто';
case 'numOfGameActions.hold': return 'В запас'; case 'numOfGameActions.hold': return 'В запас';
case 'numOfGameActions.tspinsTotal': return 'T-spins всего'; case 'numOfGameActions.inputs': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
case 'numOfGameActions.lineClears': return 'Линий очищено'; zero: '${n} нажатий клавиш',
one: '${n} нажатие на клавишу',
two: '${n} нажатия на клавишы',
few: '${n} нажатия на клавишы',
many: '${n} нажатий на клавиш',
other: '${n} нажатий на клавиш',
);
case 'numOfGameActions.tspinsTotal': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} T-спинов всего',
one: 'Всего ${n} T-спин',
two: '${n} T-спина всего',
few: '${n} T-спина всего',
many: '${n} T-спинов всего',
other: '${n} T-спинов всего',
);
case 'numOfGameActions.lineClears': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
zero: '${n} линий очищено',
one: '${n} линия очищена',
two: '${n} линии очищено',
few: '${n} линии очищено',
many: '${n} линий очищено',
other: '${n} линий очищено',
);
case 'popupActions.cancel': return 'Отменить'; case 'popupActions.cancel': return 'Отменить';
case 'popupActions.submit': return 'Подтвердить'; case 'popupActions.submit': return 'Подтвердить';
case 'popupActions.ok': return 'OK'; case 'popupActions.ok': return 'OK';
case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}'; case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
case 'errors.noSuchUser': return 'Нет такого пользователя'; case 'errors.noSuchUser': return 'Нет такого пользователя';
case 'errors.noSuchUserSub': return 'Либо вы ошиблись при вводе, либо аккаунта больше не существует';
case 'errors.discordNotAssigned': return 'К данному Discord ID не привязан аккаунт';
case 'errors.discordNotAssignedSub': return 'Убедитесь в том, что вы вставили правильный ID';
case 'errors.history': return 'История данного игрока отсутствует'; case 'errors.history': return 'История данного игрока отсутствует';
case 'errors.actionSuggestion': return 'Возможно, вы хотите';
case 'errors.p1nkl0bst3rTLmatches': return 'Старых матчей Тетра Лиги не было найдено'; case 'errors.p1nkl0bst3rTLmatches': return 'Старых матчей Тетра Лиги не было найдено';
case 'errors.clientException': return 'Нет соединения с интернетом'; case 'errors.clientException': return 'Нет соединения с интернетом';
case 'errors.forbidden': return 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом'; case 'errors.forbidden': return 'Ваш IP адрес заблокирован';
case 'errors.tooManyRequests': return 'Слишком много запросов. Попробуйте позже'; case 'errors.forbiddenSub': return ({required Object nickname}) => 'Если у вас работает VPN или прокси, выключите его. Если это не помогло, свяжитесь с ${nickname}';
case 'errors.tooManyRequests': return 'Слишком много запросов';
case 'errors.tooManyRequestsSub': return 'Подождите немного и попробуйте снова';
case 'errors.internal': return 'Что-то случилось на стороне tetr.io'; case 'errors.internal': return 'Что-то случилось на стороне tetr.io';
case 'errors.internalSub': return 'Скорее всего, osk уже в курсе об этом';
case 'errors.internalWebVersion': return 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)'; case 'errors.internalWebVersion': return 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)';
case 'errors.oskwareBridge': return 'Что-то случилось с oskware_bridge. Дайте dan63047 знать'; case 'errors.internalWebVersionSub': return 'Если статус страница osk-а говорит, что всё ок - свяжитесь с dan63047';
case 'errors.p1nkl0bst3rForbidden': return 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом'; case 'errors.oskwareBridge': return 'Что-то случилось с oskware_bridge';
case 'errors.oskwareBridgeSub': return 'Дайте dan63047 знать';
case 'errors.p1nkl0bst3rForbidden': return 'Стороннее API заблокировало ваш IP адрес';
case 'errors.p1nkl0bst3rTooManyRequests': return 'Слишком много запросов к стороннему API. Попробуйте позже'; case 'errors.p1nkl0bst3rTooManyRequests': return 'Слишком много запросов к стороннему API. Попробуйте позже';
case 'errors.p1nkl0bst3rinternal': return 'Что-то случилось на стороне p1nkl0bst3r-а'; case 'errors.p1nkl0bst3rinternal': return 'Что-то случилось на стороне p1nkl0bst3r-а';
case 'errors.p1nkl0bst3rinternalWebVersion': return 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)'; case 'errors.p1nkl0bst3rinternalWebVersion': return 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)';
case 'errors.replayAlreadySaved': return 'Повтор уже сохранён'; case 'errors.replayAlreadySaved': return 'Повтор уже сохранён';
case 'errors.replayExpired': return 'Повтор истёк и больше недоступен'; case 'errors.replayExpired': return 'Повтор истёк и больше недоступен';
case 'errors.replayRejected': return 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с szy'; case 'errors.replayRejected': return 'Стороннее API заблокировало ваш IP адрес';
case 'countries.': return 'Не выбрана'; case 'countries.': return 'Не выбрана';
case 'countries.AF': return 'Афганистан'; case 'countries.AF': return 'Афганистан';
case 'countries.AX': return 'Аландские острова'; case 'countries.AX': return 'Аландские острова';

View File

@ -14,6 +14,8 @@ class TetrioPlayerAlreadyExist implements Exception {}
class TetrioPlayerNotExist implements Exception {} class TetrioPlayerNotExist implements Exception {}
class TetrioDiscordNotExist implements Exception {}
class TetrioHistoryNotExist implements Exception {} class TetrioHistoryNotExist implements Exception {}
class TetrioTooManyRequests implements Exception {} class TetrioTooManyRequests implements Exception {}

View File

@ -74,6 +74,7 @@ class TetrioService extends DB {
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]}
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {}; final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
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, TetraLeagueAlphaStream> _tlStreamsCache = {}; final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
@ -142,6 +143,14 @@ class TetrioService extends DB {
db.insert(tetrioTLReplayStatsTable, {idCol: replay.id, "data": jsonEncode(replay.toJson())}); db.insert(tetrioTLReplayStatsTable, {idCol: replay.id, "data": jsonEncode(replay.toJson())});
} }
void cacheLeaderboardPositions(String userID, PlayerLeaderboardPosition positions){
_lbPositions[userID] = positions;
}
PlayerLeaderboardPosition? getCachedLeaderboardPositions(String userID){
return _lbPositions[userID];
}
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have /// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay /// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
/// as string and as binary. /// as string and as binary.
@ -406,12 +415,12 @@ class TetrioService extends DB {
// parsing data into TetraLeagueAlphaRecord objects // parsing data into TetraLeagueAlphaRecord objects
for (var entry in csv){ for (var entry in csv){
TetraLeagueAlphaRecord match = TetraLeagueAlphaRecord( TetraLeagueAlphaRecord match = TetraLeagueAlphaRecord(
replayId: entry[0], replayId: entry[0].toString(),
ownId: entry[0], // i gonna disting p1nkl0bst3r entries with it ownId: entry[0].toString(), // i gonna disting p1nkl0bst3r entries with it
timestamp: DateTime.parse(entry[1]), timestamp: DateTime.parse(entry[1]),
endContext: [ endContext: [
EndContextMulti( EndContextMulti(
userId: entry[2], userId: entry[2].toString(),
username: entry[3].toString(), username: entry[3].toString(),
naturalOrder: 0, naturalOrder: 0,
inputs: -1, inputs: -1,
@ -428,7 +437,7 @@ class TetrioService extends DB {
success: true success: true
), ),
EndContextMulti( EndContextMulti(
userId: entry[8], userId: entry[8].toString(),
username: entry[9].toString(), username: entry[9].toString(),
naturalOrder: 1, naturalOrder: 1,
inputs: -1, inputs: -1,
@ -504,6 +513,7 @@ class TetrioService extends DB {
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200:
_lbPositions.clear();
var rawJson = jsonDecode(response.body); var rawJson = jsonDecode(response.body);
if (rawJson['success']) { // if api confirmed that everything ok if (rawJson['success']) { // if api confirmed that everything ok
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at'])); TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
@ -535,6 +545,12 @@ class TetrioService extends DB {
} }
} }
TetrioPlayersLeaderboard? getCachedLeaderboard(){
return _leaderboardsCache.entries.firstOrNull?.value;
// That function will break if i decide to recive other leaderboards
// TODO: Think about better solution
}
/// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve. /// Retrieves and returns 100 latest news entries from Tetra Channel api for given [userID]. Throws an exception if fails to retrieve.
Future<List<News>> fetchNews(String userID) async{ Future<List<News>> fetchNews(String userID) async{
try{ try{
@ -712,6 +728,14 @@ class TetrioService extends DB {
return matches; return matches;
} }
/// Gets and returns an amount of stored Tetra League mathes between [ourPlayerID] and [enemyPlayerID].
Future<int> getNumberOfTLMatchesBetweenPlayers(String ourPlayerID, String enemyPlayerID) async {
await ensureDbIsOpen();
final db = getDatabaseOrThrow();
final results = await db.rawQuery("SELECT COUNT(*) from tetrioAlphaLeagueMathces WHERE (player1id = $ourPlayerID AND player2id = $enemyPlayerID) OR (player1id = $enemyPlayerID AND player2id = $ourPlayerID)");
return results.first.values.first as int;
}
/// Deletes match and stats of that match with given [matchID] from local DB. Throws an exception if fails. /// Deletes match and stats of that match with given [matchID] from local DB. Throws an exception if fails.
Future<void> deleteTLMatch(String matchID) async { Future<void> deleteTLMatch(String matchID) async {
await ensureDbIsOpen(); await ensureDbIsOpen();
@ -950,7 +974,7 @@ class TetrioService extends DB {
user = json['data']['user']['_id']; user = json['data']['user']['_id'];
} else { // fail - throw an exception } else { // fail - throw an exception
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body); developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw TetrioPlayerNotExist(); throw TetrioDiscordNotExist();
} }
break; break;
// more exceptions to god of exceptions // more exceptions to god of exceptions

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
Color getColorOfRank(int rank){
if (rank == 1) return Colors.yellowAccent;
if (rank == 2) return Colors.blueGrey;
if (rank == 3) return Colors.brown[400]!;
if (rank <= 9) return Colors.blueAccent;
if (rank <= 99) return Colors.greenAccent;
return Colors.grey;
}

View File

@ -0,0 +1,11 @@
import 'package:intl/intl.dart';
import 'package:tetra_stats/gen/strings.g.dart';
final NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = 3;
final NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
final NumberFormat f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4);
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat f2l = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..minimumFractionDigits = 0;
final NumberFormat f0 = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode);
final NumberFormat percentage = NumberFormat.percentPattern(LocaleSettings.currentLocale.languageCode)..maximumFractionDigits = 2;

View File

@ -13,7 +13,6 @@ double? vs;
NerdStats? nerdStats; NerdStats? nerdStats;
EstTr? estTr; EstTr? estTr;
Playstyle? playstyle; Playstyle? playstyle;
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
late String oldWindowTitle; late String oldWindowTitle;
class CalcView extends StatefulWidget { class CalcView extends StatefulWidget {
@ -67,13 +66,12 @@ class CalcState extends State<CalcView> {
title: Text(t.statsCalc), title: Text(t.statsCalc),
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SingleChildScrollView(
child: NestedScrollView( child: Center(
controller: _scrollController, child: Container(
headerSliverBuilder: (context, value) { constraints: const BoxConstraints(maxWidth: 768),
return [ child: Column(children: [
SliverToBoxAdapter( Padding(
child: Padding(
padding: const EdgeInsets.fromLTRB(14, 16, 16, 32), padding: const EdgeInsets.fromLTRB(14, 16, 16, 32),
child: Row( child: Row(
children: [ children: [
@ -111,16 +109,9 @@ class CalcState extends State<CalcView> {
], ],
), ),
), ),
), Divider(),
const SliverToBoxAdapter( if (nerdStats == null) Text(t.calcViewNoValues)
child: Divider(), else Column(children: [
)
];
},
body: nerdStats == null
? Text(t.calcViewNoValues)
: ListView(
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),
_ListEntry(value: nerdStats!.vsapm, label: "VS/APM", fractionDigits: 3), _ListEntry(value: nerdStats!.vsapm, label: "VS/APM", fractionDigits: 3),
_ListEntry(value: nerdStats!.dss, label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), _ListEntry(value: nerdStats!.dss, label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
@ -131,9 +122,11 @@ class CalcState extends State<CalcView> {
_ListEntry(value: nerdStats!.nyaapp, label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), _ListEntry(value: nerdStats!.nyaapp, label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
_ListEntry(value: nerdStats!.area, label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), _ListEntry(value: nerdStats!.area, label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3),
_ListEntry(value: estTr!.esttr, label: t.statCellNum.estOfTR, fractionDigits: 3), _ListEntry(value: estTr!.esttr, label: t.statCellNum.estOfTR, fractionDigits: 3),
Graphs(apm!, pps!, vs!, nerdStats!, playstyle!), Graphs(apm!, pps!, vs!, nerdStats!, playstyle!)
], ],)
)), ],),
),
),
), ),
); );
} }

View File

@ -255,13 +255,14 @@ class CompareState extends State<CompareView> {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text("$titleGreenSide ${t.vs} $titleRedSide")), appBar: AppBar(title: Text("$titleGreenSide ${t.vs} $titleRedSide")),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SingleChildScrollView(
child: NestedScrollView(
controller: _scrollController, controller: _scrollController,
headerSliverBuilder: (context, value) { physics: AlwaysScrollableScrollPhysics(),
return [ child: Center(
SliverToBoxAdapter( child: Container(
child: Padding( constraints: const BoxConstraints(maxWidth: 768),
child: Column(children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -316,18 +317,10 @@ class CompareState extends State<CompareView> {
], ],
), ),
), ),
), Divider(),
const SliverToBoxAdapter( if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column(
child: Divider(), children: [
) if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned")
];
},
body: ListView(
children: !listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])? [
if (theGreenSide[0] != null &&
theRedSide[0] != null &&
theGreenSide[0]!.role != "banned" &&
theRedSide[0]!.role != "banned")
Column( Column(
children: [ children: [
CompareRegTimeThingy( CompareRegTimeThingy(
@ -341,8 +334,7 @@ class CompareState extends State<CompareView> {
higherIsBetter: true, higherIsBetter: true,
fractionDigits: 2, fractionDigits: 2,
), ),
if (!theGreenSide[0].gameTime.isNegative && if (!theGreenSide[0].gameTime.isNegative && !theRedSide[0].gameTime.isNegative)
!theRedSide[0].gameTime.isNegative)
CompareThingy( CompareThingy(
greenSide: theGreenSide[0].gameTime.inMicroseconds / greenSide: theGreenSide[0].gameTime.inMicroseconds /
1000000 / 1000000 /
@ -356,16 +348,14 @@ class CompareState extends State<CompareView> {
higherIsBetter: true, higherIsBetter: true,
fractionDigits: 2, fractionDigits: 2,
), ),
if (theGreenSide[0].gamesPlayed >= 0 && if (theGreenSide[0].gamesPlayed >= 0 && theRedSide[0].gamesPlayed >= 0)
theRedSide[0].gamesPlayed >= 0)
CompareThingy( CompareThingy(
label: t.statCellNum.onlineGames.replaceAll(RegExp(r'\n'), " "), label: t.statCellNum.onlineGames.replaceAll(RegExp(r'\n'), " "),
greenSide: theGreenSide[0].gamesPlayed, greenSide: theGreenSide[0].gamesPlayed,
redSide: theRedSide[0].gamesPlayed, redSide: theRedSide[0].gamesPlayed,
higherIsBetter: true, higherIsBetter: true,
), ),
if (theGreenSide[0].gamesWon >= 0 && if (theGreenSide[0].gamesWon >= 0 && theRedSide[0].gamesWon >= 0)
theRedSide[0].gamesWon >= 0)
CompareThingy( CompareThingy(
label: t.statCellNum.gamesWon.replaceAll(RegExp(r'\n'), " "), label: t.statCellNum.gamesWon.replaceAll(RegExp(r'\n'), " "),
greenSide: theGreenSide[0].gamesWon, greenSide: theGreenSide[0].gamesWon,
@ -381,17 +371,14 @@ class CompareState extends State<CompareView> {
const Divider(), const Divider(),
], ],
), ),
if (theGreenSide[0] != null && if (theGreenSide[0] != null && theRedSide[0] != null && (theGreenSide[0]!.role == "banned" || theRedSide[0]!.role == "banned"))
theRedSide[0] != null &&
(theGreenSide[0]!.role == "banned" ||
theRedSide[0]!.role == "banned"))
CompareBoolThingy( CompareBoolThingy(
greenSide: theGreenSide[0].role == "banned", greenSide: theGreenSide[0].role == "banned",
redSide: theRedSide[0].role == "banned", redSide: theRedSide[0].role == "banned",
label: t.normalBanned, label: t.normalBanned,
trueIsBetter: false), trueIsBetter: false
(theGreenSide[2].gamesPlayed > 0 || greenSideMode == Mode.stats) && ),
(theRedSide[2].gamesPlayed > 0 || redSideMode == Mode.stats) (theGreenSide[2].gamesPlayed > 0 || greenSideMode == Mode.stats) && (theRedSide[2].gamesPlayed > 0 || redSideMode == Mode.stats)
? Column( ? Column(
children: [ children: [
Padding( Padding(
@ -688,11 +675,15 @@ class CompareState extends State<CompareView> {
), ),
], ],
) )
] : [Padding( ],
)
else Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text(t.compareViewNoValues(avgR: "\$avgR"), textAlign: TextAlign.center), child: Text(t.compareViewNoValues(avgR: "\$avgR"), textAlign: TextAlign.center),
)], // This is so fucked up holy shit
) )
],
),
),
), ),
), ),
); );
@ -786,6 +777,8 @@ class PlayerSelector extends StatelessWidget {
} }
} }
const TextStyle verdictStyle = TextStyle(fontSize: 14, fontFamily: "Eurostile Round Condensed", color: Colors.grey, height: 1.1);
class CompareThingy extends StatelessWidget { class CompareThingy extends StatelessWidget {
final num greenSide; final num greenSide;
final num redSide; final num redSide;
@ -868,7 +861,7 @@ class CompareThingy extends StatelessWidget {
Text( Text(
verdict(greenSide, redSide, verdict(greenSide, redSide,
fractionDigits != null ? fractionDigits! + 2 : 0), fractionDigits != null ? fractionDigits! + 2 : 0),
style: const TextStyle(fontSize: 16), style: verdictStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
) )
], ],
@ -981,11 +974,7 @@ class CompareBoolThingy extends StatelessWidget {
style: const TextStyle(fontSize: 22), style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const Text( const Text("---", style: verdictStyle, textAlign: TextAlign.center)
"---",
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
], ],
), ),
Expanded( Expanded(
@ -1085,10 +1074,7 @@ class CompareDurationThingy extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Text( Text(
verdict(greenSide, redSide).toString(), verdict(greenSide, redSide).toString(), style: verdictStyle, textAlign: TextAlign.center)
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
], ],
), ),
Expanded( Expanded(
@ -1176,11 +1162,7 @@ class CompareRegTimeThingy extends StatelessWidget {
style: const TextStyle(fontSize: 22), style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Text( Text(verdict(greenSide, redSide), style: verdictStyle, textAlign: TextAlign.center)
verdict(greenSide, redSide),
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
], ],
), ),
Expanded( Expanded(

View File

@ -20,6 +20,7 @@ class CustomizationView extends StatefulWidget {
class CustomizationState extends State<CustomizationView> { class CustomizationState extends State<CustomizationView> {
late SharedPreferences prefs; late SharedPreferences prefs;
late bool oskKagariGimmick;
void changeColor(Color color) { void changeColor(Color color) {
setState(() => pickerColor = color); setState(() => pickerColor = color);
@ -31,7 +32,7 @@ class CustomizationState extends State<CustomizationView> {
windowManager.getTitle().then((value) => oldWindowTitle = value); windowManager.getTitle().then((value) => oldWindowTitle = value);
windowManager.setTitle("Tetra Stats: ${t.settings}"); windowManager.setTitle("Tetra Stats: ${t.settings}");
} }
_getPreferences(); _getPreferences().then((value) => setState((){}));
super.initState(); super.initState();
} }
@ -43,6 +44,11 @@ class CustomizationState extends State<CustomizationView> {
Future<void> _getPreferences() async { Future<void> _getPreferences() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
if (prefs.getBool("oskKagariGimmick") != null) {
oskKagariGimmick = prefs.getBool("oskKagariGimmick")!;
} else {
oskKagariGimmick = true;
}
} }
ThemeData getTheme(BuildContext context, Color color){ ThemeData getTheme(BuildContext context, Color color){
@ -66,59 +72,48 @@ class CustomizationState extends State<CustomizationView> {
body: SafeArea( body: SafeArea(
child: ListView( child: ListView(
children: [ children: [
ListTile( // ListTile(
title: const Text("Accent Color"), // title: const Text("Accent color"),
trailing: ColorIndicator(HSVColor.fromColor(Theme.of(context).colorScheme.primary)), // trailing: ColorIndicator(HSVColor.fromColor(Theme.of(context).colorScheme.primary)),
onTap: () { // onTap: () {
showDialog( // showDialog(
context: context, // context: context,
builder: (BuildContext context) => AlertDialog( // builder: (BuildContext context) => AlertDialog(
title: const Text('Pick a color!'), // title: const Text('Pick an accent color'),
content: SingleChildScrollView( // content: SingleChildScrollView(
child: ColorPicker( // child: ColorPicker(
pickerColor: pickerColor,
onColorChanged: changeColor,
),
// Use Material color picker:
//
// child: MaterialPicker(
// pickerColor: pickerColor, // pickerColor: pickerColor,
// onColorChanged: changeColor, // onColorChanged: changeColor,
// showLabel: true, // only on portrait mode
// ), // ),
//
// Use Block color picker:
//
// child: BlockPicker(
// pickerColor: currentColor,
// onColorChanged: changeColor,
// ), // ),
// // actions: <Widget>[
// child: MultipleChoiceBlockPicker( // ElevatedButton(
// pickerColors: currentColors, // child: const Text('Set'),
// onColorsChanged: changeColors, // onPressed: () {
// setState(() {
// setAccentColor(pickerColor);
// });
// Navigator.of(context).pop();
// },
// ), // ),
), // ]));
actions: <Widget>[ // }),
ElevatedButton( // const ListTile(
child: const Text('Got it'), // title: Text("Font"),
onPressed: () { // subtitle: Text("Not implemented"),
// ),
// const ListTile(
// title: Text("Stats Table in TL mathes list"),
// subtitle: Text("Not implemented"),
// ),
ListTile(title: Text(t.oskKagari),
subtitle: Text(t.oskKagariDescription),
trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){
prefs.setBool("oskKagariGimmick", value);
setState(() { setState(() {
setAccentColor(pickerColor); oskKagariGimmick = value;
}); });
Navigator.of(context).pop(); }),)
},
),
]));
}),
const ListTile(
title: Text("Font"),
subtitle: Text("Not implemented"),
),
const ListTile(
title: Text("Stats Table in TL mathes list"),
subtitle: Text("Not implemented"),
),
], ],
)), )),
); );

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -16,6 +16,7 @@ Stats _chartsX = Stats.tr;
Stats _chartsY = Stats.apm; Stats _chartsY = Stats.apm;
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))];
Stats _sortBy = Stats.tr; Stats _sortBy = Stats.tr;
late List<TetrioPlayerFromLeaderboard> they;
bool _reversed = false; bool _reversed = false;
List<DropdownMenuItem> _itemCountries = [for (MapEntry e in t.countries.entries) DropdownMenuItem(value: e.key, child: Text(e.value))]; List<DropdownMenuItem> _itemCountries = [for (MapEntry e in t.countries.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
String _country = ""; String _country = "";
@ -61,6 +62,7 @@ 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);
recalculateBoundaries(); recalculateBoundaries();
resetScale(); resetScale();
} }
@ -73,7 +75,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else { } else {
return element; return element;
} }
}).getStatByEnum(_chartsX) as double; }).getStatByEnum(_chartsX).toDouble();
actualMaxX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) { actualMaxX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
num n = max(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX)); num n = max(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX));
if (value.getStatByEnum(_chartsX) == n) { if (value.getStatByEnum(_chartsX) == n) {
@ -81,7 +83,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else { } else {
return element; return element;
} }
}).getStatByEnum(_chartsX) as double; }).getStatByEnum(_chartsX).toDouble();
actualMinY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) { actualMinY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
num n = min(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY)); num n = min(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
if (value.getStatByEnum(_chartsY) == n) { if (value.getStatByEnum(_chartsY) == n) {
@ -89,7 +91,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else { } else {
return element; return element;
} }
}).getStatByEnum(_chartsY) as double; }).getStatByEnum(_chartsY).toDouble();
actualMaxY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) { actualMaxY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
num n = max(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY)); num n = max(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
if (value.getStatByEnum(_chartsY) == n) { if (value.getStatByEnum(_chartsY) == n) {
@ -97,7 +99,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else { } else {
return element; return element;
} }
}).getStatByEnum(_chartsY) as double; }).getStatByEnum(_chartsY).toDouble();
} }
void resetScale(){ void resetScale(){
@ -164,7 +166,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
previousAxisTitles = _chartsX.toString()+_chartsY.toString(); previousAxisTitles = _chartsX.toString()+_chartsY.toString();
} }
final t = Translations.of(context); final t = Translations.of(context);
List<TetrioPlayerFromLeaderboard> 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);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())), title: Text(widget.rank[1]["everyone"] ? t.everyoneAverages : t.rankAverages(rank: widget.rank[0].rank.toUpperCase())),
@ -327,8 +329,8 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
for (TetrioPlayerFromLeaderboard entry in widget.rank[1]["entries"]) 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 if (entry.apm != 0.0 && entry.vs != 0.0) // prevents from ScatterChart "Offset argument contained a NaN value." exception
_MyScatterSpot( _MyScatterSpot(
entry.getStatByEnum(_chartsX) as double, entry.getStatByEnum(_chartsX).toDouble(),
entry.getStatByEnum(_chartsY) as double, entry.getStatByEnum(_chartsY).toDouble(),
entry.userId, entry.userId,
entry.username, entry.username,
dotPainter: FlDotCirclePainter(color: rankColors[entry.rank]??Colors.white, radius: 3)) dotPainter: FlDotCirclePainter(color: rankColors[entry.rank]??Colors.white, radius: 3))
@ -403,7 +405,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
value: _sortBy, value: _sortBy,
onChanged: ((value) { onChanged: ((value) {
_sortBy = value; _sortBy = value;
setState(() {}); setState(() {
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
});
}), }),
), ),
], ],

View File

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

View File

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:go_router/go_router.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/main.dart' show packageInfo; import 'package:tetra_stats/main.dart' show packageInfo;
import 'package:file_selector/file_selector.dart'; import 'package:file_selector/file_selector.dart';
@ -26,6 +27,7 @@ class SettingsState extends State<SettingsView> {
late SharedPreferences prefs; late SharedPreferences prefs;
final TetrioService teto = TetrioService(); final TetrioService teto = TetrioService();
String defaultNickname = "Checking..."; String defaultNickname = "Checking...";
late bool showPositions;
final TextEditingController _playertext = TextEditingController(); final TextEditingController _playertext = TextEditingController();
@override @override
@ -46,6 +48,11 @@ 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")!;
} else {
showPositions = false;
}
_setDefaultNickname(prefs.getString("player")); _setDefaultNickname(prefs.getString("player"));
} }
@ -254,12 +261,20 @@ class SettingsState extends State<SettingsView> {
}, },
), ),
), ),
ListTile(title: const Text("Customization"), ListTile(title: Text(t.customization),
subtitle: const Text("I don't want to implement this"), subtitle: Text(t.customizationDescription),
trailing: const Icon(Icons.arrow_right), trailing: const Icon(Icons.arrow_right),
onTap: () { onTap: () {
Navigator.pushNamed(context, "/customization"); context.go("/customization");
},), },),
ListTile(title: Text(t.lbStats),
subtitle: Text(t.lbStatsDescription),
trailing: Switch(value: showPositions, onChanged: (bool value){
prefs.setBool("showPositions", value);
setState(() {
showPositions = value;
});
}),),
const Divider(), const Divider(),
ListTile( ListTile(
onTap: (){ onTap: (){
@ -267,6 +282,7 @@ class SettingsState extends State<SettingsView> {
}, },
title: Text(t.aboutApp), title: Text(t.aboutApp),
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)
), ),
], ],
)), )),

View File

@ -71,7 +71,7 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
case ConnectionState.none: case ConnectionState.none:
case ConnectionState.waiting: case ConnectionState.waiting:
case ConnectionState.active: case ConnectionState.active:
return const Center(child: Text('Fetching...')); return const Center(child: CircularProgressIndicator());
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)}");
@ -175,7 +175,8 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
return ListTile( return ListTile(
leading: Text((index+1).toString(), style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) : null), leading: Text((index+1).toString(), style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28) : null),
title: Text(allPlayers[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")), title: Text(allPlayers[index].username, style: const TextStyle(fontFamily: "Eurostile Round Extended")),
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)),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View File

@ -1,9 +1,11 @@
// 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;
import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart';
import 'package:tetra_stats/widgets/vs_graphs.dart'; import 'package:tetra_stats/widgets/vs_graphs.dart';
import 'main_view.dart' show teto, secs; import 'main_view.dart' show teto, secs;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -20,6 +22,9 @@ import 'package:window_manager/window_manager.dart';
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
int roundSelector = -1; // -1 = match averages, otherwise round number-1 int roundSelector = -1; // -1 = match averages, otherwise round number-1
List<DropdownMenuItem> rounds = []; // index zero will be match stats List<DropdownMenuItem> rounds = []; // index zero will be match stats
bool timeWeightedStatsAvaliable = true;
int greenSidePlayer = 0;
int redSidePlayer = 1;
late String oldWindowTitle; late String oldWindowTitle;
Duration framesToTime(int frames){ Duration framesToTime(int frames){
@ -36,12 +41,10 @@ class TlMatchResultView extends StatefulWidget {
} }
class TlMatchResultState extends State<TlMatchResultView> { class TlMatchResultState extends State<TlMatchResultView> {
late ScrollController _scrollController;
late Future<ReplayData?> replayData; late Future<ReplayData?> replayData;
@override @override
void initState(){ void initState(){
_scrollController = ScrollController();
rounds = [DropdownMenuItem(value: -1, child: Text(t.match))]; rounds = [DropdownMenuItem(value: -1, child: Text(t.match))];
rounds.addAll([for (int i = 0; i < widget.record.endContext.first.secondaryTracking.length; i++) DropdownMenuItem(value: i, child: Text(t.roundNumber(n: i+1)))]); rounds.addAll([for (int i = 0; i < widget.record.endContext.first.secondaryTracking.length; i++) DropdownMenuItem(value: i, child: Text(t.roundNumber(n: i+1)))]);
replayData = teto.analyzeReplay(widget.record.replayId, widget.record.replayAvalable); replayData = teto.analyzeReplay(widget.record.replayId, widget.record.replayAvalable);
@ -59,64 +62,44 @@ class TlMatchResultState extends State<TlMatchResultView> {
super.dispose(); super.dispose();
} }
@override Widget buildComparison(bool bigScreen, bool showMobileSelector){
Widget build(BuildContext context) { return FutureBuilder(future: replayData, builder: (context, snapshot){
final t = Translations.of(context); late Duration time;
bool bigScreen = MediaQuery.of(context).size.width > 768; late String readableTime;
return Scaffold( late String reason;
appBar: AppBar( timeWeightedStatsAvaliable = true;
title: Text("${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}"), if (snapshot.connectionState != ConnectionState.done) return const LinearProgressIndicator();
actions: [ if (!snapshot.hasError){
PopupMenuButton( if (rounds.indexWhere((element) => element.value == -2) == -1) rounds.insert(1, DropdownMenuItem(value: -2, child: Text(t.timeWeightedmatch)));
enabled: widget.record.replayAvalable, greenSidePlayer = snapshot.data!.endcontext.indexWhere((element) => element.userId == widget.initPlayerId);
itemBuilder: (BuildContext context) => <PopupMenuEntry>[ redSidePlayer = snapshot.data!.endcontext.indexWhere((element) => element.userId != widget.initPlayerId);
PopupMenuItem( if (roundSelector.isNegative){
value: 1, time = framesToTime(snapshot.data!.totalLength);
child: Text(t.downloadReplay), readableTime = "${t.matchLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}";
),
PopupMenuItem(
value: 2,
child: Text(t.openReplay),
),
],
onSelected: (value) async {
switch (value) {
case 1:
if (kIsWeb){
// final _base64 = base64Encode([1,2,3,4,5]);
// final anchor = AnchorElement(href: 'data:application/octet-stream;base64,$_base64')..target = 'blank';
//final anchor = AnchorElement(href: 'https://inoue.szy.lol/api/replay/${widget.record.replayId}')..target = 'blank';
//anchor.download = "${widget.record.replayId}.ttrm";
//document.body!.append(anchor);
//anchor.click();
//anchor.remove();
}else{ }else{
try{ time = framesToTime(snapshot.data!.roundLengths[roundSelector]);
String path = await teto.saveReplay(widget.record.replayId); readableTime = "${t.roundLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}\n${t.winner}: ${snapshot.data!.roundWinners[roundSelector][1]}";
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.replaySaved(path: path))));
} on TetrioReplayAlreadyExist{
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.replayAlreadySaved)));
} on SzyNotFound {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.replayExpired)));
} on SzyForbidden {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.replayRejected)));
} on SzyTooManyRequests {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.tooManyRequests)));
}
} }
}else{
switch (snapshot.error.runtimeType){
case ReplayNotAvalable:
reason = t.matchIsTooOld;
break; break;
case 2: case SzyNotFound:
await launchInBrowser(Uri.parse("https://tetr.io/#r:${widget.record.replayId}")); reason = t.matchIsTooOld;
break;
case SzyForbidden:
reason = t.errors.replayRejected;
break;
case SzyTooManyRequests:
reason = t.errors.tooManyRequests;
break; break;
default: default:
reason = snapshot.error.toString();
break;
} }
}) }
] return NestedScrollView(
),
backgroundColor: Colors.black,
body: SafeArea(
child: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value) { headerSliverBuilder: (context, value) {
return [ return [
SliverToBoxAdapter( SliverToBoxAdapter(
@ -178,7 +161,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
), ),
), ),
), ),
SliverToBoxAdapter( if (showMobileSelector) SliverToBoxAdapter(
child: Center( child: Center(
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -195,48 +178,10 @@ class TlMatchResultState extends State<TlMatchResultView> {
), ),
), ),
), ),
if (widget.record.ownId == widget.record.replayId) SliverToBoxAdapter( if (widget.record.ownId == widget.record.replayId && showMobileSelector) SliverToBoxAdapter(
child: Center(child: Text(t.p1nkl0bst3rAlert, textAlign: TextAlign.center)), child: Center(child: Text(t.p1nkl0bst3rAlert, textAlign: TextAlign.center)),
), ),
SliverToBoxAdapter(child: FutureBuilder(future: replayData, builder: (context, snapshot) { if (showMobileSelector) SliverToBoxAdapter(child: Center(child: Text(snapshot.hasError ? reason : readableTime, textAlign: TextAlign.center))),
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const LinearProgressIndicator();
case ConnectionState.done:
if (!snapshot.hasError){
if (roundSelector.isNegative){
var time = framesToTime(snapshot.data!.totalLength);
return Center(child: Text("${t.matchLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}", textAlign: TextAlign.center));
}else{
var time = framesToTime(snapshot.data!.roundLengths[roundSelector]);
return Center(child: Text("${t.roundLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}\n${t.winner}: ${snapshot.data!.roundWinners[roundSelector][1]}", textAlign: TextAlign.center,));
}
}else{
String reason;
switch (snapshot.error.runtimeType){
case ReplayNotAvalable:
reason = t.matchIsTooOld;
break;
case SzyNotFound:
reason = t.matchIsTooOld;
break;
case SzyForbidden:
reason = t.errors.replayRejected;
break;
case SzyTooManyRequests:
reason = t.errors.tooManyRequests;
break;
default:
reason = snapshot.error.toString();
break;
}
return Text("${t.replayIssue}: $reason", textAlign: TextAlign.center);
}
}
},),),
const SliverToBoxAdapter( const SliverToBoxAdapter(
child: Divider(), child: Divider(),
) )
@ -248,47 +193,47 @@ class TlMatchResultState extends State<TlMatchResultView> {
children: [ children: [
CompareThingy( CompareThingy(
label: "APM", label: "APM",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondaryTracking[roundSelector], greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].apm :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondaryTracking[roundSelector], roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondaryTracking[roundSelector],
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].apm :
roundSelector == -1 ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondaryTracking[roundSelector],
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "PPS", label: "PPS",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiaryTracking[roundSelector], greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].pps:
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiaryTracking[roundSelector], roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiaryTracking[roundSelector],
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].pps :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiary: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiaryTracking[roundSelector],
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "VS", label: "VS",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extraTracking[roundSelector], greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].vs :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extraTracking[roundSelector], roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extraTracking[roundSelector],
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].vs :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extraTracking[roundSelector],
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: true,
), ),
FutureBuilder(future: replayData, builder: (BuildContext context, AsyncSnapshot<ReplayData?> snapshot){ if (snapshot.hasData) Column(children: [
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const LinearProgressIndicator();
case ConnectionState.done:
if (!snapshot.hasError){
var greenSidePlayer = snapshot.data!.endcontext.indexWhere((element) => element.userId == widget.initPlayerId);
var redSidePlayer = snapshot.data!.endcontext.indexWhere((element) => element.userId != widget.initPlayerId);
return Column(children: [
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].inputs : snapshot.data!.stats[roundSelector][greenSidePlayer].inputs, CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].inputs : snapshot.data!.stats[roundSelector][greenSidePlayer].inputs,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].inputs : snapshot.data!.stats[roundSelector][redSidePlayer].inputs, redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].inputs : snapshot.data!.stats[roundSelector][redSidePlayer].inputs,
label: "Inputs", higherIsBetter: true), label: "Inputs", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].piecesPlaced : snapshot.data!.stats[roundSelector][greenSidePlayer].piecesPlaced, CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].piecesPlaced : snapshot.data!.stats[roundSelector][greenSidePlayer].piecesPlaced,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].piecesPlaced : snapshot.data!.stats[roundSelector][redSidePlayer].piecesPlaced, redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].piecesPlaced : snapshot.data!.stats[roundSelector][redSidePlayer].piecesPlaced,
label: "Pieces Placed", higherIsBetter: true), label: "Pieces Placed", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp, CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kpp :
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][redSidePlayer].kpp, roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[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.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps, CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kps :
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kps : snapshot.data!.stats[roundSelector][redSidePlayer].kps, roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[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,
@ -296,8 +241,10 @@ class TlMatchResultState extends State<TlMatchResultView> {
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].score : snapshot.data!.stats[roundSelector][greenSidePlayer].score, CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].score : snapshot.data!.stats[roundSelector][greenSidePlayer].score,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].score : snapshot.data!.stats[roundSelector][redSidePlayer].score, redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].score : snapshot.data!.stats[roundSelector][redSidePlayer].score,
label: "Score", higherIsBetter: true), label: "Score", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp, CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].spp :
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][redSidePlayer].spp, roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[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,
@ -342,13 +289,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].clears.quads : snapshot.data!.stats[roundSelector][greenSidePlayer].clears.quads, CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].clears.quads : snapshot.data!.stats[roundSelector][greenSidePlayer].clears.quads,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].clears.quads : snapshot.data!.stats[roundSelector][redSidePlayer].clears.quads, redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].clears.quads : snapshot.data!.stats[roundSelector][redSidePlayer].clears.quads,
label: "Quads", higherIsBetter: true), label: "Quads", higherIsBetter: true),
],); ],),
}else{
return Container();
}
}
})
], ],
), ),
const Divider(), const Divider(),
@ -363,113 +304,141 @@ class TlMatchResultState extends State<TlMatchResultView> {
), ),
CompareThingy( CompareThingy(
label: "APP", label: "APP",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.app : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].app, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.app :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.app : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].app, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.app : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].app,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.app :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.app : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].app,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "VS/APM", label: "VS/APM",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.vsapm : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].vsapm, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.vsapm :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.vsapm : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].vsapm, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.vsapm : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].vsapm,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.vsapm :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.vsapm : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].vsapm,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "DS/S", label: "DS/S",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dss : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].dss, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.dss :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dss : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].dss, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dss : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].dss,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.dss :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dss : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].dss,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "DS/P", label: "DS/P",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dsp : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].dsp, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.dsp :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dsp : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].dsp, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.dsp : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].dsp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.dsp :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.dsp : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].dsp,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "APP + DS/P", label: "APP + DS/P",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.appdsp : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].appdsp, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.appdsp :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.appdsp : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].appdsp, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.appdsp : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].appdsp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.appdsp :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.appdsp : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].appdsp,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "),
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.cheese : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].cheese, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.cheese :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.cheese : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].cheese, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.cheese : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].cheese,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.cheese :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.cheese : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].cheese,
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: false,
), ),
CompareThingy( CompareThingy(
label: "Gb Eff.", label: "Gb Eff.",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.gbe : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].gbe, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.gbe :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.gbe : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].gbe, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.gbe : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].gbe,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.gbe :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.gbe : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].gbe,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "wAPP", label: "wAPP",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.nyaapp : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].nyaapp, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.nyaapp :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.nyaapp : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].nyaapp, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.nyaapp : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].nyaapp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.nyaapp :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.nyaapp : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].nyaapp,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "Area", label: "Area",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.area : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].area, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats.area :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.area : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].area, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats.area : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector].area,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats.area :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats.area : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector].area,
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: t.statCellNum.estOfTRShort, label: t.statCellNum.estOfTRShort,
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).estTr.esttr : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).estTrTracking[roundSelector].esttr, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].estTr.esttr :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).estTr.esttr : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).estTrTracking[roundSelector].esttr, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).estTr.esttr : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).estTrTracking[roundSelector].esttr,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].estTr.esttr :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).estTr.esttr : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).estTrTracking[roundSelector].esttr,
fractionDigits: 2, fractionDigits: 2,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "Opener", label: "Opener",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.opener : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].opener, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].playstyle.opener :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.opener : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].opener, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.opener : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].opener,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].playstyle.opener :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.opener : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].opener,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "Plonk", label: "Plonk",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.plonk : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].plonk, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].playstyle.plonk :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.plonk : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].plonk, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.plonk : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].plonk,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].playstyle.plonk :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.plonk : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].plonk,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "Stride", label: "Stride",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.stride : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].stride, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].playstyle.stride :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.stride : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].stride, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.stride : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].stride,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].playstyle.stride :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.stride : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].stride,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
CompareThingy( CompareThingy(
label: "Inf. DS", label: "Inf. DS",
greenSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.infds : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].infds, greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].playstyle.infds :
redSide: roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.infds : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].infds, roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle.infds : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector].infds,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].playstyle.infds :
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle.infds : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector].infds,
fractionDigits: 3, fractionDigits: 3,
higherIsBetter: true, higherIsBetter: true,
), ),
VsGraphs( VsGraphs(
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondaryTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].apm : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondaryTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiaryTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].pps : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiaryTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extraTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].vs : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extraTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].nerdStats : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStats : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).nerdStatsTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].playstyle : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyle : widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).playstyleTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondaryTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].apm : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondaryTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiaryTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].pps : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiary : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiaryTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extraTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].vs : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extra : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extraTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector], (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].nerdStats : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStats : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).nerdStatsTracking[roundSelector],
roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector] (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].playstyle : roundSelector.isNegative ? widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyle : widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).playstyleTracking[roundSelector]
) )
], ],
), ),
@ -507,8 +476,268 @@ class TlMatchResultState extends State<TlMatchResultView> {
) )
], ],
) )
);
});
}
Widget buildRoundSelector(double width){
return Padding(
padding: const EdgeInsets.all(8.000000),
child: SizedBox(
width: width,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverToBoxAdapter(child:
Wrap(
alignment: WrapAlignment.spaceBetween,
children: [
FutureBuilder(future: replayData, builder: (context, snapshot) {
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const CircularProgressIndicator();
case ConnectionState.done:
if (!snapshot.hasError){
var time = framesToTime(snapshot.data!.totalLength);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(t.matchLength),
RichText(
text: TextSpan(
text: "${time.inMinutes}:${NumberFormat("00", LocaleSettings.currentLocale.languageCode).format(time.inSeconds%60)}",
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white),
children: [TextSpan(text: ".${NumberFormat("000", LocaleSettings.currentLocale.languageCode).format(time.inMilliseconds%1000)}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
), ),
)
],);
}else{
String reason;
switch (snapshot.error.runtimeType){
case ReplayNotAvalable:
reason = t.matchIsTooOld;
break;
case SzyNotFound:
reason = t.matchIsTooOld;
break;
case SzyForbidden:
reason = t.errors.replayRejected;
break;
case SzyTooManyRequests:
reason = t.errors.tooManyRequests;
break;
default:
reason = snapshot.error.toString();
break;
}
timeWeightedStatsAvaliable = false;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.record.ownId != widget.record.replayId) Text("${t.replayIssue}: $reason"),
if (widget.record.ownId == widget.record.replayId) Center(child: Text(t.p1nkl0bst3rAlert, textAlign: TextAlign.center)),
if (widget.record.ownId != widget.record.replayId) RichText(
text: const TextSpan(
text: "-:--",
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.grey),
children: [TextSpan(text: ".---", style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
),
)
],);
}
}
},),
if (widget.record.ownId != widget.record.replayId) Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(t.numberOfRounds),
RichText(
text: TextSpan(
text: widget.record.endContext.first.secondaryTracking.isNotEmpty ? widget.record.endContext.first.secondaryTracking.length.toString() : "---",
style: TextStyle(
fontFamily: "Eurostile Round Extended",
fontSize: 28,
fontWeight: FontWeight.w500,
color: widget.record.endContext.first.secondaryTracking.isEmpty ? Colors.grey : Colors.white
),
),
)
],),
Column(children: [
OverflowBar(
alignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
TextButton( style: roundSelector == -1 ? ButtonStyle(backgroundColor: MaterialStatePropertyAll(Colors.grey.shade900)) : null,
onPressed: () {
roundSelector = -1;
setState(() {});
}, child: Text(t.matchStats)),
TextButton( style: roundSelector == -2 ? ButtonStyle(backgroundColor: MaterialStatePropertyAll(Colors.grey.shade900)) : null,
onPressed: timeWeightedStatsAvaliable ? () {
roundSelector = -2;
setState(() {});
} : null, child: Text(t.timeWeightedmatchStats)) ,
//TextButton( child: const Text('Button 3'), onPressed: () {}),
],
)
]),
// Column(
// children: [
// ListTile(
// leading: Text("Round time"),
// title: Text("Winner", textAlign: TextAlign.center,),
// trailing: Text("Round stats"),
// )
// ],
// )
],
)
)
];
},
body: ListView.builder(itemCount: widget.record.endContext.first.secondaryTracking.length,
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(future: replayData, builder: (context, snapshot) {
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const LinearProgressIndicator();
case ConnectionState.done:
if (!snapshot.hasError){
var time = framesToTime(snapshot.data!.roundLengths[index]);
var accentColor = snapshot.data!.roundWinners[index][0] == widget.initPlayerId ? Colors.green : Colors.red;
var bgColor = roundSelector == index ? Colors.grey.shade900 : Colors.transparent;
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
stops: const [0, 0.05],
colors: [accentColor, bgColor]
)
),
child: ListTile(
leading:RichText(
text: TextSpan(
text: "${time.inMinutes}:${NumberFormat("00", LocaleSettings.currentLocale.languageCode).format(time.inSeconds%60)}",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 22, fontWeight: FontWeight.w500, color: Colors.white),
children: [TextSpan(text: ".${NumberFormat("000", LocaleSettings.currentLocale.languageCode).format(time.inMilliseconds%1000)}", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
),
),
title: Text(snapshot.data!.roundWinners[index][1], textAlign: TextAlign.center),
trailing: TrailingStats(
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extraTracking[index],
widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extraTracking[index]
),
onTap:(){
roundSelector = index;
setState(() {});
},
),
);
}else{
return Container(
decoration: BoxDecoration(
color: roundSelector == index ? Colors.grey.shade900 : Colors.transparent
),
child: ListTile(
leading: RichText(
text: const TextSpan(
text: "-:--",
style: TextStyle(fontFamily: "Eurostile Round", fontSize: 22, fontWeight: FontWeight.w500, color: Colors.grey),
children: [TextSpan(text: ".---", style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
),
),
title: const Text("---", style: TextStyle(color: Colors.grey), textAlign: TextAlign.center),
trailing: TrailingStats(
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).secondaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).tertiaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).extraTracking[index],
widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).secondaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).tertiaryTracking[index],
widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).extraTracking[index]
),
onTap:(){
roundSelector = index;
setState(() {});
},
), ),
); );
} }
} }
}
);
})
),
),
);
}
Widget getMainWidget(double viewportWidth) {
if (viewportWidth <= 1200) {
return Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 768),
child: buildComparison(viewportWidth > 768, true)
),
);
} else {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 768,
child: buildComparison(true, false)
),
Container(
constraints: const BoxConstraints(maxWidth: 768),
child: buildRoundSelector(max(viewportWidth-768-16, 200)),
)
],
);
}
}
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
return Scaffold(
appBar: AppBar(
title: Text("${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}"),
actions: [
PopupMenuButton(
enabled: widget.record.replayAvalable,
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
value: 1,
child: Text(t.downloadReplay),
),
PopupMenuItem(
value: 2,
child: Text(t.openReplay),
),
],
onSelected: (value) async {
switch (value) {
case 1:
await launchInBrowser(Uri.parse("https://inoue.szy.lol/api/replay/${widget.record.replayId}"));
break;
case 2:
await launchInBrowser(Uri.parse("https://tetr.io/#r:${widget.record.replayId}"));
break;
default:
}
})
]
),
backgroundColor: Colors.black,
body: getMainWidget(MediaQuery.of(context).size.width),
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/utils/text_shadow.dart';
class FinesseThingy extends StatelessWidget{
final Finesse? finesse;
final double? finessePercentage;
const FinesseThingy(this.finesse, this.finessePercentage, {super.key});
Color getFinesseColor(){
if (finesse == null) return Colors.grey;
if (finesse!.faults == 0) return Colors.purpleAccent;
if (finessePercentage! > 0.4) return Colors.white;
else return Colors.redAccent;
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.bottomStart,
children: [
Text("f", style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 65,
height: 1.2,
)),
Positioned(child: Text("inesse", style: TextStyle(fontFamily: "Eurostile Round Extended")), left: 25, top: 20),
Positioned(
child: Text("${finesse != null ? finesse!.faults : "---"}F", style: TextStyle(
color: getFinesseColor()
)), right: 0, top: 20),
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text("${finesse != null ? f2.format(finessePercentage! * 100) : "---.--"}%", style: TextStyle(
shadows: textShadow,
fontFamily: "Eurostile Round Extended",
fontSize: 36,
fontWeight: FontWeight.w500,
color: getFinesseColor()
)),
)
],
);
}
}

110
lib/widgets/gauget_num.dart Normal file
View File

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

View File

@ -1,10 +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: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/utils/numers_formats.dart';
final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
class Graphs extends StatelessWidget{ class Graphs extends StatelessWidget{
const Graphs( const Graphs(
@ -125,13 +122,13 @@ class Graphs extends StatelessWidget{
getTitle: (index, angle) { getTitle: (index, angle) {
switch (index) { switch (index) {
case 0: case 0:
return RadarChartTitle(text: 'Opener\n${_f2.format(playstyle.opener)}', angle: 0, positionPercentageOffset: 0.05); return RadarChartTitle(text: 'Opener\n${percentage.format(playstyle.opener)}', angle: 0, positionPercentageOffset: 0.05);
case 1: case 1:
return RadarChartTitle(text: 'Stride\n${_f2.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${_f2.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${_f2.format(playstyle.plonk)}', angle: 0, positionPercentageOffset: 0.05); return RadarChartTitle(text: 'Plonk\n${percentage.format(playstyle.plonk)}', angle: 0, positionPercentageOffset: 0.05);
default: default:
return const RadarChartTitle(text: ''); return const RadarChartTitle(text: '');
} }

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
class LineclearsThingy extends StatelessWidget{
final Clears clears;
final int lines;
final int holds;
final int tSpins;
const LineclearsThingy(this.clears, this.lines, this.holds, this.tSpins, {super.key});
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 20,
children: [
SizedBox(
width: 150,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(t.numOfGameActions.lineClears(n: lines), style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round Extended"), textAlign: TextAlign.center),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Quads"), Text(clears.quads.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Triples"), Text(clears.triples.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Doubles"), Text(clears.doubles.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Singles"), Text(clears.singles.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Text("\n${t.numOfGameActions.pc}"), Text("\n${clears.allClears.toString()}")]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Text(t.numOfGameActions.hold), Text(holds.toString())]),
],
),
),
SizedBox(
width: 150,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(t.numOfGameActions.tspinsTotal(n: tSpins), style: const TextStyle(color: Colors.white, fontFamily: "Eurostile Round Extended"), textAlign: TextAlign.center),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("T-spins triples"), Text(clears.tSpinTriples.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("T-spins doubles"), Text(clears.tSpinDoubles.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("T-spins singles"), Text(clears.tSpinSingles.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("T-spins zeros"), Text(clears.tSpinZeros.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Mini T-spins doubles"), Text(clears.tSpinMiniDoubles.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Mini T-spins singles"), Text(clears.tSpinMiniSingles.toString())]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [const Text("Mini T-spins zeros"), Text(clears.tSpinMiniZeros.toString())]),
],
),
),
],
);
}
}

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
class TrailingStats extends StatelessWidget{
final double yourAPM;
final double yourPPS;
final double yourVS;
final double notyourAPM;
final double notyourPPS;
final double notyourVS;
const TrailingStats(this.yourAPM, this.yourPPS, this.yourVS, this.notyourAPM, this.notyourPPS, this.notyourVS, {super.key});
@override
Widget build(BuildContext context) {
const TextStyle style = TextStyle(height: 1.1, fontWeight: FontWeight.w100);
return Table(
defaultColumnWidth: const IntrinsicColumnWidth(),
defaultVerticalAlignment: TableCellVerticalAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
columnWidths: const {
0: FixedColumnWidth(42),
2: FixedColumnWidth(42),
},
children: [
TableRow(children: [Text(f2.format(yourAPM), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourAPM), textAlign: TextAlign.right, style: style), const Text(" APM", textAlign: TextAlign.right, style: style)]),
TableRow(children: [Text(f2.format(yourPPS), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourPPS), textAlign: TextAlign.right, style: style), const Text(" PPS", textAlign: TextAlign.right, style: style)]),
TableRow(children: [Text(f2.format(yourVS), textAlign: TextAlign.right, style: style), const Text(" :", style: style), Text(f2.format(notyourVS), textAlign: TextAlign.right, style: style), const Text(" VS", textAlign: TextAlign.right, style: style)]),
],
);
}
}

View File

@ -1,6 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.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/gen/strings.g.dart';
import 'package:tetra_stats/utils/colors_functions.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
class StatCellNum extends StatelessWidget { class StatCellNum extends StatelessWidget {
const StatCellNum( const StatCellNum(
@ -8,11 +11,12 @@ class StatCellNum extends StatelessWidget {
required this.playerStat, required this.playerStat,
required this.playerStatLabel, required this.playerStatLabel,
required this.isScreenBig, required this.isScreenBig,
this.smallDecimal = true,
this.alertWidgets, this.alertWidgets,
this.fractionDigits, this.fractionDigits,
this.oldPlayerStat, this.oldPlayerStat,
required this.higherIsBetter, required this.higherIsBetter,
this.okText, this.alertTitle}); this.okText, this.alertTitle, this.pos, this.averageStat});
final num playerStat; final num playerStat;
final num? oldPlayerStat; final num? oldPlayerStat;
@ -20,39 +24,57 @@ class StatCellNum extends StatelessWidget {
final String playerStatLabel; final String playerStatLabel;
final String? okText; final String? okText;
final bool isScreenBig; final bool isScreenBig;
final bool smallDecimal;
final String? alertTitle; final String? alertTitle;
final List<Widget>? alertWidgets; final List<Widget>? alertWidgets;
final int? fractionDigits; final int? fractionDigits;
final LeaderboardPosition? pos;
final num? averageStat;
Color getStatColor(){
if (averageStat == null) return Colors.white;
num percentile = (higherIsBetter ? playerStat / averageStat! : averageStat! / playerStat).abs();
if (percentile > 1.50) return Colors.purpleAccent;
if (percentile > 1.20) return Colors.blueAccent;
if (percentile > 0.90) return Colors.greenAccent;
if (percentile > 0.70) return Colors.yellowAccent;
return Colors.redAccent;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = fractionDigits ?? 0; NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = fractionDigits ?? 0;
NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
NumberFormat fractionf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0)..maximumIntegerDigits = 0; NumberFormat fractionf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0)..maximumIntegerDigits = 0;
num fraction = playerStat.isNegative ? 1 - (playerStat - playerStat.floor()) : playerStat - playerStat.floor(); num fraction = playerStat.isNegative ? 1 - (playerStat - playerStat.floor()) : playerStat - playerStat.floor();
int integer = playerStat.isNegative ? (playerStat + fraction).toInt() : (playerStat - fraction).toInt(); int integer = playerStat.isNegative ? (playerStat + fraction).toInt() : (playerStat - fraction).toInt();
// String valueAsString = fractionDigits == null ? f.format(playerStat.floor()) : f.format(playerStat);
// var exploded = valueAsString.split(".");
return Column( return Column(
children: [ children: [
RichText( RichText(
text: TextSpan(text: intf.format(integer), text: TextSpan(text: intf.format(integer),
children: [ children: [
TextSpan(text: fractionf.format(fraction).substring(1), style: const TextStyle(fontSize: 16)) TextSpan(text: fractionf.format(fraction).substring(1), style: smallDecimal ? const TextStyle(fontSize: 16) : null)
], ],
style: TextStyle( style: TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round Extended",
//fontWeight: FontWeight.bold,
fontSize: isScreenBig ? 32 : 24, fontSize: isScreenBig ? 32 : 24,
color: Colors.white color: getStatColor()
) )
) )
), ),
if (oldPlayerStat != null) Text(comparef.format(playerStat - oldPlayerStat!), style: TextStyle( if (oldPlayerStat != null || pos != null) RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
children: [
if (oldPlayerStat != null) TextSpan(text: comparef.format(playerStat - oldPlayerStat!), style: TextStyle(
color: higherIsBetter ? color: higherIsBetter ?
oldPlayerStat! > playerStat ? Colors.red : Colors.green : oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
oldPlayerStat! < playerStat ? Colors.red : Colors.green oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
),), ),),
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)))
]
),
),
alertWidgets == null alertWidgets == null
? Text( ? Text(
playerStatLabel, playerStatLabel,

View File

@ -3,13 +3,16 @@ import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart'; import 'package:syncfusion_flutter_gauges/gauges.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:tetra_stats/utils/colors_functions.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/widgets/gauget_num.dart';
import 'package:tetra_stats/widgets/graphs.dart'; import 'package:tetra_stats/widgets/graphs.dart';
import 'package:tetra_stats/widgets/stat_sell_num.dart'; import 'package:tetra_stats/widgets/stat_sell_num.dart';
var fDiff = NumberFormat("+#,###.###;-#,###.###"); var fDiff = NumberFormat("+#,###.###;-#,###.###");
var intFDiff = NumberFormat("+#,###;-#,###");
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
late RangeValues _currentRangeValues; late RangeValues _currentRangeValues;
TetraLeagueAlpha? oldTl; TetraLeagueAlpha? oldTl;
late TetraLeagueAlpha currentTl; late TetraLeagueAlpha currentTl;
@ -23,18 +26,26 @@ class TLThingy extends StatefulWidget {
final bool bot; final bool bot;
final bool guest; final bool guest;
final double? topTR; final double? topTR;
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR}); final PlayerLeaderboardPosition? lbPositions;
final TetraLeagueAlpha? averages;
final double? thatRankCutoff;
final double? thatRankTarget;
final double? nextRankCutoff;
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});
@override @override
State<TLThingy> createState() => _TLThingyState(); State<TLThingy> createState() => _TLThingyState();
} }
class _TLThingyState extends State<TLThingy> { class _TLThingyState extends State<TLThingy> {
late bool oskKagariGimmick;
@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;
try{ try{
oldTl = sortedStates[1].tlSeason1; oldTl = sortedStates[1].tlSeason1;
}on RangeError{ }on RangeError{
@ -47,9 +58,11 @@ class _TLThingyState extends State<TLThingy> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Translations.of(context); final t = Translations.of(context);
NumberFormat fractionfEstTR = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..maximumIntegerDigits = 0;
NumberFormat fractionfEstTRAcc = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3)..maximumIntegerDigits = 0;
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,)); if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(builder: (context, constraints) {
bool bigScreen = constraints.maxWidth > 768; bool bigScreen = constraints.maxWidth >= 768;
return ListView.builder( return ListView.builder(
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
itemCount: 1, itemCount: 1,
@ -87,7 +100,7 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
children: [ children: [
widget.userID == "5e32fc85ab319c2ab1beb07c" // he love her so much, you can't even imagine (widget.userID == "5e32fc85ab319c2ab1beb07c" && oskKagariGimmick) // he love her so much, you can't even imagine
? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform? ? Image.asset("res/icons/kagari.png", height: 128) // Btw why she wearing Kazamatsuri high school uniform?
: Image.asset("res/tetrio_tl_alpha_ranks/${currentTl.rank}.png", height: 128), : Image.asset("res/tetrio_tl_alpha_ranks/${currentTl.rank}.png", height: 128),
Column( Column(
@ -146,12 +159,12 @@ class _TLThingyState extends State<TLThingy> {
softWrap: true, softWrap: true,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontFamily: "Eurostile Round Extended", fontFamily: "Eurostile Round",
fontSize: bigScreen ? 42 : 28, fontSize: bigScreen ? 42 : 28,
overflow: TextOverflow.visible, overflow: TextOverflow.visible,
)), )),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), padding: const EdgeInsets.fromLTRB(8, 16, 8, 48),
child: Wrap( child: Wrap(
direction: Axis.horizontal, direction: Axis.horizontal,
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
@ -159,13 +172,13 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.start, crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
children: [ children: [
if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm), if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm, pos: widget.lbPositions?.apm, averageStat: widget.averages?.apm),
if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps), if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps, pos: widget.lbPositions?.pps, averageStat: widget.averages?.pps, smallDecimal: false),
if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs), if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs, pos: widget.lbPositions?.vs, averageStat: widget.averages?.vs),
if (currentTl.standingLocal > 0) StatCellNum(playerStat: currentTl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal), if (currentTl.standingLocal > 0) StatCellNum(playerStat: currentTl.standingLocal, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.lbpc, higherIsBetter: false, oldPlayerStat: oldTl?.standingLocal),
StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed), StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed, pos: widget.lbPositions?.gamesPlayed),
StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon), StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon, pos: widget.lbPositions?.gamesWon),
StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null), StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null, pos: widget.lbPositions?.winrate, averageStat: widget.averages != null ? widget.averages!.winrate * 100 : null),
], ],
), ),
), ),
@ -182,134 +195,31 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.start, crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
children: [ children: [
SizedBox( GaugetNum(playerStat: currentTl.nerdStats!.app, playerStatLabel: t.statCellNum.app, higherIsBetter: true, minimum: 0, maximum: 1, ranges: [
width: 200,
height: 120,
child: SfRadialGauge(
title: GaugeTitle(text: t.statCellNum.app),
axes: [RadialAxis(
startAngle: 180,
endAngle: 360,
showLabels: false,
showTicks: false,
radiusFactor: 2.1,
centerY: 0.5,
minimum: 0,
maximum: 1,
ranges: [
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red), GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow), GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green), GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue), GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple), GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
], ], alertWidgets: [
pointers: [
NeedlePointer(
value: currentTl.nerdStats!.app,
enableAnimation: true,
needleLength: 0.9,
needleStartWidth: 2,
needleEndWidth: 15,
knobStyle: const KnobStyle(color: Colors.transparent),
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
],
annotations: [GaugeAnnotation(
widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.app),
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)),
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(t.statCellNum.app,
style: const TextStyle(
fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView(
child: ListBody(children: [
Text(t.statCellNum.appDescription), Text(t.statCellNum.appDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.app}") Text("${t.exactValue}: ${currentTl.nerdStats!.app}")
]), ], oldPlayerStat: oldTl?.nerdStats?.app, pos: widget.lbPositions?.app,
), averageStat: widget.averages?.nerdStats?.app),
actions: <Widget>[ GaugetNum(playerStat: currentTl.nerdStats!.vsapm, playerStatLabel: "VS / APM", higherIsBetter: true, minimum: 1.8, maximum: 2.4, ranges: [
TextButton(
child: Text(t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,),
if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle(
color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ?
Colors.red :
Colors.green
),), positionFactor: 0.05,)],
)],),
),
SizedBox(
width: 200,
height: 120,
child: SfRadialGauge(
title: const GaugeTitle(text: "VS / APM"),
axes: [RadialAxis(
startAngle: 180,
endAngle: 360,
showTicks: false,
showLabels: false,
radiusFactor: 2.1,
centerY: 0.5,
minimum: 1.8,
maximum: 2.4,
ranges: [
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green), GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue), GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple), GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
], ], alertWidgets: [
pointers: [
NeedlePointer(
value: currentTl.nerdStats!.vsapm,
enableAnimation: true,
needleLength: 0.9,
needleStartWidth: 2,
needleEndWidth: 15,
knobStyle: const KnobStyle(color: Colors.transparent),
gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),)
],
annotations: [GaugeAnnotation(
widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.vsapm),
style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)),
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text("VS / APM",
style: TextStyle(
fontFamily: "Eurostile Round Extended")),
content: SingleChildScrollView(
child: ListBody(children: [
Text(t.statCellNum.vsapmDescription), Text(t.statCellNum.vsapmDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}") Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}")
], oldPlayerStat: oldTl?.nerdStats?.vsapm, pos: widget.lbPositions?.vsapm,
averageStat: widget.averages?.nerdStats?.vsapm)
]), ]),
), ),
actions: <Widget>[ Padding(
TextButton( padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Text(t.popupActions.ok), child: Wrap(
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05),
if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm), style: TextStyle(
color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ?
Colors.red :
Colors.green
),), positionFactor: 0.05,)],
)],),
),]),
),
Wrap(
direction: Axis.horizontal, direction: Axis.horizontal,
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
spacing: 25, spacing: 25,
@ -317,6 +227,8 @@ class _TLThingyState extends State<TLThingy> {
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
children: [ children: [
StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss, StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss,
pos: widget.lbPositions?.dss,
averageStat: widget.averages?.nerdStats?.dss, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.dssDescription), alertWidgets: [Text(t.statCellNum.dssDescription),
Text("${t.formula}: (VS / 100) - (APM / 60)"), Text("${t.formula}: (VS / 100) - (APM / 60)"),
Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),],
@ -324,6 +236,8 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true, higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.dss,), oldPlayerStat: oldTl?.nerdStats?.dss,),
StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp, StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp,
pos: widget.lbPositions?.dsp,
averageStat: widget.averages?.nerdStats?.dsp, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.dspDescription), alertWidgets: [Text(t.statCellNum.dspDescription),
Text("${t.formula}: DS/S / PPS"), Text("${t.formula}: DS/S / PPS"),
Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),],
@ -331,6 +245,8 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true, higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.dsp,), oldPlayerStat: oldTl?.nerdStats?.dsp,),
StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp, StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp,
pos: widget.lbPositions?.appdsp,
averageStat: widget.averages?.nerdStats?.appdsp, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.appdspDescription), alertWidgets: [Text(t.statCellNum.appdspDescription),
Text("${t.formula}: APP + DS/P"), Text("${t.formula}: APP + DS/P"),
Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),],
@ -338,13 +254,17 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true, higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.appdsp,), oldPlayerStat: oldTl?.nerdStats?.appdsp,),
StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese, StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese,
pos: widget.lbPositions?.cheese,
//averageStat: rankAverages?.nerdStats?.cheese, TODO: questonable
alertWidgets: [Text(t.statCellNum.cheeseDescription), alertWidgets: [Text(t.statCellNum.cheeseDescription),
Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"), Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"),
Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),],
okText: t.popupActions.ok, okText: t.popupActions.ok,
higherIsBetter: true, higherIsBetter: false,
oldPlayerStat: oldTl?.nerdStats?.cheese,), oldPlayerStat: oldTl?.nerdStats?.cheese,),
StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe, StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe,
pos: widget.lbPositions?.gbe,
averageStat: widget.averages?.nerdStats?.gbe, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.gbeDescription), alertWidgets: [Text(t.statCellNum.gbeDescription),
Text("${t.formula}: APP * DS/P * 2"), Text("${t.formula}: APP * DS/P * 2"),
Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),],
@ -352,6 +272,8 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true, higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.gbe,), oldPlayerStat: oldTl?.nerdStats?.gbe,),
StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp, StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp,
pos: widget.lbPositions?.nyaapp,
averageStat: widget.averages?.nerdStats?.nyaapp, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.nyaappDescription), alertWidgets: [Text(t.statCellNum.nyaappDescription),
Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"), Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"),
Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),],
@ -359,53 +281,89 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true, higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.nyaapp,), oldPlayerStat: oldTl?.nerdStats?.nyaapp,),
StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area, StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area,
pos: widget.lbPositions?.area,
averageStat: widget.averages?.nerdStats?.area,
alertWidgets: [Text(t.statCellNum.areaDescription), alertWidgets: [Text(t.statCellNum.areaDescription),
Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"), Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"),
Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),], Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),],
okText: t.popupActions.ok, okText: t.popupActions.ok,
higherIsBetter: true, higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.area,) oldPlayerStat: oldTl?.nerdStats?.area,)
]) ]),
)
], ],
), ),
if (currentTl.estTr != null) if (currentTl.estTr != null)
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), padding: const EdgeInsets.fromLTRB(0, 20, 0, 20),
child: SizedBox( child: Container(
//alignment: Alignment.center,
width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85, width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85,
height: 70,
constraints: const BoxConstraints(maxWidth: 768),
child: Stack(
children: [
Positioned(
left: 0,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Text(t.statCellNum.estOfTR, style: const TextStyle(height: 0.1),),
mainAxisAlignment: MainAxisAlignment.spaceBetween, RichText(
text: TextSpan(
text: intf.format(currentTl.estTr!.esttr.truncate()),
style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
children: [TextSpan(text: fractionfEstTR.format(currentTl.estTr!.esttr - currentTl.estTr!.esttr.truncate()).substring(1), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))]
),
),
RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5),
children: [ children: [
Text( if (oldTl?.estTr?.esttr != null) TextSpan(text: comparef.format(currentTl.estTr!.esttr - oldTl!.estTr!.esttr), style: TextStyle(
"${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:", color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent
style: const TextStyle(fontSize: 24), ),),
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) const TextSpan(text: ""),
TextSpan(text: "Glicko: ${f2.format(currentTl.estTr!.estglicko)}")
]
), ),
Text(
f2.format(currentTl.estTr!.esttr),
style: const TextStyle(fontSize: 24),
), ),
], ],),
), ),
if (currentTl.rating >= 0) Positioned(
Row( right: 0,
mainAxisAlignment: MainAxisAlignment.spaceBetween, child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Text(t.statCellNum.accOfEst, style: const TextStyle(height: 0.1),),
"${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:", RichText(
style: const TextStyle(fontSize: 24), text: TextSpan(
text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? intFDiff.format(currentTl.esttracc!.truncate()) : "---",
style: TextStyle(fontFamily: "Eurostile Round", fontSize: bigScreen ? 36 : 30, fontWeight: FontWeight.w500, color: Colors.white),
children: [
TextSpan(text: (currentTl.esttracc != null && currentTl.bestRank != "z") ? fractionfEstTRAcc.format(currentTl.esttracc!.isNegative ? 1 - (currentTl.esttracc! - currentTl.esttracc!.truncate()) : (currentTl.esttracc! - currentTl.esttracc!.truncate())).substring(1) : ".---", style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))
]
), ),
Text(
fDiff.format(currentTl.esttracc!),
style: const TextStyle(fontSize: 24),
), ),
if ((oldTl?.esttracc != null || widget.lbPositions != null) && currentTl.bestRank != "z") RichText(text: TextSpan(
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5),
children: [
if (oldTl?.esttracc != null) TextSpan(text: comparef.format(currentTl.esttracc! - oldTl!.esttracc!), style: TextStyle(
color: oldTl!.esttracc! > currentTl.esttracc! ? Colors.redAccent : Colors.greenAccent
),),
if (oldTl?.esttracc != null && widget.lbPositions?.accOfEst != null) const TextSpan(text: ""),
if (widget.lbPositions?.accOfEst != null) TextSpan(text: widget.lbPositions!.accOfEst!.position >= 1000 ? "${t.top} ${f2.format(widget.lbPositions!.accOfEst!.percentage*100)}%" : "${widget.lbPositions!.accOfEst!.position}", style: TextStyle(color: getColorOfRank(widget.lbPositions!.accOfEst!.position)))
]
),
),
],),
)
], ],
), ),
], )
),
),
), ),
if (currentTl.nerdStats != null) Graphs(currentTl.apm!, currentTl.pps!, currentTl.vs!, currentTl.nerdStats!, currentTl.playstyle!) if (currentTl.nerdStats != null) Graphs(currentTl.apm!, currentTl.pps!, currentTl.vs!, currentTl.nerdStats!, currentTl.playstyle!)
] ]

View File

@ -91,6 +91,7 @@ class UserThingy extends StatelessWidget {
? 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("https://tetr.io/user-content/avatars/${player.userId}.jpg?rv=${player.avatarRevision}",
// 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);
return Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight); return Image.asset("res/avatars/tetrio_anon.png", fit: BoxFit.fitHeight, height: pfpHeight);
@ -271,7 +272,7 @@ class UserThingy extends StatelessWidget {
playerStatLabel: t.statCellNum.hoursPlayed, playerStatLabel: t.statCellNum.hoursPlayed,
isScreenBig: bigScreen, isScreenBig: bigScreen,
alertTitle: t.exactGametime, alertTitle: t.exactGametime,
alertWidgets: [Text(player.gameTime.toString(), style: const TextStyle(fontFamily: "Eurostile Round Extended"),)], alertWidgets: [Text(player.gameTime.toString(), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 24),)],
higherIsBetter: true,), higherIsBetter: true,),
if (player.gamesPlayed >= 0) if (player.gamesPlayed >= 0)
StatCellNum( StatCellNum(
@ -328,13 +329,29 @@ class UserThingy extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
child: Text( child: RichText(
"${player.country != null ? "${t.countries[player.country]}" : ""}${t.playerRole[player.role]}${t.playerRoleAccount}${player.registrationTime == null ? t.wasFromBeginning : '${t.created} ${dateFormat.format(player.registrationTime!)}'}${player.botmaster != null ? " ${t.botCreatedBy} ${player.botmaster}" : ""} ${player.supporterTier == 0 ? t.notSupporter : t.supporter(tier: player.supporterTier)}",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( text: TextSpan(text: "", style: const TextStyle(
fontFamily: "Eurostile Round", fontFamily: "Eurostile Round",
fontSize: 16, fontSize: 16,
)), color: Colors.white,
),
children: [
if (player.country != null) TextSpan(text: "${t.countries[player.country]}"),
TextSpan(text: "${t.playerRole[player.role]}${t.playerRoleAccount}${player.registrationTime == null ? t.wasFromBeginning : '${t.created} ${dateFormat.format(player.registrationTime!)}'}"),
if (player.supporterTier > 0) const TextSpan(text: ""),
if (player.supporterTier > 0) WidgetSpan(child: Icon(player.supporterTier > 1 ? Icons.star : Icons.star_border, color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white), alignment: PlaceholderAlignment.middle, baseline: TextBaseline.alphabetic),
if (player.supporterTier > 0) TextSpan(text: player.supporterTier.toString(), style: TextStyle(color: player.supporterTier > 1 ? Colors.yellowAccent : Colors.white))
]
)
),
// Text(
// "${player.country != null ? "${t.countries[player.country]}" : ""}${t.playerRole[player.role]}${t.playerRoleAccount}${player.registrationTime == null ? t.wasFromBeginning : '${t.created} ${dateFormat.format(player.registrationTime!)}'}${player.botmaster != null ? " ${t.botCreatedBy} ${player.botmaster}" : ""} ${player.supporterTier == 0 ? t.notSupporter : t.supporter(tier: player.supporterTier)}",
// textAlign: TextAlign.center,
// style: const TextStyle(
// fontFamily: "Eurostile Round",
// fontSize: 16,
// )),
) )
], ],
), ),

View File

@ -5,26 +5,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "64.0.0" version: "67.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.0" version: "6.4.1"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.9" version: "3.4.10"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -141,10 +141,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dev_build name: dev_build
sha256: e476ac99174842cdb01e64c1d379d041d2150f9ad2cdd2713eb1ca1bdbf30507 sha256: e5d575f3de4b0e5f004e065e1e2d98fa012d634b61b5855216b5698ed7f1e443
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.16.3+2" version: "0.16.4+3"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
@ -165,10 +165,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -181,34 +181,34 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6" sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.2.1"
file_selector: file_selector:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_selector name: file_selector
sha256: "84eaf3e034d647859167d1f01cfe7b6352488f34c1b4932635012b202014c25b" sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.3"
file_selector_android: file_selector_android:
dependency: transitive dependency: transitive
description: description:
name: file_selector_android name: file_selector_android
sha256: b7556052dbcc25ef88f6eba45ab98aa5600382af8dfdabc9d644a93d97b7be7f sha256: "1cd66575f063b689e041aec836905ba7be18d76c9f0634d0d75daec825f67095"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0+4" version: "0.5.0+7"
file_selector_ios: file_selector_ios:
dependency: transitive dependency: transitive
description: description:
name: file_selector_ios name: file_selector_ios
sha256: "2f48db7e338b2255101c35c604b7ca5ab588dce032db7fc418a2fe5f28da63f8" sha256: b015154e6d9fddbc4d08916794df170b44531798c8dd709a026df162d07ad81d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1+7" version: "0.5.1+8"
file_selector_linux: file_selector_linux:
dependency: transitive dependency: transitive
description: description:
@ -229,10 +229,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file_selector_platform_interface name: file_selector_platform_interface
sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.1" version: "2.6.2"
file_selector_web: file_selector_web:
dependency: transitive dependency: transitive
description: description:
@ -253,10 +253,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: fl_chart name: fl_chart
sha256: fe6fec7d85975a99c73b9515a69a6e291364accfa0e4a5b3ce6de814d74b9a1c sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.66.0" version: "0.66.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -295,10 +295,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" sha256: "87e11b9df25a42e2db315b8b7a51fae8e66f57a4b2f50ec4b822d0fa155e6b52"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.18+2" version: "0.6.22"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -311,10 +311,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.9" version: "2.0.10+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -345,18 +345,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: ca7e4a2249f96773152f1853fa25933ac752495cdd7fdf5dafb9691bd05830fd sha256: "7ecb2f391edbca5473db591b48555a8912dde60edd0fb3013bd6743033b2d3f8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "13.2.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.2.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -377,10 +377,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.3" version: "4.1.7"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -421,6 +421,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -441,34 +465,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: markdown name: markdown
sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.1.1" version: "7.2.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -513,10 +537,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_parsing: path_parsing:
dependency: transitive dependency: transitive
description: description:
@ -529,26 +553,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -561,10 +585,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
@ -585,26 +609,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "3.1.4"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.7" version: "2.1.8"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.3" version: "3.7.4"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -617,10 +641,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: process_run name: process_run
sha256: "3d5335d17003a7c2fd5148be2d313f5b84244ab2e625162fdd44b4aaa48bea66" sha256: "8d9c6198b98fbbfb511edd42e7364e24d85c163e47398919871b952dc86a423e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.3+1" version: "0.14.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -657,10 +681,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.4" version: "2.3.5"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -673,10 +697,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
@ -734,18 +758,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: slang name: slang
sha256: "77fd99f7b0da15e671ef0289b24a0a63e74f693c58a0ca54111388e4c0ddb1dd" sha256: "5e08ac915ac27a3508863f37734280d30c3713d56746cd2e4a5da77413da4b95"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.28.0" version: "3.30.1"
slang_flutter: slang_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: slang_flutter name: slang_flutter
sha256: "57817bb15553bb5df37aed3bac497286bdd8c2eab6763f4de6815efe2c0becee" sha256: "9ee040b0d364d3a4d692e4af536acff6ef513870689403494ebc6d59b0dccea6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.28.0" version: "3.30.0"
source_map_stack_trace: source_map_stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -774,50 +798,50 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite name: sqflite
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.2"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0+2" version: "2.5.4"
sqflite_common_ffi: sqflite_common_ffi:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite_common_ffi name: sqflite_common_ffi
sha256: "873677ee78738a723d1ded4ccb23980581998d873d30ee9c331f6a81748663ff" sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.3"
sqflite_common_ffi_web: sqflite_common_ffi_web:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite_common_ffi_web name: sqflite_common_ffi_web
sha256: "3d4b550a09fda9eb0a9ce3bd98c8080f57b2cc1f4b19e844290e82843e73b63d" sha256: "0c2921454d2e4a227675fb952be9fef916cf65fb9e9b606b54cfdf080d3e9450"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.2+2" version: "0.4.2+3"
sqlite3: sqlite3:
dependency: transitive dependency: transitive
description: description:
name: sqlite3 name: sqlite3
sha256: c4a4c5a4b2a32e2d0f6837b33d7c91a67903891a5b7dbe706cf4b1f6b0c798c5 sha256: "072128763f1547e3e9b4735ce846bfd226d68019ccda54db4cd427b12dfdedc9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.0"
sqlite3_flutter_libs: sqlite3_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: "3e3583b77cf888a68eae2e49ee4f025f66b86623ef0d83c297c8d903daa14871" sha256: d6c31c8511c441d1f12f20b607343df1afe4eddf24a1cf85021677c8eea26060
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.18" version: "0.5.20"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -846,18 +870,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: syncfusion_flutter_core name: syncfusion_flutter_core
sha256: "69c827931957d5b121ee9f0b9b0b8d7d0d1ac537b61bcdd5c3fbffc044bbe86e" sha256: "7666506885ebc8f62bb928ad4588a73e20caaff2b2cf2b2b56f67d98f4113525"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "24.1.41" version: "24.2.9"
syncfusion_flutter_gauges: syncfusion_flutter_gauges:
dependency: "direct main" dependency: "direct main"
description: description:
name: syncfusion_flutter_gauges name: syncfusion_flutter_gauges
sha256: "78515dcfbed952c36ab9ca4a1e6c99737beb7c6cf2312efe8beca2bd314a3955" sha256: "87be13e520fc1676a725691446f411f549ea5b6ff2580234db0479799f213757"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "24.1.41" version: "24.2.9"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@ -910,26 +934,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.2" version: "6.2.5"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.0" version: "6.3.0"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.1" version: "6.2.5"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@ -950,18 +974,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.2"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -974,26 +998,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+1" version: "1.1.11+1"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+1" version: "1.1.11+1"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.9+1" version: "1.1.11+1"
vector_math: vector_math:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1022,18 +1046,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.4.2"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.3"
webkit_inspection_protocol: webkit_inspection_protocol:
dependency: transitive dependency: transitive
description: description:
@ -1046,26 +1070,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.3.0"
window_manager: window_manager:
dependency: "direct main" dependency: "direct main"
description: description:
name: window_manager name: window_manager
sha256: dcc865277f26a7dad263a47d0e405d77e21f12cb71f30333a52710a408690bd7 sha256: b3c895bdf936c77b83c5254bec2e6b3f066710c1f89c38b20b8acc382b525494
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.7" version: "0.3.8"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.4"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -1083,5 +1107,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.0" flutter: ">=3.19.0"

View File

@ -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.4.1+15 version: 1.5.0+16
environment: environment:
sdk: '>=3.0.0' sdk: '>=3.0.0'

View File

@ -46,6 +46,14 @@
"stoppedBeingTracked": "Removed from tracking list!", "stoppedBeingTracked": "Removed from tracking list!",
"tlLeaderboard": "Tetra League leaderboard", "tlLeaderboard": "Tetra League leaderboard",
"noRecords": "No records", "noRecords": "No records",
"noOldRecords": {
"zero": "No records",
"one": "Only $n record",
"two": "Only $n records",
"few": "Only $n records",
"many": "Only $n records",
"other": "Only $n records"
},
"noRecord": "No record", "noRecord": "No record",
"botRecord": "Bots are not allowed to set records", "botRecord": "Bots are not allowed to set records",
"anonRecord": "Guests are not allowed to set records", "anonRecord": "Guests are not allowed to set records",
@ -69,7 +77,10 @@
"supporter": "Supporter tier ${tier}", "supporter": "Supporter tier ${tier}",
"comparingWith": "Data from ${newDate} comparing with ${oldDate}", "comparingWith": "Data from ${newDate} comparing with ${oldDate}",
"top": "Top", "top": "Top",
"topRank": "Top Rank", "topRank": "Top rank",
"verdictGeneral": "$n $verdict than $rank rank average",
"verdictBetter": "better",
"verdictWorse": "worse",
"gamesUntilRanked": "${left} games until being ranked", "gamesUntilRanked": "${left} games until being ranked",
"nerdStats": "Nerd Stats", "nerdStats": "Nerd Stats",
"playersYouTrack": "Players you track", "playersYouTrack": "Players you track",
@ -93,8 +104,14 @@
"yourIDAlertTitle": "Your nickname in TETR.IO", "yourIDAlertTitle": "Your nickname in TETR.IO",
"yourIDText": "When app loads, it will retrieve data for this account", "yourIDText": "When app loads, it will retrieve data for this account",
"language": "Language", "language": "Language",
"customization": "Customization",
"customizationDescription": "There is only one toggle, planned to add more settings",
"lbStats": "Show leaderboard based stats",
"lbStatsDescription": "That will impact on loading times, but will allow you to see position on LB by stats and comparison with average values",
"aboutApp": "About app", "aboutApp": "About app",
"aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy", "aboutAppText": "${appName} (${packageName}) Version ${version} Build ${buildNumber}\n\nDeveloped by dan63047\nFormulas provided by kerrmunism\nHistory provided by p1nkl0bst3r\nTETR.IO replay grabber API by szy",
"oskKagari": "Osk Kagari gimmick",
"oskKagariDescription": "If on, osk's rank on main view will be rendered as :kagari:",
"stateViewTitle": "${nickname} account on ${date}", "stateViewTitle": "${nickname} account on ${date}",
"statesViewTitle": "${number} states of ${nickname} account", "statesViewTitle": "${number} states of ${nickname} account",
"matchesViewTitle": "${nickname} TL matches", "matchesViewTitle": "${nickname} TL matches",
@ -119,10 +136,14 @@
"openReplay": "Open replay in TETR.IO", "openReplay": "Open replay in TETR.IO",
"replaySaved": "Replay saved to ${path}", "replaySaved": "Replay saved to ${path}",
"match": "Match", "match": "Match",
"timeWeightedmatch": "Match (time-weighted)",
"roundNumber": "Round $n", "roundNumber": "Round $n",
"statsFor": "Stats for", "statsFor": "Stats for",
"numberOfRounds": "Number of rounds",
"matchLength": "Match Length", "matchLength": "Match Length",
"roundLength": "Round Length", "roundLength": "Round Length",
"matchStats": "Match stats",
"timeWeightedmatchStats": "Time-weighted match stats",
"replayIssue": "Can't process replay", "replayIssue": "Can't process replay",
"matchIsTooOld": "Replay is not available", "matchIsTooOld": "Replay is not available",
"winner": "Winner", "winner": "Winner",
@ -159,6 +180,15 @@
"many": "$n players", "many": "$n players",
"other": "$n players" "other": "$n players"
}, },
"games": {
"zero": "$n games",
"one": "$n game",
"two": "$n games",
"few": "$n games",
"many": "$n games",
"other": "$n games"
},
"gamesPlayed": "$games played",
"chart": "Chart", "chart": "Chart",
"entries": "Entries", "entries": "Entries",
"minimums": "Minimums", "minimums": "Minimums",
@ -238,8 +268,30 @@
"numOfGameActions":{ "numOfGameActions":{
"pc": "All Clears", "pc": "All Clears",
"hold": "Holds", "hold": "Holds",
"tspinsTotal": "T-spins total", "inputs": {
"lineClears": "Line clears" "zero": "$n key presses",
"one": "$n key press",
"two": "$n key presses",
"few": "$n key presses",
"many": "$n key presses",
"other": "$n key presses"
},
"tspinsTotal": {
"zero": "$n T-spins total",
"one": "$n T-spin total",
"two": "$n T-spins total",
"few": "$n T-spins total",
"many": "$n T-spins total",
"other": "$n T-spins total"
},
"lineClears": {
"zero": "$n lines cleared",
"one": "$n line cleared",
"two": "$n lines cleared",
"few": "$n lines cleared",
"many": "$n lines cleared",
"other": "$n lines cleared"
}
}, },
"popupActions":{ "popupActions":{
"cancel": "Cancel", "cancel": "Cancel",
@ -249,21 +301,30 @@
"errors":{ "errors":{
"connection": "Some issue with connection: ${code} ${message}", "connection": "Some issue with connection: ${code} ${message}",
"noSuchUser": "No such user", "noSuchUser": "No such user",
"noSuchUserSub": "Either you mistyped something, or the account no longer exists",
"discordNotAssigned": "No user assigned to given Discord ID",
"discordNotAssignedSub": "Make sure you provided valid ID",
"history": "History for that player is missing", "history": "History for that player is missing",
"actionSuggestion": "Perhaps, you want to",
"p1nkl0bst3rTLmatches": "No Tetra League matches was found", "p1nkl0bst3rTLmatches": "No Tetra League matches was found",
"clientException": "No internet connection", "clientException": "No internet connection",
"forbidden": "Your IP address is blocked.\nChange IP address or reach out to osk", "forbidden": "Your IP address is blocked",
"tooManyRequests": "You have been rate limited. Try again later", "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.",
"tooManyRequestsSub": "Wait a few moments and try again",
"internal": "Something happend on the tetr.io side", "internal": "Something happend on the tetr.io side",
"internalSub": "osk, probably, already aware about it",
"internalWebVersion": "Something happend on the tetr.io side (or on oskware_bridge, idk honestly)", "internalWebVersion": "Something happend on the tetr.io side (or on oskware_bridge, idk honestly)",
"oskwareBridge": "Something happend with oskware_bridge. Let dan63047 know", "internalWebVersionSub": "If osk status page says that everything is ok, let dan63047 know about this issue",
"p1nkl0bst3rForbidden": "Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r", "oskwareBridge": "Something happend with oskware_bridge",
"oskwareBridgeSub": "Let dan63047 know",
"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 happend on the p1nkl0bst3r side",
"p1nkl0bst3rinternalWebVersion": "Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)", "p1nkl0bst3rinternalWebVersion": "Something happend 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.\nChange IP address or reach out to szy" "replayRejected": "Third party API blocked your IP address"
}, },
"countries(map)": { "countries(map)": {
"": "Not selected", "": "Not selected",

View File

@ -46,6 +46,14 @@
"compare": "Сравнить", "compare": "Сравнить",
"tlLeaderboard": "Рейтинговая таблица", "tlLeaderboard": "Рейтинговая таблица",
"noRecords": "Нет записей", "noRecords": "Нет записей",
"noOldRecords": {
"zero": "Нет записей",
"one": "Всего один матч",
"two": "Всего $n матча",
"few": "Всего $n матча",
"many": "Всего $n матчей",
"other": "$n матчей"
},
"noRecord": "Нет рекорда", "noRecord": "Нет рекорда",
"botRecord": "Ботам нельзя ставить рекорды", "botRecord": "Ботам нельзя ставить рекорды",
"anonRecord": "Гостям нельзя ставить рекорды", "anonRecord": "Гостям нельзя ставить рекорды",
@ -69,7 +77,10 @@
"assignedManualy": "Этот значок был присвоен вручную администрацией TETR.IO", "assignedManualy": "Этот значок был присвоен вручную администрацией TETR.IO",
"comparingWith": "Данные от ${newDate} в сравнении с данными от ${oldDate}", "comparingWith": "Данные от ${newDate} в сравнении с данными от ${oldDate}",
"top": "Топ", "top": "Топ",
"topRank": "Топ Ранг", "topRank": "Топ ранг",
"verdictGeneral": "$verdict среднего $rank ранга на $n",
"verdictBetter": "Лучше",
"verdictWorse": "Хуже",
"gamesUntilRanked": "${left} матчей до получения рейтинга", "gamesUntilRanked": "${left} матчей до получения рейтинга",
"nerdStats": "Для задротов", "nerdStats": "Для задротов",
"playersYouTrack": "Отслеживаемые игроки", "playersYouTrack": "Отслеживаемые игроки",
@ -93,8 +104,14 @@
"yourIDAlertTitle": "Ваш ник в TETR.IO", "yourIDAlertTitle": "Ваш ник в TETR.IO",
"yourIDText": "При запуске приложения оно будет получать статистику этого игрока.", "yourIDText": "При запуске приложения оно будет получать статистику этого игрока.",
"language": "Язык (Language)", "language": "Язык (Language)",
"customization": "Кастомизация",
"customizationDescription": "Здесь только один переключатель, в планах добавить больше",
"lbStats": "Показывать статистику, основанную на рейтинговой таблице",
"lbStatsDescription": "Это повлияет на время загрузки, но позволит видеть положение в рейтинге и сравнение со средними значениями по рангу по каждой стате",
"aboutApp": "О приложении", "aboutApp": "О приложении",
"aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy", "aboutAppText": "${appName} (${packageName}) Версия ${version} Сборка ${buildNumber}\n\nРазработал dan63047\nФормулы предоставил kerrmunism\nИсторию предоставляет p1nkl0bst3r\nВозможность скачивать повторы из TETR.IO предоставляет szy",
"oskKagari": "\"Оск Кагари\" прикол",
"oskKagariDescription": "Если включено, вместо настоящего ранга оска будет рендерится :kagari:",
"stateViewTitle": "Аккаунт ${nickname} ${date}", "stateViewTitle": "Аккаунт ${nickname} ${date}",
"statesViewTitle": "${number} состояний аккаунта ${nickname}", "statesViewTitle": "${number} состояний аккаунта ${nickname}",
"matchesViewTitle": "Матчи аккаунта ${nickname}", "matchesViewTitle": "Матчи аккаунта ${nickname}",
@ -119,10 +136,14 @@
"openReplay": "Открыть повтор в TETR.IO", "openReplay": "Открыть повтор в TETR.IO",
"replaySaved": "Повтор сохранён по пути ${path}", "replaySaved": "Повтор сохранён по пути ${path}",
"match": "Матч", "match": "Матч",
"timeWeightedmatch": "Матч (взвешенная по времени)",
"roundNumber": "Раунд $n", "roundNumber": "Раунд $n",
"statsFor": "Статистика за", "statsFor": "Статистика за",
"numberOfRounds": "Количество раундов",
"matchLength": "Продолжительность матча", "matchLength": "Продолжительность матча",
"roundLength": "Продолжительность раунда", "roundLength": "Продолжительность раунда",
"matchStats": "Статистика матча",
"timeWeightedmatchStats": "Взвешенная по времени cтатистика матча",
"replayIssue": "Ошибка обработки повтора", "replayIssue": "Ошибка обработки повтора",
"matchIsTooOld": "Информация о повторе недоступна", "matchIsTooOld": "Информация о повторе недоступна",
"winner": "Победитель", "winner": "Победитель",
@ -159,6 +180,15 @@
"many": "$n игроков", "many": "$n игроков",
"other": "$n игроков" "other": "$n игроков"
}, },
"games": {
"zero": "$n игр",
"one": "$n игра",
"two": "$n игры",
"few": "$n игры",
"many": "$n игр",
"other": "$n игр"
},
"gamesPlayed": "$games сыграно",
"chart": "График", "chart": "График",
"entries": "Список", "entries": "Список",
"minimums": "Минимумы", "minimums": "Минимумы",
@ -238,8 +268,30 @@
"numOfGameActions":{ "numOfGameActions":{
"pc": "Все чисто", "pc": "Все чисто",
"hold": "В запас", "hold": "В запас",
"tspinsTotal": "T-spins всего", "inputs": {
"lineClears": "Линий очищено" "zero": "$n нажатий клавиш",
"one": "$n нажатие на клавишу",
"two": "$n нажатия на клавишы",
"few": "$n нажатия на клавишы",
"many": "$n нажатий на клавиш",
"other": "$n нажатий на клавиш"
},
"tspinsTotal": {
"zero": "$n T-спинов всего",
"one": "Всего $n T-спин",
"two": "$n T-спина всего",
"few": "$n T-спина всего",
"many": "$n T-спинов всего",
"other": "$n T-спинов всего"
},
"lineClears": {
"zero": "$n линий очищено",
"one": "$n линия очищена",
"two": "$n линии очищено",
"few": "$n линии очищено",
"many": "$n линий очищено",
"other": "$n линий очищено"
}
}, },
"popupActions":{ "popupActions":{
"cancel": "Отменить", "cancel": "Отменить",
@ -249,21 +301,30 @@
"errors":{ "errors":{
"connection": "Проблема с подключением: ${code} ${message}", "connection": "Проблема с подключением: ${code} ${message}",
"noSuchUser": "Нет такого пользователя", "noSuchUser": "Нет такого пользователя",
"noSuchUserSub": "Либо вы ошиблись при вводе, либо аккаунта больше не существует",
"discordNotAssigned": "К данному Discord ID не привязан аккаунт",
"discordNotAssignedSub": "Убедитесь в том, что вы вставили правильный ID",
"history": "История данного игрока отсутствует", "history": "История данного игрока отсутствует",
"actionSuggestion": "Возможно, вы хотите",
"p1nkl0bst3rTLmatches": "Старых матчей Тетра Лиги не было найдено", "p1nkl0bst3rTLmatches": "Старых матчей Тетра Лиги не было найдено",
"clientException": "Нет соединения с интернетом", "clientException": "Нет соединения с интернетом",
"forbidden": "Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом", "forbidden": "Ваш IP адрес заблокирован",
"tooManyRequests": "Слишком много запросов. Попробуйте позже", "forbiddenSub": "Если у вас работает VPN или прокси, выключите его. Если это не помогло, свяжитесь с $nickname",
"tooManyRequests": "Слишком много запросов",
"tooManyRequestsSub": "Подождите немного и попробуйте снова",
"internal": "Что-то случилось на стороне tetr.io", "internal": "Что-то случилось на стороне tetr.io",
"internalSub": "Скорее всего, osk уже в курсе об этом",
"internalWebVersion": "Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)", "internalWebVersion": "Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)",
"oskwareBridge": "Что-то случилось с oskware_bridge. Дайте dan63047 знать", "internalWebVersionSub": "Если статус страница osk-а говорит, что всё ок - свяжитесь с dan63047",
"p1nkl0bst3rForbidden": "Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом", "oskwareBridge": "Что-то случилось с oskware_bridge",
"oskwareBridgeSub": "Дайте dan63047 знать",
"p1nkl0bst3rForbidden": "Стороннее API заблокировало ваш IP адрес",
"p1nkl0bst3rTooManyRequests": "Слишком много запросов к стороннему API. Попробуйте позже", "p1nkl0bst3rTooManyRequests": "Слишком много запросов к стороннему API. Попробуйте позже",
"p1nkl0bst3rinternal": "Что-то случилось на стороне p1nkl0bst3r-а", "p1nkl0bst3rinternal": "Что-то случилось на стороне p1nkl0bst3r-а",
"p1nkl0bst3rinternalWebVersion": "Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)", "p1nkl0bst3rinternalWebVersion": "Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)",
"replayAlreadySaved": "Повтор уже сохранён", "replayAlreadySaved": "Повтор уже сохранён",
"replayExpired": "Повтор истёк и больше недоступен", "replayExpired": "Повтор истёк и больше недоступен",
"replayRejected": "Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с szy" "replayRejected": "Стороннее API заблокировало ваш IP адрес"
}, },
"countries(map)": { "countries(map)": {
"": "Не выбрана", "": "Не выбрана",

BIN
res/tetrio_badges/mts_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
res/tetrio_badges/mts_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
res/tetrio_badges/mts_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,5 +1,4 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -18,7 +18,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Track your and other player stats in TETR.IO"> <meta name="description" content="Track your and other player stats in TETR.IO. Made by dan63047">
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">