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
- Player history in charts
- Tetra League matches history
- Time-weighted stats in Tetra League matches
# Special thanks
- **kerrmunism** — formulas

View File

@ -25,6 +25,12 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
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 {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
@ -53,11 +59,17 @@ android {
versionName flutterVersionName
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
}
}
}

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,
"": 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 {
tr,
glicko,
@ -59,6 +78,7 @@ enum Stats {
area,
eTR,
acceTR,
acceTRabs,
opener,
plonk,
infDS,
@ -88,6 +108,7 @@ const Map<Stats, String> chartsShortTitles = {
Stats.area: "Area",
Stats.eTR: "eTR",
Stats.acceTR: "±eTR",
Stats.acceTRabs: "+eTR absolute",
Stats.opener: "Opener",
Stats.plonk: "Plonk",
Stats.infDS: "Inf. DS",
@ -117,6 +138,48 @@ const Map<String, Color> rankColors = { // thanks osk for const rankColors at ht
'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){
return t[stat.name];
}
@ -271,6 +334,10 @@ class TetrioPlayer {
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
String toString() {
return "$username ($state)";
@ -318,6 +385,8 @@ class TetrioPlayer {
return tlSeason1.estTr?.esttr;
case Stats.acceTR:
return tlSeason1.esttracc;
case Stats.acceTRabs:
return tlSeason1.esttracc?.abs();
case Stats.opener:
return tlSeason1.playstyle?.opener;
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 {
late String type;
late DateTime timestamp;
@ -1121,6 +1257,20 @@ class TetrioPlayersLeaderboard {
return lb;
}
List<TetrioPlayerFromLeaderboard> getStatRankingSequel(Stats stat){
List<TetrioPlayerFromLeaderboard> lb = List.from(leaderboard);
lb.sort(((a, b) {
if (a.getStatByEnum(stat) > b.getStatByEnum(stat)){
return -1;
}else if (a.getStatByEnum(stat) == b.getStatByEnum(stat)){
return 0;
}else{
return 1;
}
}));
return lb;
}
List<dynamic> getAverageOfRank(String rank){ // i tried to refactor it and that's was terrible
if (rank.isNotEmpty && !rankCutoffs.keys.contains(rank)) throw Exception("Invalid rank");
List<TetrioPlayerFromLeaderboard> filtredLeaderboard = List.from(leaderboard);
@ -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 => {
'x': getAverageOfRank("x"),
'u': getAverageOfRank("u"),
@ -1732,6 +1906,26 @@ class TetrioPlayersLeaderboard {
'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) {
type = t;
timestamp = ts;
@ -1785,7 +1979,11 @@ class TetrioPlayerFromLeaderboard {
this.apm,
this.pps,
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 esttracc => estTr.esttr - rating;
@ -1857,6 +2055,8 @@ class TetrioPlayerFromLeaderboard {
return estTr.esttr;
case Stats.acceTR:
return esttracc;
case Stats.acceTRabs:
return esttracc.abs();
case Stats.opener:
return playstyle.opener;
case Stats.plonk:

View File

@ -1,4 +1,6 @@
import 'dart:math';
import 'package:vector_math/vector_math_64.dart';
import 'tetrio.dart';
// 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{
late String id;
late Map<dynamic, dynamic> rawJson;
late List<EndContextMulti> endcontext;
late List<AggregateStats> timeWeightedStats;
late List<List<ReplayStats>> stats;
late List<ReplayStats> totalStats;
late List<List<String>> roundWinners;
@ -178,20 +199,45 @@ class ReplayData{
totalLength = 0;
stats = [];
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()];
for(var round in json['data']) {
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;
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']);
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);
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 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]);
totalStats[0] = totalStats[0] + playerOne;
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(){
@ -212,3 +258,402 @@ class ReplayData{
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`
///
/// 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
// ignore_for_file: type=lint
@ -181,6 +181,14 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
String get stoppedBeingTracked => 'Removed from tracking list!';
String get tlLeaderboard => 'Tetra League leaderboard';
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 botRecord => 'Bots 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 comparingWith({required Object newDate, required Object oldDate}) => 'Data from ${newDate} comparing with ${oldDate}';
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 get nerdStats => 'Nerd Stats';
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 yourIDText => 'When app loads, it will retrieve data for this account';
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 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 statesViewTitle({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
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 replaySaved({required Object path}) => 'Replay saved to ${path}';
String get match => 'Match';
String get timeWeightedmatch => 'Match (time-weighted)';
String roundNumber({required Object n}) => 'Round ${n}';
String get statsFor => 'Stats for';
String get numberOfRounds => 'Number of rounds';
String get matchLength => 'Match 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 matchIsTooOld => 'Replay is not available';
String get winner => 'Winner';
@ -294,6 +315,15 @@ class Translations implements BaseTranslations<AppLocale, Translations> {
many: '${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 entries => 'Entries';
String get minimums => 'Minimums';
@ -675,8 +705,30 @@ class _StringsNumOfGameActionsEn {
// Translations
String get pc => 'All Clears';
String get hold => 'Holds';
String get tspinsTotal => 'T-spins total';
String get lineClears => 'Line clears';
String inputs({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
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
@ -700,21 +752,30 @@ class _StringsErrorsEn {
// Translations
String connection({required Object code, required Object message}) => 'Some issue with connection: ${code} ${message}';
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 actionSuggestion => 'Perhaps, you want to';
String get p1nkl0bst3rTLmatches => 'No Tetra League matches was found';
String get clientException => 'No internet connection';
String get forbidden => 'Your IP address is blocked.\nChange IP address or reach out to osk';
String get tooManyRequests => 'You have been rate limited. Try again later';
String get forbidden => 'Your IP address is blocked';
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 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 oskwareBridge => 'Something happend with oskware_bridge. Let dan63047 know';
String get p1nkl0bst3rForbidden => 'Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r';
String get internalWebVersionSub => 'If osk status page says that everything is ok, let dan63047 know about this issue';
String get oskwareBridge => 'Something happend with oskware_bridge';
String get 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 p1nkl0bst3rinternal => 'Something happend on the p1nkl0bst3r side';
String get p1nkl0bst3rinternalWebVersion => 'Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)';
String get replayAlreadySaved => 'Replay already saved';
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>
@ -774,6 +835,14 @@ class _StringsRu implements Translations {
@override String get compare => 'Сравнить';
@override String get tlLeaderboard => 'Рейтинговая таблица';
@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 botRecord => 'Ботам нельзя ставить рекорды';
@override String get anonRecord => 'Гостям нельзя ставить рекорды';
@ -797,7 +866,10 @@ class _StringsRu implements Translations {
@override String get assignedManualy => 'Этот значок был присвоен вручную администрацией TETR.IO';
@override String comparingWith({required Object newDate, required Object oldDate}) => 'Данные от ${newDate} в сравнении с данными от ${oldDate}';
@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 get nerdStats => 'Для задротов';
@override String get playersYouTrack => 'Отслеживаемые игроки';
@ -821,8 +893,14 @@ class _StringsRu implements Translations {
@override String get yourIDAlertTitle => 'Ваш ник в TETR.IO';
@override String get yourIDText => 'При запуске приложения оно будет получать статистику этого игрока.';
@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 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 statesViewTitle({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
@override String matchesViewTitle({required Object nickname}) => 'Матчи аккаунта ${nickname}';
@ -847,10 +925,14 @@ class _StringsRu implements Translations {
@override String get openReplay => 'Открыть повтор в TETR.IO';
@override String replaySaved({required Object path}) => 'Повтор сохранён по пути ${path}';
@override String get match => 'Матч';
@override String get timeWeightedmatch => 'Матч (взвешенная по времени)';
@override String roundNumber({required Object n}) => 'Раунд ${n}';
@override String get statsFor => 'Статистика за';
@override String get numberOfRounds => 'Количество раундов';
@override String get matchLength => 'Продолжительность матча';
@override String get roundLength => 'Продолжительность раунда';
@override String get matchStats => 'Статистика матча';
@override String get timeWeightedmatchStats => 'Взвешенная по времени cтатистика матча';
@override String get replayIssue => 'Ошибка обработки повтора';
@override String get matchIsTooOld => 'Информация о повторе недоступна';
@override String get winner => 'Победитель';
@ -887,6 +969,15 @@ class _StringsRu implements Translations {
many: '${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 entries => 'Список';
@override String get minimums => 'Минимумы';
@ -1268,8 +1359,30 @@ class _StringsNumOfGameActionsRu implements _StringsNumOfGameActionsEn {
// Translations
@override String get pc => 'Все чисто';
@override String get hold => 'В запас';
@override String get tspinsTotal => 'T-spins всего';
@override String get lineClears => 'Линий очищено';
@override String inputs({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
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
@ -1293,21 +1406,30 @@ class _StringsErrorsRu implements _StringsErrorsEn {
// Translations
@override String connection({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
@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 actionSuggestion => 'Возможно, вы хотите';
@override String get p1nkl0bst3rTLmatches => 'Старых матчей Тетра Лиги не было найдено';
@override String get clientException => 'Нет соединения с интернетом';
@override String get forbidden => 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом';
@override String get tooManyRequests => 'Слишком много запросов. Попробуйте позже';
@override String get forbidden => 'Ваш IP адрес заблокирован';
@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 internalSub => 'Скорее всего, osk уже в курсе об этом';
@override String get internalWebVersion => 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)';
@override String get oskwareBridge => 'Что-то случилось с oskware_bridge. Дайте dan63047 знать';
@override String get p1nkl0bst3rForbidden => 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом';
@override String get internalWebVersionSub => 'Если статус страница osk-а говорит, что всё ок - свяжитесь с dan63047';
@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 p1nkl0bst3rinternal => 'Что-то случилось на стороне p1nkl0bst3r-а';
@override String get p1nkl0bst3rinternalWebVersion => 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)';
@override String get replayAlreadySaved => 'Повтор уже сохранён';
@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.
@ -1359,6 +1481,14 @@ extension on Translations {
case 'stoppedBeingTracked': return 'Removed from tracking list!';
case 'tlLeaderboard': return 'Tetra League leaderboard';
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 'botRecord': return 'Bots 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 'comparingWith': return ({required Object newDate, required Object oldDate}) => 'Data from ${newDate} comparing with ${oldDate}';
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 'nerdStats': return 'Nerd Stats';
case 'playersYouTrack': return 'Players you track';
@ -1406,8 +1539,14 @@ extension on Translations {
case 'yourIDAlertTitle': return 'Your nickname in TETR.IO';
case 'yourIDText': return 'When app loads, it will retrieve data for this account';
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 '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 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} states of ${nickname} account';
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 'replaySaved': return ({required Object path}) => 'Replay saved to ${path}';
case 'match': return 'Match';
case 'timeWeightedmatch': return 'Match (time-weighted)';
case 'roundNumber': return ({required Object n}) => 'Round ${n}';
case 'statsFor': return 'Stats for';
case 'numberOfRounds': return 'Number of rounds';
case 'matchLength': return 'Match 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 'matchIsTooOld': return 'Replay is not available';
case 'winner': return 'Winner';
@ -1472,6 +1615,15 @@ extension on Translations {
many: '${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 'entries': return 'Entries';
case 'minimums': return 'Minimums';
@ -1546,28 +1698,59 @@ extension on Translations {
case 'playerRole.anon': return 'Anonymous';
case 'numOfGameActions.pc': return 'All Clears';
case 'numOfGameActions.hold': return 'Holds';
case 'numOfGameActions.tspinsTotal': return 'T-spins total';
case 'numOfGameActions.lineClears': return 'Line clears';
case 'numOfGameActions.inputs': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n,
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.submit': return 'Submit';
case 'popupActions.ok': return 'OK';
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.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.actionSuggestion': return 'Perhaps, you want to';
case 'errors.p1nkl0bst3rTLmatches': return 'No Tetra League matches was found';
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.tooManyRequests': return 'You have been rate limited. Try again later';
case 'errors.forbidden': return 'Your IP address is blocked';
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.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.oskwareBridge': return 'Something happend with oskware_bridge. Let dan63047 know';
case 'errors.p1nkl0bst3rForbidden': return 'Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r';
case 'errors.internalWebVersionSub': return 'If osk status page says that everything is ok, let dan63047 know about this issue';
case 'errors.oskwareBridge': return 'Something happend with oskware_bridge';
case 'errors.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.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.replayAlreadySaved': return 'Replay already saved';
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.AF': return 'Afghanistan';
case 'countries.AX': return 'Åland Islands';
@ -1878,6 +2061,14 @@ extension on _StringsRu {
case 'compare': return 'Сравнить';
case 'tlLeaderboard': 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 'botRecord': return 'Ботам нельзя ставить рекорды';
case 'anonRecord': return 'Гостям нельзя ставить рекорды';
@ -1901,7 +2092,10 @@ extension on _StringsRu {
case 'assignedManualy': return 'Этот значок был присвоен вручную администрацией TETR.IO';
case 'comparingWith': return ({required Object newDate, required Object oldDate}) => 'Данные от ${newDate} в сравнении с данными от ${oldDate}';
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 'nerdStats': return 'Для задротов';
case 'playersYouTrack': return 'Отслеживаемые игроки';
@ -1925,8 +2119,14 @@ extension on _StringsRu {
case 'yourIDAlertTitle': return 'Ваш ник в TETR.IO';
case 'yourIDText': return 'При запуске приложения оно будет получать статистику этого игрока.';
case 'language': return 'Язык (Language)';
case 'customization': return 'Кастомизация';
case 'customizationDescription': return 'Здесь только один переключатель, в планах добавить больше';
case 'lbStats': return 'Показывать статистику, основанную на рейтинговой таблице';
case 'lbStatsDescription': 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 'oskKagari': return '"Оск Кагари" прикол';
case 'oskKagariDescription': return 'Если включено, вместо настоящего ранга оска будет рендерится :kagari:';
case 'stateViewTitle': return ({required Object nickname, required Object date}) => 'Аккаунт ${nickname} ${date}';
case 'statesViewTitle': return ({required Object number, required Object nickname}) => '${number} состояний аккаунта ${nickname}';
case 'matchesViewTitle': return ({required Object nickname}) => 'Матчи аккаунта ${nickname}';
@ -1951,10 +2151,14 @@ extension on _StringsRu {
case 'openReplay': return 'Открыть повтор в TETR.IO';
case 'replaySaved': return ({required Object path}) => 'Повтор сохранён по пути ${path}';
case 'match': return 'Матч';
case 'timeWeightedmatch': return 'Матч (взвешенная по времени)';
case 'roundNumber': return ({required Object n}) => 'Раунд ${n}';
case 'statsFor': return 'Статистика за';
case 'numberOfRounds': return 'Количество раундов';
case 'matchLength': return 'Продолжительность матча';
case 'roundLength': return 'Продолжительность раунда';
case 'matchStats': return 'Статистика матча';
case 'timeWeightedmatchStats': return 'Взвешенная по времени cтатистика матча';
case 'replayIssue': return 'Ошибка обработки повтора';
case 'matchIsTooOld': return 'Информация о повторе недоступна';
case 'winner': return 'Победитель';
@ -1991,6 +2195,15 @@ extension on _StringsRu {
many: '${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 'entries': return 'Список';
case 'minimums': return 'Минимумы';
@ -2065,28 +2278,59 @@ extension on _StringsRu {
case 'playerRole.anon': return 'Аноним';
case 'numOfGameActions.pc': return 'Все чисто';
case 'numOfGameActions.hold': return 'В запас';
case 'numOfGameActions.tspinsTotal': return 'T-spins всего';
case 'numOfGameActions.lineClears': return 'Линий очищено';
case 'numOfGameActions.inputs': return ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('ru'))(n,
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.submit': return 'Подтвердить';
case 'popupActions.ok': return 'OK';
case 'errors.connection': return ({required Object code, required Object message}) => 'Проблема с подключением: ${code} ${message}';
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.actionSuggestion': return 'Возможно, вы хотите';
case 'errors.p1nkl0bst3rTLmatches': return 'Старых матчей Тетра Лиги не было найдено';
case 'errors.clientException': return 'Нет соединения с интернетом';
case 'errors.forbidden': return 'Ваш IP адрес заблокирован.\nСмените IP адрес или свяжитесь с osk-ом';
case 'errors.tooManyRequests': return 'Слишком много запросов. Попробуйте позже';
case 'errors.forbidden': return 'Ваш IP адрес заблокирован';
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.internalSub': return 'Скорее всего, osk уже в курсе об этом';
case 'errors.internalWebVersion': return 'Что-то случилось на стороне tetr.io (или на стороне oskware_bridge, я хз если честно)';
case 'errors.oskwareBridge': return 'Что-то случилось с oskware_bridge. Дайте dan63047 знать';
case 'errors.p1nkl0bst3rForbidden': return 'Стороннее API заблокировало ваш IP адрес.\nСмените IP адрес или свяжитесь с p1nkl0bst3r-ом';
case 'errors.internalWebVersionSub': return 'Если статус страница osk-а говорит, что всё ок - свяжитесь с dan63047';
case 'errors.oskwareBridge': return 'Что-то случилось с oskware_bridge';
case 'errors.oskwareBridgeSub': return 'Дайте dan63047 знать';
case 'errors.p1nkl0bst3rForbidden': return 'Стороннее API заблокировало ваш IP адрес';
case 'errors.p1nkl0bst3rTooManyRequests': return 'Слишком много запросов к стороннему API. Попробуйте позже';
case 'errors.p1nkl0bst3rinternal': return 'Что-то случилось на стороне p1nkl0bst3r-а';
case 'errors.p1nkl0bst3rinternalWebVersion': return 'Что-то случилось на стороне p1nkl0bst3r-а (или на стороне oskware_bridge, я хз если честно)';
case 'errors.replayAlreadySaved': 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.AF': return 'Афганистан';
case 'countries.AX': return 'Аландские острова';

View File

@ -14,6 +14,8 @@ class TetrioPlayerAlreadyExist implements Exception {}
class TetrioPlayerNotExist implements Exception {}
class TetrioDiscordNotExist implements Exception {}
class TetrioHistoryNotExist 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, dynamic> _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]}
final Map<String, TetrioPlayersLeaderboard> _leaderboardsCache = {};
final Map<String, PlayerLeaderboardPosition> _lbPositions = {};
final Map<String, List<News>> _newsCache = {};
final Map<String, Map<String, double?>> _topTRcache = {};
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {};
@ -142,6 +143,14 @@ class TetrioService extends DB {
db.insert(tetrioTLReplayStatsTable, {idCol: replay.id, "data": jsonEncode(replay.toJson())});
}
void cacheLeaderboardPositions(String userID, PlayerLeaderboardPosition positions){
_lbPositions[userID] = positions;
}
PlayerLeaderboardPosition? getCachedLeaderboardPositions(String userID){
return _lbPositions[userID];
}
/// Downloads replay from inoue (szy API). Requiers [replayID]. If request have
/// different from 200 statusCode, it will throw an excepction. Returns list, that contains same replay
/// as string and as binary.
@ -406,12 +415,12 @@ class TetrioService extends DB {
// parsing data into TetraLeagueAlphaRecord objects
for (var entry in csv){
TetraLeagueAlphaRecord match = TetraLeagueAlphaRecord(
replayId: entry[0],
ownId: entry[0], // i gonna disting p1nkl0bst3r entries with it
replayId: entry[0].toString(),
ownId: entry[0].toString(), // i gonna disting p1nkl0bst3r entries with it
timestamp: DateTime.parse(entry[1]),
endContext: [
EndContextMulti(
userId: entry[2],
userId: entry[2].toString(),
username: entry[3].toString(),
naturalOrder: 0,
inputs: -1,
@ -428,7 +437,7 @@ class TetrioService extends DB {
success: true
),
EndContextMulti(
userId: entry[8],
userId: entry[8].toString(),
username: entry[9].toString(),
naturalOrder: 1,
inputs: -1,
@ -504,6 +513,7 @@ class TetrioService extends DB {
switch (response.statusCode) {
case 200:
_lbPositions.clear();
var rawJson = jsonDecode(response.body);
if (rawJson['success']) { // if api confirmed that everything ok
TetrioPlayersLeaderboard leaderboard = TetrioPlayersLeaderboard.fromJson(rawJson['data']['users'], "league", DateTime.fromMillisecondsSinceEpoch(rawJson['cache']['cached_at']));
@ -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.
Future<List<News>> fetchNews(String userID) async{
try{
@ -712,6 +728,14 @@ class TetrioService extends DB {
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.
Future<void> deleteTLMatch(String matchID) async {
await ensureDbIsOpen();
@ -950,7 +974,7 @@ class TetrioService extends DB {
user = json['data']['user']['_id'];
} else { // fail - throw an exception
developer.log("fetchPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body);
throw TetrioPlayerNotExist();
throw TetrioDiscordNotExist();
}
break;
// 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;
EstTr? estTr;
Playstyle? playstyle;
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
late String oldWindowTitle;
class CalcView extends StatefulWidget {
@ -67,13 +66,12 @@ class CalcState extends State<CalcView> {
title: Text(t.statsCalc),
),
backgroundColor: Colors.black,
body: SafeArea(
child: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value) {
return [
SliverToBoxAdapter(
child: Padding(
body: SingleChildScrollView(
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 768),
child: Column(children: [
Padding(
padding: const EdgeInsets.fromLTRB(14, 16, 16, 32),
child: Row(
children: [
@ -111,16 +109,9 @@ class CalcState extends State<CalcView> {
],
),
),
),
const SliverToBoxAdapter(
child: Divider(),
)
];
},
body: nerdStats == null
? Text(t.calcViewNoValues)
: ListView(
children: [
Divider(),
if (nerdStats == null) Text(t.calcViewNoValues)
else Column(children: [
_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!.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!.area, label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), 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(
appBar: AppBar(title: Text("$titleGreenSide ${t.vs} $titleRedSide")),
backgroundColor: Colors.black,
body: SafeArea(
child: NestedScrollView(
body: SingleChildScrollView(
controller: _scrollController,
headerSliverBuilder: (context, value) {
return [
SliverToBoxAdapter(
child: Padding(
physics: AlwaysScrollableScrollPhysics(),
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 768),
child: Column(children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -316,18 +317,10 @@ class CompareState extends State<CompareView> {
],
),
),
),
const SliverToBoxAdapter(
child: Divider(),
)
];
},
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")
Divider(),
if (!listEquals(theGreenSide, [null, null, null]) && !listEquals(theRedSide, [null, null, null])) Column(
children: [
if (theGreenSide[0] != null && theRedSide[0] != null && theGreenSide[0]!.role != "banned" && theRedSide[0]!.role != "banned")
Column(
children: [
CompareRegTimeThingy(
@ -341,8 +334,7 @@ class CompareState extends State<CompareView> {
higherIsBetter: true,
fractionDigits: 2,
),
if (!theGreenSide[0].gameTime.isNegative &&
!theRedSide[0].gameTime.isNegative)
if (!theGreenSide[0].gameTime.isNegative && !theRedSide[0].gameTime.isNegative)
CompareThingy(
greenSide: theGreenSide[0].gameTime.inMicroseconds /
1000000 /
@ -356,16 +348,14 @@ class CompareState extends State<CompareView> {
higherIsBetter: true,
fractionDigits: 2,
),
if (theGreenSide[0].gamesPlayed >= 0 &&
theRedSide[0].gamesPlayed >= 0)
if (theGreenSide[0].gamesPlayed >= 0 && theRedSide[0].gamesPlayed >= 0)
CompareThingy(
label: t.statCellNum.onlineGames.replaceAll(RegExp(r'\n'), " "),
greenSide: theGreenSide[0].gamesPlayed,
redSide: theRedSide[0].gamesPlayed,
higherIsBetter: true,
),
if (theGreenSide[0].gamesWon >= 0 &&
theRedSide[0].gamesWon >= 0)
if (theGreenSide[0].gamesWon >= 0 && theRedSide[0].gamesWon >= 0)
CompareThingy(
label: t.statCellNum.gamesWon.replaceAll(RegExp(r'\n'), " "),
greenSide: theGreenSide[0].gamesWon,
@ -381,17 +371,14 @@ class CompareState extends State<CompareView> {
const Divider(),
],
),
if (theGreenSide[0] != null &&
theRedSide[0] != null &&
(theGreenSide[0]!.role == "banned" ||
theRedSide[0]!.role == "banned"))
if (theGreenSide[0] != null && theRedSide[0] != null && (theGreenSide[0]!.role == "banned" || theRedSide[0]!.role == "banned"))
CompareBoolThingy(
greenSide: theGreenSide[0].role == "banned",
redSide: theRedSide[0].role == "banned",
label: t.normalBanned,
trueIsBetter: false),
(theGreenSide[2].gamesPlayed > 0 || greenSideMode == Mode.stats) &&
(theRedSide[2].gamesPlayed > 0 || redSideMode == Mode.stats)
trueIsBetter: false
),
(theGreenSide[2].gamesPlayed > 0 || greenSideMode == Mode.stats) && (theRedSide[2].gamesPlayed > 0 || redSideMode == Mode.stats)
? Column(
children: [
Padding(
@ -688,11 +675,15 @@ class CompareState extends State<CompareView> {
),
],
)
] : [Padding(
],
)
else Padding(
padding: const EdgeInsets.all(8.0),
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 {
final num greenSide;
final num redSide;
@ -868,7 +861,7 @@ class CompareThingy extends StatelessWidget {
Text(
verdict(greenSide, redSide,
fractionDigits != null ? fractionDigits! + 2 : 0),
style: const TextStyle(fontSize: 16),
style: verdictStyle,
textAlign: TextAlign.center,
)
],
@ -981,11 +974,7 @@ class CompareBoolThingy extends StatelessWidget {
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center,
),
const Text(
"---",
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
const Text("---", style: verdictStyle, textAlign: TextAlign.center)
],
),
Expanded(
@ -1085,10 +1074,7 @@ class CompareDurationThingy extends StatelessWidget {
textAlign: TextAlign.center,
),
Text(
verdict(greenSide, redSide).toString(),
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
verdict(greenSide, redSide).toString(), style: verdictStyle, textAlign: TextAlign.center)
],
),
Expanded(
@ -1176,11 +1162,7 @@ class CompareRegTimeThingy extends StatelessWidget {
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center,
),
Text(
verdict(greenSide, redSide),
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
Text(verdict(greenSide, redSide), style: verdictStyle, textAlign: TextAlign.center)
],
),
Expanded(

View File

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

View File

@ -16,6 +16,7 @@ Stats _chartsX = Stats.tr;
Stats _chartsY = Stats.apm;
List<DropdownMenuItem> _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
Stats _sortBy = Stats.tr;
late List<TetrioPlayerFromLeaderboard> they;
bool _reversed = false;
List<DropdownMenuItem> _itemCountries = [for (MapEntry e in t.countries.entries) DropdownMenuItem(value: e.key, child: Text(e.value))];
String _country = "";
@ -61,6 +62,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
}
super.initState();
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country);
recalculateBoundaries();
resetScale();
}
@ -73,7 +75,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else {
return element;
}
}).getStatByEnum(_chartsX) as double;
}).getStatByEnum(_chartsX).toDouble();
actualMaxX = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
num n = max(value.getStatByEnum(_chartsX), element.getStatByEnum(_chartsX));
if (value.getStatByEnum(_chartsX) == n) {
@ -81,7 +83,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else {
return element;
}
}).getStatByEnum(_chartsX) as double;
}).getStatByEnum(_chartsX).toDouble();
actualMinY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
num n = min(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
if (value.getStatByEnum(_chartsY) == n) {
@ -89,7 +91,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else {
return element;
}
}).getStatByEnum(_chartsY) as double;
}).getStatByEnum(_chartsY).toDouble();
actualMaxY = (widget.rank[1]["entries"] as List<TetrioPlayerFromLeaderboard>).reduce((value, element) {
num n = max(value.getStatByEnum(_chartsY), element.getStatByEnum(_chartsY));
if (value.getStatByEnum(_chartsY) == n) {
@ -97,7 +99,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
} else {
return element;
}
}).getStatByEnum(_chartsY) as double;
}).getStatByEnum(_chartsY).toDouble();
}
void resetScale(){
@ -164,7 +166,7 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
previousAxisTitles = _chartsX.toString()+_chartsY.toString();
}
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(
appBar: AppBar(
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"])
if (entry.apm != 0.0 && entry.vs != 0.0) // prevents from ScatterChart "Offset argument contained a NaN value." exception
_MyScatterSpot(
entry.getStatByEnum(_chartsX) as double,
entry.getStatByEnum(_chartsY) as double,
entry.getStatByEnum(_chartsX).toDouble(),
entry.getStatByEnum(_chartsY).toDouble(),
entry.userId,
entry.username,
dotPainter: FlDotCirclePainter(color: rankColors[entry.rank]??Colors.white, radius: 3))
@ -403,7 +405,9 @@ class RankState extends State<RankView> with SingleTickerProviderStateMixin {
value: _sortBy,
onChanged: ((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 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/views/rank_averages_view.dart';
import 'package:window_manager/window_manager.dart';
import 'main_view.dart'; // lol
@ -40,7 +40,6 @@ class RanksAverages extends State<RankAveragesView> {
@override
Widget build(BuildContext context) {
final NumberFormat f2 = NumberFormat.decimalPattern(LocaleSettings.currentLocale.languageCode)..maximumFractionDigits = 2;
return Scaffold(
appBar: AppBar(
title: Text(t.rankAveragesViewTitle),
@ -55,7 +54,8 @@ class RanksAverages extends State<RankAveragesView> {
return ListTile(
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")),
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),
onTap: (){
if (averages[keys[index]]?[1]["players"] > 0) {

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'package:go_router/go_router.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/main.dart' show packageInfo;
import 'package:file_selector/file_selector.dart';
@ -26,6 +27,7 @@ class SettingsState extends State<SettingsView> {
late SharedPreferences prefs;
final TetrioService teto = TetrioService();
String defaultNickname = "Checking...";
late bool showPositions;
final TextEditingController _playertext = TextEditingController();
@override
@ -46,6 +48,11 @@ class SettingsState extends State<SettingsView> {
Future<void> _getPreferences() async {
prefs = await SharedPreferences.getInstance();
if (prefs.getBool("showPositions") != null) {
showPositions = prefs.getBool("showPositions")!;
} else {
showPositions = false;
}
_setDefaultNickname(prefs.getString("player"));
}
@ -254,12 +261,20 @@ class SettingsState extends State<SettingsView> {
},
),
),
ListTile(title: const Text("Customization"),
subtitle: const Text("I don't want to implement this"),
ListTile(title: Text(t.customization),
subtitle: Text(t.customizationDescription),
trailing: const Icon(Icons.arrow_right),
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(),
ListTile(
onTap: (){
@ -267,6 +282,7 @@ class SettingsState extends State<SettingsView> {
},
title: Text(t.aboutApp),
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.waiting:
case ConnectionState.active:
return const Center(child: Text('Fetching...'));
return const Center(child: CircularProgressIndicator());
case ConnectionState.done:
final allPlayers = snapshot.data?.getStatRanking(snapshot.data!.leaderboard, _sortBy, reversed: reversed, country: _country);
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) windowManager.setTitle("Tetra Stats: ${t.tlLeaderboard} - ${t.players(n: allPlayers!.length)}");
@ -175,7 +175,8 @@ class TLLeaderboardState extends State<TLLeaderboardView> {
return ListTile(
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")),
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(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -1,9 +1,11 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'dart:math';
import 'package:tetra_stats/data_objects/tetrio_multiplayer_replay.dart';
import 'package:tetra_stats/services/crud_exceptions.dart';
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 'main_view.dart' show teto, secs;
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();
int roundSelector = -1; // -1 = match averages, otherwise round number-1
List<DropdownMenuItem> rounds = []; // index zero will be match stats
bool timeWeightedStatsAvaliable = true;
int greenSidePlayer = 0;
int redSidePlayer = 1;
late String oldWindowTitle;
Duration framesToTime(int frames){
@ -36,12 +41,10 @@ class TlMatchResultView extends StatefulWidget {
}
class TlMatchResultState extends State<TlMatchResultView> {
late ScrollController _scrollController;
late Future<ReplayData?> replayData;
@override
void initState(){
_scrollController = ScrollController();
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)))]);
replayData = teto.analyzeReplay(widget.record.replayId, widget.record.replayAvalable);
@ -59,64 +62,44 @@ class TlMatchResultState extends State<TlMatchResultView> {
super.dispose();
}
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
bool bigScreen = MediaQuery.of(context).size.width > 768;
return Scaffold(
appBar: AppBar(
title: Text("${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${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:
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();
Widget buildComparison(bool bigScreen, bool showMobileSelector){
return FutureBuilder(future: replayData, builder: (context, snapshot){
late Duration time;
late String readableTime;
late String reason;
timeWeightedStatsAvaliable = true;
if (snapshot.connectionState != ConnectionState.done) return const LinearProgressIndicator();
if (!snapshot.hasError){
if (rounds.indexWhere((element) => element.value == -2) == -1) rounds.insert(1, DropdownMenuItem(value: -2, child: Text(t.timeWeightedmatch)));
greenSidePlayer = snapshot.data!.endcontext.indexWhere((element) => element.userId == widget.initPlayerId);
redSidePlayer = snapshot.data!.endcontext.indexWhere((element) => element.userId != widget.initPlayerId);
if (roundSelector.isNegative){
time = framesToTime(snapshot.data!.totalLength);
readableTime = "${t.matchLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}";
}else{
try{
String path = await teto.saveReplay(widget.record.replayId);
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)));
}
time = framesToTime(snapshot.data!.roundLengths[roundSelector]);
readableTime = "${t.roundLength}: ${time.inMinutes}:${secs.format(time.inMicroseconds /1000000 % 60)}\n${t.winner}: ${snapshot.data!.roundWinners[roundSelector][1]}";
}
}else{
switch (snapshot.error.runtimeType){
case ReplayNotAvalable:
reason = t.matchIsTooOld;
break;
case 2:
await launchInBrowser(Uri.parse("https://tetr.io/#r:${widget.record.replayId}"));
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;
}
})
]
),
backgroundColor: Colors.black,
body: SafeArea(
child: NestedScrollView(
controller: _scrollController,
}
return NestedScrollView(
headerSliverBuilder: (context, value) {
return [
SliverToBoxAdapter(
@ -178,7 +161,7 @@ class TlMatchResultState extends State<TlMatchResultView> {
),
),
),
SliverToBoxAdapter(
if (showMobileSelector) SliverToBoxAdapter(
child: Center(
child: Row(
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)),
),
SliverToBoxAdapter(child: 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){
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);
}
}
},),),
if (showMobileSelector) SliverToBoxAdapter(child: Center(child: Text(snapshot.hasError ? reason : readableTime, textAlign: TextAlign.center))),
const SliverToBoxAdapter(
child: Divider(),
)
@ -248,47 +193,47 @@ class TlMatchResultState extends State<TlMatchResultView> {
children: [
CompareThingy(
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],
redSide: 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 :
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,
higherIsBetter: true,
),
CompareThingy(
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],
redSide: 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:
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,
higherIsBetter: true,
),
CompareThingy(
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],
redSide: 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 :
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,
higherIsBetter: true,
),
FutureBuilder(future: replayData, builder: (BuildContext context, AsyncSnapshot<ReplayData?> snapshot){
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: [
if (snapshot.hasData) Column(children: [
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,
label: "Inputs", higherIsBetter: true),
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,
label: "Pieces Placed", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][redSidePlayer].kpp,
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kpp :
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kpp : snapshot.data!.stats[roundSelector][greenSidePlayer].kpp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].kpp :
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kpp : snapshot.data!.stats[roundSelector][redSidePlayer].kpp,
label: "KpP", higherIsBetter: false, fractionDigits: 2,),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].kps : snapshot.data!.stats[roundSelector][redSidePlayer].kps,
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].kps :
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].kps : snapshot.data!.stats[roundSelector][greenSidePlayer].kps,
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,),
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,
@ -296,8 +241,10 @@ class TlMatchResultState extends State<TlMatchResultView> {
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,
label: "Score", higherIsBetter: true),
CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][redSidePlayer].spp,
CompareThingy(greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].spp :
roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].spp : snapshot.data!.stats[roundSelector][greenSidePlayer].spp,
redSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[redSidePlayer].spp :
roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].spp : snapshot.data!.stats[roundSelector][redSidePlayer].spp,
label: "SpP", higherIsBetter: true, fractionDigits: 2,),
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,
@ -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,
redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].clears.quads : snapshot.data!.stats[roundSelector][redSidePlayer].clears.quads,
label: "Quads", higherIsBetter: true),
],);
}else{
return Container();
}
}
})
],),
],
),
const Divider(),
@ -363,113 +304,141 @@ class TlMatchResultState extends State<TlMatchResultView> {
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
higherIsBetter: false,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
CompareThingy(
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,
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,
greenSide: (roundSelector == -2 && snapshot.hasData) ? snapshot.data!.timeWeightedStats[greenSidePlayer].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,
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,
higherIsBetter: true,
),
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.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.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.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.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.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].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 == -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 == -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 == -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 == -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 == -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 == -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 == -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 == -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 == -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:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart';
final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
import 'package:tetra_stats/utils/numers_formats.dart';
class Graphs extends StatelessWidget{
const Graphs(
@ -125,13 +122,13 @@ class Graphs extends StatelessWidget{
getTitle: (index, angle) {
switch (index) {
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:
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:
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:
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:
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:intl/intl.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';
class StatCellNum extends StatelessWidget {
const StatCellNum(
@ -8,11 +11,12 @@ class StatCellNum extends StatelessWidget {
required this.playerStat,
required this.playerStatLabel,
required this.isScreenBig,
this.smallDecimal = true,
this.alertWidgets,
this.fractionDigits,
this.oldPlayerStat,
required this.higherIsBetter,
this.okText, this.alertTitle});
this.okText, this.alertTitle, this.pos, this.averageStat});
final num playerStat;
final num? oldPlayerStat;
@ -20,39 +24,57 @@ class StatCellNum extends StatelessWidget {
final String playerStatLabel;
final String? okText;
final bool isScreenBig;
final bool smallDecimal;
final String? alertTitle;
final List<Widget>? alertWidgets;
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
Widget build(BuildContext context) {
NumberFormat comparef = NumberFormat("+#,###.###;-#,###.###")..maximumFractionDigits = fractionDigits ?? 0;
NumberFormat intf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 0);
NumberFormat fractionf = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: fractionDigits ?? 0)..maximumIntegerDigits = 0;
num fraction = playerStat.isNegative ? 1 - (playerStat - playerStat.floor()) : playerStat - playerStat.floor();
int integer = playerStat.isNegative ? (playerStat + fraction).toInt() : (playerStat - fraction).toInt();
// String valueAsString = fractionDigits == null ? f.format(playerStat.floor()) : f.format(playerStat);
// var exploded = valueAsString.split(".");
return Column(
children: [
RichText(
text: TextSpan(text: intf.format(integer),
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(
fontFamily: "Eurostile Round Extended",
//fontWeight: FontWeight.bold,
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 ?
oldPlayerStat! > playerStat ? Colors.red : Colors.green :
oldPlayerStat! < playerStat ? Colors.red : Colors.green
oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent :
oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent
),),
if (oldPlayerStat != null && pos != null) const TextSpan(text: ""),
if (pos != null) TextSpan(text: pos!.position >= 1000 ? "${t.top} ${f2.format(pos!.percentage*100)}%" : "${pos!.position}", style: TextStyle(color: getColorOfRank(pos!.position)))
]
),
),
alertWidgets == null
? Text(
playerStatLabel,

View File

@ -3,13 +3,16 @@ import 'package:intl/intl.dart';
import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/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/stat_sell_num.dart';
var fDiff = NumberFormat("+#,###.###;-#,###.###");
var intFDiff = NumberFormat("+#,###;-#,###");
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
final NumberFormat f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2);
final NumberFormat f3 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3);
late RangeValues _currentRangeValues;
TetraLeagueAlpha? oldTl;
late TetraLeagueAlpha currentTl;
@ -23,18 +26,26 @@ class TLThingy extends StatefulWidget {
final bool bot;
final bool guest;
final double? topTR;
const TLThingy({super.key, required this.tl, required this.userID, required this.states, this.showTitle = true, this.bot=false, this.guest=false, this.topTR});
final PlayerLeaderboardPosition? lbPositions;
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
State<TLThingy> createState() => _TLThingyState();
}
class _TLThingyState extends State<TLThingy> {
late bool oskKagariGimmick;
@override
void initState() {
_currentRangeValues = const RangeValues(0, 1);
sortedStates = widget.states.reversed.toList();
oskKagariGimmick = prefs.getBool("oskKagariGimmick")??true;
try{
oldTl = sortedStates[1].tlSeason1;
}on RangeError{
@ -47,9 +58,11 @@ class _TLThingyState extends State<TLThingy> {
@override
Widget build(BuildContext context) {
final t = Translations.of(context);
NumberFormat fractionfEstTR = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2)..maximumIntegerDigits = 0;
NumberFormat fractionfEstTRAcc = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 3)..maximumIntegerDigits = 0;
if (currentTl.gamesPlayed == 0) return Center(child: Text(widget.guest ? t.anonTL : widget.bot ? t.botTL : t.neverPlayedTL, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28), textAlign: TextAlign.center,));
return LayoutBuilder(builder: (context, constraints) {
bool bigScreen = constraints.maxWidth > 768;
bool bigScreen = constraints.maxWidth >= 768;
return ListView.builder(
physics: const ClampingScrollPhysics(),
itemCount: 1,
@ -87,7 +100,7 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.center,
clipBehavior: Clip.hardEdge,
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/tetrio_tl_alpha_ranks/${currentTl.rank}.png", height: 128),
Column(
@ -146,12 +159,12 @@ class _TLThingyState extends State<TLThingy> {
softWrap: true,
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "Eurostile Round Extended",
fontFamily: "Eurostile Round",
fontSize: bigScreen ? 42 : 28,
overflow: TextOverflow.visible,
)),
Padding(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 48),
padding: const EdgeInsets.fromLTRB(8, 16, 8, 48),
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.center,
@ -159,13 +172,13 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge,
children: [
if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm),
if (currentTl.pps != null) StatCellNum(playerStat: currentTl.pps!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.pps, higherIsBetter: true, oldPlayerStat: oldTl?.pps),
if (currentTl.vs != null) StatCellNum(playerStat: currentTl.vs!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.vs, higherIsBetter: true, oldPlayerStat: oldTl?.vs),
if (currentTl.apm != null) StatCellNum(playerStat: currentTl.apm!, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.apm, higherIsBetter: true, oldPlayerStat: oldTl?.apm, pos: widget.lbPositions?.apm, 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, 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, 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),
StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed),
StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon),
StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null),
StatCellNum(playerStat: currentTl.gamesPlayed, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesPlayed, higherIsBetter: true, oldPlayerStat: oldTl?.gamesPlayed, pos: widget.lbPositions?.gamesPlayed),
StatCellNum(playerStat: currentTl.gamesWon, isScreenBig: bigScreen, playerStatLabel: t.statCellNum.gamesWonTL, higherIsBetter: true, oldPlayerStat: oldTl?.gamesWon, pos: widget.lbPositions?.gamesWon),
StatCellNum(playerStat: currentTl.winrate * 100, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.winrate, higherIsBetter: true, oldPlayerStat: oldTl != null ? oldTl!.winrate*100 : null, pos: widget.lbPositions?.winrate, averageStat: widget.averages != null ? widget.averages!.winrate * 100 : null),
],
),
),
@ -182,134 +195,31 @@ class _TLThingyState extends State<TLThingy> {
crossAxisAlignment: WrapCrossAlignment.start,
clipBehavior: Clip.hardEdge,
children: [
SizedBox(
width: 200,
height: 120,
child: SfRadialGauge(
title: GaugeTitle(text: t.statCellNum.app),
axes: [RadialAxis(
startAngle: 180,
endAngle: 360,
showLabels: false,
showTicks: false,
radiusFactor: 2.1,
centerY: 0.5,
minimum: 0,
maximum: 1,
ranges: [
GaugetNum(playerStat: currentTl.nerdStats!.app, playerStatLabel: t.statCellNum.app, higherIsBetter: true, minimum: 0, maximum: 1, ranges: [
GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red),
GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow),
GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green),
GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue),
GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple),
],
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: [
], alertWidgets: [
Text(t.statCellNum.appDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.app}")
]),
),
actions: <Widget>[
TextButton(
child: Text(t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,),
if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle(
color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ?
Colors.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: [
], oldPlayerStat: oldTl?.nerdStats?.app, pos: widget.lbPositions?.app,
averageStat: widget.averages?.nerdStats?.app),
GaugetNum(playerStat: currentTl.nerdStats!.vsapm, playerStatLabel: "VS / APM", higherIsBetter: true, minimum: 1.8, maximum: 2.4, ranges: [
GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green),
GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue),
GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple),
],
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: [
], alertWidgets: [
Text(t.statCellNum.vsapmDescription),
Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}")
], oldPlayerStat: oldTl?.nerdStats?.vsapm, pos: widget.lbPositions?.vsapm,
averageStat: widget.averages?.nerdStats?.vsapm)
]),
),
actions: <Widget>[
TextButton(
child: Text(t.popupActions.ok),
onPressed: () {
Navigator.of(context).pop();
},
)
],
));
},), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05),
if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm), style: TextStyle(
color: currentTl.nerdStats!.vsapm - oldTl!.nerdStats!.vsapm < 0 ?
Colors.red :
Colors.green
),), positionFactor: 0.05,)],
)],),
),]),
),
Wrap(
Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.center,
spacing: 25,
@ -317,6 +227,8 @@ class _TLThingyState extends State<TLThingy> {
clipBehavior: Clip.hardEdge,
children: [
StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss,
pos: widget.lbPositions?.dss,
averageStat: widget.averages?.nerdStats?.dss, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.dssDescription),
Text("${t.formula}: (VS / 100) - (APM / 60)"),
Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),],
@ -324,6 +236,8 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.dss,),
StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp,
pos: widget.lbPositions?.dsp,
averageStat: widget.averages?.nerdStats?.dsp, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.dspDescription),
Text("${t.formula}: DS/S / PPS"),
Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),],
@ -331,6 +245,8 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.dsp,),
StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp,
pos: widget.lbPositions?.appdsp,
averageStat: widget.averages?.nerdStats?.appdsp, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.appdspDescription),
Text("${t.formula}: APP + DS/P"),
Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),],
@ -338,13 +254,17 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.appdsp,),
StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese,
pos: widget.lbPositions?.cheese,
//averageStat: rankAverages?.nerdStats?.cheese, TODO: questonable
alertWidgets: [Text(t.statCellNum.cheeseDescription),
Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"),
Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),],
okText: t.popupActions.ok,
higherIsBetter: true,
higherIsBetter: false,
oldPlayerStat: oldTl?.nerdStats?.cheese,),
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),
Text("${t.formula}: APP * DS/P * 2"),
Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),],
@ -352,6 +272,8 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.gbe,),
StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp,
pos: widget.lbPositions?.nyaapp,
averageStat: widget.averages?.nerdStats?.nyaapp, smallDecimal: false,
alertWidgets: [Text(t.statCellNum.nyaappDescription),
Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"),
Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),],
@ -359,53 +281,89 @@ class _TLThingyState extends State<TLThingy> {
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.nyaapp,),
StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.area,
pos: widget.lbPositions?.area,
averageStat: widget.averages?.nerdStats?.area,
alertWidgets: [Text(t.statCellNum.areaDescription),
Text("${t.formula}: APM * 1 + PPS * 45 + VS * 0.444 + APP * 185 + DS/S * 175 + DS/P * 450 + Garbage Effi * 315"),
Text("${t.exactValue}: ${currentTl.nerdStats!.area}"),],
okText: t.popupActions.ok,
higherIsBetter: true,
oldPlayerStat: oldTl?.nerdStats?.area,)
])
]),
)
],
),
if (currentTl.estTr != null)
Padding(
padding: const EdgeInsets.fromLTRB(0, 16, 0, 48),
child: SizedBox(
padding: const EdgeInsets.fromLTRB(0, 20, 0, 20),
child: Container(
//alignment: Alignment.center,
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Text(t.statCellNum.estOfTR, style: const TextStyle(height: 0.1),),
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: [
Text(
"${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:",
style: const TextStyle(fontSize: 24),
if (oldTl?.estTr?.esttr != null) TextSpan(text: comparef.format(currentTl.estTr!.esttr - oldTl!.estTr!.esttr), style: TextStyle(
color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent
),),
if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: ""),
if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "${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)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Positioned(
right: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:",
style: const TextStyle(fontSize: 24),
Text(t.statCellNum.accOfEst, style: const TextStyle(height: 0.1),),
RichText(
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!)
]

View File

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

View File

@ -2,7 +2,7 @@ name: tetra_stats
description: Track your and other player stats in TETR.IO
publish_to: 'none'
version: 1.4.1+15
version: 1.5.0+16
environment:
sdk: '>=3.0.0'

View File

@ -46,6 +46,14 @@
"stoppedBeingTracked": "Removed from tracking list!",
"tlLeaderboard": "Tetra League leaderboard",
"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",
"botRecord": "Bots are not allowed to set records",
"anonRecord": "Guests are not allowed to set records",
@ -69,7 +77,10 @@
"supporter": "Supporter tier ${tier}",
"comparingWith": "Data from ${newDate} comparing with ${oldDate}",
"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",
"nerdStats": "Nerd Stats",
"playersYouTrack": "Players you track",
@ -93,8 +104,14 @@
"yourIDAlertTitle": "Your nickname in TETR.IO",
"yourIDText": "When app loads, it will retrieve data for this account",
"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",
"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}",
"statesViewTitle": "${number} states of ${nickname} account",
"matchesViewTitle": "${nickname} TL matches",
@ -119,10 +136,14 @@
"openReplay": "Open replay in TETR.IO",
"replaySaved": "Replay saved to ${path}",
"match": "Match",
"timeWeightedmatch": "Match (time-weighted)",
"roundNumber": "Round $n",
"statsFor": "Stats for",
"numberOfRounds": "Number of rounds",
"matchLength": "Match Length",
"roundLength": "Round Length",
"matchStats": "Match stats",
"timeWeightedmatchStats": "Time-weighted match stats",
"replayIssue": "Can't process replay",
"matchIsTooOld": "Replay is not available",
"winner": "Winner",
@ -159,6 +180,15 @@
"many": "$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",
"entries": "Entries",
"minimums": "Minimums",
@ -238,8 +268,30 @@
"numOfGameActions":{
"pc": "All Clears",
"hold": "Holds",
"tspinsTotal": "T-spins total",
"lineClears": "Line clears"
"inputs": {
"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":{
"cancel": "Cancel",
@ -249,21 +301,30 @@
"errors":{
"connection": "Some issue with connection: ${code} ${message}",
"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",
"actionSuggestion": "Perhaps, you want to",
"p1nkl0bst3rTLmatches": "No Tetra League matches was found",
"clientException": "No internet connection",
"forbidden": "Your IP address is blocked.\nChange IP address or reach out to osk",
"tooManyRequests": "You have been rate limited. Try again later",
"forbidden": "Your IP address is blocked",
"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",
"internalSub": "osk, probably, already aware about it",
"internalWebVersion": "Something happend on the tetr.io side (or on oskware_bridge, idk honestly)",
"oskwareBridge": "Something happend with oskware_bridge. Let dan63047 know",
"p1nkl0bst3rForbidden": "Third party API blocked your IP address.\nChange IP address or reach out to p1nkl0bst3r",
"internalWebVersionSub": "If osk status page says that everything is ok, let dan63047 know about this issue",
"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",
"p1nkl0bst3rinternal": "Something happend on the p1nkl0bst3r side",
"p1nkl0bst3rinternalWebVersion": "Something happend on the p1nkl0bst3r side (or on oskware_bridge, idk honestly)",
"replayAlreadySaved": "Replay already saved",
"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)": {
"": "Not selected",

View File

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

View File

@ -18,7 +18,7 @@
<meta charset="UTF-8">
<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 -->
<meta name="apple-mobile-web-app-capable" content="yes">