ok :droidsmile:

This commit is contained in:
dan63047 2024-08-24 17:41:07 +03:00
parent e5ffa9711e
commit 7cb1fc0543
6 changed files with 336 additions and 165 deletions

View File

@ -3,6 +3,7 @@
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';
@ -42,25 +43,26 @@ const Map<String, double> rankCutoffs = {
"z": -1, "z": -1,
"": 0.5 "": 0.5
}; };
// const Map<String, double> rankTargets = { const Map<String, double> rankTargets = {
// "x": 24503.75, // where that comes from? "x+": 24000.00,
// "u": 23038, "x": 22500.00,
// "ss": 21583, "u": 20000.00,
// "s+": 20128, "ss": 18000.00,
// "s": 18673, "s+": 16500.00,
// "s-": 16975, "s": 15200.00,
// "a+": 15035, "s-": 13800.00,
// "a": 13095, "a+": 12000.00,
// "a-": 11155, "a": 10500.00,
// "b+": 9215, "a-": 9000.00,
// "b": 7275, "b+": 7400.00,
// "b-": 5335, "b": 5700.00,
// "c+": 3880, "b-": 4200.00,
// "c": 2425, "c+": 3000.00,
// "c-": 1213, "c": 2000.00,
// "d+": 606, "c-": 1300.00,
// "d": 0, "d+": 800.00,
// }; "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 {
@ -424,7 +426,9 @@ 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;
@ -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['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']);
@ -2612,3 +2618,42 @@ 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 DateTime timestamp;
late int total;
Map<String, CutoffTetrio> data = {};
CutoffsTetrio.fromJson(Map<String, dynamic> json){
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, // 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<Cutoffs?> fetchCutoffs() async { Future<CutoffsTetrio?> fetchCutoffsTetrio() async {
CutoffsTetrio? cached = _cache.get("", 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;
@ -429,7 +475,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("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; 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:
@ -442,10 +488,10 @@ class TetrioService extends DB {
case 502: case 502:
case 503: case 503:
case 504: 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; return null;
default: 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"); 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.fetchCutoffs(), teto.fetchCutoffsBeanserver(),
(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.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.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, recent, top, ex, exRecent, exTop} enum CardMod {info, records, ex, exRecords}
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") 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]!), "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 stream, bool isTop, BoxConstraints constraints){ Widget getListOfRecords(String recentStream, String topStream, BoxConstraints constraints){
return Column( return Column(
children: [ children: [
Card( Card(
@ -883,7 +883,8 @@ class _DestinationHomeState extends State<DestinationHome> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ 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,65 +892,137 @@ class _DestinationHomeState extends State<DestinationHome> {
), ),
Card( Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: FutureBuilder<SingleplayerStream>( child: DefaultTabController(length: 2,
future: teto.fetchStream(widget.searchFor, stream), child: Column(
builder: (context, snapshot) { mainAxisSize: MainAxisSize.min,
switch (snapshot.connectionState){ children: [
case ConnectionState.none: TabBar(
case ConnectionState.waiting: tabs: [
case ConnectionState.active: Tab(text: "Recent"),
return const Center(child: CircularProgressIndicator()); Tab(text: "Top"),
case ConnectionState.done: ],
if (snapshot.hasData){ ),
return Column( SizedBox(
height: 400,
child: TabBarView(
children: [ children: [
for (int i = 0; i < snapshot.data!.records.length; i++) ListTile( FutureBuilder<SingleplayerStream>(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SingleplayerRecordView(record: snapshot.data!.records[i]))), future: teto.fetchStream(widget.searchFor, recentStream),
leading: Text( builder: (context, snapshot) {
isTop ? "#${i+1}" : switch (snapshot.data!.records[i].gamemode){ switch (snapshot.connectionState){
"40l" => "40L", case ConnectionState.none:
"blitz" => "BLZ", case ConnectionState.waiting:
"5mblast" => "5MB", case ConnectionState.active:
"zenith" => "QP", return const Center(child: CircularProgressIndicator());
"zenithex" => "QPE", case ConnectionState.done:
String() => "huh", 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(
switch (snapshot.data!.records[i].gamemode){
"40l" => "40L",
"blitz" => "BLZ",
"5mblast" => "5MB",
"zenith" => "QP",
"zenithex" => "QPE",
String() => "huh",
},
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?");
}, },
style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 28, shadows: textShadow, height: 0.9)
), ),
title: Text( FutureBuilder<SingleplayerStream>(
switch (snapshot.data!.records[i].gamemode){ future: teto.fetchStream(widget.searchFor, topStream),
"40l" => get40lTime(snapshot.data!.records[i].stats.finalTime.inMicroseconds), builder: (context, snapshot) {
"blitz" => t.blitzScore(p: NumberFormat.decimalPattern().format(snapshot.data!.records[i].stats.score)), switch (snapshot.connectionState){
"5mblast" => get40lTime(snapshot.data!.records[i].stats.finalTime.inMicroseconds), case ConnectionState.none:
"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)})" : ""}", case ConnectionState.waiting:
"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)})" : ""}", case ConnectionState.active:
String() => "huh", 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?");
}, },
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?");
},
),
), ),
], ],
); );
@ -1296,14 +1369,14 @@ class _DestinationHomeState extends State<DestinationHome> {
), ),
], ],
Cards.tetraLeague: [ Cards.tetraLeague: [
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.info, value: CardMod.info,
label: Text('Standing'), label: Text('Standing'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.recent, value: CardMod.records,
label: Text('Recent Matches'), label: Text('Recent Matches'),
), ),
], ],
Cards.quickPlay: [ Cards.quickPlay: [
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
@ -1311,25 +1384,17 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('Normal'), label: Text('Normal'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.recent, value: CardMod.records,
label: Text('Recent Normal'), label: Text('Records'),
),
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.exRecent, value: CardMod.exRecords,
label: Text('Recent Expert'), label: Text('Expert Records'),
), )
const ButtonSegment<CardMod>(
value: CardMod.exTop,
label: Text('Top Expert'),
),
], ],
Cards.blitz: [ Cards.blitz: [
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
@ -1337,13 +1402,9 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('PB'), label: Text('PB'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.recent, value: CardMod.records,
label: Text('Recent'), label: Text('Records'),
), )
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top'),
),
], ],
Cards.sprint: [ Cards.sprint: [
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
@ -1351,13 +1412,9 @@ class _DestinationHomeState extends State<DestinationHome> {
label: Text('PB'), label: Text('PB'),
), ),
const ButtonSegment<CardMod>( const ButtonSegment<CardMod>(
value: CardMod.recent, value: CardMod.records,
label: Text('Recent'), label: Text('Records'),
), )
const ButtonSegment<CardMod>(
value: CardMod.top,
label: Text('Top'),
),
] ]
}; };
super.initState(); super.initState();
@ -1389,8 +1446,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!.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!.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!.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!.league.rank != "x+") ? 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;
@ -1467,28 +1524,24 @@ 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.recent => getRecentTLrecords(widget.constraints), CardMod.records => 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.recent => getListOfRecords("zenith/recent", false, widget.constraints), CardMod.records => getListOfRecords("zenith/recent", "zenith/top", 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.exRecent => getListOfRecords("zenithex/recent", false, widget.constraints), CardMod.exRecords => getListOfRecords("zenithex/recent", "zenithex/top", 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.recent => getListOfRecords("40l/recent", false, widget.constraints), CardMod.records => getListOfRecords("40l/recent", "40l/top", 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.recent => getListOfRecords("blitz/recent", false, widget.constraints), CardMod.records => getListOfRecords("blitz/recent", "blitz/top", widget.constraints),
CardMod.top => getListOfRecords("blitz/top", true, widget.constraints),
_ => Center(child: Text("huh?")) _ => Center(child: Text("huh?"))
}, },
}, },
@ -2333,12 +2386,13 @@ class TetraLeagueThingy extends StatelessWidget{
backgroundColor: Colors.black, backgroundColor: Colors.black,
axes: [ axes: [
RadialAxis( RadialAxis(
minimum: 0.4, minimum: 0.0,
maximum: 0.6, maximum: 1.0,
radiusFactor: 1.01, radiusFactor: 1.01,
showTicks: true, showTicks: true,
showLabels: false, showLabels: false,
interval: 0.1, interval: 0.25,
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,13 +1,10 @@
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;
@ -40,14 +37,13 @@ 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<Cutoffs?>(future: teto.fetchCutoffs(), builder: (context, snapshot){ child: FutureBuilder<CutoffsTetrio?>(future: teto.fetchCutoffsTetrio(), builder: (context, snapshot){
switch (snapshot.connectionState) { switch (snapshot.connectionState) {
case ConnectionState.none: case ConnectionState.none:
case ConnectionState.waiting: case ConnectionState.waiting:
@ -61,8 +57,7 @@ class RanksAverages extends State<RankAveragesView> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
width: MediaQuery.of(context).size.width, width: 900,
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(
@ -71,54 +66,85 @@ 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 {0: FixedColumnWidth(48)}, columnWidths: const {
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)),
Padding( const Padding(
padding: const EdgeInsets.only(right: 8.0), padding: 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)), 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(
padding: const EdgeInsets.only(right: 8.0), 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)), 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)),
),
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!.tr.keys) TableRow( for (String rank in snapshot.data!.data.keys) TableRow(
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(100), rankColors[rank]!.withAlpha(200)])), decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(200), rankColors[rank]!.withAlpha(100)])),
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!.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(
padding: const EdgeInsets.only(right: 8.0), 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(
padding: const EdgeInsets.only(right: 8.0), 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(
padding: const EdgeInsets.only(right: 8.0), 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 description: Track your and other player stats in TETR.IO
publish_to: 'none' publish_to: 'none'
version: 1.6.7+33 version: 1.6.8+34
environment: environment:
sdk: '>=3.0.0' sdk: '>=3.0.0'