From 7cb1fc05439df522ac5e4b937f6985df997636f3 Mon Sep 17 00:00:00 2001 From: dan63047 Date: Sat, 24 Aug 2024 17:41:07 +0300 Subject: [PATCH] ok :droidsmile: --- lib/data_objects/tetrio.dart | 83 +++++++-- lib/services/tetrio_crud.dart | 54 +++++- lib/views/main_view.dart | 12 +- lib/views/main_view_tiles.dart | 270 +++++++++++++++++------------ lib/views/ranks_averages_view.dart | 80 ++++++--- pubspec.yaml | 2 +- 6 files changed, 336 insertions(+), 165 deletions(-) diff --git a/lib/data_objects/tetrio.dart b/lib/data_objects/tetrio.dart index 277b5fa..1906636 100644 --- a/lib/data_objects/tetrio.dart +++ b/lib/data_objects/tetrio.dart @@ -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 rankCutoffs = { "z": -1, "": 0.5 }; -// const Map 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 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 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,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 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 data = {}; + + CutoffsTetrio.fromJson(Map 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); + } + } +} \ No newline at end of file diff --git a/lib/services/tetrio_crud.dart b/lib/services/tetrio_crud.dart index 4e8da1c..1559fa4 100644 --- a/lib/services/tetrio_crud.dart +++ b/lib/services/tetrio_crud.dart @@ -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 fetchCutoffs() async { + Future 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 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 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 diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index 324bed9..9b06306 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -175,10 +175,10 @@ class _MainState extends State 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, ()=>>[]), + //prefs.getBool("showPositions") != true ? teto.fetchCutoffsBeanserver() : Future.delayed(Duration.zero, ()=>>[]), //(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 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 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 ), diff --git a/lib/views/main_view_tiles.dart b/lib/views/main_view_tiles.dart index da979c4..6c13992 100644 --- a/lib/views/main_view_tiles.dart +++ b/lib/views/main_view_tiles.dart @@ -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 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 { ); } - 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 { 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,65 +892,137 @@ class _DestinationHomeState extends State { ), Card( clipBehavior: Clip.antiAlias, - child: FutureBuilder( - 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( + child: DefaultTabController(length: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + tabs: [ + Tab(text: "Recent"), + Tab(text: "Top"), + ], + ), + SizedBox( + height: 400, + child: TabBarView( 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( - isTop ? "#${i+1}" : switch (snapshot.data!.records[i].gamemode){ - "40l" => "40L", - "blitz" => "BLZ", - "5mblast" => "5MB", - "zenith" => "QP", - "zenithex" => "QPE", - String() => "huh", + FutureBuilder( + 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?"); }, - 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", + FutureBuilder( + 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?"); }, - 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 { ), ], Cards.tetraLeague: [ - const ButtonSegment( - value: CardMod.info, - label: Text('Standing'), - ), - const ButtonSegment( - value: CardMod.recent, - label: Text('Recent Matches'), - ), + const ButtonSegment( + value: CardMod.info, + label: Text('Standing'), + ), + const ButtonSegment( + value: CardMod.records, + label: Text('Recent Matches'), + ), ], Cards.quickPlay: [ const ButtonSegment( @@ -1311,25 +1384,17 @@ class _DestinationHomeState extends State { label: Text('Normal'), ), const ButtonSegment( - value: CardMod.recent, - label: Text('Recent Normal'), - ), - const ButtonSegment( - value: CardMod.top, - label: Text('Top Normal'), + value: CardMod.records, + label: Text('Records'), ), const ButtonSegment( value: CardMod.ex, label: Text('Expert'), ), const ButtonSegment( - value: CardMod.exRecent, - label: Text('Recent Expert'), - ), - const ButtonSegment( - value: CardMod.exTop, - label: Text('Top Expert'), - ), + value: CardMod.exRecords, + label: Text('Expert Records'), + ) ], Cards.blitz: [ const ButtonSegment( @@ -1337,13 +1402,9 @@ class _DestinationHomeState extends State { label: Text('PB'), ), const ButtonSegment( - value: CardMod.recent, - label: Text('Recent'), - ), - const ButtonSegment( - value: CardMod.top, - label: Text('Top'), - ), + value: CardMod.records, + label: Text('Records'), + ) ], Cards.sprint: [ const ButtonSegment( @@ -1351,13 +1412,9 @@ class _DestinationHomeState extends State { label: Text('PB'), ), const ButtonSegment( - value: CardMod.recent, - label: Text('Recent'), - ), - const ButtonSegment( - value: CardMod.top, - label: Text('Top'), - ), + value: CardMod.records, + label: Text('Records'), + ) ] }; super.initState(); @@ -1389,8 +1446,8 @@ class _DestinationHomeState extends State { ); } 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 { 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) ], diff --git a/lib/views/ranks_averages_view.dart b/lib/views/ranks_averages_view.dart index 1749c6a..c2f6387 100644 --- a/lib/views/ranks_averages_view.dart +++ b/lib/views/ranks_averages_view.dart @@ -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 { @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(future: teto.fetchCutoffs(), builder: (context, snapshot){ + child: FutureBuilder(future: teto.fetchCutoffsTetrio(), builder: (context, snapshot){ switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: @@ -61,8 +57,7 @@ class RanksAverages extends State { 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 { 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))) ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index f4cc18a..4ac97d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: tetra_stats description: Track your and other player stats in TETR.IO publish_to: 'none' -version: 1.6.7+33 +version: 1.6.8+34 environment: sdk: '>=3.0.0'