diff --git a/README.md b/README.md index d7506ff..093c477 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/android/app/build.gradle b/android/app/build.gradle index 3863f27..ebd2bb0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 } } } diff --git a/lib/data_objects/freyhoe_test.dart b/lib/data_objects/freyhoe_test.dart new file mode 100644 index 0000000..5275e86 --- /dev/null +++ b/lib/data_objects/freyhoe_test.dart @@ -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 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> board = [for (var i = 0 ; i < 40; i++) [for (var i = 0 ; i < 10; i++) Tetromino.empty]]; + // print(replay.rawJson); + + //print(""); +// exit(0); +//} \ No newline at end of file diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 6df9f83..fba0986 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -38,6 +38,25 @@ const Map rankCutoffs = { "z": -1, "": 0.5 }; +const Map 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 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 rankColors = { // thanks osk for const rankColors at ht 'z': Color(0xFF375433) }; +const Map 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 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 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 getStatRankingSequel(Stats stat){ + List 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 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 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 copyOfLeaderboard; + if (leaderboard.indexWhere((element) => element.userId == user.userId) == -1){ + fakePositions =true; + copyOfLeaderboard = List.of(leaderboard); + copyOfLeaderboard.add(user.convertToPlayerFromLeaderboard()); + } + List 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 results = []; + for (Stats stat in stats) { + List 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> get averages => { 'x': getAverageOfRank("x"), 'u': getAverageOfRank("u"), @@ -1732,6 +1906,26 @@ class TetrioPlayersLeaderboard { 'z': getAverageOfRank("z") }; + Map 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 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: diff --git a/lib/data_objects/tetrio_multiplayer_replay.dart b/lib/data_objects/tetrio_multiplayer_replay.dart index 34491cf..e453dd3 100644 --- a/lib/data_objects/tetrio_multiplayer_replay.dart +++ b/lib/data_objects/tetrio_multiplayer_replay.dart @@ -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 rawJson; late List endcontext; + late List timeWeightedStats; late List> stats; late List totalStats; late List> roundWinners; @@ -178,20 +199,45 @@ class ReplayData{ totalLength = 0; stats = []; roundWinners = []; + int roundID = 0; + List APMmultipliedByWeights = [0, 0]; + List PPSmultipliedByWeights = [0, 0]; + List VSmultipliedByWeights = [0, 0]; + List SPPmultipliedByWeights = [0, 0]; + List KPPmultipliedByWeights = [0, 0]; + List 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 toJson(){ @@ -211,4 +257,403 @@ class ReplayData{ } return data; } -} \ No newline at end of file +} + +// can't belive i have to implement that difficult shit + +// List readEventList(Map json){ +// List 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 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 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>? board; +// List? bag; +// double? g; +// bool? playing; +// Hold? hold; +// String? piece; +// Handling? handling; + +// DataFullGame.fromJson(Map 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 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 shuffleList(List 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 tetrominoes = [Tetromino.Z, Tetromino.L, Tetromino.O, Tetromino.S, Tetromino.I, Tetromino.J, Tetromino.T]; +// List>> 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 spawnPositionFixes = [Vector2(1, 1), Vector2(1, 1), Vector2(0, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1), Vector2(1, 1)]; + +// const Map 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 comboTable = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5]; diff --git a/lib/gen/strings.g.dart b/lib/gen/strings.g.dart index b28bfe7..1bed291 100644 --- a/lib/gen/strings.g.dart +++ b/lib/gen/strings.g.dart @@ -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 { 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 { 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 { 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 { 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 { 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: @@ -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 'Аландские острова'; diff --git a/lib/services/crud_exceptions.dart b/lib/services/crud_exceptions.dart index 303eaa0..3f34f91 100644 --- a/lib/services/crud_exceptions.dart +++ b/lib/services/crud_exceptions.dart @@ -14,6 +14,8 @@ class TetrioPlayerAlreadyExist implements Exception {} class TetrioPlayerNotExist implements Exception {} +class TetrioDiscordNotExist implements Exception {} + class TetrioHistoryNotExist implements Exception {} class TetrioTooManyRequests implements Exception {} diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index b5bf668..a78532f 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -74,6 +74,7 @@ class TetrioService extends DB { final Map> _recordsCache = {}; final Map _replaysCache = {}; // the only one is different: {"replayID": [replayString, replayBytes]} final Map _leaderboardsCache = {}; + final Map _lbPositions = {}; final Map> _newsCache = {}; final Map> _topTRcache = {}; final Map _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> 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 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 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 diff --git a/lib/utils/colors_functions.dart b/lib/utils/colors_functions.dart new file mode 100644 index 0000000..70277d9 --- /dev/null +++ b/lib/utils/colors_functions.dart @@ -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; +} \ No newline at end of file diff --git a/lib/utils/numers_formats.dart b/lib/utils/numers_formats.dart new file mode 100644 index 0000000..412820d --- /dev/null +++ b/lib/utils/numers_formats.dart @@ -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; \ No newline at end of file diff --git a/lib/views/calc_view.dart b/lib/views/calc_view.dart index 0828a73..5688d26 100644 --- a/lib/views/calc_view.dart +++ b/lib/views/calc_view.dart @@ -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,73 +66,67 @@ class CalcState extends State { title: Text(t.statsCalc), ), backgroundColor: Colors.black, - body: SafeArea( - child: NestedScrollView( - controller: _scrollController, - headerSliverBuilder: (context, value) { - return [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(14, 16, 16, 32), - child: Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(right: 12), - child: TextField( - onSubmitted: (value) => calc(), - controller: apmController, - keyboardType: TextInputType.number, - decoration: const InputDecoration(label: Text("APM"), alignLabelWithHint: true), - ), - )), - Expanded( - child: TextField( - onSubmitted: (value) => calc(), - controller: ppsController, - keyboardType: TextInputType.number, - decoration: const InputDecoration(label: Text("PPS"), alignLabelWithHint: true), - )), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 12), - child: TextField( - onSubmitted: (value) => calc(), - controller: vsController, - keyboardType: TextInputType.number, - decoration: const InputDecoration(label: Text("VS"), alignLabelWithHint: true), - ), - )), - TextButton( - onPressed: () => calc(), - child: Text(t.calc), - ), - ], + 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: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 12), + child: TextField( + onSubmitted: (value) => calc(), + controller: apmController, + keyboardType: TextInputType.number, + decoration: const InputDecoration(label: Text("APM"), alignLabelWithHint: true), + ), + )), + Expanded( + child: TextField( + onSubmitted: (value) => calc(), + controller: ppsController, + keyboardType: TextInputType.number, + decoration: const InputDecoration(label: Text("PPS"), alignLabelWithHint: true), + )), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 12), + child: TextField( + onSubmitted: (value) => calc(), + controller: vsController, + keyboardType: TextInputType.number, + decoration: const InputDecoration(label: Text("VS"), alignLabelWithHint: true), + ), + )), + TextButton( + onPressed: () => calc(), + child: Text(t.calc), ), - ), + ], ), - const SliverToBoxAdapter( - child: Divider(), - ) - ]; - }, - body: nerdStats == null - ? Text(t.calcViewNoValues) - : ListView( - children: [ - _ListEntry(value: nerdStats!.app, label: t.statCellNum.app.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: nerdStats!.vsapm, label: "VS/APM", fractionDigits: 3), - _ListEntry(value: nerdStats!.dss, label: t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: nerdStats!.dsp, label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: nerdStats!.appdsp, label: "APP + DS/P", fractionDigits: 3), - _ListEntry(value: nerdStats!.cheese, label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: nerdStats!.gbe, label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: nerdStats!.nyaapp, label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: nerdStats!.area, label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), - _ListEntry(value: estTr!.esttr, label: t.statCellNum.estOfTR, fractionDigits: 3), - Graphs(apm!, pps!, vs!, nerdStats!, playstyle!), - ], - )), + ), + 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), + _ListEntry(value: nerdStats!.dsp, label: t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), + _ListEntry(value: nerdStats!.appdsp, label: "APP + DS/P", fractionDigits: 3), + _ListEntry(value: nerdStats!.cheese, label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), + _ListEntry(value: nerdStats!.gbe, label: t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), + _ListEntry(value: nerdStats!.nyaapp, label: t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), + _ListEntry(value: nerdStats!.area, label: t.statCellNum.area.replaceAll(RegExp(r'\n'), " "), fractionDigits: 3), + _ListEntry(value: estTr!.esttr, label: t.statCellNum.estOfTR, fractionDigits: 3), + Graphs(apm!, pps!, vs!, nerdStats!, playstyle!) + ],) + ],), + ), + ), ), ); } diff --git a/lib/views/compare_view.dart b/lib/views/compare_view.dart index 2dd567f..44d0156 100644 --- a/lib/views/compare_view.dart +++ b/lib/views/compare_view.dart @@ -255,444 +255,435 @@ class CompareState extends State { return Scaffold( appBar: AppBar(title: Text("$titleGreenSide ${t.vs} $titleRedSide")), backgroundColor: Colors.black, - body: SafeArea( - child: NestedScrollView( - controller: _scrollController, - headerSliverBuilder: (context, value) { - return [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Colors.green, Colors.transparent], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, 0.4], - )), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: PlayerSelector( - data: theGreenSide, - mode: greenSideMode, - fetch: fetchGreenSide, - change: changeGreenSide, - updateState: _justUpdate, - ), + body: SingleChildScrollView( + controller: _scrollController, + 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, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Colors.green, Colors.transparent], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0.0, 0.4], + )), + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), + child: PlayerSelector( + data: theGreenSide, + mode: greenSideMode, + fetch: fetchGreenSide, + change: changeGreenSide, + updateState: _justUpdate, ), ), ), - const Padding( - padding: EdgeInsets.only(top: 16), - child: Text("VS"), - ), - Expanded( - child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [Colors.red, Colors.transparent], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, 0.4], - )), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: PlayerSelector( - data: theRedSide, - mode: redSideMode, - fetch: fetchRedSide, - change: changeRedSide, - updateState: _justUpdate, - ), + ), + const Padding( + padding: EdgeInsets.only(top: 16), + child: Text("VS"), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Colors.red, Colors.transparent], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0.0, 0.4], + )), + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), + child: PlayerSelector( + data: theRedSide, + mode: redSideMode, + fetch: fetchRedSide, + change: changeRedSide, + updateState: _justUpdate, ), ), ), - ], - ), + ), + ], ), ), - 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") - Column( - children: [ - CompareRegTimeThingy( - greenSide: theGreenSide[0].registrationTime, - redSide: theRedSide[0].registrationTime, - label: t.registred), - CompareThingy( - label: t.statCellNum.level, - greenSide: theGreenSide[0].level, - redSide: theRedSide[0].level, - higherIsBetter: true, - fractionDigits: 2, - ), - if (!theGreenSide[0].gameTime.isNegative && - !theRedSide[0].gameTime.isNegative) - CompareThingy( - greenSide: theGreenSide[0].gameTime.inMicroseconds / - 1000000 / - 60 / - 60, - redSide: theRedSide[0].gameTime.inMicroseconds / - 1000000 / - 60 / - 60, - label: t.statCellNum.hoursPlayed.replaceAll(RegExp(r'\n'), " "), - higherIsBetter: true, - fractionDigits: 2, - ), - 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) - CompareThingy( - label: t.statCellNum.gamesWon.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[0].gamesWon, - redSide: theRedSide[0].gamesWon, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.friends, - greenSide: theGreenSide[0].friendCount, - redSide: theRedSide[0].friendCount, - higherIsBetter: true, - ), - const Divider(), - ], + 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( + greenSide: theGreenSide[0].registrationTime, + redSide: theRedSide[0].registrationTime, + label: t.registred), + CompareThingy( + label: t.statCellNum.level, + greenSide: theGreenSide[0].level, + redSide: theRedSide[0].level, + higherIsBetter: true, + fractionDigits: 2, ), - 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) - ? Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.tetraLeague, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - if (theGreenSide[2].gamesPlayed > 9 && - theRedSide[2].gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "TR", - greenSide: theGreenSide[2].rating, - redSide: theRedSide[2].rating, - fractionDigits: 2, - higherIsBetter: true, - ), - if (greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].gamesPlayed, - redSide: theRedSide[2].gamesPlayed, - higherIsBetter: true, - ), - if (greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: t.statCellNum.gamesWonTL.replaceAll(RegExp(r'\n'), " "), - greenSide: theGreenSide[2].gamesWon, - redSide: theRedSide[2].gamesWon, - higherIsBetter: true, - ), - if (greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "WR %", - greenSide: - theGreenSide[2].winrate * 100, - redSide: theRedSide[2].winrate * 100, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].gamesPlayed > 9 && - theRedSide[2].gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "Glicko", - greenSide: theGreenSide[2].glicko!, - redSide: theRedSide[2].glicko!, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].gamesPlayed > 9 && - theRedSide[2].gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: "RD", - greenSide: theGreenSide[2].rd!, - redSide: theRedSide[2].rd!, - fractionDigits: 3, - higherIsBetter: false, - ), - if (theGreenSide[2].standing > 0 && - theRedSide[2].standing > 0 && - greenSideMode == Mode.player && - redSideMode == Mode.player) - CompareThingy( - label: t.statCellNum.lbpShort, - greenSide: theGreenSide[2].standing, - redSide: theRedSide[2].standing, - higherIsBetter: false, - ), - if (theGreenSide[2].standingLocal > 0 && - theRedSide[2].standingLocal > 0 && - greenSideMode == Mode.player && - redSideMode == Mode.player) - CompareThingy( - label: t.statCellNum.lbpcShort, - greenSide: - theGreenSide[2].standingLocal, - redSide: theRedSide[2].standingLocal, - higherIsBetter: false, - ), - if (theGreenSide[2].apm != null && - theRedSide[2].apm != null) - CompareThingy( - label: "APM", - greenSide: theGreenSide[2].apm!, - redSide: theRedSide[2].apm!, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].pps != null && - theRedSide[2].pps != null) - CompareThingy( - label: "PPS", - greenSide: theGreenSide[2].pps!, - redSide: theRedSide[2].pps!, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].vs != null && - theRedSide[2].vs != null) - CompareThingy( - label: "VS", - greenSide: theGreenSide[2].vs!, - redSide: theRedSide[2].vs!, - fractionDigits: 2, - higherIsBetter: true, - ), - ], - ) - : CompareBoolThingy( - greenSide: theGreenSide[2].gamesPlayed > 0, - redSide: theRedSide[2].gamesPlayed > 0, - label: t.playedTL, - trueIsBetter: false), + if (!theGreenSide[0].gameTime.isNegative && !theRedSide[0].gameTime.isNegative) + CompareThingy( + greenSide: theGreenSide[0].gameTime.inMicroseconds / + 1000000 / + 60 / + 60, + redSide: theRedSide[0].gameTime.inMicroseconds / + 1000000 / + 60 / + 60, + label: t.statCellNum.hoursPlayed.replaceAll(RegExp(r'\n'), " "), + higherIsBetter: true, + fractionDigits: 2, + ), + 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) + CompareThingy( + label: t.statCellNum.gamesWon.replaceAll(RegExp(r'\n'), " "), + greenSide: theGreenSide[0].gamesWon, + redSide: theRedSide[0].gamesWon, + higherIsBetter: true, + ), + CompareThingy( + label: t.statCellNum.friends, + greenSide: theGreenSide[0].friendCount, + redSide: theRedSide[0].friendCount, + higherIsBetter: true, + ), + const Divider(), + ], + ), + 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) + ? Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text(t.tetraLeague, + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ), + if (theGreenSide[2].gamesPlayed > 9 && + theRedSide[2].gamesPlayed > 9 && + greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: "TR", + greenSide: theGreenSide[2].rating, + redSide: theRedSide[2].rating, + fractionDigits: 2, + higherIsBetter: true, + ), + if (greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: t.statCellNum.gamesPlayed.replaceAll(RegExp(r'\n'), " "), + greenSide: theGreenSide[2].gamesPlayed, + redSide: theRedSide[2].gamesPlayed, + higherIsBetter: true, + ), + if (greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: t.statCellNum.gamesWonTL.replaceAll(RegExp(r'\n'), " "), + greenSide: theGreenSide[2].gamesWon, + redSide: theRedSide[2].gamesWon, + higherIsBetter: true, + ), + if (greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: "WR %", + greenSide: + theGreenSide[2].winrate * 100, + redSide: theRedSide[2].winrate * 100, + fractionDigits: 2, + higherIsBetter: true, + ), + if (theGreenSide[2].gamesPlayed > 9 && + theRedSide[2].gamesPlayed > 9 && + greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: "Glicko", + greenSide: theGreenSide[2].glicko!, + redSide: theRedSide[2].glicko!, + fractionDigits: 2, + higherIsBetter: true, + ), + if (theGreenSide[2].gamesPlayed > 9 && + theRedSide[2].gamesPlayed > 9 && + greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: "RD", + greenSide: theGreenSide[2].rd!, + redSide: theRedSide[2].rd!, + fractionDigits: 3, + higherIsBetter: false, + ), + if (theGreenSide[2].standing > 0 && + theRedSide[2].standing > 0 && + greenSideMode == Mode.player && + redSideMode == Mode.player) + CompareThingy( + label: t.statCellNum.lbpShort, + greenSide: theGreenSide[2].standing, + redSide: theRedSide[2].standing, + higherIsBetter: false, + ), + if (theGreenSide[2].standingLocal > 0 && + theRedSide[2].standingLocal > 0 && + greenSideMode == Mode.player && + redSideMode == Mode.player) + CompareThingy( + label: t.statCellNum.lbpcShort, + greenSide: + theGreenSide[2].standingLocal, + redSide: theRedSide[2].standingLocal, + higherIsBetter: false, + ), + if (theGreenSide[2].apm != null && + theRedSide[2].apm != null) + CompareThingy( + label: "APM", + greenSide: theGreenSide[2].apm!, + redSide: theRedSide[2].apm!, + fractionDigits: 2, + higherIsBetter: true, + ), + if (theGreenSide[2].pps != null && + theRedSide[2].pps != null) + CompareThingy( + label: "PPS", + greenSide: theGreenSide[2].pps!, + redSide: theRedSide[2].pps!, + fractionDigits: 2, + higherIsBetter: true, + ), + if (theGreenSide[2].vs != null && + theRedSide[2].vs != null) + CompareThingy( + label: "VS", + greenSide: theGreenSide[2].vs!, + redSide: theRedSide[2].vs!, + fractionDigits: 2, + higherIsBetter: true, + ), + ], + ) + : CompareBoolThingy( + greenSide: theGreenSide[2].gamesPlayed > 0, + redSide: theRedSide[2].gamesPlayed > 0, + label: t.playedTL, + trueIsBetter: false), + const Divider(), + if (theGreenSide[2].nerdStats != null && + theRedSide[2].nerdStats != null) + Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text(t.nerdStats, + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ), + CompareThingy( + label: "APP", + greenSide: theGreenSide[2].nerdStats!.app, + redSide: theRedSide[2].nerdStats!.app, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "VS/APM", + greenSide: theGreenSide[2].nerdStats!.vsapm, + redSide: theRedSide[2].nerdStats!.vsapm, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "DS/S", + greenSide: theGreenSide[2].nerdStats!.dss, + redSide: theRedSide[2].nerdStats!.dss, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "DS/P", + greenSide: theGreenSide[2].nerdStats!.dsp, + redSide: theRedSide[2].nerdStats!.dsp, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "APP + DS/P", + greenSide: + theGreenSide[2].nerdStats!.appdsp, + redSide: theRedSide[2].nerdStats!.appdsp, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), + greenSide: + theGreenSide[2].nerdStats!.cheese, + redSide: theRedSide[2].nerdStats!.cheese, + fractionDigits: 2, + higherIsBetter: true, + ), + CompareThingy( + label: "Gb Eff.", + greenSide: theGreenSide[2].nerdStats!.gbe, + redSide: theRedSide[2].nerdStats!.gbe, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "wAPP", + greenSide: + theGreenSide[2].nerdStats!.nyaapp, + redSide: theRedSide[2].nerdStats!.nyaapp, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "Area", + greenSide: theGreenSide[2].nerdStats!.area, + redSide: theRedSide[2].nerdStats!.area, + fractionDigits: 2, + higherIsBetter: true, + ), + CompareThingy( + label: t.statCellNum.estOfTRShort, + greenSide: theGreenSide[2].estTr!.esttr, + redSide: theRedSide[2].estTr!.esttr, + fractionDigits: 2, + higherIsBetter: true, + ), + if (theGreenSide[2].gamesPlayed > 9 && + theGreenSide[2].gamesPlayed > 9 && + greenSideMode != Mode.stats && + redSideMode != Mode.stats) + CompareThingy( + label: t.statCellNum.accOfEstShort, + greenSide: theGreenSide[2].esttracc!, + redSide: theRedSide[2].esttracc!, + fractionDigits: 2, + higherIsBetter: true, + ), + CompareThingy( + label: "Opener", + greenSide: theGreenSide[2].playstyle!.opener, + redSide: theRedSide[2].playstyle!.opener, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "Plonk", + greenSide: theGreenSide[2].playstyle!.plonk, + redSide: theRedSide[2].playstyle!.plonk, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "Stride", + greenSide: theGreenSide[2].playstyle!.stride, + redSide: theRedSide[2].playstyle!.stride, + fractionDigits: 3, + higherIsBetter: true, + ), + CompareThingy( + label: "Inf. DS", + greenSide: theGreenSide[2].playstyle!.infds, + redSide: theRedSide[2].playstyle!.infds, + fractionDigits: 3, + higherIsBetter: true, + ), + VsGraphs(theGreenSide[2].apm!, theGreenSide[2].pps!, theGreenSide[2].vs!, theGreenSide[2].nerdStats!, theGreenSide[2].playstyle!, theRedSide[2].apm!, theRedSide[2].pps!, theRedSide[2].vs!, theRedSide[2].nerdStats!, theRedSide[2].playstyle!), const Divider(), - if (theGreenSide[2].nerdStats != null && - theRedSide[2].nerdStats != null) - Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.nerdStats, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - label: "APP", - greenSide: theGreenSide[2].nerdStats!.app, - redSide: theRedSide[2].nerdStats!.app, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "VS/APM", - greenSide: theGreenSide[2].nerdStats!.vsapm, - redSide: theRedSide[2].nerdStats!.vsapm, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/S", - greenSide: theGreenSide[2].nerdStats!.dss, - redSide: theRedSide[2].nerdStats!.dss, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "DS/P", - greenSide: theGreenSide[2].nerdStats!.dsp, - redSide: theRedSide[2].nerdStats!.dsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "APP + DS/P", - greenSide: - theGreenSide[2].nerdStats!.appdsp, - redSide: theRedSide[2].nerdStats!.appdsp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "), - greenSide: - theGreenSide[2].nerdStats!.cheese, - redSide: theRedSide[2].nerdStats!.cheese, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Gb Eff.", - greenSide: theGreenSide[2].nerdStats!.gbe, - redSide: theRedSide[2].nerdStats!.gbe, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "wAPP", - greenSide: - theGreenSide[2].nerdStats!.nyaapp, - redSide: theRedSide[2].nerdStats!.nyaapp, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Area", - greenSide: theGreenSide[2].nerdStats!.area, - redSide: theRedSide[2].nerdStats!.area, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: t.statCellNum.estOfTRShort, - greenSide: theGreenSide[2].estTr!.esttr, - redSide: theRedSide[2].estTr!.esttr, - fractionDigits: 2, - higherIsBetter: true, - ), - if (theGreenSide[2].gamesPlayed > 9 && - theGreenSide[2].gamesPlayed > 9 && - greenSideMode != Mode.stats && - redSideMode != Mode.stats) - CompareThingy( - label: t.statCellNum.accOfEstShort, - greenSide: theGreenSide[2].esttracc!, - redSide: theRedSide[2].esttracc!, - fractionDigits: 2, - higherIsBetter: true, - ), - CompareThingy( - label: "Opener", - greenSide: theGreenSide[2].playstyle!.opener, - redSide: theRedSide[2].playstyle!.opener, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Plonk", - greenSide: theGreenSide[2].playstyle!.plonk, - redSide: theRedSide[2].playstyle!.plonk, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Stride", - greenSide: theGreenSide[2].playstyle!.stride, - redSide: theRedSide[2].playstyle!.stride, - fractionDigits: 3, - higherIsBetter: true, - ), - CompareThingy( - label: "Inf. DS", - greenSide: theGreenSide[2].playstyle!.infds, - redSide: theRedSide[2].playstyle!.infds, - fractionDigits: 3, - higherIsBetter: true, - ), - VsGraphs(theGreenSide[2].apm!, theGreenSide[2].pps!, theGreenSide[2].vs!, theGreenSide[2].nerdStats!, theGreenSide[2].playstyle!, theRedSide[2].apm!, theRedSide[2].pps!, theRedSide[2].vs!, theRedSide[2].nerdStats!, theRedSide[2].playstyle!), - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.winChance, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - if (greenSideMode != Mode.stats && redSideMode != Mode.stats && - theGreenSide[2].gamesPlayed > 9 && theRedSide[2].gamesPlayed > 9) - CompareThingy( - label: t.byGlicko, - greenSide: getWinrateByTR( - theGreenSide[2].glicko!, - theGreenSide[2].rd!, - theRedSide[2].glicko!, - theRedSide[2].rd!) * - 100, - redSide: getWinrateByTR( - theRedSide[2].glicko!, - theRedSide[2].rd!, - theGreenSide[2].glicko!, - theGreenSide[2].rd!) * - 100, - fractionDigits: 2, - higherIsBetter: true, - postfix: "%", - ), - CompareThingy( - label: t.byEstTR, - greenSide: getWinrateByTR( - theGreenSide[2].estTr!.estglicko, - theGreenSide[2].rd ?? noTrRd, - theRedSide[2].estTr!.estglicko, - theRedSide[2].rd ?? noTrRd) * - 100, - redSide: getWinrateByTR( - theRedSide[2].estTr!.estglicko, - theRedSide[2].rd ?? noTrRd, - theGreenSide[2].estTr!.estglicko, - theGreenSide[2].rd ?? noTrRd) * - 100, - fractionDigits: 2, - higherIsBetter: true, - postfix: "%", - ), - ], - ) - ] : [Padding( - padding: const EdgeInsets.all(8.0), - child: Text(t.compareViewNoValues(avgR: "\$avgR"), textAlign: TextAlign.center), - )], // This is so fucked up holy shit + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text(t.winChance, + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ), + if (greenSideMode != Mode.stats && redSideMode != Mode.stats && + theGreenSide[2].gamesPlayed > 9 && theRedSide[2].gamesPlayed > 9) + CompareThingy( + label: t.byGlicko, + greenSide: getWinrateByTR( + theGreenSide[2].glicko!, + theGreenSide[2].rd!, + theRedSide[2].glicko!, + theRedSide[2].rd!) * + 100, + redSide: getWinrateByTR( + theRedSide[2].glicko!, + theRedSide[2].rd!, + theGreenSide[2].glicko!, + theGreenSide[2].rd!) * + 100, + fractionDigits: 2, + higherIsBetter: true, + postfix: "%", + ), + CompareThingy( + label: t.byEstTR, + greenSide: getWinrateByTR( + theGreenSide[2].estTr!.estglicko, + theGreenSide[2].rd ?? noTrRd, + theRedSide[2].estTr!.estglicko, + theRedSide[2].rd ?? noTrRd) * + 100, + redSide: getWinrateByTR( + theRedSide[2].estTr!.estglicko, + theRedSide[2].rd ?? noTrRd, + theGreenSide[2].estTr!.estglicko, + theGreenSide[2].rd ?? noTrRd) * + 100, + fractionDigits: 2, + higherIsBetter: true, + postfix: "%", + ), + ], ) + ], + ) + else Padding( + padding: const EdgeInsets.all(8.0), + child: Text(t.compareViewNoValues(avgR: "\$avgR"), textAlign: TextAlign.center), + ) + ], + ), + ), ), ), ); @@ -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( diff --git a/lib/views/customization_view.dart b/lib/views/customization_view.dart index 71806ba..e7c22a4 100644 --- a/lib/views/customization_view.dart +++ b/lib/views/customization_view.dart @@ -20,6 +20,7 @@ class CustomizationView extends StatefulWidget { class CustomizationState extends State { late SharedPreferences prefs; + late bool oskKagariGimmick; void changeColor(Color color) { setState(() => pickerColor = color); @@ -31,7 +32,7 @@ class CustomizationState extends State { 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 { Future _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 { 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( - // 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: [ - ElevatedButton( - child: const Text('Got it'), - onPressed: () { - setState(() { - setAccentColor(pickerColor); - }); - 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"), - ), + // 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, + // ), + // ), + // actions: [ + // ElevatedButton( + // child: const Text('Set'), + // onPressed: () { + // setState(() { + // setAccentColor(pickerColor); + // }); + // 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"), + // ), + ListTile(title: Text(t.oskKagari), + subtitle: Text(t.oskKagariDescription), + trailing: Switch(value: oskKagariGimmick, onChanged: (bool value){ + prefs.setBool("oskKagariGimmick", value); + setState(() { + oskKagariGimmick = value; + }); + }),) ], )), ); diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index c56a593..c7e46e0 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -16,10 +16,14 @@ import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/services/tetrio_crud.dart'; import 'package:tetra_stats/main.dart' show prefs; import 'package:tetra_stats/services/crud_exceptions.dart'; +import 'package:tetra_stats/utils/numers_formats.dart'; import 'package:tetra_stats/utils/text_shadow.dart'; import 'package:tetra_stats/views/ranks_averages_view.dart' show RankAveragesView; import 'package:tetra_stats/views/tl_leaderboard_view.dart' show TLLeaderboardView; import 'package:tetra_stats/views/tl_match_view.dart' show TlMatchResultView; +import 'package:tetra_stats/widgets/finesse_thingy.dart'; +import 'package:tetra_stats/widgets/lineclears_thingy.dart'; +import 'package:tetra_stats/widgets/list_tile_trailing_stats.dart'; import 'package:tetra_stats/widgets/search_box.dart'; import 'package:tetra_stats/widgets/stat_sell_num.dart'; import 'package:tetra_stats/widgets/tl_thingy.dart'; @@ -28,19 +32,13 @@ import 'package:window_manager/window_manager.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:go_router/go_router.dart'; -Future me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up -String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for -String _titleNickname = "dan63047"; final TetrioService teto = TetrioService(); // thing, that manadge our local DB -/// Each dropdown menu item contains list of dots for the graph -var chartsData = >>[]; int _chartsIndex = 0; +bool _gamesPlayedInsteadOfDateAndTime = false; List _historyShortTitles = ["TR", "Glicko", "RD", "APM", "PPS", "VS", "APP", "DS/S", "DS/P", "APP + DS/P", "VS/APM", "Cheese", "GbE", "wAPP", "Area", "eTR", "±eTR", "Opener", "Plonk", "Inf. DS", "Stride"]; late ScrollController _scrollController; -final NumberFormat _timeInSec = NumberFormat("#,###.###s."); -final NumberFormat secs = NumberFormat("00.###"); -final NumberFormat _f2 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 2); -final NumberFormat _f4 = NumberFormat.decimalPatternDigits(locale: LocaleSettings.currentLocale.languageCode, decimalDigits: 4); +final NumberFormat _timeInSec = NumberFormat("#,###.###s.", LocaleSettings.currentLocale.languageCode); +final NumberFormat secs = NumberFormat("00.###", LocaleSettings.currentLocale.languageCode); final DateFormat _dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms(); @@ -51,8 +49,6 @@ class MainView extends StatefulWidget { /// if [player] username or id provided, it loads his stats. Also it hides menu drawer and three dots menu. const MainView({super.key, this.player}); - String get title => "Tetra Stats: $_titleNickname"; - @override State createState() => _MainState(); } @@ -66,17 +62,46 @@ String get40lTime(int microseconds){ return microseconds > 60000000 ? "${(microseconds/1000000/60).floor()}:${(secs.format(microseconds /1000000 % 60))}" : _timeInSec.format(microseconds / 1000000); } +/// Readable [a] - [b], without sign +String readableTimeDifference(Duration a, Duration b){ + Duration result = a - b; + + return NumberFormat("0.000s;0.000s", LocaleSettings.currentLocale.languageCode).format(result.inMilliseconds/1000); +} + +/// Readable [a] - [b], without sign +String readableIntDifference(int a, int b){ + int result = a - b; + + return NumberFormat("#,###;#,###", LocaleSettings.currentLocale.languageCode).format(result); +} + class _MainState extends State with TickerProviderStateMixin { + Future me = Future.delayed(const Duration(seconds: 60), () => [null, null, null, null, null, null]); // I love lists shut up + TetrioPlayersLeaderboard? everyone; + PlayerLeaderboardPosition? meAmongEveryone; + TetraLeagueAlpha? rankAverages; + String _searchFor = "6098518e3d5155e6ec429cdc"; // who we looking for + String _titleNickname = "dan63047"; + /// Each dropdown menu item contains list of dots for the graph + var chartsData = >>[]; + var chartsDataGamesPlayed = >>[]; + //var tableData = []; final bodyGlobalKey = GlobalKey(); bool _showSearchBar = false; + bool _TLHistoryWasFetched = false; late TabController _tabController; + late TabController _wideScreenTabController; late bool fixedScroll; + String get title => "Tetra Stats: $_titleNickname"; + @override void initState() { initDB(); _scrollController = ScrollController(); _tabController = TabController(length: 6, vsync: this); + _wideScreenTabController = TabController(length: 4, vsync: this); // We need to show something if (widget.player != null){ // if we have user input, @@ -122,6 +147,7 @@ class _MainState extends State with TickerProviderStateMixin { /// If at least one request to Tetra Channel API fails, whole function will throw an exception. Future fetch(String nickOrID, {bool fetchHistory = false, bool fetchTLmatches = false}) async { TetrioPlayer me; + _TLHistoryWasFetched = false; // If user trying to search with discord id if (nickOrID.startsWith("ds:")){ @@ -133,7 +159,7 @@ class _MainState extends State with TickerProviderStateMixin { // Change view title and window title if avaliable setState((){_titleNickname = me.username;}); - if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) await windowManager.setTitle(widget.title); + if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) await windowManager.setTitle(title); // Requesting Tetra League (alpha), records, news and top TR of player late List requests; @@ -152,6 +178,17 @@ class _MainState extends State with TickerProviderStateMixin { news = requests[2] as List; topTR = requests.elementAtOrNull(3) as double?; // No TR - no Top TR + meAmongEveryone = teto.getCachedLeaderboardPositions(me.userId); + if (meAmongEveryone == null && prefs.getBool("showPositions") == true){ + // Get tetra League leaderboard + everyone = teto.getCachedLeaderboard(); + everyone ??= await teto.fetchTLLeaderboard(); + meAmongEveryone = await compute(everyone!.getLeaderboardPosition, me); + if (meAmongEveryone != null) teto.cacheLeaderboardPositions(me.userId, meAmongEveryone!); + } + + if (everyone != null && me.tlSeason1.gamesPlayed > 9) rankAverages = everyone?.averages[me.tlSeason1.percentileRank]?[0]; + // Making list of Tetra League matches List tlMatches = []; bool isTracking = await teto.isPlayerTracking(me.userId); @@ -179,8 +216,11 @@ class _MainState extends State with TickerProviderStateMixin { if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rinternal))); }on P1nkl0bst3rTooManyRequests{ if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.p1nkl0bst3rTooManyRequests))); + }finally{ + _TLHistoryWasFetched = true; } - } + } + if (storedRecords.isNotEmpty) _TLHistoryWasFetched = true; for (var match in storedRecords) { // add stored match to list only if it missing from retrived ones if (!tlMatches.contains(match)) tlMatches.add(match); @@ -214,9 +254,14 @@ class _MainState extends State with TickerProviderStateMixin { if (uniqueTL.isEmpty) uniqueTL.add(element.tlSeason1); } // Also i need previous Tetra League State for comparison if avaliable - compareWith = uniqueTL.length >= 2 ? uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2) : null; - - chartsData = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid + // tableData = [ + // TableRow(children: [ Text("Date & Time"), Text("Tr"), Text("Glicko"), Text("RD"), Text("GP"), Text("GW"), Text("APM"), Text("PPS"), Text("VS"), Text("APP"), Text("VS/APM"), Text("DS/S"), Text("DS/P"), Text("APP+DS/P"), Text("Cheese"), Text("GbE"), Text("wAPP"), Text("Area"), Text("eTR"), Text("±eTR"), Text("Opener"), Text("Plonk"), Text("Inf. DS"), Text("Stride")], + // decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.white)))), + // for (var state in states) TableRow(children: [Text(dateFormat.format(state.tlSeason1.timestamp)), Text(f4.format(state.tlSeason1.rating)), Text(f4.format(state.tlSeason1.glicko)), Text(f4.format(state.tlSeason1.rd)), Text(f0.format(state.tlSeason1.gamesPlayed)), Text(f0.format(state.tlSeason1.gamesWon)), Text(f2.format(state.tlSeason1.apm)), Text(f2.format(state.tlSeason1.pps)), Text(state.tlSeason1.vs != null ? f2.format(state.tlSeason1.vs) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.app) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.vsapm) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.dss) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.dsp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.appdsp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.cheese) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.gbe) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.nyaapp) : "---"), Text(state.tlSeason1.nerdStats != null ? f4.format(state.tlSeason1.nerdStats?.area) : "---"), Text(state.tlSeason1.estTr != null ? f4.format(state.tlSeason1.estTr?.esttr) : "---"), Text(state.tlSeason1.esttracc != null ? f4.format(state.tlSeason1.esttracc) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.opener) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.plonk) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.infds) : "---"), Text(state.tlSeason1.playstyle != null ? f4.format(state.tlSeason1.playstyle?.stride) : "---")]), + // ]; + if (uniqueTL.length >= 2){ + compareWith = uniqueTL.toList().elementAtOrNull(uniqueTL.length - 2); + chartsData = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rating)], child: Text(t.statCellNum.tr)), DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.glicko!)], child: const Text("Glicko")), DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.rd!)], child: const Text("Rating Deviation")), @@ -239,6 +284,33 @@ class _MainState extends State with TickerProviderStateMixin { DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.infds)], child: const Text("Inf. DS")), DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.timestamp.millisecondsSinceEpoch.toDouble(), tl.playstyle!.stride)], child: const Text("Stride")), ]; + chartsDataGamesPlayed = >>[ // Dumping charts data into dropdown menu items, while cheking if every entry is valid + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.rating)], child: Text(t.statCellNum.tr)), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.glicko!)], child: const Text("Glicko")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.gamesPlayed > 9) FlSpot(tl.gamesPlayed.toDouble(), tl.rd!)], child: const Text("Rating Deviation")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.apm != null) FlSpot(tl.gamesPlayed.toDouble(), tl.apm!)], child: Text(t.statCellNum.apm.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.pps != null) FlSpot(tl.gamesPlayed.toDouble(), tl.pps!)], child: Text(t.statCellNum.pps.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.vs != null) FlSpot(tl.gamesPlayed.toDouble(), tl.vs!)], child: Text(t.statCellNum.vs.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.app)], child: Text(t.statCellNum.app.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.dss)], child: Text(t.statCellNum.dss.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.dsp)], child: Text(t.statCellNum.dsp.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.appdsp)], child: const Text("APP + DS/P")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.vsapm)], child: const Text("VS/APM")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.cheese)], child: Text(t.statCellNum.cheese.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.gbe)], child: Text(t.statCellNum.gbe.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.nyaapp)], child: Text(t.statCellNum.nyaapp.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.nerdStats != null) FlSpot(tl.gamesPlayed.toDouble(), tl.nerdStats!.area)], child: Text(t.statCellNum.area.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.estTr != null) FlSpot(tl.gamesPlayed.toDouble(), tl.estTr!.esttr)], child: Text(t.statCellNum.estOfTR.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.esttracc != null) FlSpot(tl.gamesPlayed.toDouble(), tl.esttracc!)], child: Text(t.statCellNum.accOfEst.replaceAll(RegExp(r'\n'), " "))), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.opener)], child: const Text("Opener")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.plonk)], child: const Text("Plonk")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.infds)], child: const Text("Inf. DS")), + DropdownMenuItem(value: [for (var tl in uniqueTL) if (tl.playstyle != null) FlSpot(tl.gamesPlayed.toDouble(), tl.playstyle!.stride)], child: const Text("Stride")), + ]; + }else{ + compareWith = null; + chartsData = []; + } return [me, records, states, tlMatches, compareWith, isTracking, news, topTR]; } @@ -250,12 +322,12 @@ class _MainState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final t = Translations.of(context); - bool bigScreen = MediaQuery.of(context).size.width > 768; + bool bigScreen = MediaQuery.of(context).size.width > 1024; return Scaffold( drawer: widget.player == null ? NavDrawer(changePlayer) : null, // Side menu hidden if player provided drawerEdgeDragWidth: MediaQuery.of(context).size.width * 0.2, // 20% of left side of the screen used of Drawer gesture appBar: AppBar( - title: _showSearchBar ? SearchBox(onSubmit: changePlayer, bigScreen: bigScreen) : Text(widget.title, style: const TextStyle(shadows: textShadow)), + title: _showSearchBar ? SearchBox(onSubmit: changePlayer, bigScreen: MediaQuery.of(context).size.width > 768) : Text(title, style: const TextStyle(shadows: textShadow)), backgroundColor: Colors.black, actions: widget.player == null ? [ // search bar and PopupMenuButton hidden if player provided TODO: Subject to change _showSearchBar @@ -345,8 +417,8 @@ class _MainState extends State with TickerProviderStateMixin { return notification.depth == 0; }, child: NestedScrollView( + scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false, physics: const AlwaysScrollableScrollPhysics()), controller: _scrollController, - physics: const AlwaysScrollableScrollPhysics(), headerSliverBuilder: (context, value) { return [ SliverToBoxAdapter( @@ -357,10 +429,15 @@ class _MainState extends State with TickerProviderStateMixin { )), SliverToBoxAdapter( child: TabBar( - controller: _tabController, + controller: bigScreen ? _wideScreenTabController : _tabController, padding: const EdgeInsets.all(0.0), isScrollable: true, - tabs: [ + tabs: bigScreen ? [ + Tab(text: t.tetraLeague,), + Tab(text: t.history), + Tab(text: "${t.sprint} & ${t.blitz}"), + Tab(text: t.other), + ] : [ Tab(text: t.tetraLeague), Tab(text: t.tlRecords), Tab(text: t.history), @@ -373,13 +450,48 @@ class _MainState extends State with TickerProviderStateMixin { ]; }, body: TabBarView( - controller: _tabController, - children: [ - TLThingy(tl: snapshot.data![0].tlSeason1, userID: snapshot.data![0].userId, states: snapshot.data![2], topTR: snapshot.data![7], bot: snapshot.data![0].role == "bot", guest: snapshot.data![0].role == "anon"), - _TLRecords(userID: snapshot.data![0].userId, data: snapshot.data![3]), - _History(states: snapshot.data![2], update: _justUpdate), - _RecordThingy(record: snapshot.data![1]['sprint']), - _RecordThingy(record: snapshot.data![1]['blitz']), + controller: bigScreen ? _wideScreenTabController : _tabController, + children: bigScreen ? [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: MediaQuery.of(context).size.width-450, + constraints: const BoxConstraints(maxWidth: 1024), + child: TLThingy( + tl: snapshot.data![0].tlSeason1, + userID: snapshot.data![0].userId, + states: snapshot.data![2], + topTR: snapshot.data![7], + bot: snapshot.data![0].role == "bot", + guest: snapshot.data![0].role == "anon", + averages: rankAverages, + lbPositions: meAmongEveryone + ), + ), + SizedBox( + width: 450, + child: _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched, separateScrollController: true,) + ), + ],), + _History(chartsData: chartsData, chartsDataGamesPlayed: chartsDataGamesPlayed, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0), + _TwoRecordsThingy(sprint: snapshot.data![1]['sprint'], blitz: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank,), + _OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],) + ] : [ + TLThingy( + tl: snapshot.data![0].tlSeason1, + userID: snapshot.data![0].userId, + states: snapshot.data![2], + topTR: snapshot.data![7], + bot: snapshot.data![0].role == "bot", + guest: snapshot.data![0].role == "anon", + averages: rankAverages, + lbPositions: meAmongEveryone + ), + _TLRecords(userID: snapshot.data![0].userId, changePlayer: changePlayer, data: snapshot.data![3], wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0, oldMathcesHere: _TLHistoryWasFetched), + _History(chartsData: chartsData, chartsDataGamesPlayed: chartsDataGamesPlayed, changePlayer: changePlayer, userID: _searchFor, update: _justUpdate, wasActiveInTL: snapshot.data![0].tlSeason1.gamesPlayed > 0), + _RecordThingy(record: snapshot.data![1]['sprint'], rank: snapshot.data![0].tlSeason1.percentileRank), + _RecordThingy(record: snapshot.data![1]['blitz'], rank: snapshot.data![0].tlSeason1.percentileRank), _OtherThingy(zen: snapshot.data![1]['zen'], bio: snapshot.data![0].bio, distinguishment: snapshot.data![0].distinguishment, newsletter: snapshot.data![6],) ], ), @@ -387,28 +499,34 @@ class _MainState extends State with TickerProviderStateMixin { ); } else if (snapshot.hasError) { String errText = ""; + String? subText; switch (snapshot.error.runtimeType){ case TetrioPlayerNotExist: errText = t.errors.noSuchUser; + subText = t.errors.noSuchUserSub; break; + case TetrioDiscordNotExist: + errText = t.errors.discordNotAssigned; + subText = t.errors.discordNotAssignedSub; case ConnectionIssue: var err = snapshot.error as ConnectionIssue; errText = t.errors.connection(code: err.code, message: err.message); break; - case TetrioHistoryNotExist: - errText = t.errors.history; - break; case TetrioForbidden: errText = t.errors.forbidden; + subText = t.errors.forbiddenSub(nickname: 'osk'); break; case TetrioTooManyRequests: errText = t.errors.tooManyRequests; + subText = t.errors.tooManyRequestsSub; break; case TetrioOskwareBridgeProblem: errText = t.errors.oskwareBridge; + subText = t.errors.oskwareBridgeSub; break; case TetrioInternalProblem: errText = kIsWeb ? t.errors.internalWebVersion : t.errors.internal; + subText = kIsWeb ? t.errors.internalWebVersionSub : t.errors.internalSub; break; case ClientException: errText = t.errors.clientException; @@ -416,7 +534,18 @@ class _MainState extends State with TickerProviderStateMixin { default: errText = snapshot.error.toString(); } - return Center(child: Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center)); + return Center(child: + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(errText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center), + if (subText != null) Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text(subText, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 18)), + ), + ], + ) + ); } break; } @@ -554,20 +683,46 @@ class _NavDrawerState extends State { class _TLRecords extends StatelessWidget { final String userID; + final Function changePlayer; final List data; + final bool wasActiveInTL; + final bool oldMathcesHere; + final bool separateScrollController; /// Widget, that displays Tetra League records. /// Accepts list of TL records ([data]) and [userID] of player from the view - const _TLRecords({required this.userID, required this.data}); + const _TLRecords({required this.userID, required this.changePlayer, required this.data, required this.wasActiveInTL, required this.oldMathcesHere, this.separateScrollController = false}); @override Widget build(BuildContext context) { - if (data.isEmpty) return Center(child: Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); - bool bigScreen = MediaQuery.of(context).size.width > 768; + if (data.isEmpty) { + return Center(child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(t.noRecords, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), + if (wasActiveInTL) Text(t.errors.actionSuggestion), + if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchTLmatches: true);}, child: Text(t.fetchAndSaveOldTLmatches)) + ], + )); + } + bool bigScreen = MediaQuery.of(context).size.width >= 768; + int length = data.length; return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), - itemCount: data.length, + controller: separateScrollController ? ScrollController() : null, + itemCount: oldMathcesHere ? length : length + 1, itemBuilder: (BuildContext context, int index) { + if (index == length) { + return Center(child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(t.noOldRecords(n: length), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), + if (wasActiveInTL) Text(t.errors.actionSuggestion), + if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchTLmatches: true);}, child: Text(t.fetchAndSaveOldTLmatches)) + ], + )); + } + var accentColor = data[index].endContext.firstWhere((element) => element.userId == userID).success ? Colors.green : Colors.red; return Container( decoration: BoxDecoration( @@ -577,23 +732,18 @@ class _TLRecords extends StatelessWidget { ) ), child: ListTile( - // tileColor: data[index].endContext.firstWhere((element) => element.userId == userID).success ? Colors.green[900] : Colors.red[900], leading: Text("${data[index].endContext.firstWhere((element) => element.userId == userID).points} : ${data[index].endContext.firstWhere((element) => element.userId != userID).points}", style: bigScreen ? const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 28, shadows: textShadow) : const TextStyle(fontSize: 28, shadows: textShadow)), title: Text("vs. ${data[index].endContext.firstWhere((element) => element.userId != userID).username}"), subtitle: Text(_dateFormat.format(data[index].timestamp)), - trailing: Table(defaultColumnWidth: const IntrinsicColumnWidth(), - defaultVerticalAlignment: TableCellVerticalAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - columnWidths: const { - 0: FixedColumnWidth(50), - 2: FixedColumnWidth(50), - }, - children: [ - TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).secondary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" APM", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).tertiary), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" PPS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - TableRow(children: [Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId == userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" :", style: TextStyle(height: 1.1)), Text(_f2.format(data[index].endContext.firstWhere((element) => element.userId != userID).extra), textAlign: TextAlign.right, style: const TextStyle(height: 1.1)), const Text(" VS", textAlign: TextAlign.right, style: TextStyle(height: 1.1))]), - ],), + trailing: TrailingStats( + data[index].endContext.firstWhere((element) => element.userId == userID).secondary, + data[index].endContext.firstWhere((element) => element.userId == userID).tertiary, + data[index].endContext.firstWhere((element) => element.userId == userID).extra, + data[index].endContext.firstWhere((element) => element.userId != userID).secondary, + data[index].endContext.firstWhere((element) => element.userId != userID).tertiary, + data[index].endContext.firstWhere((element) => element.userId != userID).extra + ), onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => TlMatchResultView(record: data[index], initPlayerId: userID))), ), ); @@ -602,32 +752,84 @@ class _TLRecords extends StatelessWidget { } class _History extends StatelessWidget{ - final List states; + final List>> chartsData; + final List>> chartsDataGamesPlayed; + final String userID; final Function update; + final Function changePlayer; + final bool wasActiveInTL; /// Widget, that can show history of some stat of the player on the graph. /// Requires player [states], which is list of states and function [update], which rebuild widgets - const _History({required this.states, required this.update}); + const _History({required this.chartsData, required this.chartsDataGamesPlayed, required this.userID, required this.changePlayer, required this.update, required this.wasActiveInTL}); @override Widget build(BuildContext context) { - bool bigScreen = MediaQuery.of(context).size.width > 768; - return states.isNotEmpty ? - Column( + if (chartsData.isEmpty) { + return Center(child: Column( + mainAxisSize: MainAxisSize.min, children: [ - DropdownButton( - items: chartsData, - value: chartsData[_chartsIndex].value, - onChanged: (value) { - _chartsIndex = chartsData.indexWhere((element) => element.value == value); - update(); - } - ), - if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: chartsData[_chartsIndex].value!, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? _f2 : NumberFormat.compact(),) - else Center(child: Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))) + Text(t.noHistorySaved, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), + if (wasActiveInTL) Text(t.errors.actionSuggestion), + if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchHistory: true);}, child: Text(t.fetchAndsaveTLHistory)) ], - ) - : Center(child: Text(t.noHistorySaved, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); + )); + } + bool bigScreen = MediaQuery.of(context).size.width > 768; + return SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + primary: true, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Wrap( + spacing: 20, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Padding(padding: EdgeInsets.all(8.0), child: Text("X:", style: TextStyle(fontSize: 22))), + DropdownButton( + items: const [DropdownMenuItem(value: false, child: Text("Date & Time")), DropdownMenuItem(value: true, child: Text("Games Played"))], + value: _gamesPlayedInsteadOfDateAndTime, + onChanged: (value) { + _gamesPlayedInsteadOfDateAndTime = value!; + update(); + } + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Padding(padding: EdgeInsets.all(8.0), child: Text("Y:", style: TextStyle(fontSize: 22))), + DropdownButton( + items: chartsData, + value: chartsData[_chartsIndex].value, + onChanged: (value) { + _chartsIndex = chartsData.indexWhere((element) => element.value == value); + update(); + } + ), + ], + ), + ], + ), + if(chartsData[_chartsIndex].value!.length > 1) _HistoryChartThigy(data: _gamesPlayedInsteadOfDateAndTime ? chartsDataGamesPlayed[_chartsIndex].value! : chartsData[_chartsIndex].value!, yAxisTitle: _historyShortTitles[_chartsIndex], bigScreen: bigScreen, leftSpace: bigScreen? 80 : 45, yFormat: bigScreen? f2 : NumberFormat.compact(), xFormat: NumberFormat.compact()) + else if (chartsData[_chartsIndex].value!.length <= 1) Center(child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(t.notEnoughData, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28)), + if (wasActiveInTL) Text(t.errors.actionSuggestion), + if (wasActiveInTL) TextButton(onPressed: (){changePlayer(userID, fetchHistory: true);}, child: Text(t.fetchAndsaveTLHistory)) + ], + )) + ], + ), + ), + ); } } @@ -637,11 +839,12 @@ class _HistoryChartThigy extends StatefulWidget{ final bool bigScreen; final double leftSpace; final NumberFormat yFormat; + final NumberFormat? xFormat; /// Implements graph for the _History widget. Requires [data] which is a list of dots for the graph. [yAxisTitle] used to keep track of changes. /// [bigScreen] tells if screen wide enough, [leftSpace] sets size, reserved for titles on the left from the graph and [yFormat] sets number format /// for left titles - const _HistoryChartThigy({required this.data, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat}); + const _HistoryChartThigy({required this.data, required this.yAxisTitle, required this.bigScreen, required this.leftSpace, required this.yFormat, this.xFormat}); @override State<_HistoryChartThigy> createState() => _HistoryChartThigyState(); @@ -649,6 +852,7 @@ class _HistoryChartThigy extends StatefulWidget{ class _HistoryChartThigyState extends State<_HistoryChartThigy> { late String previousAxisTitle; + late bool previousGamesPlayedInsteadOfDateAndTime; late double minX; late double maxX; late double minY; @@ -670,6 +874,7 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { maxX = widget.data.last.x; setMinMaxY(); previousAxisTitle = widget.yAxisTitle; + previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime; actualMaxY = maxY; actualMinY = minY; recalculateScales(); @@ -775,15 +980,19 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { @override Widget build(BuildContext context) { GlobalKey graphKey = GlobalKey(); - double xInterval = widget.bigScreen ? max(1, xScale / 6) : max(1, xScale / 3); // how far away xTitles should be between each other + if ((previousAxisTitle != widget.yAxisTitle) || (previousGamesPlayedInsteadOfDateAndTime != _gamesPlayedInsteadOfDateAndTime)) { + minX = widget.data.first.x; + maxX = widget.data.last.x; + recalculateScales(); + setMinMaxY(); + previousAxisTitle = widget.yAxisTitle; + previousGamesPlayedInsteadOfDateAndTime = _gamesPlayedInsteadOfDateAndTime; + setState((){}); + } + double xInterval = widget.bigScreen ? max(1, xScale / 8) : max(1, xScale / 4); // how far away xTitles should be between each other EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48); double graphStartX = padding.left+widget.leftSpace; double graphEndX = MediaQuery.sizeOf(context).width - padding.right; - if (previousAxisTitle != widget.yAxisTitle) { - setMinMaxY(); - recalculateScales(); - previousAxisTitle = widget.yAxisTitle; - } return SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height - 104, @@ -855,7 +1064,7 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){ return value != meta.min && value != meta.max ? SideTitleWidget( axisSide: meta.axisSide, - child: Text(DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))), + child: Text(widget.xFormat != null && _gamesPlayedInsteadOfDateAndTime ? widget.xFormat!.format(value.round()) : DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).format(DateTime.fromMillisecondsSinceEpoch(value.floor()))), ) : Container(); })), leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: widget.leftSpace, getTitlesWidget: (double value, TitleMeta meta){ @@ -877,8 +1086,8 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { hoveredPointId = -1; // not hovering over any point } else { hoveredPointId = touchResponse!.lineBarSpots!.first.spotIndex; - headerTooltip = "${_f4.format(touchResponse.lineBarSpots!.first.y)} ${widget.yAxisTitle}"; - footerTooltip = _dateFormat.format(DateTime.fromMillisecondsSinceEpoch(touchResponse.lineBarSpots!.first.x.floor())); + headerTooltip = "${f4.format(touchResponse.lineBarSpots!.first.y)} ${widget.yAxisTitle}"; + footerTooltip = _gamesPlayedInsteadOfDateAndTime ? "${f0.format(touchResponse.lineBarSpots!.first.x)} games played" : _dateFormat.format(DateTime.fromMillisecondsSinceEpoch(touchResponse.lineBarSpots!.first.x.floor())); } }); } @@ -907,177 +1116,303 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> { } } +// class _HistoryTableThingy extends StatelessWidget{ +// final List tableData; + +// const _HistoryTableThingy(this.tableData); +// // :tf: +// @override +// Widget build(BuildContext context) { +// return LayoutBuilder(builder: (context, constraints){ +// return Table( +// defaultColumnWidth: FixedColumnWidth(75), +// columnWidths: { +// 0: FixedColumnWidth(170), +// 1: FixedColumnWidth(100), +// 2: FixedColumnWidth(90), +// 18: FixedColumnWidth(100), +// 19: FixedColumnWidth(90), +// }, +// children: tableData, +// ); +// }); +// } +// } + +class _TwoRecordsThingy extends StatelessWidget { + final RecordSingle? sprint; + final RecordSingle? blitz; + final String? rank; + + const _TwoRecordsThingy({required this.sprint, required this.blitz, this.rank}); + + 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; + } + + @override + Widget build(BuildContext context) { + //if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); + late MapEntry closestAverageBlitz; + late bool blitzBetterThanClosestAverage; + bool? blitzBetterThanRankAverage = (rank != null && rank != "z" && blitz != null) ? blitz!.endContext!.score > blitzAverages[rank]! : null; + late MapEntry closestAverageSprint; + late bool sprintBetterThanClosestAverage; + bool? sprintBetterThanRankAverage = (rank != null && rank != "z" && sprint != null) ? sprint!.endContext!.finalTime < sprintAverages[rank]! : null; + if (sprint != null) { + closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-sprint!.endContext!.finalTime).abs() < (b -sprint!.endContext!.finalTime).abs() ? a : b)); + sprintBetterThanClosestAverage = sprint!.endContext!.finalTime < closestAverageSprint.value; + } + if (blitz != null){ + closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-blitz!.endContext!.score).abs() < (b -blitz!.endContext!.score).abs() ? a : b)); + blitzBetterThanClosestAverage = blitz!.endContext!.score > closestAverageBlitz.value; + } + return SingleChildScrollView(child: Padding( + padding: const EdgeInsets.only(top: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding(padding: const EdgeInsets.only(right: 8.0), + child: sprint != null ? Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96) : Image.asset("res/tetrio_tl_alpha_ranks/z.png", height: 96) + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), + RichText(text: TextSpan( + text: sprint != null ? get40lTime(sprint!.endContext!.finalTime.inMicroseconds) : "---", + style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: sprint != null ? Colors.white : Colors.grey), + //children: [TextSpan(text: get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100))] + ), + ), + if (sprint != null) RichText(text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), + children: [ + if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.endContext!.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( + color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent + )) + else TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(sprint!.endContext!.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle( + color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent + )), + if (sprint!.rank != null) TextSpan(text: "№${sprint!.rank}", style: TextStyle(color: getColorOfRank(sprint!.rank!))), + if (sprint!.rank != null) const TextSpan(text: " • "), + TextSpan(text: _dateFormat.format(sprint!.timestamp!)), + ] + ), + ), + ],), + ], + ), + if (sprint != null) Wrap( + //mainAxisSize: MainAxisSize.max, + alignment: WrapAlignment.spaceBetween, + spacing: 20, + children: [ + StatCellNum(playerStat: sprint!.endContext!.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: true, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: sprint!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: sprint!.endContext!.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false), + ], + ), + if (sprint != null) FinesseThingy(sprint?.endContext?.finesse, sprint?.endContext?.finessePercentage), + if (sprint != null) LineclearsThingy(sprint!.endContext!.clears, sprint!.endContext!.lines, sprint!.endContext!.holds, sprint!.endContext!.tSpins), + if (sprint != null) Text("${sprint!.endContext!.inputs} KP • ${f2.format(sprint!.endContext!.kps)} KpS") + ] + ), + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(t.blitz, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), + RichText( + text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white), + children: [ + TextSpan(text: blitz != null ? NumberFormat.decimalPattern().format(blitz!.endContext!.score) : "---"), + //WidgetSpan(child: Image.asset("res/icons/kagari.png", height: 48)) + ] + ), + ), + if (blitz != null) RichText( + textAlign: TextAlign.end, + text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), + children: [ + if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.endContext!.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( + color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent + )) + else TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(blitz!.endContext!.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle( + color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent + )), + TextSpan(text: _dateFormat.format(blitz!.timestamp!)), + if (blitz!.rank != null) const TextSpan(text: " • "), + if (blitz!.rank != null) TextSpan(text: "№${blitz!.rank}", style: TextStyle(color: getColorOfRank(blitz!.rank!))), + ] + ), + ), + ],), + Padding(padding: const EdgeInsets.only(left: 8.0), + child: blitz != null ? Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96) : Image.asset("res/tetrio_tl_alpha_ranks/z.png", height: 96)), + ], + ), + if (blitz != null) Wrap( + //mainAxisSize: MainAxisSize.max, + alignment: WrapAlignment.spaceBetween, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 20, + children: [ + StatCellNum(playerStat: blitz!.endContext!.level, playerStatLabel: t.statCellNum.level, isScreenBig: true, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: blitz!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: true, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: blitz!.endContext!.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: true, higherIsBetter: true) + ], + ), + if (blitz != null) FinesseThingy(blitz?.endContext?.finesse, blitz?.endContext?.finessePercentage), + if (blitz != null) LineclearsThingy(blitz!.endContext!.clears, blitz!.endContext!.lines, blitz!.endContext!.holds, blitz!.endContext!.tSpins), + if (blitz != null) Text("${blitz!.endContext!.piecesPlaced} P • ${blitz!.endContext!.inputs} KP • ${f2.format(blitz!.endContext!.kpp)} KpP • ${f2.format(blitz!.endContext!.kps)} KpS") + ], + ), + ]), + )); + } +} + class _RecordThingy extends StatelessWidget { final RecordSingle? record; + final String? rank; /// Widget that displays data from [record] - const _RecordThingy({required this.record}); + const _RecordThingy({required this.record, this.rank}); + + 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; + } @override Widget build(BuildContext context) { if (record == null) return Center(child: Text(t.noRecord, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28))); - return LayoutBuilder(builder: (context, constraints) { - bool bigScreen = constraints.maxWidth > 768; - return ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - itemCount: 1, - itemBuilder: (BuildContext context, int index) { - return Column( + late MapEntry closestAverageBlitz; + late bool blitzBetterThanClosestAverage; + bool? blitzBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext!.score > blitzAverages[rank]! : null; + late MapEntry closestAverageSprint; + late bool sprintBetterThanClosestAverage; + bool? sprintBetterThanRankAverage = (rank != null && rank != "z") ? record!.endContext!.finalTime < sprintAverages[rank]! : null; + if (record!.stream.contains("40l")) { + closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-record!.endContext!.finalTime).abs() < (b -record!.endContext!.finalTime).abs() ? a : b)); + sprintBetterThanClosestAverage = record!.endContext!.finalTime < closestAverageSprint.value; + }else if (record!.stream.contains("blitz")){ + closestAverageBlitz = blitzAverages.entries.singleWhere((element) => element.value == blitzAverages.values.reduce((a, b) => (a-record!.endContext!.score).abs() < (b -record!.endContext!.score).abs() ? a : b)); + blitzBetterThanClosestAverage = record!.endContext!.score > closestAverageBlitz.value; + } + + return LayoutBuilder( + builder: (context, constraints) { + bool bigScreen = constraints.maxWidth > 768; + return SingleChildScrollView( + controller: _scrollController, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, children: [ - // show mode title - if (record!.stream.contains("40l")) Text(t.sprint, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) - else if (record!.stream.contains("blitz")) Text(t.blitz, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - - // show main metric - if (record!.stream.contains("40l")) Text(get40lTime(record!.endContext!.finalTime.inMicroseconds), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)) - else if (record!.stream.contains("blitz")) Text(NumberFormat.decimalPattern().format(record!.endContext!.score), style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - - // Show rank if presented - if (record!.rank != null) StatCellNum(playerStat: record!.rank!, playerStatLabel: "Leaderboard Placement", isScreenBig: bigScreen, higherIsBetter: false), - - // Show when this record was obtained - Text(t.obtainDate(date: _dateFormat.format(record!.timestamp!)), textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 16)), - - // Show metrics - Padding(padding: const EdgeInsets.fromLTRB(0, 48, 0, 48), - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.spaceAround, - crossAxisAlignment: WrapCrossAlignment.start, - clipBehavior: Clip.hardEdge, - spacing: 25, - children: [ - if (record!.stream.contains("blitz")) StatCellNum(playerStat: record!.endContext!.level, playerStatLabel: t.statCellNum.level, isScreenBig: bigScreen, higherIsBetter: true), - if (record!.stream.contains("blitz")) StatCellNum(playerStat: record!.endContext!.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true), - StatCellNum(playerStat: record!.endContext!.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: bigScreen, higherIsBetter: true), - StatCellNum(playerStat: record!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true), - if (record!.endContext!.finesse != null) StatCellNum(playerStat: record!.endContext!.finesse!.faults, playerStatLabel: t.statCellNum.finesseFaults, isScreenBig: bigScreen, higherIsBetter: false), - if (record!.endContext!.finesse != null) StatCellNum(playerStat: record!.endContext!.finessePercentage * 100, playerStatLabel: t.statCellNum.finessePercentage, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true), - StatCellNum(playerStat: record!.endContext!.inputs, playerStatLabel: t.statCellNum.keys, isScreenBig: bigScreen, higherIsBetter: false), - StatCellNum(playerStat: record!.endContext!.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: false), - StatCellNum(playerStat: record!.endContext!.kps, playerStatLabel: t.statCellNum.kps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true,), - ], - ), - ), - - // List of actions - Padding(padding: const EdgeInsets.fromLTRB(0, 16, 0, 48), - child: SizedBox(width: bigScreen ? MediaQuery.of(context).size.width * 0.4 : MediaQuery.of(context).size.width * 0.85, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("${t.numOfGameActions.pc}:", style: const TextStyle(fontSize: 24)), - Text(record!.endContext!.clears.allClears.toString(), style: const TextStyle(fontSize: 24)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("${t.numOfGameActions.hold}:", style: const TextStyle(fontSize: 24)), - Text(record!.endContext!.holds.toString(), style: const TextStyle(fontSize: 24)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("${t.numOfGameActions.tspinsTotal}:", style: const TextStyle(fontSize: 24)), - Text(record!.endContext!.tSpins.toString(), style: const TextStyle(fontSize: 24)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin zero:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinZeros.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin singles:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinSingles.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin doubles:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinDoubles.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin triples:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinTriples.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin mini zero:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinMiniZeros.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin mini singles:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinMiniSingles.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - T-spin mini doubles:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.tSpinMiniDoubles.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("${t.numOfGameActions.lineClears}:", style: const TextStyle(fontSize: 24)), - Text(record!.endContext!.lines.toString(), style: const TextStyle(fontSize: 24)), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - const Text(" - Singles:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.singles.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - Doubles:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.doubles.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - Triples:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.triples.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text(" - Quads:", style: TextStyle(fontSize: 18)), - Text(record!.endContext!.clears.quads.toString(), style: const TextStyle(fontSize: 18)), - ], - ), - ], + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (record!.stream.contains("40l")) Padding(padding: const EdgeInsets.only(right: 8.0), + child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageSprint.key}.png", height: 96) ), - ), + if (record!.stream.contains("blitz")) Padding(padding: const EdgeInsets.only(right: 8.0), + child: Image.asset("res/tetrio_tl_alpha_ranks/${closestAverageBlitz.key}.png", height: 96) + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (record!.stream.contains("40l")) Text(t.sprint, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), + if (record!.stream.contains("blitz")) Text(t.blitz, style: const TextStyle(height: 0.1, fontFamily: "Eurostile Round Extended", fontSize: 18)), + RichText(text: TextSpan( + text: record!.stream.contains("40l") ? get40lTime(record!.endContext!.finalTime.inMicroseconds) : NumberFormat.decimalPattern().format(record!.endContext!.score), + style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 36 : 32, fontWeight: FontWeight.w500, color: Colors.white), + ), + ), + RichText(text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), + children: [ + if (record!.stream.contains("40l") && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.endContext!.finalTime, sprintAverages[rank]!), verdict: sprintBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( + color: sprintBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent + )) + else if (record!.stream.contains("40l") && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableTimeDifference(record!.endContext!.finalTime, closestAverageSprint.value), verdict: sprintBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageSprint.key.toUpperCase())}\n", style: TextStyle( + color: sprintBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent + )) + else if (record!.stream.contains("blitz") && (rank != null && rank != "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.endContext!.score, blitzAverages[rank]!), verdict: blitzBetterThanRankAverage??false ? t.verdictBetter : t.verdictWorse, rank: rank!.toUpperCase())}\n", style: TextStyle( + color: blitzBetterThanRankAverage??false ? Colors.greenAccent : Colors.redAccent + )) + else if (record!.stream.contains("blitz") && (rank == null || rank == "z")) TextSpan(text: "${t.verdictGeneral(n: readableIntDifference(record!.endContext!.score, closestAverageBlitz.value), verdict: blitzBetterThanClosestAverage ? t.verdictBetter : t.verdictWorse, rank: closestAverageBlitz.key.toUpperCase())}\n", style: TextStyle( + color: blitzBetterThanClosestAverage ? Colors.greenAccent : Colors.redAccent + )), + if (record!.rank != null) TextSpan(text: "№${record!.rank}", style: TextStyle(color: getColorOfRank(record!.rank!))), + if (record!.rank != null) const TextSpan(text: " • "), + TextSpan(text: _dateFormat.format(record!.timestamp!)), + ] + ), + ) + ],), + ], ), + if (record!.stream.contains("40l")) Wrap( + alignment: WrapAlignment.spaceBetween, + spacing: 20, + children: [ + StatCellNum(playerStat: record!.endContext!.piecesPlaced, playerStatLabel: t.statCellNum.pieces, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: record!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: record!.endContext!.kpp, playerStatLabel: t.statCellNum.kpp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false), + ], + ), + if (record!.stream.contains("blitz")) Wrap( + alignment: WrapAlignment.spaceBetween, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 20, + children: [ + StatCellNum(playerStat: record!.endContext!.level, playerStatLabel: t.statCellNum.level, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: record!.endContext!.pps, playerStatLabel: t.statCellNum.pps, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true, smallDecimal: false), + StatCellNum(playerStat: record!.endContext!.spp, playerStatLabel: t.statCellNum.spp, fractionDigits: 2, isScreenBig: bigScreen, higherIsBetter: true) + ], + ), + FinesseThingy(record?.endContext?.finesse, record?.endContext?.finessePercentage), + LineclearsThingy(record!.endContext!.clears, record!.endContext!.lines, record!.endContext!.holds, record!.endContext!.tSpins), + if (record!.stream.contains("40l")) Text("${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kps)} KpS"), + if (record!.stream.contains("blitz")) Text("${record!.endContext!.piecesPlaced} P • ${record!.endContext!.inputs} KP • ${f2.format(record!.endContext!.kpp)} KpP • ${f2.format(record!.endContext!.kps)} KpS") ] - ); - }); - }); + ), + ), + ); + } + ); } } @@ -1275,60 +1610,80 @@ class _OtherThingy extends StatelessWidget { } } + Widget getShit(BuildContext context, bool bigScreen, bool showNewsTitle){ + return Column( + children: [ + if (distinguishment != null) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 48), + child: Column( + children: [ + Text(t.distinguishment, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28), textAlign: TextAlign.center), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + children: getDistinguishmentTitle(distinguishment?.header), + ), + ), + Text(getDistinguishmentSubtitle(distinguishment?.footer), style: const TextStyle(fontSize: 18), textAlign: TextAlign.center), + ], + ), + ), + if (bio != null) + Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 48), + child: Column( + children: [ + Text(t.bio, style: TextStyle(fontFamily: "Eurostile Round Extended",fontSize: bigScreen ? 42 : 28)), + MarkdownBody(data: bio!, styleSheet: MarkdownStyleSheet(textScaleFactor: 1.5, textAlign: WrapAlignment.center)) // Text(bio!, style: const TextStyle(fontSize: 18)), + ], + ), + ), + if (zen != null) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 48), + child: Column( + children: [ + Text(t.zen, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + Text("${t.statCellNum.level} ${NumberFormat.decimalPattern().format(zen!.level)}", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), + Text("${t.statCellNum.score} ${NumberFormat.decimalPattern().format(zen!.score)}", style: const TextStyle(fontSize: 18)), + ], + ), + ), + if (newsletter != null && newsletter!.isNotEmpty && showNewsTitle) + Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + ], + ); + } + @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { bool bigScreen = constraints.maxWidth > 768; - return ListView.builder( + if (constraints.maxWidth >= 1024){ + return Row( + children: [ + SizedBox(width: 450, child: getShit(context, true, false)), + SizedBox(width: constraints.maxWidth - 450, child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + itemCount: newsletter!.length+1, + itemBuilder: (BuildContext context, int index) { + return index == 0 ? Center(child: Text(t.news, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42))) : getNewsTile(newsletter![index-1]); + } + )) + ] + ); + } + else { + return ListView.builder( physics: const AlwaysScrollableScrollPhysics(), itemCount: newsletter!.length+1, itemBuilder: (BuildContext context, int index) { - return index == 0 ? Column( - children: [ - if (distinguishment != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 48), - child: Column( - children: [ - Text(t.distinguishment, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28), textAlign: TextAlign.center), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: getDistinguishmentTitle(distinguishment?.header), - ), - ), - Text(getDistinguishmentSubtitle(distinguishment?.footer), style: const TextStyle(fontSize: 18), textAlign: TextAlign.center), - ], - ), - ), - if (bio != null) - Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 48), - child: Column( - children: [ - Text(t.bio, style: TextStyle(fontFamily: "Eurostile Round Extended",fontSize: bigScreen ? 42 : 28)), - MarkdownBody(data: bio!, styleSheet: MarkdownStyleSheet(textScaleFactor: 1.5, textAlign: WrapAlignment.center)) // Text(bio!, style: const TextStyle(fontSize: 18)), - ], - ), - ), - if (zen != null) - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 48), - child: Column( - children: [ - Text(t.zen, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - Text("${t.statCellNum.level} ${NumberFormat.decimalPattern().format(zen!.level)}", style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), - Text("${t.statCellNum.score} ${NumberFormat.decimalPattern().format(zen!.score)}", style: const TextStyle(fontSize: 18)), - ], - ), - ), - if (newsletter != null && newsletter!.isNotEmpty) - Text(t.news, style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ], - ) : getNewsTile(newsletter![index-1]); + return index == 0 ? getShit(context, bigScreen, true) : getNewsTile(newsletter![index-1]); }, ); + } }); } } diff --git a/lib/views/mathes_view.dart b/lib/views/mathes_view.dart index 7656d40..0a1c3f5 100644 --- a/lib/views/mathes_view.dart +++ b/lib/views/mathes_view.dart @@ -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 { diff --git a/lib/views/rank_averages_view.dart b/lib/views/rank_averages_view.dart index 96ffe5c..19651d5 100644 --- a/lib/views/rank_averages_view.dart +++ b/lib/views/rank_averages_view.dart @@ -16,6 +16,7 @@ Stats _chartsX = Stats.tr; Stats _chartsY = Stats.apm; List _itemStats = [for (MapEntry e in chartsShortTitles.entries) DropdownMenuItem(value: e.key, child: Text(e.value))]; Stats _sortBy = Stats.tr; +late List they; bool _reversed = false; List _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 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 with SingleTickerProviderStateMixin { } else { return element; } - }).getStatByEnum(_chartsX) as double; + }).getStatByEnum(_chartsX).toDouble(); actualMaxX = (widget.rank[1]["entries"] as List).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 with SingleTickerProviderStateMixin { } else { return element; } - }).getStatByEnum(_chartsX) as double; + }).getStatByEnum(_chartsX).toDouble(); actualMinY = (widget.rank[1]["entries"] as List).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 with SingleTickerProviderStateMixin { } else { return element; } - }).getStatByEnum(_chartsY) as double; + }).getStatByEnum(_chartsY).toDouble(); actualMaxY = (widget.rank[1]["entries"] as List).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 with SingleTickerProviderStateMixin { } else { return element; } - }).getStatByEnum(_chartsY) as double; + }).getStatByEnum(_chartsY).toDouble(); } void resetScale(){ @@ -164,7 +166,7 @@ class RankState extends State with SingleTickerProviderStateMixin { previousAxisTitles = _chartsX.toString()+_chartsY.toString(); } final t = Translations.of(context); - List 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 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 with SingleTickerProviderStateMixin { value: _sortBy, onChanged: ((value) { _sortBy = value; - setState(() {}); + setState(() { + they = TetrioPlayersLeaderboard("lol", []).getStatRanking(widget.rank[1]["entries"]!, _sortBy, reversed: _reversed, country: _country); + }); }), ), ], diff --git a/lib/views/ranks_averages_view.dart b/lib/views/ranks_averages_view.dart index d928728..b82d8f3 100644 --- a/lib/views/ranks_averages_view.dart +++ b/lib/views/ranks_averages_view.dart @@ -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 { @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 { 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) { diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 6edb8b9..979293e 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -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 { 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 { Future _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 { }, ), ), - 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 { }, 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) ), ], )), diff --git a/lib/views/tl_leaderboard_view.dart b/lib/views/tl_leaderboard_view.dart index 1f21484..7fb2422 100644 --- a/lib/views/tl_leaderboard_view.dart +++ b/lib/views/tl_leaderboard_view.dart @@ -71,7 +71,7 @@ class TLLeaderboardState extends State { 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 { 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: [ diff --git a/lib/views/tl_match_view.dart b/lib/views/tl_match_view.dart index 219b725..202bfb0 100644 --- a/lib/views/tl_match_view.dart +++ b/lib/views/tl_match_view.dart @@ -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 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 { - late ScrollController _scrollController; late Future 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,10 +62,651 @@ class TlMatchResultState extends State { super.dispose(); } + 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{ + 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 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 NestedScrollView( + headerSliverBuilder: (context, value) { + return [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: const [Colors.green, Colors.transparent], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0.0, widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).success ? 0.4 : 0.0], + )), + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), + child: Column(children: [ + Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username, style: bigScreen ? const TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: 28) : const TextStyle()), + Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).points.toString(), style: const TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: 42)) + ]), + ), + ), + ), + const Padding( + padding: EdgeInsets.only(top: 16), + child: Text("VS"), + ), + Expanded( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: const [Colors.red, Colors.transparent], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + stops: [0.0, widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).success ? 0.4 : 0.0], + )), + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), + child: Column(children: [ + Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username, style: bigScreen ? const TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: 28) : const TextStyle()), + Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).points.toString(), style: const TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: 42)) + ]), + ), + ), + ), + ], + ), + ), + ), + if (showMobileSelector) SliverToBoxAdapter( + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text("${t.statsFor}: ", + style: const TextStyle(color: Colors.white, fontSize: 25)), + DropdownButton(items: rounds, value: roundSelector, onChanged: ((value) { + roundSelector = value; + setState(() {}); + }),), + ], + ), + ), + ), + if (widget.record.ownId == widget.record.replayId && showMobileSelector) SliverToBoxAdapter( + child: Center(child: Text(t.p1nkl0bst3rAlert, textAlign: TextAlign.center)), + ), + if (showMobileSelector) SliverToBoxAdapter(child: Center(child: Text(snapshot.hasError ? reason : readableTime, textAlign: TextAlign.center))), + const SliverToBoxAdapter( + child: Divider(), + ) + ]; + }, + body: ListView( + children: [ + Column( + children: [ + CompareThingy( + label: "APM", + 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 == -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 == -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, + ), + 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 == -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 == -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, + label: "Lines Cleared", higherIsBetter: true), + 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 == -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, + label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topSpike : snapshot.data!.stats[roundSelector][greenSidePlayer].topSpike, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topSpike : snapshot.data!.stats[roundSelector][redSidePlayer].topSpike, + label: "Best Spike", higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topCombo : snapshot.data!.stats[roundSelector][greenSidePlayer].topCombo, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topCombo : snapshot.data!.stats[roundSelector][redSidePlayer].topCombo, + label: "Best Combo", higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topBtB : snapshot.data!.stats[roundSelector][greenSidePlayer].topBtB, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topBtB : snapshot.data!.stats[roundSelector][redSidePlayer].topBtB, + label: "Best BtB", higherIsBetter: true), + const Divider(), + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text("Garbage", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + ), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.sent : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.sent, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.sent : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.sent, + label: "Sent", higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.recived, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.recived, + label: "Recived", higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.attack, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.attack, + label: "Attack", higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.cleared : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.cleared, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.cleared : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.cleared, + label: "Cleared", higherIsBetter: true), + const Divider(), + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text("Line Clears", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), + ), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].clears.allClears : snapshot.data!.stats[roundSelector][greenSidePlayer].clears.allClears, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].clears.allClears : snapshot.data!.stats[roundSelector][redSidePlayer].clears.allClears, + label: "PC", higherIsBetter: true), + CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].tspins : snapshot.data!.stats[roundSelector][greenSidePlayer].tspins, + redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].tspins : snapshot.data!.stats[roundSelector][redSidePlayer].tspins, + label: "T-spins", higherIsBetter: true), + 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), + ],), + ], + ), + const Divider(), + Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text(t.nerdStats, + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ), + CompareThingy( + label: "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 == -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 == -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 == -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 == -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 == -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: false, + ), + CompareThingy( + label: "Gb Eff.", + 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 == -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 == -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 == -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 == -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 == -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 == -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 == -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 == -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] + ) + ], + ), + if (widget.record.ownId != widget.record.replayId) const Divider(), + if (widget.record.ownId != widget.record.replayId) Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text("Handling", + style: TextStyle( + fontFamily: "Eurostile Round Extended", + fontSize: bigScreen ? 42 : 28)), + ), + CompareThingy( + greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.das, + redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.das, + label: "DAS", fractionDigits: 1, postfix: "F", + higherIsBetter: false), + CompareThingy( + greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.arr, + redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.arr, + label: "ARR", fractionDigits: 1, postfix: "F", + higherIsBetter: false), + CompareThingy( + greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.sdf, + redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.sdf, + label: "SDF", prefix: "x", + higherIsBetter: true), + CompareBoolThingy( + greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.safeLock, + redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.safeLock, + label: "Safe HD", + trueIsBetter: true) + ], + ) + ], + ) + ); + }); + } + + 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: [ + 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); - 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)}"), @@ -82,28 +726,7 @@ class TlMatchResultState extends State { onSelected: (value) async { switch (value) { case 1: - if (kIsWeb){ - // final _base64 = base64Encode([1,2,3,4,5]); - // final anchor = AnchorElement(href: 'data:application/octet-stream;base64,$_base64')..target = 'blank'; - //final anchor = AnchorElement(href: 'https://inoue.szy.lol/api/replay/${widget.record.replayId}')..target = 'blank'; - //anchor.download = "${widget.record.replayId}.ttrm"; - //document.body!.append(anchor); - //anchor.click(); - //anchor.remove(); - } else{ - 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))); - } - } + 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}")); @@ -114,401 +737,7 @@ class TlMatchResultState extends State { ] ), backgroundColor: Colors.black, - body: SafeArea( - child: NestedScrollView( - controller: _scrollController, - headerSliverBuilder: (context, value) { - return [ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: const [Colors.green, Colors.transparent], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).success ? 0.4 : 0.0], - )), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: Column(children: [ - Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username, style: bigScreen ? const TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: 28) : const TextStyle()), - Text(widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).points.toString(), style: const TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: 42)) - ]), - ), - ), - ), - const Padding( - padding: EdgeInsets.only(top: 16), - child: Text("VS"), - ), - Expanded( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: const [Colors.red, Colors.transparent], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - stops: [0.0, widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).success ? 0.4 : 0.0], - )), - child: Padding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - child: Column(children: [ - Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username, style: bigScreen ? const TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: 28) : const TextStyle()), - Text(widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).points.toString(), style: const TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: 42)) - ]), - ), - ), - ), - ], - ), - ), - ), - SliverToBoxAdapter( - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Text("${t.statsFor}: ", - style: const TextStyle(color: Colors.white, fontSize: 25)), - DropdownButton(items: rounds, value: roundSelector, onChanged: ((value) { - roundSelector = value; - setState(() {}); - }),), - ], - ), - ), - ), - if (widget.record.ownId == widget.record.replayId) 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); - } - - } - },),), - const SliverToBoxAdapter( - child: Divider(), - ) - ]; - }, - body: ListView( - children: [ - Column( - 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], - 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], - 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], - fractionDigits: 2, - higherIsBetter: true, - ), - FutureBuilder(future: replayData, builder: (BuildContext context, AsyncSnapshot 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: [ - 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, - 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, - 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, - label: "Lines Cleared", higherIsBetter: true), - 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, - 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, - label: "Finnese", postfix: "%", fractionDigits: 2, higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topSpike : snapshot.data!.stats[roundSelector][greenSidePlayer].topSpike, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topSpike : snapshot.data!.stats[roundSelector][redSidePlayer].topSpike, - label: "Best Spike", higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topCombo : snapshot.data!.stats[roundSelector][greenSidePlayer].topCombo, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topCombo : snapshot.data!.stats[roundSelector][redSidePlayer].topCombo, - label: "Best Combo", higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].topBtB : snapshot.data!.stats[roundSelector][greenSidePlayer].topBtB, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].topBtB : snapshot.data!.stats[roundSelector][redSidePlayer].topBtB, - label: "Best BtB", higherIsBetter: true), - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text("Garbage", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.sent : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.sent, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.sent : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.sent, - label: "Sent", higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.recived, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.recived : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.recived, - label: "Recived", higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.attack, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.attack : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.attack, - label: "Attack", higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].garbage.cleared : snapshot.data!.stats[roundSelector][greenSidePlayer].garbage.cleared, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].garbage.cleared : snapshot.data!.stats[roundSelector][redSidePlayer].garbage.cleared, - label: "Cleared", higherIsBetter: true), - const Divider(), - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text("Line Clears", style: TextStyle(fontFamily: "Eurostile Round Extended", fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].clears.allClears : snapshot.data!.stats[roundSelector][greenSidePlayer].clears.allClears, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].clears.allClears : snapshot.data!.stats[roundSelector][redSidePlayer].clears.allClears, - label: "PC", higherIsBetter: true), - CompareThingy(greenSide: roundSelector.isNegative ? snapshot.data!.totalStats[greenSidePlayer].tspins : snapshot.data!.stats[roundSelector][greenSidePlayer].tspins, - redSide: roundSelector.isNegative ? snapshot.data!.totalStats[redSidePlayer].tspins : snapshot.data!.stats[roundSelector][redSidePlayer].tspins, - label: "T-spins", higherIsBetter: true), - 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(), - Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text(t.nerdStats, - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - 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, - 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, - 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, - 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, - 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, - 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, - fractionDigits: 2, - higherIsBetter: true, - ), - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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] - ) - ], - ), - if (widget.record.ownId != widget.record.replayId) const Divider(), - if (widget.record.ownId != widget.record.replayId) Column( - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text("Handling", - style: TextStyle( - fontFamily: "Eurostile Round Extended", - fontSize: bigScreen ? 42 : 28)), - ), - CompareThingy( - greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.das, - redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.das, - label: "DAS", fractionDigits: 1, postfix: "F", - higherIsBetter: false), - CompareThingy( - greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.arr, - redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.arr, - label: "ARR", fractionDigits: 1, postfix: "F", - higherIsBetter: false), - CompareThingy( - greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.sdf, - redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.sdf, - label: "SDF", prefix: "x", - higherIsBetter: true), - CompareBoolThingy( - greenSide: widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).handling.safeLock, - redSide: widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).handling.safeLock, - label: "Safe HD", - trueIsBetter: true) - ], - ) - ], - ) - ), - ), - ); + body: getMainWidget(MediaQuery.of(context).size.width), + ); } } diff --git a/lib/widgets/finesse_thingy.dart b/lib/widgets/finesse_thingy.dart new file mode 100644 index 0000000..e913572 --- /dev/null +++ b/lib/widgets/finesse_thingy.dart @@ -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() + )), + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/gauget_num.dart b/lib/widgets/gauget_num.dart new file mode 100644 index 0000000..a255908 --- /dev/null +++ b/lib/widgets/gauget_num.dart @@ -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 ranges; + final double minimum; + final double maximum; + final String playerStatLabel; + final String? okText; + final String? alertTitle; + final List? 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: [ + 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)], + )],), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/graphs.dart b/lib/widgets/graphs.dart index cadb397..caf65cb 100644 --- a/lib/widgets/graphs.dart +++ b/lib/widgets/graphs.dart @@ -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: ''); } diff --git a/lib/widgets/lineclears_thingy.dart b/lib/widgets/lineclears_thingy.dart new file mode 100644 index 0000000..2536891 --- /dev/null +++ b/lib/widgets/lineclears_thingy.dart @@ -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())]), + ], + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/list_tile_trailing_stats.dart b/lib/widgets/list_tile_trailing_stats.dart new file mode 100644 index 0000000..5690929 --- /dev/null +++ b/lib/widgets/list_tile_trailing_stats.dart @@ -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)]), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets/stat_sell_num.dart b/lib/widgets/stat_sell_num.dart index 32be43a..4a5ca7c 100644 --- a/lib/widgets/stat_sell_num.dart +++ b/lib/widgets/stat_sell_num.dart @@ -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? 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( - color: higherIsBetter ? - oldPlayerStat! > playerStat ? Colors.red : Colors.green : - oldPlayerStat! < playerStat ? Colors.red : Colors.green - ),), + if (oldPlayerStat != null || pos != null) RichText(text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), + children: [ + if (oldPlayerStat != null) TextSpan(text: comparef.format(playerStat - oldPlayerStat!), style: TextStyle( + color: higherIsBetter ? + oldPlayerStat! > playerStat ? Colors.redAccent : Colors.greenAccent : + oldPlayerStat! < playerStat ? Colors.redAccent : Colors.greenAccent + ),), + if (oldPlayerStat != null && pos != null) const TextSpan(text: " • "), + if (pos != null) TextSpan(text: pos!.position >= 1000 ? "${t.top} ${f2.format(pos!.percentage*100)}%" : "№${pos!.position}", style: TextStyle(color: getColorOfRank(pos!.position))) + ] + ), + ), alertWidgets == null ? Text( playerStatLabel, diff --git a/lib/widgets/tl_thingy.dart b/lib/widgets/tl_thingy.dart index 2207abe..b5bb240 100644 --- a/lib/widgets/tl_thingy.dart +++ b/lib/widgets/tl_thingy.dart @@ -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 createState() => _TLThingyState(); } class _TLThingyState extends State { + 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 { @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 { 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 { 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 { 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), ], ), ), @@ -176,236 +189,181 @@ class _TLThingyState extends State { Padding( padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.center, - spacing: 35, - crossAxisAlignment: WrapCrossAlignment.start, - clipBehavior: Clip.hardEdge, - children: [ - SizedBox( - width: 200, - height: 120, - child: SfRadialGauge( - title: GaugeTitle(text: t.statCellNum.app), - axes: [RadialAxis( - startAngle: 180, - endAngle: 360, - showLabels: false, - showTicks: false, - radiusFactor: 2.1, - centerY: 0.5, - minimum: 0, - maximum: 1, - ranges: [ - GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red), - GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow), - GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green), - GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue), - GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple), - ], - pointers: [ - NeedlePointer( - value: currentTl.nerdStats!.app, - enableAnimation: true, - needleLength: 0.9, - needleStartWidth: 2, - needleEndWidth: 15, - knobStyle: const KnobStyle(color: Colors.transparent), - gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),) - ], - annotations: [GaugeAnnotation( - widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.app), - style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)), - onPressed: (){ - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text(t.statCellNum.app, - style: const TextStyle( - fontFamily: "Eurostile Round Extended")), - content: SingleChildScrollView( - child: ListBody(children: [ - Text(t.statCellNum.appDescription), - Text("${t.exactValue}: ${currentTl.nerdStats!.app}") - ]), - ), - actions: [ - TextButton( - child: Text(t.popupActions.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - )); - },), verticalAlignment: GaugeAlignment.far, positionFactor: 0.05,), - if (oldTl != null && oldTl!.gamesPlayed > 0) GaugeAnnotation(widget: Text(fDiff.format(currentTl.nerdStats!.app - oldTl!.nerdStats!.app), style: TextStyle( - color: currentTl.nerdStats!.app - oldTl!.nerdStats!.app < 0 ? - Colors.red : - Colors.green - ),), positionFactor: 0.05,)], - )],), - ), - SizedBox( - width: 200, - height: 120, - child: SfRadialGauge( - title: const GaugeTitle(text: "VS / APM"), - axes: [RadialAxis( - startAngle: 180, - endAngle: 360, - showTicks: false, - showLabels: false, - radiusFactor: 2.1, - centerY: 0.5, - minimum: 1.8, - maximum: 2.4, - ranges: [ - GaugeRange(startValue: 1.8, endValue: 2.0, color: Colors.green), - GaugeRange(startValue: 2.0, endValue: 2.2, color: Colors.blue), - GaugeRange(startValue: 2.2, endValue: 2.4, color: Colors.purple), - ], - pointers: [ - NeedlePointer( - value: currentTl.nerdStats!.vsapm, - enableAnimation: true, - needleLength: 0.9, - needleStartWidth: 2, - needleEndWidth: 15, - knobStyle: const KnobStyle(color: Colors.transparent), - gradient: const LinearGradient(colors: [Colors.transparent, Colors.white], begin: Alignment.bottomCenter, end: Alignment.topCenter, stops: [0.5, 1]),) - ], - annotations: [GaugeAnnotation( - widget: TextButton(child: Text(f3.format(currentTl.nerdStats!.vsapm), - style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 36, color: Colors.white)), - onPressed: (){ - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: const Text("VS / APM", - style: TextStyle( - fontFamily: "Eurostile Round Extended")), - content: SingleChildScrollView( - child: ListBody(children: [ - Text(t.statCellNum.vsapmDescription), - Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}") - ]), - ), - actions: [ - 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( direction: Axis.horizontal, alignment: WrapAlignment.center, - spacing: 25, + spacing: 35, crossAxisAlignment: WrapCrossAlignment.start, clipBehavior: Clip.hardEdge, children: [ - StatCellNum(playerStat: currentTl.nerdStats!.dss, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dss, - alertWidgets: [Text(t.statCellNum.dssDescription), - Text("${t.formula}: (VS / 100) - (APM / 60)"), - Text("${t.exactValue}: ${currentTl.nerdStats!.dss}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.dss,), - StatCellNum(playerStat: currentTl.nerdStats!.dsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.dsp, - alertWidgets: [Text(t.statCellNum.dspDescription), - Text("${t.formula}: DS/S / PPS"), - Text("${t.exactValue}: ${currentTl.nerdStats!.dsp}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.dsp,), - StatCellNum(playerStat: currentTl.nerdStats!.appdsp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.appdsp, - alertWidgets: [Text(t.statCellNum.appdspDescription), - Text("${t.formula}: APP + DS/P"), - Text("${t.exactValue}: ${currentTl.nerdStats!.appdsp}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.appdsp,), - StatCellNum(playerStat: currentTl.nerdStats!.cheese, isScreenBig: bigScreen, fractionDigits: 2, playerStatLabel: t.statCellNum.cheese, - alertWidgets: [Text(t.statCellNum.cheeseDescription), - Text("${t.formula}: (DS/P * 150) + ((VS/APM - 2) * 50) + (0.6 - APP) * 125"), - Text("${t.exactValue}: ${currentTl.nerdStats!.cheese}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.cheese,), - StatCellNum(playerStat: currentTl.nerdStats!.gbe, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.gbe, - alertWidgets: [Text(t.statCellNum.gbeDescription), - Text("${t.formula}: APP * DS/P * 2"), - Text("${t.exactValue}: ${currentTl.nerdStats!.gbe}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.gbe,), - StatCellNum(playerStat: currentTl.nerdStats!.nyaapp, isScreenBig: bigScreen, fractionDigits: 3, playerStatLabel: t.statCellNum.nyaapp, - alertWidgets: [Text(t.statCellNum.nyaappDescription), - Text("${t.formula}: APP - 5 * tan(radians((Cheese Index / -30) + 1))"), - Text("${t.exactValue}: ${currentTl.nerdStats!.nyaapp}"),], - okText: t.popupActions.ok, - higherIsBetter: true, - oldPlayerStat: oldTl?.nerdStats?.nyaapp,), - StatCellNum(playerStat: currentTl.nerdStats!.area, isScreenBig: bigScreen, fractionDigits: 1, playerStatLabel: t.statCellNum.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,) - ]) + GaugetNum(playerStat: currentTl.nerdStats!.app, playerStatLabel: t.statCellNum.app, higherIsBetter: true, minimum: 0, maximum: 1, ranges: [ + GaugeRange(startValue: 0, endValue: 0.2, color: Colors.red), + GaugeRange(startValue: 0.2, endValue: 0.4, color: Colors.yellow), + GaugeRange(startValue: 0.4, endValue: 0.6, color: Colors.green), + GaugeRange(startValue: 0.6, endValue: 0.8, color: Colors.blue), + GaugeRange(startValue: 0.8, endValue: 1, color: Colors.purple), + ], alertWidgets: [ + Text(t.statCellNum.appDescription), + Text("${t.exactValue}: ${currentTl.nerdStats!.app}") + ], oldPlayerStat: oldTl?.nerdStats?.app, pos: widget.lbPositions?.app, + 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), + ], alertWidgets: [ + Text(t.statCellNum.vsapmDescription), + Text("${t.exactValue}: ${currentTl.nerdStats!.vsapm}") + ], oldPlayerStat: oldTl?.nerdStats?.vsapm, pos: widget.lbPositions?.vsapm, + averageStat: widget.averages?.nerdStats?.vsapm) + ]), + ), + Padding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.center, + spacing: 25, + crossAxisAlignment: WrapCrossAlignment.start, + 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}"),], + okText: t.popupActions.ok, + 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}"),], + okText: t.popupActions.ok, + 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}"),], + okText: t.popupActions.ok, + 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: 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}"),], + okText: t.popupActions.ok, + 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}"),], + okText: t.popupActions.ok, + 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, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + height: 70, + constraints: const BoxConstraints(maxWidth: 768), + child: Stack( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${bigScreen ? t.statCellNum.estOfTR : t.statCellNum.estOfTRShort}:", - style: const TextStyle(fontSize: 24), - ), - Text( - f2.format(currentTl.estTr!.esttr), - style: const TextStyle(fontSize: 24), - ), - ], - ), - if (currentTl.rating >= 0) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Positioned( + left: 0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "${bigScreen ? t.statCellNum.accOfEst : t.statCellNum.accOfEstShort}:", - style: const TextStyle(fontSize: 24), + 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))] + ), ), - Text( - fDiff.format(currentTl.esttracc!), - style: const TextStyle(fontSize: 24), + RichText(text: TextSpan( + text: "", + style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey, height: 0.5), + children: [ + if (oldTl?.estTr?.esttr != null) TextSpan(text: comparef.format(currentTl.estTr!.esttr - oldTl!.estTr!.esttr), style: TextStyle( + color: oldTl!.estTr!.esttr > currentTl.estTr!.esttr ? Colors.redAccent : Colors.greenAccent + ),), + if (oldTl?.estTr?.esttr != null && widget.lbPositions?.estTr != null) const TextSpan(text: " • "), + if (widget.lbPositions?.estTr != null) TextSpan(text: widget.lbPositions!.estTr!.position >= 1000 ? "${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)}") + ] ), - ], - ), + ), + ],), + ), + Positioned( + right: 0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + 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)) + ] + ), + ), + 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!) ] diff --git a/lib/widgets/user_thingy.dart b/lib/widgets/user_thingy.dart index 03708a4..a900051 100644 --- a/lib/widgets/user_thingy.dart +++ b/lib/widgets/user_thingy.dart @@ -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)}", - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: "Eurostile Round", - fontSize: 16, - )), + child: RichText( + textAlign: TextAlign.center, + 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, + // )), ) ], ), diff --git a/pubspec.lock b/pubspec.lock index 43f3c9c..ff532e5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml index a2fd891..acb5c88 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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' diff --git a/res/i18n/strings.i18n.json b/res/i18n/strings.i18n.json index 4145f76..ba3cb2b 100644 --- a/res/i18n/strings.i18n.json +++ b/res/i18n/strings.i18n.json @@ -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", diff --git a/res/i18n/strings_ru.i18n.json b/res/i18n/strings_ru.i18n.json index f8dba3b..88dcfad 100644 --- a/res/i18n/strings_ru.i18n.json +++ b/res/i18n/strings_ru.i18n.json @@ -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)": { "": "Не выбрана", diff --git a/res/tetrio_badges/mts_1.png b/res/tetrio_badges/mts_1.png new file mode 100644 index 0000000..0cf0b28 Binary files /dev/null and b/res/tetrio_badges/mts_1.png differ diff --git a/res/tetrio_badges/mts_2.png b/res/tetrio_badges/mts_2.png new file mode 100644 index 0000000..eb704f8 Binary files /dev/null and b/res/tetrio_badges/mts_2.png differ diff --git a/res/tetrio_badges/mts_3.png b/res/tetrio_badges/mts_3.png new file mode 100644 index 0000000..8f706f3 Binary files /dev/null and b/res/tetrio_badges/mts_3.png differ diff --git a/res/tetrio_badges/mts_participation.png b/res/tetrio_badges/mts_participation.png new file mode 100644 index 0000000..b48f401 Binary files /dev/null and b/res/tetrio_badges/mts_participation.png differ diff --git a/res/tetrio_badges/stride_1.png b/res/tetrio_badges/stride_1.png new file mode 100644 index 0000000..027ed12 Binary files /dev/null and b/res/tetrio_badges/stride_1.png differ diff --git a/res/tetrio_badges/stride_2.png b/res/tetrio_badges/stride_2.png new file mode 100644 index 0000000..04e692a Binary files /dev/null and b/res/tetrio_badges/stride_2.png differ diff --git a/res/tetrio_badges/stride_3.png b/res/tetrio_badges/stride_3.png new file mode 100644 index 0000000..a4a85f8 Binary files /dev/null and b/res/tetrio_badges/stride_3.png differ diff --git a/res/tetrio_badges/stride_participation.png b/res/tetrio_badges/stride_participation.png new file mode 100644 index 0000000..19c1160 Binary files /dev/null and b/res/tetrio_badges/stride_participation.png differ diff --git a/res/tetrio_badges/uoftflag_1.png b/res/tetrio_badges/uoftflag_1.png new file mode 100644 index 0000000..b6a51db Binary files /dev/null and b/res/tetrio_badges/uoftflag_1.png differ diff --git a/res/tetrio_badges/uoftflag_2.png b/res/tetrio_badges/uoftflag_2.png new file mode 100644 index 0000000..4113f0b Binary files /dev/null and b/res/tetrio_badges/uoftflag_2.png differ diff --git a/res/tetrio_badges/uoftflag_3.png b/res/tetrio_badges/uoftflag_3.png new file mode 100644 index 0000000..d26430c Binary files /dev/null and b/res/tetrio_badges/uoftflag_3.png differ diff --git a/test/api_test.dart b/test/api_test.dart index 3075be0..52f3e87 100644 --- a/test/api_test.dart +++ b/test/api_test.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/web/index.html b/web/index.html index 430a240..57042b3 100644 --- a/web/index.html +++ b/web/index.html @@ -18,7 +18,7 @@ - +