Compare commits

..

No commits in common. "ce2fb89ccf06361d26faf4b071439b0046d74fd8" and "e5ffa9711e80809f169f35854e84e42436abb2db" have entirely different histories.

6 changed files with 165 additions and 338 deletions

View File

@ -3,7 +3,6 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetra_stats.dart';
import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/gen/strings.g.dart';
import 'package:vector_math/vector_math.dart'; import 'package:vector_math/vector_math.dart';
@ -43,26 +42,25 @@ const Map<String, double> rankCutoffs = {
"z": -1, "z": -1,
"": 0.5 "": 0.5
}; };
const Map<String, double> rankTargets = { // const Map<String, double> rankTargets = {
"x+": 24000.00, // "x": 24503.75, // where that comes from?
"x": 22500.00, // "u": 23038,
"u": 20000.00, // "ss": 21583,
"ss": 18000.00, // "s+": 20128,
"s+": 16500.00, // "s": 18673,
"s": 15200.00, // "s-": 16975,
"s-": 13800.00, // "a+": 15035,
"a+": 12000.00, // "a": 13095,
"a": 10500.00, // "a-": 11155,
"a-": 9000.00, // "b+": 9215,
"b+": 7400.00, // "b": 7275,
"b": 5700.00, // "b-": 5335,
"b-": 4200.00, // "c+": 3880,
"c+": 3000.00, // "c": 2425,
"c": 2000.00, // "c-": 1213,
"c-": 1300.00, // "d+": 606,
"d+": 800.00, // "d": 0,
"d": 0.00, // };
};
// DateTime seasonStart = DateTime.utc(2024, 08, 16, 18); // DateTime seasonStart = DateTime.utc(2024, 08, 16, 18);
//DateTime seasonEnd = DateTime.utc(2024, 07, 26, 15); //DateTime seasonEnd = DateTime.utc(2024, 07, 26, 15);
enum Stats { enum Stats {
@ -426,9 +424,7 @@ class Summaries{
RecordSingle? sprint; RecordSingle? sprint;
RecordSingle? blitz; RecordSingle? blitz;
RecordSingle? zenith; RecordSingle? zenith;
RecordSingle? zenithCareerBest; // leaderboard best, not overall
RecordSingle? zenithEx; RecordSingle? zenithEx;
RecordSingle? zenithExCareerBest; // leaderboard best, not overall
late List<Achievement> achievements; late List<Achievement> achievements;
late TetraLeague league; late TetraLeague league;
late TetrioZen zen; late TetrioZen zen;
@ -440,9 +436,7 @@ class Summaries{
if (json['40l']['record'] != null) sprint = RecordSingle.fromJson(json['40l']['record'], json['40l']['rank'], json['40l']['rank_local']); if (json['40l']['record'] != null) sprint = RecordSingle.fromJson(json['40l']['record'], json['40l']['rank'], json['40l']['rank_local']);
if (json['blitz']['record'] != null) blitz = RecordSingle.fromJson(json['blitz']['record'], json['blitz']['rank'], json['40l']['rank_local']); if (json['blitz']['record'] != null) blitz = RecordSingle.fromJson(json['blitz']['record'], json['blitz']['rank'], json['40l']['rank_local']);
if (json['zenith']['record'] != null) zenith = RecordSingle.fromJson(json['zenith']['record'], json['zenith']['rank'], json['zenith']['rank_local']); if (json['zenith']['record'] != null) zenith = RecordSingle.fromJson(json['zenith']['record'], json['zenith']['rank'], json['zenith']['rank_local']);
if (json['zenith']['best']['record'] != null) zenithCareerBest = RecordSingle.fromJson(json['zenith']['best']['record'], json['zenith']['best']['rank'], -1);
if (json['zenithex']['record'] != null) zenithEx = RecordSingle.fromJson(json['zenithex']['record'], json['zenithex']['rank'], json['zenithex']['rank_local']); if (json['zenithex']['record'] != null) zenithEx = RecordSingle.fromJson(json['zenithex']['record'], json['zenithex']['rank'], json['zenithex']['rank_local']);
if (json['zenithex']['best']['record'] != null) zenithCareerBest = RecordSingle.fromJson(json['zenithex']['best']['record'], json['zenith']['best']['rank'], -1);
achievements = [for (var achievement in json['achievements']) Achievement.fromJson(achievement)]; achievements = [for (var achievement in json['achievements']) Achievement.fromJson(achievement)];
league = TetraLeague.fromJson(json['league'], DateTime.now()); league = TetraLeague.fromJson(json['league'], DateTime.now());
zen = TetrioZen.fromJson(json['zen']); zen = TetrioZen.fromJson(json['zen']);
@ -2618,44 +2612,3 @@ class TetrioPlayerFromLeaderboard {
} }
} }
} }
class CutoffTetrio {
late int pos;
late double percentile;
late double tr;
late double targetTr;
late double apm;
late double pps;
late double vs;
late int count;
late double countPercentile;
CutoffTetrio.fromJson(Map<String, dynamic> json, int total){
pos = json['pos'];
percentile = json['percentile'].toDouble();
tr = json['tr'].toDouble();
targetTr = json['targettr'].toDouble();
apm = json['apm'].toDouble();
pps = json['pps'].toDouble();
vs = json['vs'].toDouble();
count = json['count'];
countPercentile = count / total;
}
}
class CutoffsTetrio {
late String id;
late DateTime timestamp;
late int total;
Map<String, CutoffTetrio> data = {};
CutoffsTetrio.fromJson(Map<String, dynamic> json){
id = json['s'];
timestamp = DateTime.parse(json['t']);
total = json['data']['total'];
json['data'].remove("total");
for (String rank in json['data'].keys){
data[rank] = CutoffTetrio.fromJson(json['data'][rank], total);
}
}
}

View File

@ -407,53 +407,7 @@ class TetrioService extends DB {
// Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above, // Sidenote: as you can see, fetch functions looks and works pretty much same way, as described above,
// so i'm going to document only unique differences between them // so i'm going to document only unique differences between them
Future<CutoffsTetrio?> fetchCutoffsTetrio() async { Future<Cutoffs?> fetchCutoffs() async {
CutoffsTetrio? cached = _cache.get("league_ranks", CutoffsTetrio);
if (cached != null) return cached;
Uri url;
if (kIsWeb) {
url = Uri.https('ts.dan63.by', 'oskware_bridge.php', {"endpoint": "cutoffs"});
} else {
url = Uri.https('ch.tetr.io', 'api/labs/league_ranks');
}
try{
final response = await client.get(url);
switch (response.statusCode) {
case 200:
Map<String, dynamic> rawData = jsonDecode(response.body);
CutoffsTetrio result = CutoffsTetrio.fromJson(rawData['data']);
_cache.store(result, rawData["cache"]["cached_until"]);
return result;
case 404:
developer.log("fetchCutoffsTetrio: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
return null;
// if not 200 or 404 - throw a unique for each code exception
case 403:
throw TetrioForbidden();
case 429:
throw TetrioTooManyRequests();
case 418:
throw TetrioOskwareBridgeProblem();
case 500:
case 502:
case 503:
case 504:
developer.log("fetchCutoffsTetrio: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
return null;
default:
developer.log("fetchCutoffsTetrio: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
}
} on http.ClientException catch (e, s) { // If local http client fails
developer.log("$e, $s");
throw http.ClientException(e.message, e.uri); // just assuming, that our end user don't have acess to the internet
}
}
Future<Cutoffs?> fetchCutoffsBeanserver() async {
Cutoffs? cached = _cache.get("", Cutoffs); Cutoffs? cached = _cache.get("", Cutoffs);
if (cached != null) return cached; if (cached != null) return cached;
@ -475,7 +429,7 @@ class TetrioService extends DB {
_cache.store(result, rawData["cache_until"]); _cache.store(result, rawData["cache_until"]);
return result; return result;
case 404: case 404:
developer.log("fetchCutoffsBeanserver: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode); developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
return null; return null;
// if not 200 or 404 - throw a unique for each code exception // if not 200 or 404 - throw a unique for each code exception
case 403: case 403:
@ -488,10 +442,10 @@ class TetrioService extends DB {
case 502: case 502:
case 503: case 503:
case 504: case 504:
developer.log("fetchCutoffsBeanserver: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode); developer.log("fetchCutoffs: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
return null; return null;
default: default:
developer.log("fetchCutoffsBeanserver: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode); developer.log("fetchCutoffs: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason"); throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
} }
} on http.ClientException catch (e, s) { // If local http client fails } on http.ClientException catch (e, s) { // If local http client fails

View File

@ -175,10 +175,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
teto.fetchNews(_searchFor), teto.fetchNews(_searchFor),
teto.fetchStream(_searchFor, "zenith/recent"), teto.fetchStream(_searchFor, "zenith/recent"),
teto.fetchStream(_searchFor, "zenithex/recent"), teto.fetchStream(_searchFor, "zenithex/recent"),
teto.fetchCutoffsBeanserver(), teto.fetchCutoffs(),
(summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null), (summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
]); ]);
//prefs.getBool("showPositions") != true ? teto.fetchCutoffsBeanserver() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]), //prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
//(summaries.league.gamesPlayed > 9) ? teto.fetchTopTR(_searchFor) : Future.delayed(Duration.zero, () => null) // can retrieve this only if player has TR //(summaries.league.gamesPlayed > 9) ? teto.fetchTopTR(_searchFor) : Future.delayed(Duration.zero, () => null) // can retrieve this only if player has TR
summaries = requests[0] as Summaries; summaries = requests[0] as Summaries;
@ -478,10 +478,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
guest: snapshot.data![0].role == "anon", guest: snapshot.data![0].role == "anon",
thatRankCutoff: thatRankCutoff, thatRankCutoff: thatRankCutoff,
thatRankCutoffGlicko: thatRankGlickoCutoff, thatRankCutoffGlicko: thatRankGlickoCutoff,
thatRankTarget: snapshot.data![1].league.rank != "z" ? rankTargets[snapshot.data![1].league.rank] : null, //thatRankTarget: snapshot.data![1].league.rank != "z" ? rankTargets[snapshot.data![1].league.rank] : null,
nextRankCutoff: nextRankCutoff, nextRankCutoff: nextRankCutoff,
nextRankCutoffGlicko: nextRankGlickoCutoff, nextRankCutoffGlicko: nextRankGlickoCutoff,
nextRankTarget: (snapshot.data![1].league.rank != "z" && snapshot.data![1].league.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![1].league.rank)+1)] : null, //nextRankTarget: (snapshot.data![1].league.rank != "z" && snapshot.data![1].league.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![1].league.rank)+1)] : null,
averages: rankAverages, averages: rankAverages,
lbPositions: meAmongEveryone lbPositions: meAmongEveryone
), ),
@ -519,10 +519,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
guest: snapshot.data![0].role == "anon", guest: snapshot.data![0].role == "anon",
thatRankCutoff: thatRankCutoff, thatRankCutoff: thatRankCutoff,
thatRankCutoffGlicko: thatRankGlickoCutoff, thatRankCutoffGlicko: thatRankGlickoCutoff,
thatRankTarget: snapshot.data![1].league.rank != "z" ? rankTargets[snapshot.data![1].league.rank] : null, //thatRankTarget: snapshot.data![1].league.rank != "z" ? rankTargets[snapshot.data![1].league.rank] : null,
nextRankCutoff: nextRankCutoff, nextRankCutoff: nextRankCutoff,
nextRankCutoffGlicko: nextRankGlickoCutoff, nextRankCutoffGlicko: nextRankGlickoCutoff,
nextRankTarget: (snapshot.data![1].league.rank != "z" && snapshot.data![1].league.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![1].league.rank)+1)] : null, //nextRankTarget: (snapshot.data![1].league.rank != "z" && snapshot.data![1].league.rank != "x") ? rankTargets[ranks.elementAtOrNull(ranks.indexOf(snapshot.data![1].league.rank)+1)] : null,
averages: rankAverages, averages: rankAverages,
lbPositions: meAmongEveryone lbPositions: meAmongEveryone
), ),

View File

@ -40,7 +40,7 @@ class MainView extends StatefulWidget {
enum Page {home, leaderboards, leagueAverages, calculator, settings} enum Page {home, leaderboards, leagueAverages, calculator, settings}
enum Cards {overview, tetraLeague, quickPlay, sprint, blitz} enum Cards {overview, tetraLeague, quickPlay, sprint, blitz}
enum CardMod {info, records, ex, exRecords} enum CardMod {info, recent, top, ex, exRecent, exTop}
Map<Cards, String> cardsTitles = { Map<Cards, String> cardsTitles = {
Cards.overview: "Overview", Cards.overview: "Overview",
Cards.tetraLeague: t.tetraLeague, Cards.tetraLeague: t.tetraLeague,
@ -574,7 +574,7 @@ class RecordSummary extends StatelessWidget{
text: "", text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey), style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
children: [ children: [
if (rank != null && rank != "z" && rank != "x+") TextSpan(text: "${t.verdictGeneral(n: switch(record!.gamemode){ if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: switch(record!.gamemode){
"40l" => readableTimeDifference(record!.stats.finalTime, sprintAverages[rank]!), "40l" => readableTimeDifference(record!.stats.finalTime, sprintAverages[rank]!),
"blitz" => readableIntDifference(record!.stats.score, blitzAverages[rank]!), "blitz" => readableIntDifference(record!.stats.score, blitzAverages[rank]!),
_ => record!.stats.score.toString() _ => record!.stats.score.toString()
@ -872,7 +872,7 @@ class _DestinationHomeState extends State<DestinationHome> {
); );
} }
Widget getListOfRecords(String recentStream, String topStream, BoxConstraints constraints){ Widget getListOfRecords(String stream, bool isTop, BoxConstraints constraints){
return Column( return Column(
children: [ children: [
Card( Card(
@ -883,8 +883,7 @@ class _DestinationHomeState extends State<DestinationHome> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text("Records", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)), Text(isTop ? t.top : t.recent, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
//Text("${t.seasonStarts} ${countdown(postSeasonLeft)}", textAlign: TextAlign.center)
], ],
), ),
), ),
@ -892,22 +891,8 @@ class _DestinationHomeState extends State<DestinationHome> {
), ),
Card( Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: DefaultTabController(length: 2, child: FutureBuilder<SingleplayerStream>(
child: Column( future: teto.fetchStream(widget.searchFor, stream),
mainAxisSize: MainAxisSize.min,
children: [
TabBar(
tabs: [
Tab(text: "Recent"),
Tab(text: "Top"),
],
),
SizedBox(
height: 400,
child: TabBarView(
children: [
FutureBuilder<SingleplayerStream>(
future: teto.fetchStream(widget.searchFor, recentStream),
builder: (context, snapshot) { builder: (context, snapshot) {
switch (snapshot.connectionState){ switch (snapshot.connectionState){
case ConnectionState.none: case ConnectionState.none:
@ -921,7 +906,7 @@ class _DestinationHomeState extends State<DestinationHome> {
for (int i = 0; i < snapshot.data!.records.length; i++) ListTile( for (int i = 0; i < snapshot.data!.records.length; i++) ListTile(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: snapshot.data!.records[i]))), onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: snapshot.data!.records[i]))),
leading: Text( leading: Text(
switch (snapshot.data!.records[i].gamemode){ isTop ? "#${i+1}" : switch (snapshot.data!.records[i].gamemode){
"40l" => "40L", "40l" => "40L",
"blitz" => "BLZ", "blitz" => "BLZ",
"5mblast" => "5MB", "5mblast" => "5MB",
@ -965,64 +950,6 @@ class _DestinationHomeState extends State<DestinationHome> {
return Text("what?"); return Text("what?");
}, },
), ),
FutureBuilder<SingleplayerStream>(
future: teto.fetchStream(widget.searchFor, topStream),
builder: (context, snapshot) {
switch (snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasData){
return Column(
children: [
for (int i = 0; i < snapshot.data!.records.length; i++) ListTile(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: snapshot.data!.records[i]))),
leading: Text(
"#${i+1}",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9)
),
title: Text(
switch (snapshot.data!.records[i].gamemode){
"40l" => get40lTime(snapshot.data!.records[i].stats.finalTime.inMicroseconds),
"blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(snapshot.data!.records[i].stats.score)),
"5mblast" => get40lTime(snapshot.data!.records[i].stats.finalTime.inMicroseconds),
"zenith" => "${f2.format(snapshot.data!.records[i].stats.zenith!.altitude)} m${(snapshot.data!.records[i].extras as ZenithExtras).mods.isNotEmpty ? " (${t.withModsPlural(n: (snapshot.data!.records[i].extras as ZenithExtras).mods.length)})" : ""}",
"zenithex" => "${f2.format(snapshot.data!.records[i].stats.zenith!.altitude)} m${(snapshot.data!.records[i].extras as ZenithExtras).mods.isNotEmpty ? " (${t.withModsPlural(n: (snapshot.data!.records[i].extras as ZenithExtras).mods.length)})" : ""}",
String() => "huh",
},
style: const TextStyle(fontSize: 18)),
subtitle: Text(timestamp(snapshot.data!.records[i].timestamp), style: const TextStyle(color: Colors.grey, height: 0.85)),
trailing: SpTrailingStats(snapshot.data!.records[i], snapshot.data!.records[i].gamemode)
)
],
);
}
if (snapshot.hasError){
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(t.errors.noSuchUser, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 42, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(t.errors.noSuchUserSub, textAlign: TextAlign.center),
),
],
)
);
}
}
return Text("what?");
},
),
]
),
)
],
),
)
), ),
], ],
); );
@ -1374,7 +1301,7 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('Standing'), label: Text('Standing'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.records, value: CardMod.recent,
label: Text('Recent Matches'), label: Text('Recent Matches'),
), ),
], ],
@ -1384,17 +1311,25 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('Normal'), label: Text('Normal'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.records, value: CardMod.recent,
label: Text('Records'), label: Text('Recent Normal'),
),
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top Normal'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.ex, value: CardMod.ex,
label: Text('Expert'), label: Text('Expert'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.exRecords, value: CardMod.exRecent,
label: Text('Expert Records'), label: Text('Recent Expert'),
) ),
const ButtonSegment<CardMod>(
value: CardMod.exTop,
label: Text('Top Expert'),
),
], ],
Cards.blitz: [ Cards.blitz: [
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
@ -1402,9 +1337,13 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('PB'), label: Text('PB'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.records, value: CardMod.recent,
label: Text('Records'), label: Text('Recent'),
) ),
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top'),
),
], ],
Cards.sprint: [ Cards.sprint: [
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
@ -1412,9 +1351,13 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('PB'), label: Text('PB'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.records, value: CardMod.recent,
label: Text('Records'), label: Text('Recent'),
) ),
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top'),
),
] ]
}; };
super.initState(); super.initState();
@ -1446,8 +1389,8 @@ class _DestinationHomeState extends State<DestinationHome> {
); );
} }
if (snapshot.hasData){ if (snapshot.hasData){
blitzBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.blitz != null && snapshot.data!.summaries!.league.rank != "x+") ? snapshot.data!.summaries!.blitz!.stats.score > blitzAverages[snapshot.data!.summaries!.league.rank]! : null; blitzBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.blitz != null) ? snapshot.data!.summaries!.blitz!.stats.score > blitzAverages[snapshot.data!.summaries!.league.rank]! : null;
sprintBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.sprint != null && snapshot.data!.summaries!.league.rank != "x+") ? snapshot.data!.summaries!.sprint!.stats.finalTime < sprintAverages[snapshot.data!.summaries!.league.rank]! : null; sprintBetterThanRankAverage = (snapshot.data!.summaries!.league.rank != "z" && snapshot.data!.summaries!.sprint != null) ? snapshot.data!.summaries!.sprint!.stats.finalTime < sprintAverages[snapshot.data!.summaries!.league.rank]! : null;
if (snapshot.data!.summaries!.sprint != null) { if (snapshot.data!.summaries!.sprint != null) {
closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-snapshot.data!.summaries!.sprint!.stats.finalTime).abs() < (b -snapshot.data!.summaries!.sprint!.stats.finalTime).abs() ? a : b)); closestAverageSprint = sprintAverages.entries.singleWhere((element) => element.value == sprintAverages.values.reduce((a, b) => (a-snapshot.data!.summaries!.sprint!.stats.finalTime).abs() < (b -snapshot.data!.summaries!.sprint!.stats.finalTime).abs() ? a : b));
sprintBetterThanClosestAverage = snapshot.data!.summaries!.sprint!.stats.finalTime < closestAverageSprint!.value; sprintBetterThanClosestAverage = snapshot.data!.summaries!.sprint!.stats.finalTime < closestAverageSprint!.value;
@ -1524,24 +1467,28 @@ class _DestinationHomeState extends State<DestinationHome> {
Cards.overview => getOverviewCard(snapshot.data!.summaries!), Cards.overview => getOverviewCard(snapshot.data!.summaries!),
Cards.tetraLeague => switch (cardMod){ Cards.tetraLeague => switch (cardMod){
CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league), CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league),
CardMod.records => getRecentTLrecords(widget.constraints), CardMod.recent => getRecentTLrecords(widget.constraints),
_ => Center(child: Text("huh?")) _ => Center(child: Text("huh?"))
}, },
Cards.quickPlay => switch (cardMod){ Cards.quickPlay => switch (cardMod){
CardMod.info => getZenithCard(snapshot.data?.summaries!.zenith), CardMod.info => getZenithCard(snapshot.data?.summaries!.zenith),
CardMod.records => getListOfRecords("zenith/recent", "zenith/top", widget.constraints), CardMod.recent => getListOfRecords("zenith/recent", false, widget.constraints),
CardMod.top => getListOfRecords("zenith/top", true, widget.constraints),
CardMod.ex => getZenithCard(snapshot.data?.summaries!.zenithEx), CardMod.ex => getZenithCard(snapshot.data?.summaries!.zenithEx),
CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", widget.constraints), CardMod.exRecent => getListOfRecords("zenithex/recent", false, widget.constraints),
CardMod.exTop => getListOfRecords("zenithex/top", true, widget.constraints),
_ => Center(child: Text("huh?")) _ => Center(child: Text("huh?"))
}, },
Cards.sprint => switch (cardMod){ Cards.sprint => switch (cardMod){
CardMod.info => getRecordCard(snapshot.data?.summaries!.sprint, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank), CardMod.info => getRecordCard(snapshot.data?.summaries!.sprint, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.records => getListOfRecords("40l/recent", "40l/top", widget.constraints), CardMod.recent => getListOfRecords("40l/recent", false, widget.constraints),
CardMod.top => getListOfRecords("40l/top", true, widget.constraints),
_ => Center(child: Text("huh?")) _ => Center(child: Text("huh?"))
}, },
Cards.blitz => switch (cardMod){ Cards.blitz => switch (cardMod){
CardMod.info => getRecordCard(snapshot.data?.summaries!.blitz, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank), CardMod.info => getRecordCard(snapshot.data?.summaries!.blitz, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.records => getListOfRecords("blitz/recent", "blitz/top", widget.constraints), CardMod.recent => getListOfRecords("blitz/recent", false, widget.constraints),
CardMod.top => getListOfRecords("blitz/top", true, widget.constraints),
_ => Center(child: Text("huh?")) _ => Center(child: Text("huh?"))
}, },
}, },
@ -2386,13 +2333,12 @@ class TetraLeagueThingy extends StatelessWidget{
backgroundColor: Colors.black, backgroundColor: Colors.black,
axes: [ axes: [
RadialAxis( RadialAxis(
minimum: 0.0, minimum: 0.4,
maximum: 1.0, maximum: 0.6,
radiusFactor: 1.01, radiusFactor: 1.01,
showTicks: true, showTicks: true,
showLabels: false, showLabels: false,
interval: 0.25, interval: 0.1,
minorTicksPerInterval: 0,
ranges:[ ranges:[
GaugeRange(startValue: 0, endValue: league.winrate, color: theme.colorScheme.primary) GaugeRange(startValue: 0, endValue: league.winrate, color: theme.colorScheme.primary)
], ],

View File

@ -1,10 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tetra_stats/data_objects/tetra_stats.dart';
import 'package:tetra_stats/data_objects/tetrio.dart'; import 'package:tetra_stats/data_objects/tetrio.dart';
import 'package:tetra_stats/gen/strings.g.dart'; import 'package:tetra_stats/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.dart'; import 'package:tetra_stats/utils/numers_formats.dart';
import 'package:tetra_stats/utils/text_shadow.dart'; import 'package:tetra_stats/utils/text_shadow.dart';
import 'package:tetra_stats/views/compare_view.dart';
import 'package:tetra_stats/views/rank_averages_view.dart';
import 'package:tetra_stats/widgets/text_timestamp.dart'; import 'package:tetra_stats/widgets/text_timestamp.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:tetra_stats/main.dart' show teto; import 'package:tetra_stats/main.dart' show teto;
@ -37,13 +40,14 @@ class RanksAverages extends State<RankAveragesView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool bigScreen = MediaQuery.of(context).size.width >= 700;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(t.rankAveragesViewTitle), title: Text(t.rankAveragesViewTitle),
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( body: SafeArea(
child: FutureBuilder<CutoffsTetrio?>(future: teto.fetchCutoffsTetrio(), builder: (context, snapshot){ child: FutureBuilder<Cutoffs?>(future: teto.fetchCutoffs(), builder: (context, snapshot){
switch (snapshot.connectionState) { switch (snapshot.connectionState) {
case ConnectionState.none: case ConnectionState.none:
case ConnectionState.waiting: case ConnectionState.waiting:
@ -57,7 +61,8 @@ class RanksAverages extends State<RankAveragesView> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
width: 900, width: MediaQuery.of(context).size.width,
constraints: const BoxConstraints(maxWidth: 900, minWidth: 610),
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@ -66,85 +71,54 @@ class RanksAverages extends State<RankAveragesView> {
Table( Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle, defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.all(color: Colors.grey.shade900), border: TableBorder.all(color: Colors.grey.shade900),
columnWidths: const { columnWidths: const {0: FixedColumnWidth(48)},
0: FixedColumnWidth(48),
1: FixedColumnWidth(155),
2: FixedColumnWidth(150),
3: FixedColumnWidth(90),
4: FixedColumnWidth(130),
},
children: [ children: [
TableRow( TableRow(
children: [ children: [
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
const Padding( Padding(
padding: EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Text("TR", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)), child: Text("TR", textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Text("APM", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Text("PPS", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Text("VS", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Text("Advanced", textAlign: TextAlign.right, style: TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Text("Players (${intf.format(snapshot.data!.total)})", textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)), child: Text("Glicko", textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text("Glixare", textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text("S1 TR", textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white)),
), ),
] ]
), ),
for (String rank in snapshot.data!.data.keys) TableRow( for (String rank in snapshot.data!.tr.keys) TableRow(
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(200), rankColors[rank]!.withAlpha(100)])), decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(100), rankColors[rank]!.withAlpha(200)])),
children: [ children: [
Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/$rank.png", height: 48)), Container(decoration: BoxDecoration(boxShadow: [BoxShadow(color: Colors.black.withAlpha(132), blurRadius: 32.0, blurStyle: BlurStyle.inner)]), child: Image.asset("res/tetrio_tl_alpha_ranks/$rank.png", height: 48)),
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Text(f2.format(snapshot.data!.data[rank]!.tr), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)), child: Text(f2.format(snapshot.data!.tr[rank]), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Text(f2.format(snapshot.data!.data[rank]!.apm), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow)), child: Text(f2.format(snapshot.data!.glicko[rank]), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Text(f2.format(snapshot.data!.data[rank]!.pps), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow)), child: Text(f3.format(snapshot.data!.gxe[rank]), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
), ),
Padding( Padding(
padding: const EdgeInsets.only(right: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: Text(f2.format(snapshot.data!.data[rank]!.vs), textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow)), child: Text(f2.format(snapshot.data!.gxe[rank]!*250), textAlign: TextAlign.right, style: TextStyle(fontFamily: bigScreen ? "Eurostile Round" : "Eurostile Round Condensed", fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white, shadows: textShadow)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text("${f3.format(snapshot.data!.data[rank]!.apm / (snapshot.data!.data[rank]!.pps * 60))} APP\n${f3.format(snapshot.data!.data[rank]!.vs / snapshot.data!.data[rank]!.apm)} VS/APM", textAlign: TextAlign.right, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: RichText(
textAlign: TextAlign.right,
text: TextSpan(
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w100, color: Colors.white, shadows: textShadow),
children: [
TextSpan(text: intf.format(snapshot.data!.data[rank]!.count)),
TextSpan(text: " (${f2.format(snapshot.data!.data[rank]!.countPercentile * 100)}%)", style: const TextStyle(color: Colors.white60, shadows: null)),
TextSpan(text: "\n(from № ${intf.format(snapshot.data!.data[rank]!.pos)})", style: const TextStyle(color: Colors.white60, shadows: null))
]
))
), ),
] ]
) )
], ],
), ),
Text(t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.timestamp))) Text(t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.ts)))
], ],
), ),
), ),

View File

@ -2,7 +2,7 @@ name: tetra_stats
description: Track your and other player stats in TETR.IO description: Track your and other player stats in TETR.IO
publish_to: 'none' publish_to: 'none'
version: 1.6.8+34 version: 1.6.7+33
environment: environment:
sdk: '>=3.0.0' sdk: '>=3.0.0'