Compare commits

...

2 Commits

Author SHA1 Message Date
dan63047 ce2fb89ccf cache fix 2024-08-24 17:48:09 +03:00
dan63047 7cb1fc0543 ok :droidsmile: 2024-08-24 17:41:07 +03:00
6 changed files with 338 additions and 165 deletions

View File

@ -3,6 +3,7 @@
import 'dart:math';
import 'package:flutter/foundation.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:vector_math/vector_math.dart';
@ -42,25 +43,26 @@ const Map<String, double> rankCutoffs = {
"z": -1,
"": 0.5
};
// const Map<String, double> rankTargets = {
// "x": 24503.75, // where that comes from?
// "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,
// };
const Map<String, double> rankTargets = {
"x+": 24000.00,
"x": 22500.00,
"u": 20000.00,
"ss": 18000.00,
"s+": 16500.00,
"s": 15200.00,
"s-": 13800.00,
"a+": 12000.00,
"a": 10500.00,
"a-": 9000.00,
"b+": 7400.00,
"b": 5700.00,
"b-": 4200.00,
"c+": 3000.00,
"c": 2000.00,
"c-": 1300.00,
"d+": 800.00,
"d": 0.00,
};
// DateTime seasonStart = DateTime.utc(2024, 08, 16, 18);
//DateTime seasonEnd = DateTime.utc(2024, 07, 26, 15);
enum Stats {
@ -424,7 +426,9 @@ class Summaries{
RecordSingle? sprint;
RecordSingle? blitz;
RecordSingle? zenith;
RecordSingle? zenithCareerBest; // leaderboard best, not overall
RecordSingle? zenithEx;
RecordSingle? zenithExCareerBest; // leaderboard best, not overall
late List<Achievement> achievements;
late TetraLeague league;
late TetrioZen zen;
@ -436,7 +440,9 @@ class Summaries{
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['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']['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)];
league = TetraLeague.fromJson(json['league'], DateTime.now());
zen = TetrioZen.fromJson(json['zen']);
@ -2612,3 +2618,44 @@ 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,7 +407,53 @@ class TetrioService extends DB {
// 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
Future<Cutoffs?> fetchCutoffs() async {
Future<CutoffsTetrio?> fetchCutoffsTetrio() 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);
if (cached != null) return cached;
@ -429,7 +475,7 @@ class TetrioService extends DB {
_cache.store(result, rawData["cache_until"]);
return result;
case 404:
developer.log("fetchCutoffs: Cutoffs are gone", name: "services/tetrio_crud", error: response.statusCode);
developer.log("fetchCutoffsBeanserver: 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:
@ -442,10 +488,10 @@ class TetrioService extends DB {
case 502:
case 503:
case 504:
developer.log("fetchCutoffs: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
developer.log("fetchCutoffsBeanserver: Cutoffs are unavalable (${response.statusCode})", name: "services/tetrio_crud", error: response.statusCode);
return null;
default:
developer.log("fetchCutoffs: Failed to fetch top Cutoffs", name: "services/tetrio_crud", error: response.statusCode);
developer.log("fetchCutoffsBeanserver: 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

View File

@ -175,10 +175,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
teto.fetchNews(_searchFor),
teto.fetchStream(_searchFor, "zenith/recent"),
teto.fetchStream(_searchFor, "zenithex/recent"),
teto.fetchCutoffs(),
teto.fetchCutoffsBeanserver(),
(summaries.league.rank != "z" ? summaries.league.rank == "x+" : summaries.league.percentileRank == "x+") ? teto.fetchTopOneFromTheLeaderboard() : Future.delayed(Duration.zero, ()=>null),
]);
//prefs.getBool("showPositions") != true ? teto.fetchCutoffs() : Future.delayed(Duration.zero, ()=><Map<String, double>>[]),
//prefs.getBool("showPositions") != true ? teto.fetchCutoffsBeanserver() : 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 = requests[0] as Summaries;
@ -478,10 +478,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
guest: snapshot.data![0].role == "anon",
thatRankCutoff: thatRankCutoff,
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,
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,
lbPositions: meAmongEveryone
),
@ -519,10 +519,10 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
guest: snapshot.data![0].role == "anon",
thatRankCutoff: thatRankCutoff,
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,
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,
lbPositions: meAmongEveryone
),

View File

@ -40,7 +40,7 @@ class MainView extends StatefulWidget {
enum Page {home, leaderboards, leagueAverages, calculator, settings}
enum Cards {overview, tetraLeague, quickPlay, sprint, blitz}
enum CardMod {info, recent, top, ex, exRecent, exTop}
enum CardMod {info, records, ex, exRecords}
Map<Cards, String> cardsTitles = {
Cards.overview: "Overview",
Cards.tetraLeague: t.tetraLeague,
@ -574,7 +574,7 @@ class RecordSummary extends StatelessWidget{
text: "",
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, color: Colors.grey),
children: [
if (rank != null && rank != "z") TextSpan(text: "${t.verdictGeneral(n: switch(record!.gamemode){
if (rank != null && rank != "z" && rank != "x+") TextSpan(text: "${t.verdictGeneral(n: switch(record!.gamemode){
"40l" => readableTimeDifference(record!.stats.finalTime, sprintAverages[rank]!),
"blitz" => readableIntDifference(record!.stats.score, blitzAverages[rank]!),
_ => record!.stats.score.toString()
@ -872,7 +872,7 @@ class _DestinationHomeState extends State<DestinationHome> {
);
}
Widget getListOfRecords(String stream, bool isTop, BoxConstraints constraints){
Widget getListOfRecords(String recentStream, String topStream, BoxConstraints constraints){
return Column(
children: [
Card(
@ -883,7 +883,8 @@ class _DestinationHomeState extends State<DestinationHome> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(isTop ? t.top : t.recent, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
Text("Records", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
//Text("${t.seasonStarts} ${countdown(postSeasonLeft)}", textAlign: TextAlign.center)
],
),
),
@ -891,8 +892,22 @@ class _DestinationHomeState extends State<DestinationHome> {
),
Card(
clipBehavior: Clip.antiAlias,
child: FutureBuilder<SingleplayerStream>(
future: teto.fetchStream(widget.searchFor, stream),
child: DefaultTabController(length: 2,
child: Column(
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) {
switch (snapshot.connectionState){
case ConnectionState.none:
@ -906,7 +921,7 @@ class _DestinationHomeState extends State<DestinationHome> {
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(
isTop ? "#${i+1}" : switch (snapshot.data!.records[i].gamemode){
switch (snapshot.data!.records[i].gamemode){
"40l" => "40L",
"blitz" => "BLZ",
"5mblast" => "5MB",
@ -950,6 +965,64 @@ class _DestinationHomeState extends State<DestinationHome> {
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?");
},
),
]
),
)
],
),
)
),
],
);
@ -1301,7 +1374,7 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('Standing'),
),
const ButtonSegment<CardMod>(
value: CardMod.recent,
value: CardMod.records,
label: Text('Recent Matches'),
),
],
@ -1311,25 +1384,17 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('Normal'),
),
const ButtonSegment<CardMod>(
value: CardMod.recent,
label: Text('Recent Normal'),
),
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top Normal'),
value: CardMod.records,
label: Text('Records'),
),
const ButtonSegment<CardMod>(
value: CardMod.ex,
label: Text('Expert'),
),
const ButtonSegment<CardMod>(
value: CardMod.exRecent,
label: Text('Recent Expert'),
),
const ButtonSegment<CardMod>(
value: CardMod.exTop,
label: Text('Top Expert'),
),
value: CardMod.exRecords,
label: Text('Expert Records'),
)
],
Cards.blitz: [
const ButtonSegment<CardMod>(
@ -1337,13 +1402,9 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('PB'),
),
const ButtonSegment<CardMod>(
value: CardMod.recent,
label: Text('Recent'),
),
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top'),
),
value: CardMod.records,
label: Text('Records'),
)
],
Cards.sprint: [
const ButtonSegment<CardMod>(
@ -1351,13 +1412,9 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('PB'),
),
const ButtonSegment<CardMod>(
value: CardMod.recent,
label: Text('Recent'),
),
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top'),
),
value: CardMod.records,
label: Text('Records'),
)
]
};
super.initState();
@ -1389,8 +1446,8 @@ class _DestinationHomeState extends State<DestinationHome> {
);
}
if (snapshot.hasData){
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!.sprint!.stats.finalTime < sprintAverages[snapshot.data!.summaries!.league.rank]! : null;
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;
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;
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));
sprintBetterThanClosestAverage = snapshot.data!.summaries!.sprint!.stats.finalTime < closestAverageSprint!.value;
@ -1467,28 +1524,24 @@ class _DestinationHomeState extends State<DestinationHome> {
Cards.overview => getOverviewCard(snapshot.data!.summaries!),
Cards.tetraLeague => switch (cardMod){
CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league),
CardMod.recent => getRecentTLrecords(widget.constraints),
CardMod.records => getRecentTLrecords(widget.constraints),
_ => Center(child: Text("huh?"))
},
Cards.quickPlay => switch (cardMod){
CardMod.info => getZenithCard(snapshot.data?.summaries!.zenith),
CardMod.recent => getListOfRecords("zenith/recent", false, widget.constraints),
CardMod.top => getListOfRecords("zenith/top", true, widget.constraints),
CardMod.records => getListOfRecords("zenith/recent", "zenith/top", widget.constraints),
CardMod.ex => getZenithCard(snapshot.data?.summaries!.zenithEx),
CardMod.exRecent => getListOfRecords("zenithex/recent", false, widget.constraints),
CardMod.exTop => getListOfRecords("zenithex/top", true, widget.constraints),
CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", widget.constraints),
_ => Center(child: Text("huh?"))
},
Cards.sprint => switch (cardMod){
CardMod.info => getRecordCard(snapshot.data?.summaries!.sprint, sprintBetterThanRankAverage, closestAverageSprint, sprintBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.recent => getListOfRecords("40l/recent", false, widget.constraints),
CardMod.top => getListOfRecords("40l/top", true, widget.constraints),
CardMod.records => getListOfRecords("40l/recent", "40l/top", widget.constraints),
_ => Center(child: Text("huh?"))
},
Cards.blitz => switch (cardMod){
CardMod.info => getRecordCard(snapshot.data?.summaries!.blitz, blitzBetterThanRankAverage, closestAverageBlitz, blitzBetterThanClosestAverage, snapshot.data!.summaries!.league.rank),
CardMod.recent => getListOfRecords("blitz/recent", false, widget.constraints),
CardMod.top => getListOfRecords("blitz/top", true, widget.constraints),
CardMod.records => getListOfRecords("blitz/recent", "blitz/top", widget.constraints),
_ => Center(child: Text("huh?"))
},
},
@ -2333,12 +2386,13 @@ class TetraLeagueThingy extends StatelessWidget{
backgroundColor: Colors.black,
axes: [
RadialAxis(
minimum: 0.4,
maximum: 0.6,
minimum: 0.0,
maximum: 1.0,
radiusFactor: 1.01,
showTicks: true,
showLabels: false,
interval: 0.1,
interval: 0.25,
minorTicksPerInterval: 0,
ranges:[
GaugeRange(startValue: 0, endValue: league.winrate, color: theme.colorScheme.primary)
],

View File

@ -1,13 +1,10 @@
import 'dart:io';
import 'package:flutter/foundation.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/gen/strings.g.dart';
import 'package:tetra_stats/utils/numers_formats.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:window_manager/window_manager.dart';
import 'package:tetra_stats/main.dart' show teto;
@ -40,14 +37,13 @@ class RanksAverages extends State<RankAveragesView> {
@override
Widget build(BuildContext context) {
bool bigScreen = MediaQuery.of(context).size.width >= 700;
return Scaffold(
appBar: AppBar(
title: Text(t.rankAveragesViewTitle),
),
backgroundColor: Colors.black,
body: SafeArea(
child: FutureBuilder<Cutoffs?>(future: teto.fetchCutoffs(), builder: (context, snapshot){
child: FutureBuilder<CutoffsTetrio?>(future: teto.fetchCutoffsTetrio(), builder: (context, snapshot){
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
@ -61,8 +57,7 @@ class RanksAverages extends State<RankAveragesView> {
scrollDirection: Axis.horizontal,
child: Container(
alignment: Alignment.center,
width: MediaQuery.of(context).size.width,
constraints: const BoxConstraints(maxWidth: 900, minWidth: 610),
width: 900,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
@ -71,54 +66,85 @@ class RanksAverages extends State<RankAveragesView> {
Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.all(color: Colors.grey.shade900),
columnWidths: const {0: FixedColumnWidth(48)},
columnWidths: const {
0: FixedColumnWidth(48),
1: FixedColumnWidth(155),
2: FixedColumnWidth(150),
3: FixedColumnWidth(90),
4: FixedColumnWidth(130),
},
children: [
TableRow(
children: [
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white)),
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text("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("TR", 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("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: const EdgeInsets.only(right: 8.0),
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)),
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)),
),
]
),
for (String rank in snapshot.data!.tr.keys) TableRow(
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(100), rankColors[rank]!.withAlpha(200)])),
for (String rank in snapshot.data!.data.keys) TableRow(
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(200), rankColors[rank]!.withAlpha(100)])),
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)),
Padding(
padding: const EdgeInsets.only(right: 8.0),
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)),
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)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
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)),
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)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
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)),
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)),
),
Padding(
padding: const EdgeInsets.only(right: 8.0),
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)),
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)),
),
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!.ts)))
Text(t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.timestamp)))
],
),
),

View File

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