Compare commits
No commits in common. "ce2fb89ccf06361d26faf4b071439b0046d74fd8" and "e5ffa9711e80809f169f35854e84e42436abb2db" have entirely different histories.
ce2fb89ccf
...
e5ffa9711e
|
@ -3,7 +3,6 @@
|
|||
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';
|
||||
|
||||
|
@ -43,26 +42,25 @@ const Map<String, double> rankCutoffs = {
|
|||
"z": -1,
|
||||
"": 0.5
|
||||
};
|
||||
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,
|
||||
};
|
||||
// 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,
|
||||
// };
|
||||
// DateTime seasonStart = DateTime.utc(2024, 08, 16, 18);
|
||||
//DateTime seasonEnd = DateTime.utc(2024, 07, 26, 15);
|
||||
enum Stats {
|
||||
|
@ -426,9 +424,7 @@ 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;
|
||||
|
@ -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['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']);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
// so i'm going to document only unique differences between them
|
||||
|
||||
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 {
|
||||
Future<Cutoffs?> fetchCutoffs() async {
|
||||
Cutoffs? cached = _cache.get("", Cutoffs);
|
||||
if (cached != null) return cached;
|
||||
|
||||
|
@ -475,7 +429,7 @@ class TetrioService extends DB {
|
|||
_cache.store(result, rawData["cache_until"]);
|
||||
return result;
|
||||
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;
|
||||
// if not 200 or 404 - throw a unique for each code exception
|
||||
case 403:
|
||||
|
@ -488,10 +442,10 @@ class TetrioService extends DB {
|
|||
case 502:
|
||||
case 503:
|
||||
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;
|
||||
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");
|
||||
}
|
||||
} on http.ClientException catch (e, s) { // If local http client fails
|
||||
|
|
|
@ -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.fetchCutoffsBeanserver(),
|
||||
teto.fetchCutoffs(),
|
||||
(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 = 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
|
||||
),
|
||||
|
|
|
@ -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, records, ex, exRecords}
|
||||
enum CardMod {info, recent, top, ex, exRecent, exTop}
|
||||
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" && 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]!),
|
||||
"blitz" => readableIntDifference(record!.stats.score, blitzAverages[rank]!),
|
||||
_ => 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(
|
||||
children: [
|
||||
Card(
|
||||
|
@ -883,8 +883,7 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text("Records", style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
|
||||
//Text("${t.seasonStarts} ${countdown(postSeasonLeft)}", textAlign: TextAlign.center)
|
||||
Text(isTop ? t.top : t.recent, style: const TextStyle(fontFamily: "Eurostile Round Extended", fontSize: 42)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -892,137 +891,65 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
),
|
||||
Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: DefaultTabController(length: 2,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TabBar(
|
||||
tabs: [
|
||||
Tab(text: "Recent"),
|
||||
Tab(text: "Top"),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 400,
|
||||
child: TabBarView(
|
||||
child: FutureBuilder<SingleplayerStream>(
|
||||
future: teto.fetchStream(widget.searchFor, stream),
|
||||
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: [
|
||||
FutureBuilder<SingleplayerStream>(
|
||||
future: teto.fetchStream(widget.searchFor, recentStream),
|
||||
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(
|
||||
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?");
|
||||
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){
|
||||
"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)
|
||||
),
|
||||
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?");
|
||||
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?");
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -1369,14 +1296,14 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
),
|
||||
],
|
||||
Cards.tetraLeague: [
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.info,
|
||||
label: Text('Standing'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.records,
|
||||
label: Text('Recent Matches'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.info,
|
||||
label: Text('Standing'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.recent,
|
||||
label: Text('Recent Matches'),
|
||||
),
|
||||
],
|
||||
Cards.quickPlay: [
|
||||
const ButtonSegment<CardMod>(
|
||||
|
@ -1384,17 +1311,25 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
label: Text('Normal'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.records,
|
||||
label: Text('Records'),
|
||||
value: CardMod.recent,
|
||||
label: Text('Recent Normal'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.top,
|
||||
label: Text('Top Normal'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.ex,
|
||||
label: Text('Expert'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.exRecords,
|
||||
label: Text('Expert Records'),
|
||||
)
|
||||
value: CardMod.exRecent,
|
||||
label: Text('Recent Expert'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.exTop,
|
||||
label: Text('Top Expert'),
|
||||
),
|
||||
],
|
||||
Cards.blitz: [
|
||||
const ButtonSegment<CardMod>(
|
||||
|
@ -1402,9 +1337,13 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
label: Text('PB'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.records,
|
||||
label: Text('Records'),
|
||||
)
|
||||
value: CardMod.recent,
|
||||
label: Text('Recent'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.top,
|
||||
label: Text('Top'),
|
||||
),
|
||||
],
|
||||
Cards.sprint: [
|
||||
const ButtonSegment<CardMod>(
|
||||
|
@ -1412,9 +1351,13 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
label: Text('PB'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.records,
|
||||
label: Text('Records'),
|
||||
)
|
||||
value: CardMod.recent,
|
||||
label: Text('Recent'),
|
||||
),
|
||||
const ButtonSegment<CardMod>(
|
||||
value: CardMod.top,
|
||||
label: Text('Top'),
|
||||
),
|
||||
]
|
||||
};
|
||||
super.initState();
|
||||
|
@ -1446,8 +1389,8 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
);
|
||||
}
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
|
@ -1524,24 +1467,28 @@ class _DestinationHomeState extends State<DestinationHome> {
|
|||
Cards.overview => getOverviewCard(snapshot.data!.summaries!),
|
||||
Cards.tetraLeague => switch (cardMod){
|
||||
CardMod.info => getTetraLeagueCard(snapshot.data!.summaries!.league),
|
||||
CardMod.records => getRecentTLrecords(widget.constraints),
|
||||
CardMod.recent => getRecentTLrecords(widget.constraints),
|
||||
_ => Center(child: Text("huh?"))
|
||||
},
|
||||
Cards.quickPlay => switch (cardMod){
|
||||
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.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?"))
|
||||
},
|
||||
Cards.sprint => switch (cardMod){
|
||||
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?"))
|
||||
},
|
||||
Cards.blitz => switch (cardMod){
|
||||
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?"))
|
||||
},
|
||||
},
|
||||
|
@ -2386,13 +2333,12 @@ class TetraLeagueThingy extends StatelessWidget{
|
|||
backgroundColor: Colors.black,
|
||||
axes: [
|
||||
RadialAxis(
|
||||
minimum: 0.0,
|
||||
maximum: 1.0,
|
||||
minimum: 0.4,
|
||||
maximum: 0.6,
|
||||
radiusFactor: 1.01,
|
||||
showTicks: true,
|
||||
showLabels: false,
|
||||
interval: 0.25,
|
||||
minorTicksPerInterval: 0,
|
||||
interval: 0.1,
|
||||
ranges:[
|
||||
GaugeRange(startValue: 0, endValue: league.winrate, color: theme.colorScheme.primary)
|
||||
],
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
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;
|
||||
|
@ -37,13 +40,14 @@ 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<CutoffsTetrio?>(future: teto.fetchCutoffsTetrio(), builder: (context, snapshot){
|
||||
child: FutureBuilder<Cutoffs?>(future: teto.fetchCutoffs(), builder: (context, snapshot){
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
|
@ -57,7 +61,8 @@ class RanksAverages extends State<RankAveragesView> {
|
|||
scrollDirection: Axis.horizontal,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: 900,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
constraints: const BoxConstraints(maxWidth: 900, minWidth: 610),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
|
@ -66,85 +71,54 @@ class RanksAverages extends State<RankAveragesView> {
|
|||
Table(
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
border: TableBorder.all(color: Colors.grey.shade900),
|
||||
columnWidths: const {
|
||||
0: FixedColumnWidth(48),
|
||||
1: FixedColumnWidth(155),
|
||||
2: FixedColumnWidth(150),
|
||||
3: FixedColumnWidth(90),
|
||||
4: FixedColumnWidth(130),
|
||||
},
|
||||
columnWidths: const {0: FixedColumnWidth(48)},
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
Text(t.rank, textAlign: TextAlign.center, style: const TextStyle(fontFamily: "Eurostile Round", fontSize: 14, 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("TR", 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("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(
|
||||
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(200), rankColors[rank]!.withAlpha(100)])),
|
||||
for (String rank in snapshot.data!.tr.keys) TableRow(
|
||||
decoration: BoxDecoration(gradient: LinearGradient(colors: [rankColors[rank]!.withAlpha(100), rankColors[rank]!.withAlpha(200)])),
|
||||
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!.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: 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: 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: 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)),
|
||||
),
|
||||
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))
|
||||
]
|
||||
))
|
||||
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)),
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.timestamp)))
|
||||
Text(t.sprintAndBlitsRelevance(date: timestamp(snapshot.data!.ts)))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -2,7 +2,7 @@ name: tetra_stats
|
|||
description: Track your and other player stats in TETR.IO
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.6.8+34
|
||||
version: 1.6.7+33
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0'
|
||||
|
|
Loading…
Reference in New Issue